MCP Server Request Flow and Architecture in flux159/mcp-server-kubernetes

The MCP Kubernetes server uses a modular, request-driven architecture that routes client requests through STDIO, SSE, or HTTP transports to a central Server core, which validates, filters, and dispatches calls to KubernetesManager-backed tools while wrapping every operation in telemetry middleware.

The flux159/mcp-server-kubernetes repository implements a Model Context Protocol (MCP) server that bridges AI clients with Kubernetes clusters. Understanding its request flow reveals how it safely exposes cluster operations through a clean separation between transport concerns, request routing, and domain-specific Kubernetes logic.

Transport Layer and Server Startup

The entry point at src/index.ts initializes the server and selects the transport protocol based on environment variables. The implementation uses the official MCP SDK to create a Server instance configured via serverConfig from src/config/server-config.ts.

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { startSSEServer } from "./utils/sse.js";
import { startStreamableHTTPServer } from "./utils/streamable-http.js";

if (process.env.ENABLE_UNSAFE_SSE_TRANSPORT) {
  startSSEServer(server);
} else if (process.env.ENABLE_UNSAFE_STREAMABLE_HTTP_TRANSPORT) {
  startStreamableHTTPServer(server);
} else {
  const transport = new StdioServerTransport();
  server.connect(transport);
}

The server supports three transport mechanisms:

  • STDIO (default) – Used by CLI clients; reads JSON-RPC messages from stdin and writes to stdout
  • SSE – Server-Sent Events for streaming responses; enabled via ENABLE_UNSAFE_SSE_TRANSPORT
  • Streamable HTTP – Custom HTTP transport; enabled via ENABLE_UNSAFE_STREAMABLE_HTTP_TRANSPORT

Each transport forwards raw JSON-RPC payloads to the server core without processing the message content.

Server Core and Request Routing

The server core registers request handlers for four primary MCP methods in src/index.ts. Each handler maps to a specific schema defined by the MCP SDK:

server.setRequestHandler(ListResourcesRequestSchema, resourceHandlers.listResources);
server.setRequestHandler(ReadResourceRequestSchema, resourceHandlers.readResource);
server.setRequestHandler(ListToolsRequestSchema, async () => { ... });
server.setRequestHandler(CallToolRequestSchema, withTelemetry(async request => { ... }));

ListResources and ReadResource dispatch to the resource handlers in src/resources/handlers.ts. ListTools returns the available tool schemas after applying environment-based filtering. CallTool serves as the primary execution entry point, receiving tool names and arguments before routing to the appropriate implementation.

Every CallTool request passes through withTelemetry middleware defined in src/middleware/telemetry-middleware.ts, which captures timing metrics, success states, and error details before the request reaches domain logic.

Environment-Driven Tool Filtering

Before exposing tools to clients, the server applies security filtering based on three environment variables defined in src/index.ts:

  • ALLOW_ONLY_READONLY_TOOLS – Exposes only safe, read-only operations (e.g., kubectl_get, list_resources)
  • ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS – Excludes explicitly destructive operations while permitting state-changing but non-destructive actions
  • ALLOWED_TOOLS – Comma-separated whitelist specifying exactly which tools to expose

The filtering logic compares requested tools against hardcoded arrays readonlyTools and destructiveTools:

if (allowedToolsEnv) { 
  // Parse comma-separated list
}
else if (allowOnlyReadonlyTools) { 
  tools = readonlyTools; 
}
else if (nonDestructiveTools) { 
  tools = allTools.filter(t => !destructiveTools.includes(t)); 
}

This mechanism prevents accidental cluster modifications when running in restricted environments.

Domain Logic and KubernetesManager

Once a tool call passes filtering, the server dispatches to type-guarded function implementations. The k8sManager instance from src/utils/kubernetes-manager.ts injects into every tool, providing authenticated access to the cluster:

if (name === "kubectl_get") {
  return await kubectlGet(k8sManager, input as KubectlGetArgs);
}
switch (name) {
  case "install_helm_chart": return await installHelmChart(input);
  case "port_forward":      return await startPortForward(k8sManager, input);
}

