mcp-server-kubernetes 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -393,6 +393,17 @@ declare const allTools: ({
393
393
  };
394
394
  readonly required: readonly ["operation"];
395
395
  };
396
+ } | {
397
+ readonly name: "kubectl_reconnect";
398
+ readonly description: "Reconnect to the Kubernetes API server by recreating all API clients. Use this after cluster upgrades (e.g., EKS control plane upgrades that rotate ENIs/IPs) to force fresh DNS resolution and new TCP connections.";
399
+ readonly annotations: {
400
+ readonly readOnlyHint: false;
401
+ };
402
+ readonly inputSchema: {
403
+ readonly type: "object";
404
+ readonly properties: {};
405
+ readonly required: readonly [];
406
+ };
396
407
  } | {
397
408
  readonly name: "kubectl_get";
398
409
  readonly description: "Get or list Kubernetes resources by resource type, name, and optionally namespace";
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { startSSEServer } from "./utils/sse.js";
18
18
  import { startPortForward, PortForwardSchema, stopPortForward, StopPortForwardSchema, } from "./tools/port_forward.js";
19
19
  import { kubectlScale, kubectlScaleSchema } from "./tools/kubectl-scale.js";
20
20
  import { kubectlContext, kubectlContextSchema, } from "./tools/kubectl-context.js";
21
+ import { kubectlReconnect, kubectlReconnectSchema, } from "./tools/kubectl-reconnect.js";
21
22
  import { kubectlGet, kubectlGetSchema } from "./tools/kubectl-get.js";
22
23
  import { kubectlDescribe, kubectlDescribeSchema, } from "./tools/kubectl-describe.js";
23
24
  import { kubectlApply, kubectlApplySchema } from "./tools/kubectl-apply.js";
@@ -41,6 +42,7 @@ const readonlyTools = [
41
42
  kubectlDescribeSchema,
42
43
  kubectlLogsSchema,
43
44
  kubectlContextSchema,
45
+ kubectlReconnectSchema,
44
46
  explainResourceSchema,
45
47
  listApiResourcesSchema,
46
48
  pingSchema,
@@ -69,6 +71,7 @@ const allTools = [
69
71
  kubectlRolloutSchema,
70
72
  // Kubernetes context management
71
73
  kubectlContextSchema,
74
+ kubectlReconnectSchema,
72
75
  // Special operations that aren't covered by simple kubectl commands
73
76
  explainResourceSchema,
74
77
  // Helm operations
@@ -129,6 +132,9 @@ server.setRequestHandler(CallToolRequestSchema, withTelemetry(async (request) =>
129
132
  if (name === "kubectl_context") {
130
133
  return await kubectlContext(k8sManager, input);
131
134
  }
135
+ if (name === "kubectl_reconnect") {
136
+ return await kubectlReconnect(k8sManager);
137
+ }
132
138
  if (name === "kubectl_get") {
133
139
  return await kubectlGet(k8sManager, input);
134
140
  }
@@ -0,0 +1,19 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const kubectlReconnectSchema: {
3
+ readonly name: "kubectl_reconnect";
4
+ readonly description: "Reconnect to the Kubernetes API server by recreating all API clients. Use this after cluster upgrades (e.g., EKS control plane upgrades that rotate ENIs/IPs) to force fresh DNS resolution and new TCP connections.";
5
+ readonly annotations: {
6
+ readonly readOnlyHint: false;
7
+ };
8
+ readonly inputSchema: {
9
+ readonly type: "object";
10
+ readonly properties: {};
11
+ readonly required: readonly [];
12
+ };
13
+ };
14
+ export declare function kubectlReconnect(k8sManager: KubernetesManager): Promise<{
15
+ content: {
16
+ type: string;
17
+ text: string;
18
+ }[];
19
+ }>;
@@ -0,0 +1,32 @@
1
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2
+ export const kubectlReconnectSchema = {
3
+ name: "kubectl_reconnect",
4
+ description: "Reconnect to the Kubernetes API server by recreating all API clients. Use this after cluster upgrades (e.g., EKS control plane upgrades that rotate ENIs/IPs) to force fresh DNS resolution and new TCP connections.",
5
+ annotations: {
6
+ readOnlyHint: false,
7
+ },
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {},
11
+ required: [],
12
+ },
13
+ };
14
+ export async function kubectlReconnect(k8sManager) {
15
+ try {
16
+ k8sManager.refreshApiClients();
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: JSON.stringify({
22
+ success: true,
23
+ message: "API clients refreshed. DNS will be re-resolved on the next request.",
24
+ }, null, 2),
25
+ },
26
+ ],
27
+ };
28
+ }
29
+ catch (error) {
30
+ throw new McpError(ErrorCode.InternalError, `Failed to reconnect: ${error.message}`);
31
+ }
32
+ }
@@ -1,9 +1,8 @@
1
1
  import { spawn } from "child_process";
2
2
  // Use spawn instead of exec because port-forward is a long-running process
3
- async function executeKubectlCommandAsync(command) {
3
+ async function executePortForward(args) {
4
4
  return new Promise((resolve, reject) => {
5
- const [cmd, ...args] = command.split(" ");
6
- const process = spawn(cmd, args);
5
+ const process = spawn("kubectl", args);
7
6
  let output = "";
8
7
  let errorOutput = "";
9
8
  process.stdout.on("data", (data) => {
@@ -54,13 +53,14 @@ export const PortForwardSchema = {
54
53
  },
55
54
  };
56
55
  export async function startPortForward(k8sManager, input) {
57
- let command = `kubectl port-forward`;
56
+ const args = ["port-forward"];
58
57
  if (input.namespace) {
59
- command += ` -n ${input.namespace}`;
58
+ args.push("-n", input.namespace);
60
59
  }
61
- command += ` ${input.resourceType}/${input.resourceName} ${input.localPort}:${input.targetPort}`;
60
+ args.push(`${input.resourceType}/${input.resourceName}`);
61
+ args.push(`${input.localPort}:${input.targetPort}`);
62
62
  try {
63
- const result = await executeKubectlCommandAsync(command);
63
+ const result = await executePortForward(args);
64
64
  // Track the port-forward process
65
65
  k8sManager.trackPortForward({
66
66
  id: `${input.resourceType}-${input.resourceName}-${input.localPort}`,
@@ -1,3 +1,15 @@
1
+ import { timingSafeEqual } from "crypto";
2
+ /** Constant-time string comparison that prevents timing attacks (CWE-208). */
3
+ function timingSafeCompare(a, b) {
4
+ const bufA = Buffer.from(a);
5
+ const bufB = Buffer.from(b);
6
+ if (bufA.length !== bufB.length) {
7
+ // Compare against itself to keep constant time, then return false
8
+ timingSafeEqual(bufA, bufA);
9
+ return false;
10
+ }
11
+ return timingSafeEqual(bufA, bufB);
12
+ }
1
13
  /**
2
14
  * Authentication middleware for MCP HTTP transports.
3
15
  *
@@ -31,7 +43,19 @@ export function createAuthMiddleware() {
31
43
  });
32
44
  return;
33
45
  }
34
- if (providedToken !== authToken) {
46
+ // Reject array-valued headers (e.g. duplicate X-MCP-AUTH)
47
+ if (Array.isArray(providedToken)) {
48
+ res.status(401).json({
49
+ jsonrpc: "2.0",
50
+ error: {
51
+ code: -32001,
52
+ message: "Unauthorized: Only single X-MCP-AUTH header is allowed",
53
+ },
54
+ id: null,
55
+ });
56
+ return;
57
+ }
58
+ if (!timingSafeCompare(providedToken, authToken)) {
35
59
  res.status(403).json({
36
60
  jsonrpc: "2.0",
37
61
  error: {
@@ -51,6 +51,7 @@ export declare class KubernetesManager {
51
51
  *
52
52
  * @param contextName
53
53
  */
54
+ refreshApiClients(): void;
54
55
  setCurrentContext(contextName: string): void;
55
56
  cleanup(): Promise<void>;
56
57
  trackResource(kind: string, name: string, namespace: string): void;
@@ -186,6 +186,11 @@ export class KubernetesManager {
186
186
  *
187
187
  * @param contextName
188
188
  */
189
+ refreshApiClients() {
190
+ this.k8sApi = this.kc.makeApiClient(k8s.CoreV1Api);
191
+ this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
192
+ this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
193
+ }
189
194
  setCurrentContext(contextName) {
190
195
  // Get all available contexts
191
196
  const contexts = this.kc.getContexts();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,7 +47,7 @@
47
47
  "@opentelemetry/semantic-conventions": "^1.39.0",
48
48
  "express": "4.21.2",
49
49
  "js-yaml": "4.1.1",
50
- "yaml": "2.7.0",
50
+ "yaml": "2.8.3",
51
51
  "zod": "3.25.76"
52
52
  },
53
53
  "devDependencies": {