mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-06 16:14:49 +08:00
Expose configured MCP servers from the CLI
PARITY.md called out missing MCP management in the Rust CLI, so this adds a focused read-only /mcp path instead of expanding the broader config surface first. The new command works in the REPL, with --resume, and as a direct 7[1G[2K[m⠋ 🦀 Thinking...[0m8[1G[2K[m✘ ❌ Request failed [0m entrypoint. It lists merged MCP server definitions, supports detailed inspection for one server, and adds targeted tests for parsing, help text, completion hints, and config-backed rendering. Constraint: Keep the enhancement inside the existing Rust slash-command architecture Rejected: Extend /config with a raw mcp dump only | less discoverable than a dedicated MCP workflow Confidence: high Scope-risk: narrow Directive: Keep /mcp read-only unless MCP lifecycle commands gain shared runtime orchestration Tested: cargo test -p commands parses_supported_slash_commands Tested: cargo test -p commands rejects_invalid_mcp_arguments Tested: cargo test -p commands renders_help_from_shared_specs Tested: cargo test -p commands renders_per_command_help_detail_for_mcp Tested: cargo test -p commands ignores_unknown_or_runtime_bound_slash_commands Tested: cargo test -p commands mcp_usage_supports_help_and_unexpected_args Tested: cargo test -p commands renders_mcp_reports_from_loaded_config Tested: cargo test -p rusty-claude-cli parses_login_and_logout_subcommands Tested: cargo test -p rusty-claude-cli parses_direct_agents_mcp_and_skills_slash_commands Tested: cargo test -p rusty-claude-cli repl_help_includes_shared_commands_and_exit Tested: cargo test -p rusty-claude-cli completion_candidates_include_workflow_shortcuts_and_dynamic_sessions Tested: cargo test -p rusty-claude-cli resume_supported_command_list_matches_expected_surface Tested: cargo test -p rusty-claude-cli init_help_mentions_direct_subcommand Tested: cargo run -p rusty-claude-cli -- mcp help Not-tested: Live MCP server connectivity against a real remote or stdio backend
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
#![allow(dead_code, unused_imports, unused_variables, clippy::unneeded_struct_pattern, clippy::unnecessary_wraps, clippy::unused_self)]
|
||||
#![allow(
|
||||
dead_code,
|
||||
unused_imports,
|
||||
unused_variables,
|
||||
clippy::unneeded_struct_pattern,
|
||||
clippy::unnecessary_wraps,
|
||||
clippy::unused_self
|
||||
)]
|
||||
mod init;
|
||||
mod input;
|
||||
mod render;
|
||||
@@ -22,9 +29,9 @@ use api::{
|
||||
};
|
||||
|
||||
use commands::{
|
||||
handle_agents_slash_command, handle_plugins_slash_command, handle_skills_slash_command,
|
||||
render_slash_command_help, resume_supported_slash_commands, slash_command_specs,
|
||||
validate_slash_command_input, SlashCommand,
|
||||
handle_agents_slash_command, handle_mcp_slash_command, handle_plugins_slash_command,
|
||||
handle_skills_slash_command, render_slash_command_help, resume_supported_slash_commands,
|
||||
slash_command_specs, validate_slash_command_input, SlashCommand,
|
||||
};
|
||||
use compat_harness::{extract_manifest, UpstreamPaths};
|
||||
use init::initialize_repo;
|
||||
@@ -99,6 +106,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
CliAction::DumpManifests => dump_manifests(),
|
||||
CliAction::BootstrapPlan => print_bootstrap_plan(),
|
||||
CliAction::Agents { args } => LiveCli::print_agents(args.as_deref())?,
|
||||
CliAction::Mcp { args } => LiveCli::print_mcp(args.as_deref())?,
|
||||
CliAction::Skills { args } => LiveCli::print_skills(args.as_deref())?,
|
||||
CliAction::PrintSystemPrompt { cwd, date } => print_system_prompt(cwd, date),
|
||||
CliAction::Version => print_version(),
|
||||
@@ -139,6 +147,9 @@ enum CliAction {
|
||||
Agents {
|
||||
args: Option<String>,
|
||||
},
|
||||
Mcp {
|
||||
args: Option<String>,
|
||||
},
|
||||
Skills {
|
||||
args: Option<String>,
|
||||
},
|
||||
@@ -334,6 +345,9 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
"agents" => Ok(CliAction::Agents {
|
||||
args: join_optional_args(&rest[1..]),
|
||||
}),
|
||||
"mcp" => Ok(CliAction::Mcp {
|
||||
args: join_optional_args(&rest[1..]),
|
||||
}),
|
||||
"skills" => Ok(CliAction::Skills {
|
||||
args: join_optional_args(&rest[1..]),
|
||||
}),
|
||||
@@ -392,6 +406,7 @@ fn bare_slash_command_guidance(command_name: &str) -> Option<String> {
|
||||
"dump-manifests"
|
||||
| "bootstrap-plan"
|
||||
| "agents"
|
||||
| "mcp"
|
||||
| "skills"
|
||||
| "system-prompt"
|
||||
| "login"
|
||||
@@ -427,6 +442,14 @@ fn parse_direct_slash_cli_action(rest: &[String]) -> Result<CliAction, String> {
|
||||
match SlashCommand::parse(&raw) {
|
||||
Ok(Some(SlashCommand::Help)) => Ok(CliAction::Help),
|
||||
Ok(Some(SlashCommand::Agents { args })) => Ok(CliAction::Agents { args }),
|
||||
Ok(Some(SlashCommand::Mcp { action, target })) => Ok(CliAction::Mcp {
|
||||
args: match (action, target) {
|
||||
(None, None) => None,
|
||||
(Some(action), None) => Some(action),
|
||||
(Some(action), Some(target)) => Some(format!("{action} {target}")),
|
||||
(None, Some(target)) => Some(target),
|
||||
},
|
||||
}),
|
||||
Ok(Some(SlashCommand::Skills { args })) => Ok(CliAction::Skills { args }),
|
||||
Ok(Some(SlashCommand::Unknown(name))) => Err(format_unknown_direct_slash_command(&name)),
|
||||
Ok(Some(command)) => Err({
|
||||
@@ -1345,6 +1368,19 @@ fn run_resume_command(
|
||||
session: session.clone(),
|
||||
message: Some(render_config_report(section.as_deref())?),
|
||||
}),
|
||||
SlashCommand::Mcp { action, target } => {
|
||||
let cwd = env::current_dir()?;
|
||||
let args = match (action.as_deref(), target.as_deref()) {
|
||||
(None, None) => None,
|
||||
(Some(action), None) => Some(action.to_string()),
|
||||
(Some(action), Some(target)) => Some(format!("{action} {target}")),
|
||||
(None, Some(target)) => Some(target.to_string()),
|
||||
};
|
||||
Ok(ResumeCommandOutcome {
|
||||
session: session.clone(),
|
||||
message: Some(handle_mcp_slash_command(args.as_deref(), &cwd)?),
|
||||
})
|
||||
}
|
||||
SlashCommand::Memory => Ok(ResumeCommandOutcome {
|
||||
session: session.clone(),
|
||||
message: Some(render_memory_report()?),
|
||||
@@ -1795,6 +1831,16 @@ impl LiveCli {
|
||||
Self::print_config(section.as_deref())?;
|
||||
false
|
||||
}
|
||||
SlashCommand::Mcp { action, target } => {
|
||||
let args = match (action.as_deref(), target.as_deref()) {
|
||||
(None, None) => None,
|
||||
(Some(action), None) => Some(action.to_string()),
|
||||
(Some(action), Some(target)) => Some(format!("{action} {target}")),
|
||||
(None, Some(target)) => Some(target.to_string()),
|
||||
};
|
||||
Self::print_mcp(args.as_deref())?;
|
||||
false
|
||||
}
|
||||
SlashCommand::Memory => {
|
||||
Self::print_memory()?;
|
||||
false
|
||||
@@ -2056,6 +2102,12 @@ impl LiveCli {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_mcp(args: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cwd = env::current_dir()?;
|
||||
println!("{}", handle_mcp_slash_command(args, &cwd)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_skills(args: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cwd = env::current_dir()?;
|
||||
println!("{}", handle_skills_slash_command(args, &cwd)?);
|
||||
@@ -4048,6 +4100,9 @@ fn slash_command_completion_candidates_with_sessions(
|
||||
"/config hooks",
|
||||
"/config model",
|
||||
"/config plugins",
|
||||
"/mcp ",
|
||||
"/mcp list",
|
||||
"/mcp show ",
|
||||
"/export ",
|
||||
"/issue ",
|
||||
"/model ",
|
||||
@@ -4073,6 +4128,7 @@ fn slash_command_completion_candidates_with_sessions(
|
||||
"/teleport ",
|
||||
"/ultraplan ",
|
||||
"/agents help",
|
||||
"/mcp help",
|
||||
"/skills help",
|
||||
] {
|
||||
completions.insert(candidate.to_string());
|
||||
@@ -4763,6 +4819,7 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
|
||||
writeln!(out, " claw dump-manifests")?;
|
||||
writeln!(out, " claw bootstrap-plan")?;
|
||||
writeln!(out, " claw agents")?;
|
||||
writeln!(out, " claw mcp")?;
|
||||
writeln!(out, " claw skills")?;
|
||||
writeln!(out, " claw system-prompt [--cwd PATH] [--date YYYY-MM-DD]")?;
|
||||
writeln!(out, " claw login")?;
|
||||
@@ -4834,6 +4891,7 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
|
||||
" claw --resume {LATEST_SESSION_REFERENCE} /status /diff /export notes.txt"
|
||||
)?;
|
||||
writeln!(out, " claw agents")?;
|
||||
writeln!(out, " claw mcp show my-server")?;
|
||||
writeln!(out, " claw /skills")?;
|
||||
writeln!(out, " claw login")?;
|
||||
writeln!(out, " claw init")?;
|
||||
@@ -5106,6 +5164,10 @@ mod tests {
|
||||
parse_args(&["agents".to_string()]).expect("agents should parse"),
|
||||
CliAction::Agents { args: None }
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["mcp".to_string()]).expect("mcp should parse"),
|
||||
CliAction::Mcp { args: None }
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["skills".to_string()]).expect("skills should parse"),
|
||||
CliAction::Skills { args: None }
|
||||
@@ -5165,11 +5227,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_direct_agents_and_skills_slash_commands() {
|
||||
fn parses_direct_agents_mcp_and_skills_slash_commands() {
|
||||
assert_eq!(
|
||||
parse_args(&["/agents".to_string()]).expect("/agents should parse"),
|
||||
CliAction::Agents { args: None }
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["/mcp".to_string(), "show".to_string(), "demo".to_string()])
|
||||
.expect("/mcp show demo should parse"),
|
||||
CliAction::Mcp {
|
||||
args: Some("show demo".to_string())
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(&["/skills".to_string()]).expect("/skills should parse"),
|
||||
CliAction::Skills { args: None }
|
||||
@@ -5376,6 +5445,7 @@ mod tests {
|
||||
assert!(help.contains("/cost"));
|
||||
assert!(help.contains("/resume <session-path>"));
|
||||
assert!(help.contains("/config [env|hooks|model|plugins]"));
|
||||
assert!(help.contains("/mcp [list|show <server>|help]"));
|
||||
assert!(help.contains("/memory"));
|
||||
assert!(help.contains("/init"));
|
||||
assert!(help.contains("/diff"));
|
||||
@@ -5406,6 +5476,7 @@ mod tests {
|
||||
assert!(completions.contains(&"/session list".to_string()));
|
||||
assert!(completions.contains(&"/session switch session-current".to_string()));
|
||||
assert!(completions.contains(&"/resume session-old".to_string()));
|
||||
assert!(completions.contains(&"/mcp list".to_string()));
|
||||
assert!(completions.contains(&"/ultraplan ".to_string()));
|
||||
}
|
||||
|
||||
@@ -5442,7 +5513,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"help", "status", "sandbox", "compact", "clear", "cost", "config", "memory",
|
||||
"help", "status", "sandbox", "compact", "clear", "cost", "config", "mcp", "memory",
|
||||
"init", "diff", "version", "export", "agents", "skills",
|
||||
]
|
||||
);
|
||||
@@ -5515,6 +5586,7 @@ mod tests {
|
||||
assert!(help.contains("claw sandbox"));
|
||||
assert!(help.contains("claw init"));
|
||||
assert!(help.contains("claw agents"));
|
||||
assert!(help.contains("claw mcp"));
|
||||
assert!(help.contains("claw skills"));
|
||||
assert!(help.contains("claw /skills"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user