# Error codes

> What FrontMCP's JSON-RPC error codes and tool error results mean, and how to fix them.

Source: https://frontmcp.dev/reference/errors

FrontMCP reports problems in two ways. **Protocol errors** are JSON-RPC `error` responses with a numeric code: the request itself couldn't be handled. **Tool errors** are normal results with `isError: true`: the request was fine, but the tool couldn't do what was asked. Models can read tool errors and try again. [Error classes](https://frontmcp.dev/reference/sdk/error-classes) lists the class behind each code, and which ones keep their message in production.

## Tool errors

A tool error arrives as a result like this:

```json
{
  "content": [{ "type": "text", "text": "There's no ticket T-9." }],
  "isError": true,
  "_meta": { "errorId": "err_4418a34f3368d9dc", "code": "PUBLIC_ERROR" }
}
```

| `_meta.code` | Cause | Fix |
| --- | --- | --- |
| `INVALID_INPUT` | The arguments didn't match `inputSchema`. `execute()` didn't run. The text lists each problem. | Check the field paths in the message. If a model keeps getting it wrong, describe the field better. |
| `PUBLIC_ERROR` | The tool failed with a `PublicMcpError`, through `this.fail()` or `throw`. A `PublicMcpError` with a code of its own sends that code instead. | Intended: the message is for the model. |
| `SERVER_ERROR` | The tool called `this.fail(new Error(...))` with a plain `Error`. In production the text is "Internal FrontMCP error" and an error ID. | Use `PublicMcpError` if the message is meant for the model. |
| `INTERNAL_ERROR` | The tool failed with an `InternalMcpError` through `this.fail()`. An `InternalMcpError` with a code of its own sends that code. In production the text is "Internal FrontMCP error" and an error ID. | Intended, if the message is only for your logs. |
| `TOOL_EXECUTION_ERROR` | `execute()` threw an error that isn't public, like a plain `Error` or an `InternalMcpError`. In development the text is prefixed `Tool "…" execution failed:` and ends with the stack; in production it's "Internal FrontMCP error" and an error ID. | Find the error ID in the server's log, next to the original message and stack. Use `PublicMcpError` for errors the model should read. |
| `INVALID_OUTPUT` | The result didn't match the tool's `outputSchema`, or held `NaN` or `Infinity`. An [agent's](https://frontmcp.dev/reference/sdk/agent#returning-structured-output) answer that doesn't match its `outputSchema` fails the same way (since 1.8.7; 1.8.5 and 1.8.6 reported `TOOL_EXECUTION_ERROR`). In development the text names the first field that didn't match; in production it's "Output validation failed. Please contact support." | Fix the result, or the schema if the result is right. See [Shaping Tool Results](https://frontmcp.dev/learn/shaping-tool-results#declaring-the-shape-with-outputschema). |
| `FLOW_EXITED_WITHOUT_OUTPUT` | `execute()` returned `undefined`. | Return a value, even `{ ok: true }`. |
| `TOOL_NOT_FOUND` | No tool has that name, the tool has `visibility: "internal"`, or [CodeCall](https://frontmcp.dev/reference/plugins/codecall) takes it out of `tools/list` (since 1.9). The text is `Tool "…" not found`. | Check the name in `tools/list`. |
| `AUTHORITY_DENIED` | The caller doesn't pass the tool's [`authorities`](https://frontmcp.dev/reference/auth/authorities#what-a-refused-caller-gets) rule. `execute()` didn't run. | Sign in as a caller the rule allows, or change the rule. |
| `ENTRY_UNAVAILABLE` | The tool's [`availableWhen`](https://frontmcp.dev/reference/server/environment) doesn't match the environment the server runs in. | Run the server where the tool is meant to be available, or change `availableWhen`. |
| `RATE_LIMIT_EXCEEDED`, `CONCURRENCY_LIMIT`, `QUEUE_TIMEOUT`, `EXECUTION_TIMEOUT` | A tool's `rateLimit`, `concurrency` or `timeout`, or a `throttle` default, stopped the call. The message says when to retry, or which limit. | Intended. See [Guard options](https://frontmcp.dev/reference/sdk/guard#what-a-caller-gets-back). |
| `APPROVAL_REQUIRED`, `APPROVAL_OPERATION_FAILED`, `APPROVAL_SCOPE_NOT_ALLOWED`, `CHALLENGE_VALIDATION_FAILED` | The [Approval plugin](https://frontmcp.dev/reference/plugins/approval#errors) refused a call that needs the user's approval (`APPROVAL_REQUIRED`, whose text is the tool's `approvalMessage`), or a grant it can't make. They are public errors, so the client reads the message in production too. | Grant the approval, or change the grant the tool makes. |
| `REMEMBER_SCOPE_NOT_ALLOWED` | A [memory tool](https://frontmcp.dev/reference/plugins/remember#rememberscopenotallowederror) was called with a scope outside the plugin's `tools.allowedScopes`, the default scope included. The message names the scope and the allowed ones. | Pass an allowed scope, or add it to `allowedScopes`. |
| `FEATURE_FLAG_DISABLED` | The tool is [disabled by a feature flag](https://frontmcp.dev/reference/plugins/feature-flags#the-featureflag-option). The text names the tool and the flag. | Turn the flag on for the caller, or call a different tool. |
| `REMEMBER_IDENTITY_REQUIRED` | A [Remember](https://frontmcp.dev/reference/plugins/remember#rememberidentityerror) scope needs a caller to keep values apart, and the caller is anonymous (`session` and `user` scope). | Sign the caller in, or use `global` scope. |
| `AGENT_CALL_DEPTH_EXCEEDED` | An agent called another, which called another, past [`swarm.maxCallDepth`](https://frontmcp.dev/reference/sdk/agent#swarm) (3 by default). The text lists the agents running. New in 1.9. | Give a chain of agents a way to end before the limit: see [Agents That Call Agents](https://frontmcp.dev/learn/agents-that-call-agents#handoffs-that-never-stop). |
| `JOB_NOT_AUTHORIZED` | `execute_job` or `execute_workflow` was called with a name that doesn't exist, or that the caller may not run. | Check the name with `list_jobs`. See [`@Job`](https://frontmcp.dev/reference/sdk/job). |

## Protocol errors

| Code | Meaning | Fix |
| --- | --- | --- |
| `-32001` | **Unauthorized**. A tool needs a credential from one of its `authProviders` that the user hasn't connected, and `data.authUrl` says where to connect it ([Progressive auth](https://frontmcp.dev/reference/auth/progressive#asking-for-a-credential-when-a-tool-needs-it)). Also: a resource or prompt that failed with `UnauthorizedError`, or a request refused by `throttle.ipFilter` (with HTTP `403`). | Send the user to `authUrl`, or mark the provider `required: false`. For `ipFilter`, see [Guard options](https://frontmcp.dev/reference/sdk/guard#ipfilter). |
| `-32003` | **Forbidden**. A resource or prompt refused by an [`authorities`](https://frontmcp.dev/reference/auth/authorities#what-a-refused-caller-gets) rule, or that failed with a public error whose `statusCode` is `403`. | Sign in as a caller who's allowed. |
| `-32020` | **Header mismatch** (MCP 2026-07-28). A mirrored header (`Mcp-Method`, `Mcp-Name`, `MCP-Protocol-Version`) disagrees with the request body. | Send headers that match the body. |
| `-32021` | **Missing client capability** (MCP 2026-07-28). The request needs a capability the client didn't declare in `_meta`. | Declare the capability in `io.modelcontextprotocol/clientCapabilities`. |
| `-32022` | **Unsupported protocol version**. The error lists the `supported` and `requested` versions. | Use a version the server supports. FrontMCP serves every revision from `2024-11-05` to `2026-07-28`. |
| `-32029` | **Rate limited**. The server's `throttle.global` limit refused the request, with HTTP `429` and a `Retry-After` header. The message says how many seconds to wait. | Wait that long. See [Guard options](https://frontmcp.dev/reference/sdk/guard#global). |
| `-32601` | **Method not found**. Under 2026-07-28 this includes removed methods such as `initialize`, `ping` and `logging/setLevel`. | Use the 2026-07-28 equivalents, such as `server/discover`. |
| `-32602` | **Invalid params**, including a resource or prompt that doesn't exist (a missing resource was `-32002` before 2026-07-28), a missing prompt argument, a call to a task-only tool from a client without the tasks extension, and a public error from a resource or prompt, such as a `PublicMcpError`, whose code is in `data.code`. | Check the method's parameters and the resource URI, or read the message. |
| `-32603` | **Internal error**: a resource or prompt failed with an error that isn't public, or with a public error whose `statusCode` is 500 or more. For errors that aren't public, production's message is "Internal FrontMCP error" and an error ID. | Find the error ID in the server's log. Fail with a `PublicMcpError` if the message is for the client. See [`this.fail`](https://frontmcp.dev/reference/sdk/fail#in-resources-and-prompts). |

[Error classes](https://frontmcp.dev/reference/sdk/error-classes#from-a-resource-or-a-prompt) shows how an error's `statusCode` picks its code.

## HTTP status codes

Under 2026-07-28, `GET` and `DELETE` on the MCP endpoint return `405`: every request is a `POST`, and there's no session to close. Tool errors, including every tool limit, arrive with `200`. `throttle.global` answers with `429` and `throttle.ipFilter` with `403`, before the request reaches a tool (see [Guard options](https://frontmcp.dev/reference/sdk/guard#what-a-caller-gets-back)), and the Node HTTP server answers `413` for a body over `http.bodyLimit`.
