r/mcp•
Future_AGI

An MCP tool call can return HTTP 200 and still have failed, and a standard OTel span won't show it

showcase

A tool call over Streamable HTTP comes back 200. The span closes green, normal latency, no error recorded. The agent reads the result and moves on. The call still failed, and nothing in the trace says so.

The common shape: a tool hits a downstream timeout and returns a result with isError: true and a text block saying the fetch failed. That's a valid JSON-RPC result, so the transport is 200 and the span is clean. The model treats the error text as data and keeps going. You notice later, when the final answer is wrong.

MCP has two separate error channels, and they land in different places:

Failure How it surfaces Span showsWhat to assert 
Tool ran, logic failedisError: true in the result200, OKresult.isError is false 
Unknown or disabled toolJSON-RPC error, code -32602  200, OKno error on the response
Bad argumentsJSON-RPC error, code -32602 200, OKno error on the response
Output breaks its schemaclient-side result-validation errorvariesoutput matches outputSchema

Two things hide it. Execution errors sit in isError inside a successful result, protocol errors sit in a JSON-RPC error object, so one check never covers both. And over Streamable HTTP both ride an HTTP 200, where OTel's HTTP conventions leave span status unset on any 2xx. Complete span, wrong result.

What actually catches it: assert on isError and on a JSON-RPC error code, not the HTTP status. Validate tool output against its schema, not just the input. Capture tool input and output as eval cases, so a wrong-but-200 result becomes a failing test instead of a green line.

We build an MCP gateway, so we've watched this one closely. Key off the tool result, not the transport.

How are you separating an unknown tool from bad arguments when both come back as -32602? String-matching the message feels brittle, so curious what's worked for people.

Posted Aug 3, 2026, 2:01 PM UTC

516
Loading comments…