KubernetesManager handles critical responsibilities:

  • Configuration loading – Supports YAML, JSON, minimal token+server pairs, in-cluster config, or default ~/.kube/config via the constructor
  • API client exposure – Provides typed clients via getCoreApi() (CoreV1Api), getAppsApi() (AppsV1Api), and getBatchApi() (BatchV1Api)
  • Resource lifecycle trackingtrackResource() and cleanup() manage temporary resources
  • Network operationstrackPortForward() and trackWatch() manage long-lived connections

Tools like kubectl_get (src/tools/kubectl-get.ts) and port_forward (src/tools/port_forward.ts) use these clients to execute cluster operations without managing authentication or connection state themselves.

Resource Handlers

The src/resources/handlers.ts file implements the resource discovery protocol, allowing clients to browse available cluster data without prior knowledge of the Kubernetes API structure:

  • listResources – Returns a static catalog of URI templates (e.g., k8s://default/pods, k8s://namespaces) that clients can query
  • readResource – Parses incoming URIs to determine whether the request targets a cluster-scoped resource (nodes, namespaces) or namespaced resource (pods, deployments), then calls the appropriate KubernetesManager method (listNamespacedPod, listNamespacedDeployment, etc.)

This design decouples the client's data discovery from the underlying API implementation details.

Response Flow and Error Handling

After domain logic execution completes, the server core serializes the result or an McpError back through the same transport layer that received the request. For streaming transports (SSE and streamable HTTP), the response may be chunked, but the logical flow remains consistent:

  1. Tool or handler returns data
  2. Server wraps the payload in MCP response format
  3. Transport serializes to JSON-RPC or SSE events
  4. Client receives the formatted response

The telemetry middleware captures final status codes and timing metrics before the response leaves the server boundary, ensuring complete observability of the request lifecycle.

Summary

  • Modular transport layer supports STDIO, SSE, and HTTP transports selected via environment variables in src/index.ts
  • Request routing occurs through schema-specific handlers (ListResources, ReadResource, ListTools, CallTool) registered on the MCP Server instance
  • Security filtering happens at startup via ALLOW_ONLY_READONLY_TOOLS, ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS, or ALLOWED_TOOLS environment variables
  • Domain isolation keeps Kubernetes API interactions contained within KubernetesManager (src/utils/kubernetes-manager.ts), which handles authentication, client initialization, and resource cleanup
  • Telemetry coverage wraps every CallTool request via withTelemetry middleware in src/middleware/telemetry-middleware.ts

Frequently Asked Questions

How does the MCP server handle different transport protocols?

The server selects transports based on environment variables at startup. By default, it uses StdioServerTransport for CLI communication. When ENABLE_UNSAFE_SSE_TRANSPORT is set, it initializes startSSEServer from src/utils/sse.ts for Server-Sent Events. When ENABLE_UNSAFE_STREAMABLE_HTTP_TRANSPORT is set, it uses startStreamableHTTPServer from src/utils/streamable-http.ts. All transports forward raw JSON-RPC to the same request handlers, ensuring consistent behavior regardless of the connection method.

What security mechanisms control tool access?

The server implements three environment-driven filtering strategies in src/index.ts. ALLOW_ONLY_READONLY_TOOLS restricts the server to read operations like kubectl_get. ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS filters out explicitly destructive tools while allowing state changes. ALLOWED_TOOLS accepts a comma-separated whitelist. These filters apply at server initialization, modifying the tool list returned by ListTools and preventing filtered tools from executing even if requested.

How does KubernetesManager handle cluster authentication?

KubernetesManager (src/utils/kubernetes-manager.ts) loads kubeconfig data through a priority-based constructor that handles multiple credential sources: explicit YAML/JSON files, minimal token and server pairs, in-cluster service account tokens, or the default ~/.kube/config. It exposes typed API clients (CoreV1Api, AppsV1Api, BatchV1Api) that tools consume, abstracting authentication and connection management from individual tool implementations.

What happens when a tool call fails?

Failures propagate through the withTelemetry middleware in src/middleware/telemetry-middleware.ts, which captures error details and timing before the server returns an McpError to the client. The telemetry system records the failure state for observability, while the transport layer serializes the error according to MCP protocol specifications, ensuring clients receive structured error information regardless of whether they connect via STDIO, SSE, or HTTP.

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 →