Errors¶
13.1 Protocol errors versus tool errors¶
MCP has two channels for failure. A JSON-RPC error means the request itself was wrong; an
isError: true tool result means the tool ran and failed, which the model can read and react
to (fix its arguments, pick another asset). cloudg uses them like this:
| Failure | Tool call | Resource read | Prompt get | Completion |
|---|---|---|---|---|
| Unknown or hidden name / URI | JSON-RPC -32602 | JSON-RPC -32002 (handshake) or -32602 (2026) | JSON-RPC -32602 | empty values |
| Invalid arguments | isError, -32602 |
n/a | JSON-RPC -32602 (missing required) | n/a |
Secret in an argument (standard and stricter) |
isError, -32602 |
JSON-RPC -32602 | JSON-RPC -32602 | JSON-RPC -32602 |
| Policy denial | isError, -31001 |
JSON-RPC -31001 | JSON-RPC -31001 | JSON-RPC -31001 |
| Rate limit (policy, concurrency, live guard) | isError, -31029 |
JSON-RPC -31029 | JSON-RPC -31029 | JSON-RPC -31029 |
| Not found inside the handler (asset, dataset) | isError, -32602 |
JSON-RPC -32002 / -32602 | JSON-RPC error | empty values |
| Path outside allowed roots | isError, -31001 |
n/a | n/a | n/a |
| Timeout | isError, "timeout" |
n/a | n/a | n/a |
| Unexpected exception | isError, "handler_error" |
JSON-RPC -32603 "Internal error" (native) | JSON-RPC -32603 | empty values |
(In-process, the "JSON-RPC" cells are raised MCPLayerError subclasses with the same code.)
13.2 Codes¶
| Code | Name | Raised for |
|---|---|---|
| -32700 | Parse error | Body or line is not JSON |
| -32600 | Invalid request | Bad JSON-RPC envelope, batches where not allowed, not initialized, HTTP-level rejections in the native server's body |
| -32601 | Method not found | Unknown method, or a method of the other era |
| -32602 | Invalid params | InvalidArgumentsError, NotFoundError (unknown tool, prompt, resource on 2026, stale cursor), bad cursors |
| -32603 | Internal error | Base MCPLayerError, unexpected server errors; the live tools' PreflightError |
| -32002 | Resource not found | Unknown resource on handshake-era connections |
| -32020 | Header mismatch | 2026 HTTP headers that are missing or disagree with the body |
| -32022 | Unsupported protocol version | 2026 envelope with an unknown version; initialize on a 2026 connection |
| -31001 | Access denied | AccessDeniedError: policy denial, path outside the allowed roots, force=true on a live tool without the admin or operator role |
| -31029 | Rate limited | RateLimitedError: policy token bucket, concurrency queue timeout, the live operation guard (LiveOperationRefused, section 15) |
cloudg's own codes sit in -31xxx because -32768 to -32000 belong to JSON-RPC and -32020 to -32099 are reserved by MCP.
13.3 What an error result looks like¶
cloudg/error_code is either one of the numeric codes above or a string (timeout,
handler_error). cloudg/error_data carries the error's data, passed through the output
pipeline. Real results:
Invalid arguments (cloudg mcp call find_assets --args '{"limit": 0, "colour": "red"}' --raw):
{
"content": [{"type": "text", "text": "Invalid arguments: limit: Input should be greater than or equal to 1; colour: Extra inputs are not permitted"}],
"isError": true,
"_meta": {
"cloudg/error_code": -32602,
"cloudg/error_data": [
{"loc": "limit", "msg": "Input should be greater than or equal to 1", "type": "greater_than_equal"},
{"loc": "colour", "msg": "Extra inputs are not permitted", "type": "extra_forbidden"}
]
}
}
Policy denial, from a policy with hide_denied: false and deny_categories: [admin]:
{
"content": [{"type": "text", "text": "Access to tool 'reset' denied: category 'admin' denied by policy 'doc-demo'"}],
"isError": true,
"_meta": {"cloudg/error_code": -31001,
"cloudg/error_data": {"policy": "doc-demo", "reason": "category 'admin' denied by policy 'doc-demo'"}}
}
Rate limit (one call per minute, second call):
{
"content": [{"type": "text", "text": "Rate limit exceeded for tool 'boom' (1/minute); retry in 60.0s"}],
"isError": true,
"_meta": {"cloudg/error_code": -31029,
"cloudg/error_data": {"retry_after": 60.0, "limit": "1/minute", "scope": "principal"}}
}
Timeout (a sync tool with timeout_seconds=0.5 asked to sleep 2 seconds):
{"content": [{"type": "text", "text": "Tool slow_sync timed out"}], "isError": true,
"_meta": {"cloudg/error_code": "timeout"}}
Unexpected exception:
{"content": [{"type": "text", "text": "RuntimeError: could not reach arn:aws:iam::123456789012:role/Deploy"}],
"isError": true, "_meta": {"cloudg/error_code": "handler_error"}}
That last one ran under a policy without transforms, so the ARN in the exception text came
through. Under standard and stricter profiles the error text goes through the same output
pipeline as data. The traceback is logged on the server (Tool boom failed, logger
cloudg.mcp).
Protocol errors from the native server, in-process (session.handle(...), handshake era):
tools/call nope {"code": -32602, "message": "Unknown tool: nope", "data": {"name": "nope"}}
resources/read test://items/omega
{"code": -32002, "message": "No item omega", "data": {"uri": "test://items/omega"}}
resources/read test://nothing {"code": -32002, "message": "Unknown resource: test://nothing", "data": {"uri": "test://nothing"}}
prompts/get greet (denied) {"code": -31001, "message": "Access to prompt 'greet' denied: prompt denied by policy 'deny-prompts'", "data": {"policy": "deny-prompts", "reason": "prompt denied by policy 'deny-prompts'"}}
tools/call with "arguments": "oops"
{"code": -32602, "message": "arguments must be an object"}
no/such {"code": -32601, "message": "Method not found", "data": "no/such"}
resources/read test://items/omega on a 2026-07-28 request
{"code": -32602, "message": "No item omega", "data": {"uri": "test://items/omega"}}
In-process, the same failures raise. From the catalog:
read_resource("cloudg://nope") NotFoundError -32602 "Unknown resource: cloudg://nope"
read_resource("cloudg://assets/does-not-exist") ReferenceNotFoundError -32602 "No asset matches the reference in this dataset. ..." data={"value": "does-not-exist", "dataset": "prod", "suggestions": []}
get_prompt("investigate_asset", {}) InvalidArgumentsError -32602 "Missing required prompt arguments: ref"
The exception classes, all in cloudg.mcp.core and re-exported from cloudg.mcp:
class MCPLayerError(Exception): # code -32603; .message, .data
class InvalidArgumentsError(MCPLayerError): # -32602
class NotFoundError(MCPLayerError): # -32602
class AccessDeniedError(MCPLayerError): # -31001
class RateLimitedError(MCPLayerError): # -31029
cloudg.mcp.state adds NoDatasetError and ReferenceNotFoundError, both NotFoundError
subclasses. Raise them from your own handlers with a helpful message and data=; the model
sees both.