Error codes and limits
Tool errors (C10)
Section titled “Tool errors (C10)”A failed tool call returns a tool result with isError: true. The text content is one JSON object with error set to one of the codes below and a message of at most 500 characters. The message is safe text: it never contains a backend response, a secret or an argument value.
| Code | Extra fields | When |
|---|---|---|
unauthenticated |
none | The call is not bound to a verified gateway request |
access_denied |
none | The user, the AI client or a policy does not allow the call |
tool_disabled |
none | An administrator disabled the tool or its server, or its definition waits for approval |
not_found_or_denied |
none | The resource does not exist, the user may not see it, or it lies outside the granted scope. One text for all three, so the answer does not reveal which. Also the answer for a tool that the endpoint filter of the request leaves out |
invalid_argument |
none | The arguments break the tool’s inputSchema, or the text fails the hygiene rules. The message may name a declared argument and the broken rule, never the value |
credential_required |
system, connectUrl |
The user has not connected an account for the system. connectUrl is the portal page |
confirmation_required |
confirmationId, description, previewUrl, registerUrl, expiresAt (all optional) |
A DESTRUCTIVE call needs the current code of the user’s authenticator. Either a pending call exists, or the user has no authenticator and only registerUrl is given |
confirmation_failed |
none | No pending call, expired, different arguments, wrong or used code, or attempts exhausted |
confirmation_superseded |
none | A newer destructive operation of the same user replaced the pending call. The user starts again |
confirmation_cancelled |
none | The owner cancelled the pending call in the portal |
confirmation_blocked |
retryAfterSeconds |
Another destructive operation of this user awaits confirmation. A client must not ask for a code for this call |
step_up_required |
stepUpUrl |
The call needs a fresher multi-factor login |
rate_limited |
retryAfterSeconds |
The call is over a gateway limit. retryAfterSeconds is at least 1 |
upstream_unavailable |
none | The target system, the secret store or the policy source is unavailable. A server also answers this when its policy data is missing, not approved or does not match its policies |
internal_error |
none | Anything the server did not anticipate, a result that breaks its output schema, or a credential type outside the server’s accepted types |
In a server, the only exception that leaves a tool is SanitizedToolException. The starter maps for you: no credential to credential_required, InvalidToolArgumentException to invalid_argument, SecurityException to access_denied, an unreachable backend or a 502, 503 or 504 to upstream_unavailable, everything else to internal_error. Add your own mapping with a ToolErrorMapping bean, using C10 codes only.
HTTP errors before the tool layer
Section titled “HTTP errors before the tool layer”| Response | Body | When |
|---|---|---|
401 |
unauthenticated, with WWW-Authenticate: Bearer resource_metadata="..." |
The client token is missing or invalid |
400 |
{"error":"invalid_request","reason":"invalid_filter"} |
The query of the /mcp address is not a valid endpoint filter |
400 or 414 |
{"error":"invalid_request"} |
The query is longer than the transport accepts (about 15 KB, depends on the deployment) |
429 |
rate_limited with retryAfterSeconds and a Retry-After header |
A request that is not a tool call is over a limit |
503 |
upstream_unavailable with Retry-After |
The gateway is overloaded, the session store or the identity provider’s keys are unavailable, or the rate counter does not answer |
502, 504 |
bad_gateway, gateway_timeout |
A gateway instance failed or timed out during the request. The body says: The request may or may not have been processed. Check the result before repeating a write. |
An UPDATE call is delivered at least once. The gateway never retries UPDATE or DESTRUCTIVE calls, so design an update tool so that a repeat is harmless (see Write a server from the template).
What a server returns to the gateway
Section titled “What a server returns to the gateway”| Response | When |
|---|---|
401 |
The internal JWT is missing or fails a check, see Authentication. On the two /.well-known/ paths the body also carries the reason, for example wrong_purpose |
409 policy_data_stale |
The server holds no policy data or an older revision than the token’s prv. The gateway pushes the data and repeats the call once |
400 |
The request body is not a single JSON object, or the policy data is not valid |
413 |
The request body is larger than max-request-body |
503 upstream_unavailable |
The gateway’s key set or the Redis store of jti is unavailable |
Error bodies of the internal APIs
Section titled “Error bodies of the internal APIs”| API | Values |
|---|---|
| Credential broker, authenticator enrollment | invalid_code, enrollment_invalid |
| Credential broker, OAuth completion | flow_invalid, system_not_configured |
| Credential broker, saving a secret | secret_invalid |
| Portal, connecting an account | flow_invalid, provider_error, cancelled |
| Portal, sign-in | cancelled, idp_error, session_expired, step_up_failed |
When an administrator adds a server, C5 can answer 422 with address_refused, plain_http_not_allowed, tls_handshake_failed, manifest_unreachable, manifest_invalid, system_mismatch, system_reserved or namespace_conflict, and 409 with server_exists or connection_changed.
Limits
Section titled “Limits”Values are defaults of the gateway and starter; an operator can change them with the listed setting.
Calls per user
Section titled “Calls per user”Counted in Redis per window. A tools/call over a limit gets rate_limited in the tool result; other MCP requests get 429. Gateway responses carry no X-RateLimit-* headers.
| Limit | Default | Setting |
|---|---|---|
| Tool calls per user | 600 | MCP_RATE_LIMIT_PER_USER |
| Per user and AI client | 300 | MCP_RATE_LIMIT_PER_CLIENT |
| Per user and tool | 120 | MCP_RATE_LIMIT_PER_TOOL |
| Per user and destructive tool | 10 | MCP_RATE_LIMIT_DESTRUCTIVE_PER_TOOL |
| Window | 1m | MCP_RATE_LIMIT_WINDOW |
| Limits on | true |
MCP_RATE_LIMIT_ENABLED |
If Redis does not answer, the gateway refuses the call (upstream_unavailable) instead of letting traffic through uncounted.
Concurrency
Section titled “Concurrency”| Limit | Default | Setting |
|---|---|---|
| MCP requests served at once per gateway instance | 128 | MCP_MAX_CONCURRENT_REQUESTS |
Wait for a free place before 503 |
100ms | MCP_CONCURRENCY_QUEUE_WAIT |
Retry-After of that 503 |
2 s | MCP_SHED_RETRY_AFTER_SECONDS |
Retry-After when the session store fails |
10 s | MCP_SESSION_STORE_RETRY_AFTER_SECONDS |
| Open event streams per instance | 2000 | MCP_MAX_OPEN_STREAMS |
Time one server has to answer tools/list |
3000 ms | MCP_TOOLS_LIST_TIMEOUT |
A server that is slower than that is left out of the tool list of that request.
Server side
Section titled “Server side”| Limit | Default | Setting |
|---|---|---|
| Request body of a server | 4MB | aiwcorpo.mcp.platform.internal-jwt.max-request-body |
| Tool result | 1MB | aiwcorpo.mcp.platform.tools.max-result-size |
| List in a result checked by policy | 200 elements | aiwcorpo.mcp.platform.tools.max-filtered-elements |
| Lifetime of an internal JWT | 60 s | fixed by the contract |
| Clock tolerance | 30 s | aiwcorpo.mcp.platform.internal-jwt.clock-skew, at most 30 s |
| Validity of a paging cursor | 15m | aiwcorpo.mcp.platform.paging.cursor-validity |
| Backend response read by the template server | 1MB | example.backend.max-response-size (rename for your system) |
A result over max-result-size or a list over max-filtered-elements becomes internal_error, so a tool that returns lists pages them. An identifier longer than 1024 characters is invalid before any matching.
Broker and portal
Section titled “Broker and portal”| Limit | Default | Setting |
|---|---|---|
| OAuth starts per user and window | 10 | broker.rate-limits.oauth-starts |
| OAuth completions per user and window | 20 | broker.rate-limits.oauth-completions |
| Window of those two | 1m | broker.rate-limits.window |
| Token saves per portal session and window | 10 per 1m | PORTAL_LIMIT_SECRET_WRITE_MAX, PORTAL_LIMIT_SECRET_WRITE_WINDOW |
| Authenticator writes per portal session | 10 per 10m | PORTAL_LIMIT_AUTHENTICATOR_MAX, PORTAL_LIMIT_AUTHENTICATOR_WINDOW |
| Authenticator registration starts per user and hour | 10 | broker.authenticator.enrollment-starts-per-hour |
| Request body of the broker | 16384 bytes | broker.max-request-bytes |
| Unused connection kept before cleanup | 180d | broker.retention.unused-after |
| Activity retention | 30d | MCP_ACTIVITY_RETENTION |
