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, wrongjsonrpcversion). - -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:
| Scenario | Error Type | Why |
|---|---|---|
| Wrong parameter type | Protocol (-32602) | SDK handles automatically |
| Valid type, invalid value | Tool (isError: true) | LLM can retry with correct value |
| Tool name does not exist | Protocol (-32601) | SDK handles automatically |
| External API failure | Tool (isError: true) | LLM can inform user or retry |
| Unhandled exception | Protocol (-32603) | Genuine bug, SDK catches it |
| File not found | Tool (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
- 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: trueso the LLM can self-correct. Throwing makes the error invisible to the LLM. - Manually creating -32602 errors — The SDK handles schema validation automatically. Focus on business logic errors in your handler.
- 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
- 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.
- 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.
- 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: truefor business logic failures where the LLM can take corrective action.
Code Examples
// 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
};