Error codes
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 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:
{
"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 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. |
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 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 rule. execute() didn't run. | Sign in as a caller the rule allows, or change the rule. |
ENTRY_UNAVAILABLE | The tool's availableWhen 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. |
APPROVAL_REQUIRED, APPROVAL_OPERATION_FAILED, APPROVAL_SCOPE_NOT_ALLOWED, CHALLENGE_VALIDATION_FAILED | The Approval plugin 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 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. 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 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 (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. |
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. |
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). 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. |
-32003 | Forbidden. A resource or prompt refused by an authorities 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. |
-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. |
Error classes 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), and the Node HTTP server answers 413 for a body over http.bodyLimit.