An unknown tool should get a JSON-RPC error, not isError
On 25 October the maintainer of a public MCP server wrote to us. Their report showed one observed risk, error_taxonomy, and they had already changed their code: a call to a tool that does not exist now gets a JSON-RPC error with code -32602 and the list of available tools. The change goes live with their next deploy, and our first run after that will record it.
That is the loop the reports are for. We publish what we observed, the maintainer reads it, and the next signed run shows what changed. The issue behind this check turns out to be common, so here is what the check looks at and how to test your own server for it.
What the check sends
Once per run, after the handshake and tools/list, our probe calls a tool name no server should have:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"__mcpcheckup_probe_nonexistent_tool__","arguments":{}}}
The check records VERIFIED when the reply is a well-formed JSON-RPC error object, whatever its code. It records OBSERVED_RISK when the reply is anything else, most often an ordinary result with isError: true. A 401 or 403 counts neither way: the request was turned away before the server reached its tool dispatch, so the check is UNVERIFIED.
Why the difference matters
The MCP specification separates two kinds of failure in its tools section:
- Protocol errors: the request cannot be handled at all, for example because it names a tool the server does not have. These are JSON-RPC errors. The specification's example for an unknown tool uses code -32602.
- Tool execution errors: the tool ran and failed, for example because an upstream API timed out. These go in a normal result with
isError: true, so the model can read the message and try something else.
An unknown tool name is a protocol error, because no tool ran. Answering it with isError: true tells the client that a tool by that name exists and failed. A client that treats the two cases differently, retrying a failed tool or refreshing its tool list after a protocol error, then takes the wrong path.
Test your own server
Start a session (Streamable HTTP, protocol revision 2025-11-25):
URL=https://your-server.example/mcp
curl -sS -D - "$URL" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2025-11-25' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"error-taxonomy-test","version":"1"}}}'
If the response has an Mcp-Session-Id header, send it back as a header on the next request. Then call a tool that does not exist:
curl -sS "$URL" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2025-11-25' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"no_such_tool","arguments":{}}}'
A conformant answer has an error member:
{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"Unknown tool: no_such_tool"}}
The shape we see most often instead:
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Tool no_such_tool not found"}],"isError":true}}
Where the fix goes depends on your stack. In a hand-written dispatcher it is usually the branch that looks the tool name up: return the JSON-RPC error there instead of building a result. Some frameworks wrap every exception from a tool handler as isError, "tool not found" included. If yours does, check whether a newer release answers unknown names with a protocol error.
How common it is
These are each tracked server's latest run from the past three days: 5,525 servers, read on 25 October 2026 at 13:26 UTC.
| error_taxonomy | Servers |
|---|---|
| VERIFIED | 1,850 |
| OBSERVED_RISK | 1,459 |
| UNVERIFIED | 2,216 |
Of the 1,459 observed risks:
- 1,411 were an ordinary result with
isError: true; - 29 were a result without
isError, as if the call had worked; - 19 were a reply that was not a valid JSON-RPC message.
Most UNVERIFIED servers asked for credentials before any tool dispatch, and our probe never sends credentials.
So of the 3,309 servers where the check could run, 44% recorded this risk. Like every check on MCP Checkup, it is reported on its own and never combined with the others into one number. For the seven kinds of answer we record and how they split when we first looked, see We asked 1,144 MCP servers for a made-up tool.
Check your own server
Look your server up at mcpcheckup.com/servers, or ask an agent connected to MCP Checkup to run get_mcp_report on it. Each check shows its state, and an observed risk shows what came back. After you deploy a fix, the next scheduled run records the new state. If you claim your server, it is checked more often and you get an email when a check fails.