developmentHowTo

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.

typescript
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.

text
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.

M
MCPserver Team

MCP documentation and protocol implementation team

Published: 2026-07-21
Updated: 2026-07-21

References & Technical Specifications