Skip to content
Developer Preview

Errors

An MSP error is the fourth envelope shape: a response frame carrying error instead of result, where error has an integer code, a one-sentence human-readable message, and an optional structured data object. Exactly one of result or error appears on a response.

Branch on data.kind, never on message text

Section titled “Branch on data.kind, never on message text”

Every MSP-defined error carries error.data.kind, a stable camelCase category string; error.code is only the coarse numeric mapping. Clients should branch on data.kind and must never parse error.message — the text is for humans and carries no stability promise.

Here is a real denial from the corpus — a client calls session/userShell without having been granted that capability, and the server answers with the typed capabilityRequired error (the usershell-without-grant scenario):

{"dir":"client","raw":"{\"jsonrpc\":\"2.0\",\"id\":\"a1\",\"method\":\"session/userShell\",\"params\":{\"sessionId\":\"0198f0aa-1111-7000-8000-0000000000aa\",\"commandId\":\"018f6a24-9999-7bbb-8ccc-00000000feed\",\"commandText\":\"git status\"}}"}
{"dir":"server","raw":"{\"error\":{\"code\":-32010,\"data\":{\"capability\":\"userShell\",\"kind\":\"capabilityRequired\",\"retryable\":false},\"message\":\"session/userShell requires the userShell capability\"},\"id\":\"a1\",\"jsonrpc\":\"2.0\"}"}

Note the string request id echoed back exactly, the kind your client branches on, and the capability member naming what was missing.

All MSP-defined codes live in the JSON-RPC implementation-defined range from -32000 through -32099, allocated in per-section blocks: the transport and cross-cutting codes on this page, then a block each for session lifecycle, the command plane, the session view, approvals, and the raw log. The generated error reference is the full table, one page per code — check there before assuming a code is unused, and see reserved identifiers for the ranges that are allocated but not yet issued.

Casing is a deliberate contract: method names, field names, and data.kind values are camelCase; two vocabularies stay snake_case because they are durable runtime vocabulary transmitted verbatim — command-rejection reason strings and raw-log record payloads.

The transport and cross-cutting kinds, with the client posture for each:

  • parseError (-32700) — your line was not valid JSON; the error frame’s id is null because nothing could be recovered. Not retryable.
  • invalidRequest (-32600) — valid JSON, invalid JSON-RPC frame; the handshake-ordering kinds notInitialized and alreadyInitialized override it on the same code.
  • methodNotFound (-32601) — unknown method; also an experimental method called without opt-in, which reports kind experimentalRequired. Deferred and profile-withheld methods answer this way too, so treat it as “not on this host”, not “never exists”.
  • invalidParams (-32602) — malformed params for a known method; also an experimental field or enum variant sent without opt-in, again as kind experimentalRequired.
  • internal (-32603) — unexpected server failure; treat as fatal for that request.
  • overloaded (-32001) — ingress saturated; the one broadly retryable error: retry with exponential backoff and jitter, as the backpressure page describes.
  • inputTooLarge (-32002) — a frame or params payload exceeded the server limit; data.limitBytes says the budget; shrink the input, do not retry as-is.
  • capabilityRequired (-32010) — the method needs a capability this connection was not granted; data.capability names it; the remedy is to reconnect and request the grant, because grants happen only at the handshake.
  • notFound (-32011) — a run, item, subscription, or cursor anchor does not exist; a missing view-cursor anchor additionally sets data.reason to missingAnchor. Session lookups use their own typed kinds in the session block instead — sessionNotFound, sessionInUse, sessionAmbiguous and sessionNotLoaded.
  • interrupted (-32013) / cancelled (-32014) — a user interrupt or cancellation pre-empted the request; these are request-level errors, but the authoritative outcome is the terminal record on the session view, never the error frame alone.

Common optional data members across all of these: kind (always), retryable, descriptor, capability, reason, limitBytes, and a free-form details object — all additive-optional, so ignore members you do not recognize.

Two boundaries that surprise new implementers

Section titled “Two boundaries that surprise new implementers”
  • Idempotent replay is not a conflict. Re-sending a command with the same commandId and an identical payload returns the original ack, value-identical; rejection fires only when the same commandId arrives with a different payload, and that rejection is a commandRejected error in the command-plane block with a snake_case data.reason. The commands and idempotency page covers the model.
  • Mid-turn runtime failures are not request errors. Model failures and quota problems during a turn surface as events on the session view stream — a turn/completed carrying a failed terminal — not as an error response to the request that started the turn. Your error handling therefore has two layers: typed request errors here, and turn/item terminal states on the view.