mcp-server-kubernetes 4.0.7 → 4.0.9

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.
@@ -1,6 +1,6 @@
1
1
  export declare const serverConfig: {
2
2
  readonly name: "kubernetes";
3
- readonly version: "4.0.7";
3
+ readonly version: "4.0.9";
4
4
  readonly capabilities: {
5
5
  readonly resources: {};
6
6
  readonly tools: {};
@@ -1,6 +1,6 @@
1
1
  export const serverConfig = {
2
2
  name: "kubernetes",
3
- version: "4.0.7",
3
+ version: "4.0.9",
4
4
  capabilities: {
5
5
  resources: {},
6
6
  tools: {},
@@ -103,6 +103,28 @@ export async function execInPod(k8sManager, input) {
103
103
  if (error.killed || error.signal === "SIGTERM") {
104
104
  throw new McpError(ErrorCode.InternalError, "Exec operation timed out (possible networking, RBAC, or cluster issue)");
105
105
  }
106
+ // A non-zero exit code from the command inside the pod is a NORMAL outcome of
107
+ // running commands, not an internal tool error. Throwing here discards stdout
108
+ // entirely (execFileSync attaches it to the error object), which loses all
109
+ // partial output — e.g. a diagnostic command that prints 20 useful lines and
110
+ // then exits 1 previously returned only "command terminated with exit code 1".
111
+ // Return stdout + stderr + the exit code instead, so callers see everything
112
+ // the command produced. Genuine spawn/infra failures (kubectl missing, RBAC,
113
+ // connection refused) have no exit status and still throw below.
114
+ if (typeof error.status === "number") {
115
+ const stdout = (error.stdout ?? "").toString();
116
+ const stderr = (error.stderr ?? "").toString();
117
+ return {
118
+ content: [
119
+ {
120
+ type: "text",
121
+ text: `Command exited with code ${error.status}\n` +
122
+ (stdout ? `--- stdout ---\n${stdout}\n` : "") +
123
+ (stderr ? `--- stderr ---\n${stderr}` : ""),
124
+ },
125
+ ],
126
+ };
127
+ }
106
128
  throw new McpError(ErrorCode.InternalError, `Failed to execute command in pod: ${error.stderr || error.message}`);
107
129
  }
108
130
  }
package/dist/utils/sse.js CHANGED
@@ -1,15 +1,65 @@
1
1
  import express from "express";
2
2
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3
3
  import { createAuthMiddleware, isAuthEnabled } from "./auth.js";
4
+ /**
5
+ * Build the default allowedHosts list for DNS rebinding protection.
6
+ * Includes localhost variants with and without port.
7
+ */
8
+ function buildDefaultAllowedHosts(host, port) {
9
+ // Always allow the bare host and host:port for common localhost addresses
10
+ const localhostAliases = ["127.0.0.1", "localhost", "::1"];
11
+ const hosts = [];
12
+ for (const alias of localhostAliases) {
13
+ hosts.push(alias);
14
+ // HTTP Host header uses bracket notation for IPv6: [::1]:3000
15
+ if (alias.includes(":")) {
16
+ hosts.push(`[${alias}]`);
17
+ hosts.push(`[${alias}]:${port}`);
18
+ }
19
+ else {
20
+ hosts.push(`${alias}:${port}`);
21
+ }
22
+ }
23
+ // Also add the configured host if it's not already covered
24
+ if (!localhostAliases.includes(host)) {
25
+ hosts.push(host);
26
+ if (host.includes(":") && !host.startsWith("[")) {
27
+ hosts.push(`[${host}]`);
28
+ hosts.push(`[${host}]:${port}`);
29
+ }
30
+ else {
31
+ hosts.push(`${host}:${port}`);
32
+ }
33
+ }
34
+ return hosts;
35
+ }
4
36
  export function startSSEServer(server) {
5
37
  const app = express();
6
38
  // Create auth middleware - when MCP_AUTH_TOKEN is set, requires X-MCP-AUTH header
7
39
  const authMiddleware = createAuthMiddleware();
40
+ // DNS rebinding protection is enabled by default. Set DNS_REBINDING_PROTECTION=false to disable.
41
+ const enableDnsRebindingProtection = process.env.DNS_REBINDING_PROTECTION !== "false";
42
+ const host = process.env.HOST || "localhost";
43
+ const parsed = parseInt(process.env.PORT || "3000", 10);
44
+ const port = Number.isNaN(parsed) ? 3000 : parsed;
45
+ const allowedHosts = process.env.DNS_REBINDING_ALLOWED_HOST
46
+ ? [process.env.DNS_REBINDING_ALLOWED_HOST]
47
+ : buildDefaultAllowedHosts(host, port);
48
+ // Warn when binding to all interfaces with DNS rebinding protection disabled
49
+ if (!enableDnsRebindingProtection && (host === "0.0.0.0" || host === "::")) {
50
+ console.warn("WARNING: DNS rebinding protection is disabled while HOST is set to " +
51
+ `'${host}'. This exposes the MCP server to DNS rebinding attacks ` +
52
+ "from any browser on the network. Set DNS_REBINDING_PROTECTION=true " +
53
+ "(the default) or restrict HOST to 'localhost' / '127.0.0.1'.");
54
+ }
8
55
  // Currently just copying from docs & allowing for multiple transport connections: https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse
9
56
  // Note: When MCP_AUTH_TOKEN is set, requests require X-MCP-AUTH header for authentication
10
57
  let transports = [];
11
58
  app.get("/sse", authMiddleware, async (req, res) => {
12
- const transport = new SSEServerTransport("/messages", res);
59
+ const transport = new SSEServerTransport("/messages", res, {
60
+ enableDnsRebindingProtection,
61
+ allowedHosts,
62
+ });
13
63
  transports.push(transport);
14
64
  await server.connect(transport);
15
65
  });
@@ -45,14 +95,6 @@ export function startSSEServer(server) {
45
95
  });
46
96
  }
47
97
  });
48
- let port = 3000;
49
- try {
50
- port = parseInt(process.env.PORT || "3000", 10);
51
- }
52
- catch (e) {
53
- console.error("Invalid PORT environment variable, using default port 3000.");
54
- }
55
- const host = process.env.HOST || "localhost";
56
98
  app.listen(port, host, () => {
57
99
  console.log(`mcp-kubernetes-server is listening on port ${port}\nUse the following url to connect to the server:\nhttp://${host}:${port}/sse`);
58
100
  if (isAuthEnabled()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  "@opentelemetry/sdk-node": "0.219.0",
47
47
  "@opentelemetry/semantic-conventions": "1.40.0",
48
48
  "express": "4.21.2",
49
- "js-yaml": "4.1.1",
49
+ "js-yaml": "4.3.0",
50
50
  "yaml": "2.8.3",
51
51
  "zod": "3.25.76"
52
52
  },