Introduction

MCP uses standard JSON-RPC 2.0 error codes to signal protocol-level failures. Understanding these codes and when they fire is critical for debugging and for designing tools that report errors through the right channel.

Key Concepts

  • -32700 ParseError: The message is not valid JSON. The SDK rejects it before your code runs.
  • -32600 InvalidRequest: The JSON is valid but does not conform to the JSON-RPC 2.0 structure (missing method, wrong jsonrpc version).
  • -32601 MethodNotFound: The client called a tool, resource, or prompt name that does not exist on the server.
  • -32602 InvalidParams: The parameters failed Zod schema validation — wrong type, missing required field, or constraint violation.
  • -32603 InternalError: An unhandled exception escaped your handler. The SDK catches it and wraps it in this error.

Real World Context

You deploy an MCP server and a client reports that tool calls are failing silently. By inspecting the JSON-RPC error codes in the MCP Inspector, you quickly identify that -32602 errors are firing because the client is sending a number where a string is expected. Without understanding error codes, this would take hours to debug.

Deep Dive

Protocol Errors vs Tool Execution Errors

This is the most important distinction in MCP error handling. Protocol errors and tool execution errors serve different audiences and have different visibility.

Protocol errors (JSON-RPC error codes) are handled by the host application, not the LLM. When a -32602 InvalidParams error fires, the host might show an error dialog or log it, but the LLM never sees the error message in its conversation context. Protocol errors are invisible to the LLM.

Tool execution errors (isError: true in the tool result) are visible to the LLM. The error message appears in the conversation, and the LLM can reason about it, apologize to the user, or retry with different parameters.

Here is a side-by-side comparison:

typescript
// Protocol error — invisible to LLM, handled by host
// This happens automatically when Zod rejects the input type
// e.g., client sends { age: "twenty" } but schema expects z.number()
// SDK responds with: { error: { code: -32602, message: '...' } }

// Tool execution error — visible to LLM, can self-correct
server.registerTool('get-weather', {
  description: 'Get weather for a city',
  inputSchema: z.object({ city: z.string() })
}, async ({ city }): Promise<CallToolResult> => {
  const data = await weatherApi.lookup(city);
  if (!data) {
    // City is a valid string (passes schema) but not a real city
    return {
      content: [{ type: 'text', text: `City "${city}" not found. Try a major city name like "London" or "Tokyo".` }],
      isError: true
    };
  }
  return { content: [{ type: 'text', text: JSON.stringify(data) }] };
});

When to Use Each Error Type

The rule is based on who needs to see the error:

ScenarioError TypeWhy
Wrong parameter typeProtocol (-32602)SDK handles automatically
Valid type, invalid valueTool (isError: true)LLM can retry with correct value
Tool name does not existProtocol (-32601)SDK handles automatically
External API failureTool (isError: true)LLM can inform user or retry
Unhandled exceptionProtocol (-32603)Genuine bug, SDK catches it
File not foundTool (isError: true)LLM can ask user for correct path

Schema-level type mismatches are caught by Zod before your handler runs and produce -32602 automatically. You never need to create protocol errors manually — the SDK handles them. Your responsibility is to return isError: true for business logic failures where the LLM can take useful action.

The Complete Error Code Reference

Here are the five standard JSON-RPC 2.0 error codes that MCP uses. You do not need to memorize them, but knowing they exist helps when reading error logs in the MCP Inspector:

typescript
// Standard JSON-RPC 2.0 error codes used by MCP
const ERROR_CODES = {
  ParseError:      -32700,  // Invalid JSON
  InvalidRequest:  -32600,  // Valid JSON, invalid JSON-RPC structure
  MethodNotFound:  -32601,  // Unknown tool/resource/prompt name
  InvalidParams:   -32602,  // Zod schema validation failed
  InternalError:   -32603,  // Unhandled exception in handler
};

All five codes follow the JSON-RPC 2.0 specification. MCP does not add custom error codes — it uses the standard set.

Common Pitfalls

  1. Throwing on business logic failures — If the input type is correct but the value is invalid (e.g., a city that does not exist), do not throw. Return isError: true so the LLM can self-correct. Throwing makes the error invisible to the LLM.
  2. Manually creating -32602 errors — The SDK handles schema validation automatically. Focus on business logic errors in your handler.
  3. Confusing error audiences — Protocol errors are for the host application; tool errors are for the LLM. Choose based on who needs to act on the error.

Best Practices

  1. Let the SDK handle protocol errors — Do not try to create JSON-RPC error responses manually. Define strict Zod schemas and let the SDK validate.
  2. Use isError for all LLM-recoverable failures — Any error where the LLM could retry with different input or inform the user should be a tool execution error.
  3. Include actionable information in tool errors — Tell the LLM what went wrong and what to try instead. This turns errors into opportunities for self-correction.

Summary

  • MCP uses five standard JSON-RPC error codes: -32700 (ParseError), -32600 (InvalidRequest), -32601 (MethodNotFound), -32602 (InvalidParams), -32603 (InternalError).
  • Protocol errors are invisible to the LLM; tool execution errors (isError: true) are visible.
  • The SDK handles protocol errors automatically through Zod schema validation.
  • Use isError: true for business logic failures where the LLM can take corrective action.

Code Examples

typescript
// Protocol error (automatic, invisible to LLM):
// SDK rejects { age: "twenty" } against z.number()
// Response: { error: { code: -32602, message: '...' } }

// Tool execution error (manual, visible to LLM):
return {
  content: [{ type: 'text', text: 'City "Atlantis" not found. Try: London, Tokyo, New York.' }],
  isError: true
};
✓ Completed