Faults
A fault is data, not a transport failure. It arrives as a normal 200 whose tool
result carries isError: true; the server answered, and the answer was a refusal.
Only a protocol-level problem uses the JSON-RPC error channel.
This matters for retries: a refusal re-earns itself, so retrying one is wasted work.
Repair the call instead — field names what to fix, and details carries the allowed
values when the server can enumerate them.
conflictdependency_unavailableforbiddengoneinternalnot_foundpayload_too_largerate_limitedunauthenticatedvalidation
The envelope
Section titled “The envelope”{ "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "vectadyne", "version": "0.1.0-local" } }, "content": [ { "text": "\u003cprose\u003e", "type": "text" } ], "isError": true, "resultType": "complete", "structuredContent": { "error": { "code": "validation", "field": "scope_key", "message": "\u003cprose\u003e", "request_id": "\u003cper-call\u003e", "retryable": false } }}retryable is the server’s judgement, not a guess from the code. Honour it:
retrying a validation fault only re-earns it, and retrying past a rate limit
deepens it. request_id is what makes a bug report actionable.