Back to News & Insights
Web Development October 11, 2026 · 5 min read

I asked MCP servers for a tool that doesn't exist. I got three kinds of answer.

Earlier today I published mcp-era-probe, a read-only instrument for checking which version of the...

I asked MCP servers for a tool that doesn't exist. I got three kinds of answer.

Earlier today I published mcp-era-probe, a read-only instrument for checking which version of the Model Context Protocol a server actually speaks. Today I shipped v0.8 of it, and the new check asks a simpler question with a more uncomfortable answer: what happens when a client calls a tool that does not exist?

The probe never touches real tools. The only tools/call it sends by default targets a name that cannot exist on any server (era-probe-nonexistent-tool — and as of v0.8.2 it can do this twice, with two distinct impossible names). Calling a nonexistent tool is the cheapest possible question to ask a server, and it turns out servers answer it in three structurally different ways.

The three answers Protocol error. A standard JSON-RPC error — in practice -32602 "Unknown tool" — on the response channel. This is what the spec's own error-handling example shows: unknown tool = protocol-level error, not a tool result. Execution error. A successful response whose result carries isError: true. The spec explicitly wants execution failures inside results this way — but "the tool name is a typo" is not an execution failure, it's a malformed request. A "successful" result with the error as text. HTTP 200, isError absent, content block saying something like "Unknown tool: X". A naive client that checks isError before trusting content will happily read the error string as the tool's output.

I ran the channel check against 11 targets: 8 modern-era servers (7 hosted HTTP endpoints plus the context7 npm server over stdio) and 3 legacy-era contrast servers: All five strict HTTP servers answered on the protocol channel with -32602 "Unknown tool" — exactly the spec example's shape. Context7 (stdio) behaves the same. A sixth (getle.ad, dual-era) also answered on the protocol channel, but with a plain 401 Authorization required — a different code, still a malformed-request answer before any tool resolution (its tool list is public; calls are auth-gated). One modern server (askmiles.ai) answered on the execution channel — isError: true. Genuine and functional, just a different error philosophy for malformed requests. The three legacy servers couldn't answer at the modern stateless route at all (session errors — they only speak inside a handshake session). Inside a legacy session, two answered with isError: true execution results; the third (agentberg.ai) went further: a plain success-shaped result, isError absent, text saying "Unknown tool". That last one is the trap: no transport-level error exists to catch, and the client that doesn't parse result text ships garbage downstream.

| server | era | unknown-tool answer | code | |---|---|---|---| | goji.agency | modern (strict) | protocol error | -32602 | | projectory.ae | modern (strict) | protocol error | -32602 | | botinfo.ai | modern (strict) | protocol error | -32602 | | bidclub.ai | modern (strict) | protocol error | -32602 | | bizmoon.ai | modern (strict) | protocol error | -32602 | | getle.ad | modern (dual-era) | protocol error | 401 (auth-gated calls; list public) | | askmiles.ai | modern (lenient) | execution error | isError: true | | context7 (npm, stdio) | modern (strict) | protocol error | -32602 | | agentberg.ai | legacy | success + error text (in legacy session) | none (isError absent) | | mcp.aivonic.ai | legacy | execution error (in legacy session) | isError: true | | mcp.autonomad.ai | legacy | execution error (in legacy session) | isError: true |

"Maybe the answer depends on the exact tool name, or was a fluke of the first call" — a reviewer rightly pushed me to close that hole. v0.8.2 re-probed the five strict HTTP servers plus the lenient one with a second, different impossible name. All six classified identically across both names — five on the protocol channel twice, the lenient one on the execution channel twice (goji.agency initially disagreed because it rate-limited the back-to-back call; a gentle single retry with the same second name agreed — the sweep driver now spaces that host out). The channel classification is a property of the server's error handling, not of the probe's name choice.

Is a success result without isError spec-legal? Yes — I verified this against the official 2026-07-28 schema page rather than inferring from behavior: isError?: boolean ("Default: false") and structuredContent?: unknown are both optional on CallToolResult. So context7 omitting isError on success is conformant, and the "result channel" classification I record for lenient servers is an observation about behavior, not a compliance verdict.

Building the check surfaced a bug in my own probe. The newest protocol era (2026-07-28, streamable HTTP transport) requires Mcp-Name on a tools/call request to mirror the tool name in the body. My first sweep sent the probe's client name in that header instead. All five strict HTTP servers rejected it on the spot with the spec's disagreement error (-32020, "the request headers and body disagree"), which is how I learned my client was wrong — a spec-required consistency check catching the caller, not the server. One lenient server accepted the malformed request silently, which is its own small lesson: strictness is where the spec's defenses actually live.

The honest caveats These servers are not independent. The five servers that rejected my bad header do so with byte-identical error wording, and the protocol-channel answers may share one or two SDK implementations behind the eight modern-era server identities. What this matrix measures well is implementation lineage; it does not prove ten independent teams all read the spec the same way. The era label is a correlate, not the driver. One modern-era server sits on the execution channel, and legacy behavior clusters by how the server was built, not by its handshake vintage. Eleven targets is a census of my reachable cohort, not of the ecosystem.

If you build MCP clients: handle all three channels, in this order of trust — protocol errors first (the malformed-request case), then isError (execution failures), and only then read content. If you build servers, the cheap conformance win is the one my probe got for free: answer an unknown tool with a protocol error and you match the spec's own example, the schema, and every strict implementation measured here.

The probe and the full evidence (per-target JSON artifacts, the consistency run, the schema extraction) are in the repo.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation