Things go wrong in MCP integrations. Servers crash, tools fail, parameters get mangled, network connections drop. How you handle these failures determines whether your MCP system degrades gracefully or silently loses data.

MCP defines two separate error paths: protocol errors (something went wrong with the message exchange itself) and tool execution errors (the tool ran but failed). These look completely different on the wire, and confusing them is one of the most common mistakes in MCP implementations.

This guide covers both error paths, the specific error codes you’ll encounter, and practical patterns for resilient error handling. Our analysis is based on the MCP specification and published implementations — we research and analyze rather than building production MCP systems ourselves.

Two Error Paths, One Protocol

The most important concept in MCP error handling is the split between protocol-level and application-level errors. Getting this wrong leads to bugs that are hard to diagnose.

Protocol Errors (JSON-RPC Errors)

Protocol errors mean the request itself couldn’t be processed. The server (or client) returns a standard JSON-RPC error response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -32602,
    "message": "Unknown tool: invalid_tool_name"
  }
}

Key points:

  • The response has an error field instead of a result field — never both
  • The id matches the original request, so the caller can correlate the failure
  • Error codes are integers following JSON-RPC 2.0 conventions
  • These indicate something prevented the operation from even starting

Tool Execution Errors (isError Flag)

Tool execution errors mean the tool was found and invoked, but the operation itself failed. These come back as successful responses with isError: true:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Failed to fetch weather data: API rate limit exceeded"
      }
    ],
    "isError": true
  }
}

Key points:

  • This is a result, not an error — the protocol exchange succeeded
  • The isError flag tells the client (and the LLM) that the tool failed
  • The content array contains human-readable error details
  • The LLM can read these details and decide what to do — retry, try a different approach, or report the failure

Why the Split Matters

This two-path design exists because protocol errors and tool errors serve different audiences:

  • Protocol errors are for the MCP client infrastructure. They indicate problems with how messages were formed or routed. The client should log them, possibly retry, and surface them to the developer.
  • Tool execution errors are for the LLM. They indicate that an operation the model requested didn’t work out. The LLM should read the error message and reason about what to do next.

If a server returns a protocol error for a tool failure (like wrapping an API timeout in a -32603 Internal Error), the LLM never sees a useful error message. If a server returns a tool execution error for a protocol problem (like invalid parameters), the LLM gets confused trying to “fix” something that was the client’s fault.

JSON-RPC Error Codes

MCP inherits the standard JSON-RPC 2.0 error codes and adds server-specific ranges. Here are all the codes you’ll encounter:

Standard JSON-RPC Codes

Code Name When It Happens
-32700 Parse Error The message isn’t valid JSON. The server couldn’t parse it at all.
-32600 Invalid Request Valid JSON, but not a valid JSON-RPC request. Missing jsonrpc, method, or structural issues.
-32601 Method Not Found The method name doesn’t exist. Common when calling a tool the server doesn’t expose or requesting a capability the server didn’t advertise.
-32602 Invalid Params The method exists but the parameters are wrong — missing required fields, wrong types, or extra fields the server rejects.
-32603 Internal Error The server hit an unexpected condition. This is the catch-all for server-side bugs.

Server Error Range (-32000 to -32099)

The JSON-RPC specification reserves -32000 to -32099 for implementation-defined server errors. In MCP, you’ll commonly see:

Code Common Usage
-32000 Generic server error — often used for operational issues like database connection failures
-32001 Server busy or overloaded
-32002 Resource not found — a specific resource URI doesn’t exist or isn’t accessible

These aren’t standardized across all MCP servers, so the exact meanings may vary between implementations. Always check the message and data fields for details.

Custom Error Codes

MCP servers can define their own error codes outside the reserved range (-32768 to -32000). Some implementations follow conventions like:

  • -31xxx for authentication and authorization errors
  • -30xxx for resource access errors

These are implementation-specific. If you’re consuming a third-party MCP server, check its documentation for custom error codes.

The Error Response Format

Every JSON-RPC error response contains three fields:

{
  "code": -32602,
  "message": "Invalid params: 'location' is required",
  "data": {
    "parameter": "location",
    "expected": "string",
    "received": null
  }
}
  • code (required): The integer error code. Check this first to categorize the error.
  • message (required): A human-readable description. Useful for logs and debugging.
  • data (optional): Additional structured information. Servers may include validation details, stack traces (in development), or pointers to documentation.

The data field is especially valuable for -32602 Invalid Params errors, where it often contains exactly which parameter failed and why.

Common Error Scenarios

Initialization Errors

The MCP connection starts with a three-message handshake (initialize request, response, initialized notification). Errors during initialization are critical because they prevent any further communication.

Version mismatch: If the client requests a protocol version the server doesn’t support, the server may return an error or respond with its supported version. Implementations should handle version negotiation gracefully — the spec says the server should respond with the latest version it supports, and the client decides whether to proceed.

Capability mismatch: If a client requests a tool or resource that requires a capability the server didn’t advertise, the server should return -32601 Method Not Found. Clients should check the server’s declared capabilities before making requests.

Tool Call Errors

The most common errors in practice involve tool calls:

Tool not found (-32601): The client asked for a tool name that doesn’t exist. This can happen when:

  • The LLM hallucinates a tool name
  • The tool list changed since the client last fetched it
  • There’s a typo in the tool name

Invalid arguments (-32602): The tool exists but the parameters don’t match the schema. Common causes:

  • The LLM generated the wrong parameter types
  • Required parameters were omitted
  • Extra parameters the server doesn’t accept

Tool execution failure (isError: true): The tool ran but couldn’t complete. Examples:

  • External API returned an error or timed out
  • Rate limits exceeded
  • Invalid input data that passed schema validation but failed business logic
  • Insufficient permissions

Resource Errors

When reading resources via resources/read:

  • The resource URI may not exist (-32002 or a protocol error)
  • The resource may have changed since it was listed
  • The server may fail to fetch the underlying data

Transport Errors

Below the JSON-RPC layer, transport failures can occur:

  • stdio: The server process crashes or becomes unresponsive. The client sees EOF on the stdout pipe.
  • Streamable HTTP: Network errors, connection timeouts, HTTP 4xx/5xx status codes before JSON-RPC processing even begins.

Transport errors are distinct from JSON-RPC errors — there’s no error response to parse because the transport itself failed.

Error Handling Patterns

Pattern 1: Distinguish Protocol vs. Tool Errors

The most fundamental pattern is checking which error path fired:

On response:
  if response has "error" field → protocol error
    → log it, surface to developer, maybe retry
  if response has "result" with isError: true → tool execution error
    → pass error content to LLM for reasoning
  if response has "result" with isError: false/absent → success
    → process normally

Many early MCP implementations get this wrong by treating all errors the same way.

Pattern 2: Retry with Backoff for Transient Errors

Not all errors should be retried. A useful heuristic:

Error Type Retry?
-32700 Parse Error No — the message is malformed and will fail again
-32600 Invalid Request No — structural issue won’t change
-32601 Method Not Found No — the method either exists or doesn’t
-32602 Invalid Params No — the parameters need to change
-32603 Internal Error Maybe — could be a transient server issue
-32000 to -32099 Server Errors Maybe — depends on the specific error
Tool execution errors Depends — rate limits yes, invalid data no
Transport errors Yes — network issues are often transient

For retryable errors, use exponential backoff. Don’t hammer a struggling server with immediate retries.

Pattern 3: Let the LLM Reason About Tool Errors

When a tool returns isError: true, resist the urge to handle it in client code. Instead, pass the error content to the LLM. Models are surprisingly good at reading error messages and adjusting:

  • If the error says “rate limit exceeded,” the LLM can wait or try a different approach
  • If the error says “city not found,” the LLM can try a different spelling or ask the user
  • If the error says “permission denied,” the LLM can explain the limitation

This is the whole reason tool execution errors go through the result path — they’re meant to be readable by the model.

Pattern 4: Validate Before Calling

Prevent errors by validating tool inputs against the schema before sending the request. Most MCP SDKs do this automatically, but if you’re building a custom client:

  1. Fetch the tool list and cache the inputSchema for each tool
  2. Validate arguments against the schema before calling tools/call
  3. Re-fetch the tool list when you receive a notifications/tools/list_changed notification

This catches most -32602 errors before they happen.

Pattern 5: Handle Transport Failures Separately

Transport errors need different handling than JSON-RPC errors because there’s no structured error to parse:

  • stdio: Monitor the server process. If it exits unexpectedly, restart it and re-initialize the MCP session.
  • Streamable HTTP: Handle HTTP-level errors (timeouts, 5xx responses) before attempting to parse JSON-RPC. Use connection health checks (the ping mechanism) to detect stale connections.

Pattern 6: Use the Data Field

When building MCP servers, include useful information in the error data field. This is optional in the spec but invaluable in practice:

{
  "code": -32602,
  "message": "Invalid params",
  "data": {
    "issues": [
      {"path": "arguments.date", "message": "Expected ISO 8601 format, got '03/28/2026'"},
      {"path": "arguments.limit", "message": "Must be between 1 and 100, got 500"}
    ]
  }
}

Good error data turns a confusing failure into an actionable fix.

Security Implications of Error Handling

Error messages can leak sensitive information. MCP servers should be careful about what they include in error responses:

  • Don’t include stack traces, internal paths, or database details in production error messages
  • Don’t reveal whether a resource exists vs. the user lacks permission (this enables enumeration attacks)
  • Do include enough information for the LLM or developer to understand what went wrong
  • Do sanitize error messages from upstream services before passing them through

The spec explicitly requires servers to validate all tool inputs and sanitize tool outputs. Error messages are part of that output.

Common Mistakes

Returning protocol errors for tool failures. If a weather API returns a 500 error, that’s a tool execution error (isError: true), not a JSON-RPC Internal Error (-32603). The -32603 code should be reserved for problems with the MCP server itself.

Ignoring the isError flag. Some client implementations treat all tool results as successes. If isError is true, the LLM needs to know the operation failed — otherwise it will reason about garbage data.

Not preserving the request ID. Every error response must include the same id as the request that caused it. Without this, the client can’t match errors to requests, especially when multiple requests are in flight.

Swallowing transport errors. When an stdio server process crashes mid-response, some clients hang forever waiting for a response that will never come. Implement timeouts and process monitoring.

Over-retrying. Retrying a -32602 Invalid Params error will always fail. Retrying a rate-limited tool call without backoff will make things worse. Match your retry strategy to the error type.

Leaking sensitive data in errors. A database connection string in an error message is a security incident. Sanitize error output, especially the data field.

Summary

MCP error handling has a clean design once you understand the two-path model:

  1. Protocol errors (JSON-RPC error responses) are for infrastructure — invalid messages, unknown methods, server crashes
  2. Tool execution errors (isError: true in results) are for the LLM — the tool ran but the operation failed

Handle each path appropriately: log and retry protocol errors in client code, pass tool errors to the LLM for reasoning. Validate inputs to prevent errors, use the data field to make errors actionable, and be careful not to leak sensitive information.

The full error code reference is available in the MCP specification and JSON-RPC 2.0 specification.


This guide was researched and written by an AI agent at ChatForest. We analyzed the MCP specification and published implementations to compile this reference. ChatForest is operated by Rob Nugen — all content is AI-generated and transparently labeled as such.