Skip to content

fix(mcp): report tool failures as typed MCP errors - #42

Merged
kyle-elliott merged 1 commit into
mainfrom
fix/typed-tool-errors
Oct 10, 2026
Merged

kyle-elliott merged 1 commit into
mainfrom
fix/typed-tool-errors

Conversation

@kyle-elliott

@kyle-elliott kyle-elliott commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Part 1 of 3 for #37, plus follow-up #46. Stack: this PR → #43 → #44 → #46.

Problem

MCP tools returned failure text inside a successful result. Hosts recorded isError: false for failed calls. The CLI found failures by parsing that text (query::cli_result).

Change

  • ApiFailure (src/client/failure.rs): each non-success HTTP answer carries its status, its first typed API error code, the bounded Retry-After delay, and the unchanged human text.
  • ToolError and FailureKind (src/query/error.rs): Retryable { after }, Unavailable, InvalidArgument, IndexPending, IndexFailed, Refused. ToolError::from_client classifies by the typed cause, not by the message text.
  • Every #[tool] and every query::* function returns Result<String, ToolError>. rmcp marks Err results with isError: true.
  • One failure surface for every tool: <op> failed: <detail>, then a next: <step> line. The <op> unavailable — … form is gone.
  • The CLI prints only the first line and exits with a non-zero status. query::cli_result is removed.
  • An unknown codebase selector is InvalidArgument. The message names the accepted values (an immutable codebase ID or a local directory path) and points to list_codebases.
  • An applied edit whose summary cannot be rendered stays a success and names the edit_id.

There are no deadline, retry, or index_codebase behavior changes in this PR.

Verification

  • cargo fmt --check, cargo test --workspace --locked, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo doc with -D warnings, and git diff --check pass.
  • New tests cover classification (409 GraphLoading with Retry-After, 502, 400/404/422, connect errors), the rendering of each kind, isError through rmcp, and a CLI exit status that is not zero.

MCP tools returned failure text inside a successful result, so hosts
recorded isError: false for failed calls. The CLI recovered failures by
parsing that text.

Add one typed failure surface:

- `ApiFailure` (client): every non-success HTTP answer carries its status,
  first typed API error code, bounded Retry-After delay, and the unchanged
  human text.
- `ToolError` and `FailureKind` (query): operation, kind, and detail.
  `from_client` classifies by the typed cause, not by message text.
  rmcp marks an `Err(ToolError)` result with isError: true.
- One rendering for every tool: `<op> failed: <detail>` and a
  `next: <step>` line. The `<op> unavailable — ...` form is removed.
- `query::*` functions and every `#[tool]` return
  `Result<String, ToolError>`. `query::cli_result` is removed. CLI commands
  propagate the error, print only the first line, and exit non-zero.
- An unknown codebase selector is an invalid argument that names the
  accepted values.
- An applied edit whose summary cannot be rendered stays a success and
  names the edit id.

No deadline, retry, or index_codebase behavior changes.

Refs #37
@kyle-elliott
kyle-elliott force-pushed the fix/typed-tool-errors branch from 7cdff4b to 3a74cee Compare October 10, 2026 00:05
@kyle-elliott
kyle-elliott merged commit 08e21ab into main Oct 10, 2026
6 checks passed
@kyle-elliott
kyle-elliott deleted the fix/typed-tool-errors branch October 10, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant