MCP errors
Two kinds of failure, and getting them the wrong way round is how an agent integration quietly dies.
The distinction
An MCP client that receives an HTTP error status for a tool call concludes the server has failed. It marks the connection unhealthy, and often stops calling the tools at all. That is the right reading for some failures and badly wrong for others.
| Reported as | Means | What a client should do |
|---|---|---|
| An HTTP status | The request could not be accepted at all. | Fix the connection, or give up on it. |
| A JSON-RPC error | The message was unroutable — unknown tool, bad argument. | Fix the call. The session is fine. |
A tool error (isError: true) | The tool ran and could not do the thing. | Show the model the message. The session is fine. |
What goes where
| Condition | Reported as | Why |
|---|---|---|
| Missing or invalid key | 401 | A client with no usable credential cannot open a session; saying otherwise inside a JSON-RPC envelope would mean pretending one exists. |
| Rate limit exceeded | 429 + retry-after | Transient and per-request. Wait a second and send the same message again. |
| Unknown tool, or a bad argument | JSON-RPC error -32602 / -32601 | The request is unroutable, which is a protocol-level fact. |
| Account suspended | Tool error | The caller authenticated fine. They simply cannot spend right now — a bill, not an outage. |
| Monthly quota exhausted | Tool error | Same. |
| No shard answered | Tool error | Reported rather than returned as an empty result set: an agent that reads "no matches" when the fleet is degraded concludes the story does not exist. |
The shape of a tool error
{
"jsonrpc": "2.0", "id": 3,
"result": {
"isError": true,
"content": [{
"type": "text",
"text": "{\"code\":\"quota_exceeded\",\"message\":\"monthly quota exceeded and this key is set to stop at its quota rather than bill overage\",\"retry_after_secs\":394200,\"quota_limit\":1000,\"quota_used\":1000,\"quota_remaining\":0}"
}]
}
} The content block is JSON, so an agent can act on it rather than pattern-matching prose.
code | Means | Retry? |
|---|---|---|
suspended | Subscription canceled or payment failed. | Not until the account is settled. |
quota_exceeded | The monthly allowance is used up on a key that stops at its quota. | After retry_after_secs — which may be days. Or upgrade. |
rate_limited | Only seen on transports that cannot carry a status. | Yes, after a second. |
retry_after_secs and the quota figures are the same numbers the REST headers carry, so a client that
understands one surface understands the other. See Limits and plans.
Successful results
A success is a single text content block whose text is the JSON payload — exactly the body the equivalent REST endpoint returns. Parse it once. If you find yourself parsing twice, you are looking at a double-encoded envelope, which is a client bug (or a different server).
{
"jsonrpc": "2.0", "id": 1,
"result": {
"content": [{ "type": "text", "text": "{\"total\":6,\"stories\":[...]}" }],
"isError": false
}
}