Building MCP Servers
Error Handling in MCP
4 min read
Robust error handling is crucial for production MCP servers. Let's explore how to handle errors gracefully.
MCP Error Types
MCP defines standard error codes following JSON-RPC conventions:
| Code | Name | Description |
|---|---|---|
| -32700 | Parse Error | Invalid JSON received |
| -32600 | Invalid Request | Malformed request |
| -32601 | Method Not Found | Unknown method called |
| -32602 | Invalid Params | Wrong parameters |
| -32603 | Internal Error | Server-side error |
Raising Errors in Tools
When a tool encounters an error, raise it with a descriptive message:
from mcp.types import McpError
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_user":
user_id = arguments.get("user_id")
if not user_id:
raise McpError(
code=-32602,
message="user_id is required"
)
user = await db.get_user(user_id)
if not user:
raise McpError(
code=-32603,
message=f"User {user_id} not found"
)
return [TextContent(type="text", text=user.to_json())]
Error Categories
Handle errors at the appropriate level:
@server.call_tool()
async def call_tool(name: str, arguments: dict):
try:
# Validation errors
validate_arguments(name, arguments)
# Business logic errors
result = await execute_tool(name, arguments)
return result
except ValidationError as e:
# Client's fault - bad parameters
raise McpError(code=-32602, message=str(e))
except NotFoundError as e:
# Resource not found
raise McpError(code=-32603, message=str(e))
except ExternalAPIError as e:
# External service failure
raise McpError(
code=-32603,
message=f"External service unavailable: {e}"
)
except Exception as e:
# Unexpected error - log it
logger.exception("Unexpected error in tool call")
raise McpError(
code=-32603,
message="An unexpected error occurred"
)
User-Friendly Error Messages
The AI reads your error messages. Make them helpful:
# Bad - cryptic
raise McpError(code=-32603, message="ERR_DB_CONN_FAIL")
# Good - actionable
raise McpError(
code=-32603,
message="Database connection failed. Please check if the database service is running."
)
Logging for Debugging
Always log errors with context:
import logging
logger = logging.getLogger("mcp-server")
@server.call_tool()
async def call_tool(name: str, arguments: dict):
logger.info(f"Tool called: {name}", extra={"arguments": arguments})
try:
result = await execute_tool(name, arguments)
logger.info(f"Tool succeeded: {name}")
return result
except Exception as e:
logger.error(
f"Tool failed: {name}",
extra={"arguments": arguments, "error": str(e)},
exc_info=True
)
raise
Next, we'll build a complete server lab exercise. :::