# How the MCP Server Handles and Propagates Errors to Client Requests

> Learn how the MCP server normalizes tool failures into McpError objects, propagates them via a central handler, and serializes them into JSON-RPC error responses for clients.

- Repository: [Suyog Sonwalkar/mcp-server-kubernetes](https://github.com/flux159/mcp-server-kubernetes)
- Tags: how-to-guide
- Published: 2026-03-02

---

**The MCP server normalizes all tool failures into `McpError` objects with standardized `ErrorCode` values, propagates them through a central handler in [`src/index.ts`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/src/index.ts) catches these errors and ensures any unhandled exceptions are similarly wrapped. Third, the transport layer—whether [`streamable-http.ts`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/src/tools/kubectl-get.ts) around line 260, JSON parsing failures are explicitly caught and re-thrown as standardized errors:

```typescript
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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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.

```typescript
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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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:

```typescript
} 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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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.

```typescript
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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/src/index.ts) preserves existing `McpError` instances while converting unexpected exceptions to `InternalError`.
- **Transport serialization**: The HTTP transport in [`src/utils/streamable-http.ts`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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`](https://github.com/flux159/mcp-server-kubernetes/blob/main/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.