How the MCP Server Handles and Propagates Errors to Client Requests

The MCP server normalizes all tool failures into McpError objects with standardized ErrorCode values, propagates them through a central handler in src/index.ts, and serializes them into JSON-RPC error responses that clients receive regardless of transport method.

The flux159/mcp-server-kubernetes repository implements a Model Context Protocol (MCP) server that exposes Kubernetes operations as callable tools. Understanding how this MCP server handles and propagates errors to client requests is critical for building resilient integrations, as it ensures consistent error reporting across STDIO, Server-Sent Events (SSE), and HTTP transports.

Error Handling Architecture Overview

The error propagation pipeline follows a four-stage normalization process. First, individual tools wrap low-level failures (such as execFileSync exceptions or JSON parsing errors) into McpError instances using error codes from the MCP SDK. Second, the central CallTool request handler in src/index.ts catches these errors and ensures any unhandled exceptions are similarly wrapped. Third, the transport layer—whether streamable-http.ts for HTTP or the SDK's built-in handlers for STDIO/SSE—serializes the error into a JSON-RPC 2.0 error response. Finally, telemetry middleware records the failure in OpenTelemetry spans before re-throwing so the original error still reaches the client.

Step 1: Tools Convert Raw Failures into McpError

Each tool implementation in src/tools/ follows a strict pattern of converting raw system errors into structured McpError objects. This ensures that Kubernetes command failures, parsing errors, or configuration issues carry consistent error codes such as ErrorCode.InternalError or ErrorCode.InvalidRequest.

For example, in src/tools/kubectl-get.ts around line 260, JSON parsing failures are explicitly caught and re-thrown as standardized errors:

if (parseError) {
  console.error("Error parsing JSON:", parseError);
  throw new McpError(
    ErrorCode.InternalError,
    `Failed to parse kubectl output: ${parseError}`
  );
}

Similarly, src/tools/kubectl-scale.ts wraps execution failures from execFileSync into McpError instances with descriptive messages indicating the specific resource type and failure reason. This pattern ensures that every tool failure is immediately tagged with an appropriate MCP error code before propagating upward.

Step 2: Central CallTool Handler Normalizes Errors

The central request handler in src/index.ts (around lines 43-49) serves as the primary gatekeeper for error normalization. When a client invokes a tool via the CallToolRequestSchema, the handler wraps the execution in a try-catch block that distinguishes between expected McpError instances and unexpected runtime exceptions.

try {
  // …dispatch to the appropriate tool
} catch (error) {
  if (error instanceof McpError) throw error;          // keep original error
  throw new McpError(
    ErrorCode.InternalError,
    `Tool execution failed: ${error}`
  );                                                    // wrap unknown errors
}

This logic ensures that well-formed errors from tools pass through unchanged, while programming errors or unexpected exceptions are captured and converted into InternalError codes. This guarantees that the transport layer always receives a predictable McpError object, regardless of the underlying failure type.

Step 3: Transport Layer Serializes Errors to JSON-RPC

Once normalized, errors must be serialized into the wire format expected by MCP clients. The flux159/mcp-server-kubernetes repository implements multiple transports, with src/utils/streamable-http.ts providing a clear example of HTTP error handling (lines 44-55).

When the request handler throws an error, the HTTP transport catches it and returns a standard JSON-RPC 2.0 error response:

} catch (error) {
  console.error("Error handling MCP request:", error);
  if (!res.headersSent) {
    res.status(500).json({
      jsonrpc: "2.0",
      error: { code: -32603, message: "Internal server error" },
      id: null,
    });
  }
}

The error code -32603 corresponds to InternalError in the JSON-RPC specification. For STDIO and SSE transports, the underlying @modelcontextprotocol/sdk automatically performs this serialization, ensuring that clients receive identical error structures regardless of how they connect to the server.

Step 4: Telemetry Middleware Records Without Intercepting

Error observability is maintained through src/middleware/telemetry-middleware.ts (lines 24-45), which wraps tool execution in an OpenTelemetry span. Crucially, this middleware records error details without swallowing the exception, allowing the error to continue propagating to the client.

try {
  const result = await handler(request);
  // record success …
  return result;
} catch (error: any) {
  // record failure attributes
  span.setAttribute("error.type", "tool_error");
  span.setAttribute("error.message", error.message);
  span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
  throw error;   // re‑throw so the client still receives the error
}

By re-throwing the error after recording telemetry, the middleware ensures that the client receives the original McpError while the server maintains full observability through distributed tracing.

Summary

The flux159/mcp-server-kubernetes MCP server implements a robust, multi-layered error handling strategy that ensures clients receive consistent, actionable error information:

  • Tool-level normalization: All tools wrap raw failures (command errors, parsing issues) into McpError objects with specific ErrorCode values.
  • Centralized error handling: The CallTool handler in src/index.ts preserves existing McpError instances while converting unexpected exceptions to InternalError.
  • Transport serialization: The HTTP transport in src/utils/streamable-http.ts converts errors to JSON-RPC format (code -32603), while STDIO/SSE rely on SDK serialization.
  • Observability without interception: Telemetry middleware records error details in OpenTelemetry spans before re-throwing, ensuring errors reach clients while maintaining traces.

This architecture guarantees that regardless of transport method or failure origin, clients receive structured JSON-RPC error responses that can be reliably parsed and handled.

Frequently Asked Questions

What error format does the MCP server return to clients?

The server returns standard JSON-RPC 2.0 error objects. For HTTP transport, this appears as a 500 response with a body containing jsonrpc: "2.0", an error object with code: -32603 (for internal errors), and a message field. STDIO and SSE transports use the same structure, automatically serialized by the MCP SDK.

How does the server distinguish between tool errors and programming errors?

The central handler in src/index.ts uses instanceof McpError checks to distinguish between intentional error responses from tools and unexpected runtime exceptions. If the error is already an McpError, it is re-thrown unchanged, preserving the original error code and message. Otherwise, the error is wrapped in a new McpError with ErrorCode.InternalError, ensuring all failures are normalized before reaching the client.

Will telemetry recording prevent errors from reaching the client?

No. The telemetry middleware in src/middleware/telemetry-middleware.ts records error details in OpenTelemetry spans but explicitly re-throws the error after recording. This design ensures that distributed tracing captures failure information while the original McpError continues propagating to the transport layer and ultimately to the client.

What happens if a Kubernetes command fails in one of the tools?

When a Kubernetes command fails (for example, kubectl get or kubectl scale), the tool catches the raw error from execFileSync and wraps it in an McpError with ErrorCode.InternalError. For instance, in src/tools/kubectl-get.ts, JSON parsing failures are explicitly caught and converted to McpError instances with descriptive messages. These standardized errors then flow through the central handler and transport layers as described above.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →