Skip to content

Commit 89d0e7f

Browse files
committed
feat(cortex-tui): centralize MCP management in interactive /mcp panel
- Deprecate /mcp-auth, /mcp-logs, /mcp-tools, /mcp-reload commands (now hidden) - All MCP commands now redirect to the interactive /mcp panel - Enhanced MCP selector with global actions: Add Server, View Tools, Reload All - Selecting a server shows contextual actions (start/stop/restart/auth/logs/remove) - Removed command usage notifications that instructed users to type commands - Updated help documentation to reflect the new interactive workflow - Updated tests to match new behavior
1 parent 52f82a4 commit 89d0e7f

File tree

5 files changed

+211
-125
lines changed

5 files changed

+211
-125
lines changed

cortex-tui/src/commands/executor.rs

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,12 @@ impl CommandExecutor {
155155
"tokens" | "max-tokens" => self.cmd_tokens(cmd),
156156

157157
// ============ MCP ============
158+
// All MCP commands redirect to the interactive panel
158159
"mcp" => self.cmd_mcp(cmd),
159-
"mcp-tools" | "tools" | "lt" => CommandResult::Async("mcp-tools".to_string()),
160-
"mcp-auth" | "auth" => self.cmd_mcp_auth(cmd),
161-
"mcp-reload" | "reload" => CommandResult::Async("mcp-reload".to_string()),
162-
"mcp-logs" => self.cmd_mcp_logs(cmd),
160+
"mcp-tools" | "tools" | "lt" => CommandResult::OpenModal(ModalType::McpManager),
161+
"mcp-auth" | "auth" => CommandResult::OpenModal(ModalType::McpManager),
162+
"mcp-reload" | "reload" => CommandResult::OpenModal(ModalType::McpManager),
163+
"mcp-logs" => CommandResult::OpenModal(ModalType::McpManager),
163164

164165
// ============ DEBUG ============
165166
"debug" | "dbg" => self.cmd_debug(cmd),
@@ -565,40 +566,19 @@ impl CommandExecutor {
565566

566567
// ========== MCP ==========
567568

568-
fn cmd_mcp(&self, cmd: &ParsedCommand) -> CommandResult {
569-
match cmd.first_arg() {
570-
None | Some("list") => CommandResult::OpenModal(ModalType::McpManager),
571-
Some("add") => {
572-
if let Some(server) = cmd.arg(1) {
573-
CommandResult::Async(format!("mcp:add:{}", server))
574-
} else {
575-
CommandResult::OpenModal(ModalType::Form("mcp-add".to_string()))
576-
}
577-
}
578-
Some("remove") => {
579-
if let Some(server) = cmd.arg(1) {
580-
CommandResult::Async(format!("mcp:remove:{}", server))
581-
} else {
582-
CommandResult::OpenModal(ModalType::Form("mcp-remove".to_string()))
583-
}
584-
}
585-
Some("status") => CommandResult::Async("mcp:status".to_string()),
586-
_ => {
587-
CommandResult::Message("Usage: /mcp [list|add|remove|status] [server]".to_string())
588-
}
589-
}
569+
fn cmd_mcp(&self, _cmd: &ParsedCommand) -> CommandResult {
570+
// Always open the interactive MCP panel - all management is centralized there
571+
CommandResult::OpenModal(ModalType::McpManager)
590572
}
591573

592-
fn cmd_mcp_auth(&self, cmd: &ParsedCommand) -> CommandResult {
593-
match cmd.first_arg() {
594-
Some(server) => CommandResult::Async(format!("mcp:auth:{}", server)),
595-
None => CommandResult::OpenModal(ModalType::Form("mcp-auth".to_string())),
596-
}
574+
fn cmd_mcp_auth(&self, _cmd: &ParsedCommand) -> CommandResult {
575+
// Deprecated: redirect to interactive MCP panel
576+
CommandResult::OpenModal(ModalType::McpManager)
597577
}
598578

599-
fn cmd_mcp_logs(&self, cmd: &ParsedCommand) -> CommandResult {
600-
let server = cmd.first_arg().unwrap_or("all");
601-
CommandResult::Async(format!("mcp:logs:{}", server))
579+
fn cmd_mcp_logs(&self, _cmd: &ParsedCommand) -> CommandResult {
580+
// Deprecated: redirect to interactive MCP panel
581+
CommandResult::OpenModal(ModalType::McpManager)
602582
}
603583

604584
// ========== DEBUG ==========
@@ -980,22 +960,23 @@ mod tests {
980960
CommandResult::OpenModal(ModalType::McpManager)
981961
));
982962

963+
// All mcp subcommands now redirect to the interactive panel
983964
let result = executor.execute_str("/mcp status");
984965
assert!(matches!(
985966
result,
986-
CommandResult::Async(ref s) if s == "mcp:status"
967+
CommandResult::OpenModal(ModalType::McpManager)
987968
));
988969

989970
let result = executor.execute_str("/mcp add myserver");
990971
assert!(matches!(
991972
result,
992-
CommandResult::Async(ref s) if s == "mcp:add:myserver"
973+
CommandResult::OpenModal(ModalType::McpManager)
993974
));
994975

995976
let result = executor.execute_str("/mcp add");
996977
assert!(matches!(
997978
result,
998-
CommandResult::OpenModal(ModalType::Form(_))
979+
CommandResult::OpenModal(ModalType::McpManager)
999980
));
1000981
}
1001982

cortex-tui/src/commands/registry.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -698,43 +698,46 @@ pub fn register_builtin_commands(registry: &mut CommandRegistry) {
698698
registry.register(CommandDef::new(
699699
"mcp",
700700
&[],
701-
"MCP server management",
702-
"/mcp <list|add|remove|status>",
701+
"MCP server management (interactive)",
702+
"/mcp",
703703
CommandCategory::Mcp,
704-
true,
704+
false,
705705
));
706706

707-
registry.register(CommandDef::new(
707+
// NOTE: /mcp-tools, /mcp-auth, /mcp-reload, /mcp-logs are deprecated.
708+
// All MCP management is now centralized in the interactive /mcp panel.
709+
// These commands are hidden but still work for backwards compatibility.
710+
registry.register(CommandDef::hidden(
708711
"mcp-tools",
709712
&["tools", "lt"],
710-
"List MCP tools",
713+
"[Deprecated] Use /mcp instead - List MCP tools",
711714
"/mcp-tools [server]",
712715
CommandCategory::Mcp,
713716
true,
714717
));
715718

716-
registry.register(CommandDef::new(
719+
registry.register(CommandDef::hidden(
717720
"mcp-auth",
718721
&["auth"],
719-
"Authenticate MCP server",
722+
"[Deprecated] Use /mcp instead - Authenticate MCP server",
720723
"/mcp-auth <server>",
721724
CommandCategory::Mcp,
722725
true,
723726
));
724727

725-
registry.register(CommandDef::new(
728+
registry.register(CommandDef::hidden(
726729
"mcp-reload",
727730
&["reload"],
728-
"Reload MCP servers",
731+
"[Deprecated] Use /mcp instead - Reload MCP servers",
729732
"/mcp-reload [server]",
730733
CommandCategory::Mcp,
731734
true,
732735
));
733736

734-
registry.register(CommandDef::new(
737+
registry.register(CommandDef::hidden(
735738
"mcp-logs",
736739
&[],
737-
"View MCP server logs",
740+
"[Deprecated] Use /mcp instead - View MCP server logs",
738741
"/mcp-logs [server]",
739742
CommandCategory::Mcp,
740743
true,

cortex-tui/src/interactive/builders/mcp.rs

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,59 @@ use crate::modal::mcp_manager::{McpServerInfo, McpStatus};
55

66
/// Build an interactive state for MCP server management.
77
pub fn build_mcp_selector(servers: &[McpServerInfo]) -> InteractiveState {
8-
let mut items: Vec<InteractiveItem> = servers
9-
.iter()
10-
.map(|server| {
11-
let (icon, status_text) = match server.status {
12-
McpStatus::Running => ('🟢', "running"),
13-
McpStatus::Starting => ('🟡', "starting"),
14-
McpStatus::Stopped => ('⚫', "stopped"),
15-
McpStatus::Error => ('🔴', "error"),
16-
};
17-
18-
let description = format!("{} - {} tools", status_text, server.tool_count);
19-
20-
let shortcut = server.name.chars().next().and_then(|c| {
21-
let c = c.to_ascii_lowercase();
22-
Some(c)
23-
});
24-
25-
let mut item = InteractiveItem::new(&server.name, &server.name)
26-
.with_icon(icon)
27-
.with_description(description);
28-
29-
if let Some(s) = shortcut {
30-
item = item.with_shortcut(s);
31-
}
32-
33-
if server.requires_auth {
34-
item = item.with_metadata("requires_auth".to_string());
35-
}
36-
37-
item
38-
})
39-
.collect();
40-
41-
// Add "Add new server" option
8+
let mut items: Vec<InteractiveItem> = Vec::new();
9+
10+
// Add global actions first (separated from servers)
4211
items.push(
43-
InteractiveItem::new("__add__", "+ Add MCP Server")
12+
InteractiveItem::new("__add__", "Add MCP Server")
4413
.with_icon('➕')
45-
.with_description("Configure a new MCP server")
46-
.with_shortcut('+'),
14+
.with_description("Configure a new server (stdio, HTTP, or from registry)")
15+
.with_shortcut('a'),
16+
);
17+
18+
items.push(
19+
InteractiveItem::new("__tools__", "View All Tools")
20+
.with_icon('🔧')
21+
.with_description("List tools from all running servers")
22+
.with_shortcut('t'),
23+
);
24+
25+
items.push(
26+
InteractiveItem::new("__reload__", "Reload All Servers")
27+
.with_icon('🔄')
28+
.with_description("Restart all MCP servers")
29+
.with_shortcut('r'),
4730
);
4831

32+
// Add separator if there are servers
33+
if !servers.is_empty() {
34+
items.push(
35+
InteractiveItem::new("__sep_servers__", "─── Configured Servers ───").as_separator(),
36+
);
37+
}
38+
39+
// Add server entries
40+
for server in servers {
41+
let (icon, status_text) = match server.status {
42+
McpStatus::Running => ('🟢', "running"),
43+
McpStatus::Starting => ('🟡', "starting"),
44+
McpStatus::Stopped => ('⚫', "stopped"),
45+
McpStatus::Error => ('🔴', "error"),
46+
};
47+
48+
let description = format!("{} - {} tools", status_text, server.tool_count);
49+
50+
let mut item = InteractiveItem::new(&server.name, &server.name)
51+
.with_icon(icon)
52+
.with_description(description);
53+
54+
if server.requires_auth {
55+
item = item.with_metadata("requires_auth".to_string());
56+
}
57+
58+
items.push(item);
59+
}
60+
4961
let title = if servers.is_empty() {
5062
"MCP Servers".to_string()
5163
} else {
@@ -57,6 +69,13 @@ pub fn build_mcp_selector(servers: &[McpServerInfo]) -> InteractiveState {
5769
};
5870

5971
InteractiveState::new(title, items, InteractiveAction::McpServerAction)
72+
.with_search()
73+
.with_hints(vec![
74+
("↑↓".to_string(), "navigate".to_string()),
75+
("Enter".to_string(), "select".to_string()),
76+
("/".to_string(), "search".to_string()),
77+
("Esc".to_string(), "close".to_string()),
78+
])
6079
}
6180

6281
/// Build an interactive state for MCP server actions (for a specific server).
@@ -130,9 +149,11 @@ mod tests {
130149
#[test]
131150
fn test_build_mcp_selector_empty() {
132151
let state = build_mcp_selector(&[]);
133-
// Should have "Add new server" option
134-
assert_eq!(state.items.len(), 1);
152+
// Should have global actions: Add, Tools, Reload (no separator when empty)
153+
assert_eq!(state.items.len(), 3);
135154
assert_eq!(state.items[0].id, "__add__");
155+
assert_eq!(state.items[1].id, "__tools__");
156+
assert_eq!(state.items[2].id, "__reload__");
136157
}
137158

138159
#[test]
@@ -142,8 +163,8 @@ mod tests {
142163
create_test_server("test2", McpStatus::Stopped),
143164
];
144165
let state = build_mcp_selector(&servers);
145-
// 2 servers + 1 add option
146-
assert_eq!(state.items.len(), 3);
166+
// 3 global actions + 1 separator + 2 servers = 6 items
167+
assert_eq!(state.items.len(), 6);
147168
}
148169

149170
#[test]

0 commit comments

Comments
 (0)