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.
How codes are organized
Section titled “How codes are organized”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 typed errors every client must handle
Section titled “The typed errors every client must handle”The transport and cross-cutting kinds, with the client posture for each:
parseError(-32700) — your line was not valid JSON; the error frame’sidis null because nothing could be recovered. Not retryable.invalidRequest(-32600) — valid JSON, invalid JSON-RPC frame; the handshake-ordering kindsnotInitializedandalreadyInitializedoverride it on the same code.methodNotFound(-32601) — unknown method; also an experimental method called without opt-in, which reports kindexperimentalRequired. 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 kindexperimentalRequired.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.limitBytessays the budget; shrink the input, do not retry as-is.capabilityRequired(-32010) — the method needs a capability this connection was not granted;data.capabilitynames 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 setsdata.reasontomissingAnchor. Session lookups use their own typed kinds in the session block instead —sessionNotFound,sessionInUse,sessionAmbiguousandsessionNotLoaded.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
commandIdand an identical payload returns the original ack, value-identical; rejection fires only when the samecommandIdarrives with a different payload, and that rejection is acommandRejectederror in the command-plane block with a snake_casedata.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/completedcarrying 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.