MCP Error Handling Patterns
Categorize and handle MCP tool errors so validation, runtime, timeout, and network failures each return a clear message instead of crashing the server.
Quick Answer / TL;DR
Wrap every MCP tool call in a try-catch, classify the failure (input validation, runtime, timeout, or downstream network error), log the full error with context server-side, and return a short, user-facing message with isError set to true rather than surfacing a raw stack trace to the client.
Key Takeaways
- Classify errors: validation, runtime, timeout, and network failures each need a different response.
- Never return a raw stack trace to the client; log it server-side and return a short message instead.
- For non-critical failures, fall back to cached or partial data instead of failing the whole call.
Classifying and returning errors
Zod validation failures mean the client sent bad input; timeouts mean an operation ran too long; everything else that throws during execution is a runtime error. Each should produce a distinct, short message back to the client.
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
return await withTimeout(executeTool(request), 30000);
} catch (error) {
if (error instanceof ZodError) {
return { content: [{ type: "text", text: `Validation error: ${error.message}` }], isError: true };
}
if (String(error).includes("timed out")) {
return { content: [{ type: "text", text: "Operation timed out. Try a smaller request." }], isError: true };
}
console.error("Tool execution failed:", { tool: request.params.name, error });
return { content: [{ type: "text", text: `Tool execution failed: ${(error as Error).message}` }], isError: true };
}
});
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
return Promise.race([
promise,
new Promise<never>((_, reject) => setTimeout(() => reject(new Error("timed out")), ms)),
]);
}Graceful degradation
When a non-critical dependency fails, prefer returning cached or partial data with a flag over failing the whole tool call. Reserve hard failures for cases where a partial answer would be misleading or unsafe.
try {
return await fetchRealTimeAnalytics();
} catch (error) {
console.warn("Falling back to cached analytics:", error);
const cached = await getCachedAnalytics();
return { ...cached, _cached: true };
}MCP Error Handling Patterns FAQs
Direct answers for developers, operators, and Indian teams evaluating MCP.