mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-05-07 05:02:13 +08:00
fix(mcp): return typed error JSON for unsupported actions (info/describe/list-filter) (#2989)
`claw mcp info nonexistent --output-format json` and `claw mcp list nonexistent --output-format json` fell through to the generic help renderer, returning an opaque envelope with only `unexpected` set — no machine-readable error_kind. Fix: - Add typed guards in render_mcp_report_for/_json_for for: - `list <filter>`: list accepts no filter argument - `info <name>` / `describe <name>`: suggest `mcp show` - New render_mcp_unsupported_action_text/json helpers emit `ok:false`, `error_kind:"unsupported_action"`, `hint`, `requested_action` - `mcp show`, `mcp list`, `mcp help` existing paths unchanged Test: mcp_unsupported_actions_return_typed_error_not_generic_help asserts kind=="mcp", ok==false, error_kind=="unsupported_action" for info/list-filter/describe paths. Pinpoint: ROADMAP #504
This commit is contained in:
parent
65aa559733
commit
5eb4b8a944
@ -2674,10 +2674,44 @@ fn render_mcp_report_for(
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(args) if args.split_whitespace().next() == Some("list") && args.contains(' ') => {
|
||||||
|
// `mcp list <filter>` — list does not accept arguments; treat as unsupported action.
|
||||||
|
Ok(render_mcp_unsupported_action_text(
|
||||||
|
args,
|
||||||
|
"list accepts no filter argument; use `claw mcp list`",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(args) if matches!(args.split_whitespace().next(), Some("info" | "describe")) => {
|
||||||
|
Ok(render_mcp_unsupported_action_text(
|
||||||
|
args,
|
||||||
|
"use `claw mcp show <server>` to inspect a server",
|
||||||
|
))
|
||||||
|
}
|
||||||
Some(args) => Ok(render_mcp_usage(Some(args))),
|
Some(args) => Ok(render_mcp_usage(Some(args))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_mcp_unsupported_action_text(action: &str, hint: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"MCP\n Error unsupported action '{action}'\n Hint {hint}\n Usage /mcp [list|show <server>|help]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_mcp_unsupported_action_json(action: &str, hint: &str) -> Value {
|
||||||
|
json!({
|
||||||
|
"kind": "mcp",
|
||||||
|
"action": "error",
|
||||||
|
"ok": false,
|
||||||
|
"error_kind": "unsupported_action",
|
||||||
|
"requested_action": action,
|
||||||
|
"hint": hint,
|
||||||
|
"usage": {
|
||||||
|
"slash_command": "/mcp [list|show <server>|help]",
|
||||||
|
"direct_cli": "claw mcp [list|show <server>|help]",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn render_mcp_report_json_for(
|
fn render_mcp_report_json_for(
|
||||||
loader: &ConfigLoader,
|
loader: &ConfigLoader,
|
||||||
cwd: &Path,
|
cwd: &Path,
|
||||||
@ -2758,6 +2792,18 @@ fn render_mcp_report_json_for(
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(args) if args.split_whitespace().next() == Some("list") && args.contains(' ') => {
|
||||||
|
Ok(render_mcp_unsupported_action_json(
|
||||||
|
args,
|
||||||
|
"list accepts no filter argument; use `claw mcp list`",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(args) if matches!(args.split_whitespace().next(), Some("info" | "describe")) => {
|
||||||
|
Ok(render_mcp_unsupported_action_json(
|
||||||
|
args,
|
||||||
|
"use `claw mcp show <server>` to inspect a server",
|
||||||
|
))
|
||||||
|
}
|
||||||
Some(args) => Ok(render_mcp_usage_json(Some(args))),
|
Some(args) => Ok(render_mcp_usage_json(Some(args))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4745,6 +4791,38 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mcp_unsupported_actions_return_typed_error_not_generic_help() {
|
||||||
|
// `mcp info <name>` and `mcp list <filter>` must return typed errors, not raw help.
|
||||||
|
// Regression for #504: these previously fell through to render_mcp_usage with
|
||||||
|
// unexpected=arg, giving no machine-readable error_kind.
|
||||||
|
use crate::handle_mcp_slash_command_json;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
let cwd = PathBuf::from("/tmp");
|
||||||
|
|
||||||
|
let info_json = handle_mcp_slash_command_json(Some("info nonexistent"), &cwd)
|
||||||
|
.expect("info nonexistent should not error at IO level");
|
||||||
|
assert_eq!(info_json["kind"], "mcp");
|
||||||
|
assert_eq!(info_json["ok"], false);
|
||||||
|
assert_eq!(info_json["error_kind"], "unsupported_action");
|
||||||
|
assert!(info_json["hint"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains("show"));
|
||||||
|
|
||||||
|
let list_filter_json = handle_mcp_slash_command_json(Some("list nonexistent"), &cwd)
|
||||||
|
.expect("list nonexistent should not error at IO level");
|
||||||
|
assert_eq!(list_filter_json["kind"], "mcp");
|
||||||
|
assert_eq!(list_filter_json["ok"], false);
|
||||||
|
assert_eq!(list_filter_json["error_kind"], "unsupported_action");
|
||||||
|
|
||||||
|
let describe_json = handle_mcp_slash_command_json(Some("describe myserver"), &cwd)
|
||||||
|
.expect("describe myserver should not error at IO level");
|
||||||
|
assert_eq!(describe_json["kind"], "mcp");
|
||||||
|
assert_eq!(describe_json["ok"], false);
|
||||||
|
assert_eq!(describe_json["error_kind"], "unsupported_action");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_invalid_mcp_arguments() {
|
fn rejects_invalid_mcp_arguments() {
|
||||||
let show_error = parse_error_message("/mcp show alpha beta");
|
let show_error = parse_error_message("/mcp show alpha beta");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user