Errors and retries
REST errors use a stable envelope:
{ "error": { "code": "insufficient_scope", "status": 403, "message": "The caller cannot perform this operation.", "request_id": "req_…", "retryable": false, "details": {} }}Branch on code, status, and retryable, never on message. Log request_id for operator
correlation. Honor retry_after_seconds when present.
Common classes include:
| Status | Typical codes | Handling |
|---|---|---|
| 400 | invalid_request, invalid_json |
Fix the request; do not retry unchanged |
| 401 | invalid_token, session_expired |
Refresh or replace credentials |
| 403 | insufficient_scope, origin_not_allowed |
Change authorization or browser origin |
| 404 | not_found, route_not_found |
Re-discover opaque IDs; verify the route |
| 409 | idempotency_conflict, draft_conflict |
Reconcile state; do not invent a new retry body |
| 413 | body or upload too large | Reduce content before retrying |
| 422 | unknown_scope, cardinality or provider constraint |
Correct semantic input |
| 429 | rate or waiter limit | Wait for the supplied retry interval |
| 502–504 | provider or upstream failure | Retry only when retryable is true |
MCP reports protocol failures as JSON-RPC errors and operation failures in the tool result. Use the
structured error fields returned by the tool. A timed-out wait is normally a successful result with
timed_out: true, not an error.
For mutating calls, retry with the same idempotency key and identical body. Reusing a key for a different operation produces a conflict by design.