Repository navigation
MCPServer answers an unknown tool with an isError result; the spec (and the TS SDK) use JSON-RPC -32602 #3659
Description
Activity
- addedbugSomething isn't workingSomething isn't workingv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)v1Affects the v1.x maintenance lineAffects the v1.x maintenance line
on Oct 8, 2026 Reproduced on current
main(91941ed) and read the full path through the code:ToolManager.call_toolraisesToolError("Unknown tool: nope")for an unknown name (src/mcp/server/mcpserver/tools/tool_manager.py:85)._handle_call_toolcatches it and returnsCallToolResult(content=[...], is_error=True)(src/mcp/server/mcpserver/server.py:456), so the client sees a successful result withis_error, never a JSON-RPC error.- The resource side already does the right thing:
_handle_read_resourcemaps an unknown resource toMCPError(code=INVALID_PARAMS)(server.py:474-475), i.e.-32602.
So the fix can mirror the resource path: distinguish "tool not found" from other
ToolErrors (for example aToolNotFoundErrorsubclass, raised intool_manager.pyat both sites), and in_handle_call_toolre-raise it asMCPError(code=INVALID_PARAMS, message=...)instead of returningis_error=True. Argument-validation failures keep returningisErrorresults as they do today, since the spec treats invalid arguments as a tool error, not a protocol error.Happy to prepare the patch if it's wanted.
I re-checked this on main@91941ed with the SDK's own
Client, pinned once to each era (mode="legacy", which negotiates 2025-11-25, andmode="2026-07-28"). In both, an unknown tool name still returnsCallToolResult(is_error=True, "Unknown tool: ..."). Invalid arguments and a tool that raises both stayis_error=True, which is correct.#1872 (approved in March) fixes this, but 11 files now conflict, and its resource half uses -32002, where
MCPServeron main now returns -32602 for an unknown resource.I have a narrow port ready locally. It adds a
ToolNotFoundError(ToolError)subclass that carries the requestedname, and thetools/callhandler turns it into -32602 only when that name is the one in the request.call_tool()still raisesToolError, and subclasscall_tool()overrides keep working.On @maxisbey's question in #2422 (what a separate exception type gets you): here it's needed for subclasses that override
call_tool(). Without overrides, the handler could just check the registry for the requested name. But an override can serve names that aren't registered, so only the exception raised by the failed lookup says the requested tool wasn't found. Thenamecheck keeps an override that looks up a different missing name atis_error=True. (A registered tool that calls a missing one already staysis_error=True, becauseTool.runre-wraps the error.)Would you rather have #1872 refreshed, or this port? I reported this and would like to fix it myself if you take an outside PR for it.
Disclosure: an AI assistant helped with the investigation and the draft patch.
Summary
The spec's error-handling section for tools lists "Unknown tool" under protocol errors, returned as a standard JSON-RPC error with
code: -32602. This is in both 2025-11-25 and 2026-07-28:server/tools.mdxin 2026-07-28, around line 742.MCPServer(mcp 2.3.0) instead returns a successfulCallToolResultwithis_error=Trueand the textUnknown tool: <name>. It comes fromToolErrorinsrc/mcp/server/mcpserver/tools/tool_manager.py, at lines 72 and 85. The behaviour is described indocs/troubleshooting.md. Meanwhiledocs/whats-new.md(the low-level example, note 5) says-32602"is the spec's own answer for an unknown tool". The TypeScript SDK returns-32602("Tool nope not found").Reproduction
Output with mcp 2.3.0:
Expected, per the spec:
Notes
server-stateless(sep-2575-server-rejects-undeclared-capability) calls a tool name the server does not list. With this behaviour the probe reports "Server executed 'test_missing_capability'…", where for the TS SDK it reports "not testable". So the deviation also produces a confusing conformance finding for ordinary servers.isErrorresult), the 2026-07-28 spec text would need to change, or the docs could say the high-level server deviates here.