Skip to content

feat(tools): check unknown-tool protocol errors and id correlation - #508

Open
astrogilda wants to merge 1 commit into
modelcontextprotocol:mainfrom
astrogilda-forks:tools-call-protocol-error
Open

astrogilda wants to merge 1 commit into
modelcontextprotocol:mainfrom
astrogilda-forks:tools-call-protocol-error

Conversation

@astrogilda

@astrogilda astrogilda commented Sep 18, 2026 •

Copy link
Copy Markdown

tools-call-protocol-error calls a tool the server does not have and reads the raw response frame. It catches an unknown tool answered with a CallToolResult carrying isError: true and no JSON-RPC error response, and a request id echoed with the wrong type.

The Tools page lists unknown tools under protocol errors, and the base protocol page makes the id echo a MUST for error and result responses.

SDK commit 2025-11-25 2026-07-28
typescript-sdk 60321700 9/9 pass 9/9 pass
python-sdk 6affe5c0 2/8 pass, 6 fail, 1 warning 2/8 pass, 6 fail, 1 warning

The ToolError raised at tool_manager.py:85 is folded into an isError result at server.py:437, which typescript-sdk changed in 2.0.0-alpha.1 (#1389), and the five dependent checks report "Not testable" per #248.

The vendored everything-server pins SDK 1.29, which folds the same way, so this change guards its tools/call handler and throws the McpError first. npm test passes 624/624. The negative fixture tools-call-unknown-tool-as-result.ts carries a vitest case for the FAILURE.

The probes are the wire half of two attack cases in agent-evidence-vectors, whose corpus runs from one command (edited 2026-09-25 to pin 0.13.0):

uvx agent-evidence-vectors==0.13.0 --verifier './verifier --json'
{"jsonrpc": "2.0", "id": 4242, "result": {"content": [{"type": "text", "text": "Unknown tool: conformance_tool_that_does_not_exist"}], "isError": true}}

…lation

Add the tools-call-protocol-error server scenario. It calls a tool the
server does not have, once with a numeric and once with a string request
id, and then tools/list with a string id, and checks eight things on the
raw frames: the unknown tool comes back as a JSON-RPC error response and
not as a CallToolResult with isError, the error code is the -32602 the
specification example uses (WARNING), error.code is an integer,
error.message is a string, an error response carries no result member,
and both error and result responses echo the request id with its type
intact.

The everything-server pins SDK 1.x, whose McpServer folds its own
InvalidParams error for an unknown tool into an isError result. Guard
the tools/call handler so the protocol error reaches the wire, the way
the 2.x SDK already does. A negative fixture answers unknown tools with
isError and stringifies ids; its vitest case asserts the FAILURE, the
WARNING, and the not-testable reporting of the dependent checks.
@astrogilda

Copy link
Copy Markdown
Author

Hey @pcarleton, the two workflow runs on this pull request are waiting for approval. I ran the test job against the merge commit on Node 24, and check, build and the test suite pass, including the new tools-call-protocol-error test.

The pkg.pr.new job passes up to its publish step, which I left to the real run.

Would you approve the runs when you have a moment?

This branch has not been deployed

No deployments
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