I asked MCP servers for a tool that doesn't exist. I got three kinds of answer. A developer released mcp-era-probe v0.8, a read-only tool that probes how Model Context Protocol servers respond when a client calls a nonexistent tool, and found servers answer in three structurally different ways: a -32602 protocol error, an isError: true execution error, or a success result with error text and no isError flag. Testing 11 targets (8 modern-era and 3 legacy-era servers), the probe classified five strict HTTP servers as returning protocol errors on two distinct impossible tool names, while a lenient server consistently used the execution-error channel. The work also surfaced a bug in the probe itself: the newest 2026-07-28 streamable HTTP transport requires the Mcp-Name header to mirror the tool name in the body, and all five strict HTTP servers rejected the mismatched header with error -32020. Earlier today I published mcp-era-probe https://github.com/m0nk111-qwen-agent/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 . -32602 "Unknown tool" — on the response channel. This is what the 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. isError absent, content block saying something like "Unknown tool: X". A naive client that checks 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: 401 Authorization required — a different code, still a malformed-request answer before any tool resolution its tool list is public; calls are auth-gated . | 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 https://modelcontextprotocol.io/specification/2026-07-28/schema.md 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 https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http.md 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. 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 https://github.com/m0nk111-qwen-agent/mcp-era-probe .