# MCP gateway errors

> How the MCP gateway refuses a call: tool results with isError for the model to read, HTTP errors before any MCP message, and JSON-RPC protocol errors.

Source: https://infro.io/docs/mcp-gateway/errors

> **Note:** INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See [Early access](https://infro.io/early-access).

> **Note:** The MCP gateway is served at `https://mcp.infro.io/gateway/mcp` and `https://mcp.infro.io/gateway/<slug>/mcp`, on the Business plan and above.

A refusal reaches the agent in one of three ways, depending on when it happens.

## Tool errors

Once the key is authenticated and the tool is known, a refusal is a tool result with `isError: true`, which the MCP specification intends for the model to read and adapt to. It carries a text block with the message and `structuredContent.error` with `type`, `code` and `message`, in the shape of INFRO's [error envelope](https://infro.io/docs/api/errors). Messages say what to do next; they never describe other teams' servers or tools.

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "isError": true,
    "content": [
      {
        "type": "text",
        "text": "`github_create_issue` changed on its server after it was approved, so INFRO is refusing it. Ask an admin to review the change and approve it again in INFRO, under MCP & connectors. Do not retry until then."
      }
    ],
    "structuredContent": {
      "error": {
        "type": "permission_denied",
        "code": "tool_definition_changed",
        "message": "`github_create_issue` changed on its server after it was approved, so INFRO is refusing it. Ask an admin to review the change and approve it again in INFRO, under MCP & connectors. Do not retry until then."
      }
    }
  }
}
```

| `code` | `type` | When | Reached the server? |
| --- | --- | --- | --- |
| `tool_not_allowed` | `permission_denied` | The key's grant does not allow the tool: no grant on the tool's server, a `write` tool under a `read` grant, or a tool not named in a `selected` grant. | No |
| `tool_not_approved` | `permission_denied` | The tool is `pending`, `blocked` or `removed`. | No |
| `tool_definition_changed` | `permission_denied` | The server changed the tool's definition after it was approved. Do not retry until an admin approves it again. | No |
| `invalid_tool_arguments` | `invalid_request_error` | The arguments do not match the approved `inputSchema` (the message names the path), exceed 256 KB, or need a network `$ref`. | No |
| `guardrail_blocked` | `permission_denied` | A guardrail `block` rule matched the arguments (input) or the result (output). The message names the detector, never the matched text. | No for input; yes for output |
| `guardrail_unavailable` | `permission_denied` | The guardrail engine failed and the policy says `on_engine_error: block`. | Depends on the stage |
| `tool_result_too_large` | `upstream_error` | The result is larger than the server's result limit. Ask for less. | Yes |
| `tool_result_invalid` | `upstream_error` | The result's `structuredContent` does not match the approved `outputSchema`. The result was withheld. | Yes |
| `upstream_unavailable` | `upstream_error` | The network failed; the server answered 5xx, a redirect or something that is not MCP; it refused the credential INFRO holds for it (401 or 403: an admin must replace the credential); or it rate-limited that credential (429). The message says which, cleaned of credentials. | Attempted |
| `upstream_timeout` | `request_timeout` | The server's time limit passed. | Yes |
| `upstream_rejected` | `upstream_error` | The server answered the call with a JSON-RPC protocol error, for example because it no longer knows the tool or refused the arguments itself. Its message, cleaned, is passed on. | Yes |

A result the server itself marked `isError: true` is not a gateway refusal: it reaches the agent unchanged, with credential-shaped text removed.

## HTTP errors

These are answered before any MCP message is processed, in INFRO's usual [error envelope](https://infro.io/docs/api/errors).

| Status | `code` | When |
| --- | --- | --- |
| 401 | — | Missing, unknown, expired or revoked gateway key. The response carries `WWW-Authenticate: Bearer`. |
| 403 | `ip_not_allowed` | The key has an IP allowlist and the request came from elsewhere. |
| 403 | `key_disabled` | The key is turned off. |
| 403 | `organization_suspended` | An operator hold is on the organization. |
| 403 | `project_archived` | The key's project is archived. |
| 403 | `plan_feature` | The organization's plan does not include the MCP gateway (from Business). |
| 403 | `subscription_required` | The organization has no active subscription or trial. |
| 404 | `mcp_server_not_found` | The per-server path's slug is unknown, disabled, outside the key's projects or environments, or not granted to the key. The answer is the same in every case. Type `not_found`. |
| 400 | `invalid_attribution` | A malformed `X-INFRO-*` attribution header or `?trace_id=`. |
| 429 | — | The key's rate limit, shared with its model calls. `Retry-After` says when to retry. |
| 429 | `mcp_server_rate_limited` | The server's own limit, across every key. `Retry-After` says when to retry. |
| 405 | — | Anything but `POST`. |

## Protocol errors

| JSON-RPC code | When |
| --- | --- |
| `-32602` | On the aggregated endpoint, a `tools/call` naming a tool that does not exist in the organization ("Unknown tool"). On the per-server endpoint, the same for a name the server does not have. |
| `-32020` | With HTTP `400`: the request's `Mcp-Method` or `Mcp-Name` header disagrees with its body. |
| `-32700` | With HTTP `400`: the body is not JSON. |

## Retrying

- Retry `upstream_unavailable`, `upstream_timeout` and both `429`s with backoff; on a `429`, wait for `Retry-After`. An `upstream_unavailable` whose message says the server refused INFRO's credential needs an admin instead.
- Do not retry `tool_not_allowed`, `tool_not_approved`, `tool_definition_changed`, `guardrail_blocked`, `401`, `403` or `404`: they need an admin or a configuration change.
- After `invalid_tool_arguments` or `tool_result_too_large`, change the arguments before calling again.

## Configuration errors

Registering and changing servers, tools and grants, in the console or the [management API](https://infro.io/docs/manage/resources#mcp-gateway), can return `400` with these codes:

| `code` | When |
| --- | --- |
| `invalid_mcp_server_url` | The URL is not public HTTPS, carries a user name, password or fragment, points at a private address or at INFRO. |
| `invalid_mcp_credential` | A credential header name is invalid or reserved, or there are too many headers. |
| `mcp_discovery_failed` | The server did not answer `tools/list` with the credential given. |
| `mcp_server_slug_taken` | Another server in the organization has this slug. |
| `mcp_tool_name_clash` | Two tools would get the same exposed name; the message lists them. |
| `mcp_tool_definition_stale` | The definition changed since the hash you approved (`expected_sha256`). Review it again. |
| `mcp_tool_removed` | The server no longer lists the tool, so there is nothing to approve. |
| `mcp_tool_not_approved` | A class can be set only on an approved tool. |
| `mcp_trifecta_unconfirmed` | The grant would give one key private data, untrusted content and a way to send data out. Repeat it with `confirm_trifecta: true` if that is intended. |
| `key_revoked` | A grant names a revoked key. |
