mcp-server-kubernetes 4.0.4 → 4.0.5

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/README.md CHANGED
@@ -425,7 +425,12 @@ Adding clusters to kubectx.
425
425
 
426
426
  ## Star History
427
427
 
428
- [![Star History Chart](https://api.star-history.com/svg?repos=Flux159/mcp-server-kubernetes&type=Date)](https://www.star-history.com/#Flux159/mcp-server-kubernetes&Date)
428
+ <a href="https://github.com/Flux159/rust-star-history">
429
+ <picture>
430
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Flux159/mcp-server-kubernetes/star-history/star-history-dark.svg">
431
+ <img alt="Star History Chart" src="https://raw.githubusercontent.com/Flux159/mcp-server-kubernetes/star-history/star-history.svg">
432
+ </picture>
433
+ </a>
429
434
 
430
435
  ## 🖊️ Cite
431
436
 
@@ -1,6 +1,6 @@
1
1
  export declare const serverConfig: {
2
2
  readonly name: "kubernetes";
3
- readonly version: "4.0.4";
3
+ readonly version: "4.0.5";
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.4",
3
+ version: "4.0.5",
4
4
  capabilities: {
5
5
  resources: {},
6
6
  tools: {},
package/dist/index.d.ts CHANGED
@@ -123,7 +123,7 @@ declare const destructiveTools: ({
123
123
  };
124
124
  readonly filename: {
125
125
  readonly type: "string";
126
- readonly description: "Path to a YAML file to delete resources from (optional)";
126
+ readonly description: "Path to a YAML file to delete resources from (optional). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
127
127
  };
128
128
  readonly allNamespaces: {
129
129
  readonly type: "boolean";
@@ -507,7 +507,7 @@ declare const allTools: ({
507
507
  };
508
508
  readonly filename: {
509
509
  readonly type: "string";
510
- readonly description: "Path to a YAML file to apply (optional - use either manifest or filename)";
510
+ readonly description: "Path to a YAML file to apply (optional - use either manifest or filename). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
511
511
  };
512
512
  readonly namespace: {
513
513
  type: "string";
@@ -564,7 +564,7 @@ declare const allTools: ({
564
564
  };
565
565
  readonly filename: {
566
566
  readonly type: "string";
567
- readonly description: "Path to a YAML file to delete resources from (optional)";
567
+ readonly description: "Path to a YAML file to delete resources from (optional). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
568
568
  };
569
569
  readonly allNamespaces: {
570
570
  readonly type: "boolean";
@@ -4,9 +4,11 @@
4
4
  * Template mode bypasses authentication issues and kubeconfig API version mismatches.
5
5
  * Supports local chart paths, remote repositories, and custom values.
6
6
  */
7
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
7
8
  import { execFileSyncSafe } from "../security/kubectl-flags.js";
8
9
  import { writeFileSync, unlinkSync } from "fs";
9
10
  import { dump } from "js-yaml";
11
+ import { isRemoteTransport } from "../security/transport.js";
10
12
  import { getSpawnMaxBuffer } from "../config/max-buffer.js";
11
13
  import { contextParameter, namespaceParameter, } from "../models/common-parameters.js";
12
14
  /**
@@ -49,7 +51,7 @@ export const installHelmChartSchema = {
49
51
  },
50
52
  valuesFile: {
51
53
  type: "string",
52
- description: "Path to values file (alternative to values object)",
54
+ description: "Path to values file (alternative to values object). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'values' to pass the values inline instead.",
53
55
  },
54
56
  useTemplate: {
55
57
  type: "boolean",
@@ -103,7 +105,7 @@ export const upgradeHelmChartSchema = {
103
105
  },
104
106
  valuesFile: {
105
107
  type: "string",
106
- description: "Path to values file (alternative to values object)",
108
+ description: "Path to values file (alternative to values object). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'values' to pass the values inline instead.",
107
109
  },
108
110
  },
109
111
  required: ["name", "chart", "namespace"],
@@ -153,6 +155,17 @@ const executeCommand = (command, args) => {
153
155
  throw new Error(`${command} command failed: ${error.message}`);
154
156
  }
155
157
  };
158
+ // Reject server-side filesystem reads on remote transports. Over SSE /
159
+ // Streamable HTTP the path resolves on the MCP server host, not the client,
160
+ // so `valuesFile` (-f) would let any client that can reach the endpoint read
161
+ // arbitrary server files (kubeconfig, service-account token,
162
+ // /proc/self/environ, etc.) via helm's parse errors. Clients on these
163
+ // transports must pass values inline via `values` instead.
164
+ const rejectValuesFileOnRemoteTransport = (valuesFile) => {
165
+ if (valuesFile && isRemoteTransport()) {
166
+ throw new McpError(ErrorCode.InvalidRequest, "The 'valuesFile' parameter reads a file from the MCP server's filesystem and is disabled on remote (SSE/Streamable HTTP) transports. Pass the values inline via 'values' instead.");
167
+ }
168
+ };
156
169
  /**
157
170
  * Install a Helm chart using template mode (helm template + kubectl apply).
158
171
  * This mode bypasses authentication issues and kubeconfig API version mismatches.
@@ -179,17 +192,7 @@ async function installHelmChartTemplate(params) {
179
192
  }
180
193
  steps.push(`Namespace ${params.namespace} already exists`);
181
194
  }
182
- // Step 3: Prepare values
183
- let valuesContent = "";
184
- if (params.valuesFile) {
185
- steps.push(`Using values file: ${params.valuesFile}`);
186
- valuesContent = executeCommand("cat", [params.valuesFile]);
187
- }
188
- else if (params.values) {
189
- steps.push("Using provided values object");
190
- valuesContent = dump(params.values);
191
- }
192
- // Step 4: Generate YAML using helm template
195
+ // Step 3: Generate YAML using helm template
193
196
  steps.push("Generating YAML using helm template");
194
197
  const templateArgs = [
195
198
  "template",
@@ -201,40 +204,40 @@ async function installHelmChartTemplate(params) {
201
204
  if (params.repo) {
202
205
  templateArgs.push("--repo", params.repo);
203
206
  }
204
- if (valuesContent) {
205
- const tempValuesFile = `/tmp/values-${Date.now()}.yaml`;
206
- writeFileSync(tempValuesFile, valuesContent);
207
+ let tempValuesFile = null;
208
+ if (params.valuesFile) {
209
+ // Hand the path to helm directly rather than reading the file's
210
+ // contents into this process.
211
+ steps.push(`Using values file: ${params.valuesFile}`);
212
+ templateArgs.push("-f", params.valuesFile);
213
+ }
214
+ else if (params.values) {
215
+ steps.push("Using provided values object");
216
+ tempValuesFile = `/tmp/values-${Date.now()}.yaml`;
217
+ writeFileSync(tempValuesFile, dump(params.values));
207
218
  templateArgs.push("-f", tempValuesFile);
208
- const yamlOutput = executeCommand("helm", templateArgs);
219
+ }
220
+ let yamlOutput;
221
+ try {
222
+ yamlOutput = executeCommand("helm", templateArgs);
223
+ }
224
+ finally {
209
225
  // Clean up temp file
210
- unlinkSync(tempValuesFile);
211
- // Step 5: Apply YAML using kubectl
212
- steps.push("Applying YAML using kubectl");
213
- const tempYamlFile = `/tmp/helm-template-${Date.now()}.yaml`;
214
- writeFileSync(tempYamlFile, yamlOutput);
215
- try {
216
- executeCommand("kubectl", ["apply", "-f", tempYamlFile]);
217
- steps.push("Helm chart installed successfully using template mode");
218
- }
219
- finally {
220
- // Clean up temp file
221
- unlinkSync(tempYamlFile);
226
+ if (tempValuesFile) {
227
+ unlinkSync(tempValuesFile);
222
228
  }
223
229
  }
224
- else {
225
- const yamlOutput = executeCommand("helm", templateArgs);
226
- // Step 5: Apply YAML using kubectl
227
- steps.push("Applying YAML using kubectl");
228
- const tempYamlFile = `/tmp/helm-template-${Date.now()}.yaml`;
229
- writeFileSync(tempYamlFile, yamlOutput);
230
- try {
231
- executeCommand("kubectl", ["apply", "-f", tempYamlFile]);
232
- steps.push("Helm chart installed successfully using template mode");
233
- }
234
- finally {
235
- // Clean up temp file
236
- unlinkSync(tempYamlFile);
237
- }
230
+ // Step 4: Apply YAML using kubectl
231
+ steps.push("Applying YAML using kubectl");
232
+ const tempYamlFile = `/tmp/helm-template-${Date.now()}.yaml`;
233
+ writeFileSync(tempYamlFile, yamlOutput);
234
+ try {
235
+ executeCommand("kubectl", ["apply", "-f", tempYamlFile]);
236
+ steps.push("Helm chart installed successfully using template mode");
237
+ }
238
+ finally {
239
+ // Clean up temp file
240
+ unlinkSync(tempYamlFile);
238
241
  }
239
242
  return {
240
243
  content: [
@@ -270,6 +273,7 @@ async function installHelmChartTemplate(params) {
270
273
  * @returns Promise with installation result
271
274
  */
272
275
  export async function installHelmChart(params) {
276
+ rejectValuesFileOnRemoteTransport(params.valuesFile);
273
277
  // Use template mode if requested
274
278
  if (params.useTemplate) {
275
279
  return installHelmChartTemplate(params);
@@ -344,6 +348,7 @@ export async function installHelmChart(params) {
344
348
  * @returns Promise with upgrade result
345
349
  */
346
350
  export async function upgradeHelmChart(params) {
351
+ rejectValuesFileOnRemoteTransport(params.valuesFile);
347
352
  try {
348
353
  // Add repository if provided
349
354
  if (params.repo) {
@@ -14,7 +14,7 @@ export declare const kubectlApplySchema: {
14
14
  };
15
15
  readonly filename: {
16
16
  readonly type: "string";
17
- readonly description: "Path to a YAML file to apply (optional - use either manifest or filename)";
17
+ readonly description: "Path to a YAML file to apply (optional - use either manifest or filename). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
18
18
  };
19
19
  readonly namespace: {
20
20
  type: "string";
@@ -5,6 +5,7 @@ import * as path from "path";
5
5
  import * as os from "os";
6
6
  import { getSpawnMaxBuffer } from "../config/max-buffer.js";
7
7
  import { contextParameter, namespaceParameter, dryRunParameter } from "../models/common-parameters.js";
8
+ import { isRemoteTransport } from "../security/transport.js";
8
9
  export const kubectlApplySchema = {
9
10
  name: "kubectl_apply",
10
11
  description: "Apply a Kubernetes YAML manifest from a string or file",
@@ -20,7 +21,7 @@ export const kubectlApplySchema = {
20
21
  },
21
22
  filename: {
22
23
  type: "string",
23
- description: "Path to a YAML file to apply (optional - use either manifest or filename)",
24
+ description: "Path to a YAML file to apply (optional - use either manifest or filename). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.",
24
25
  },
25
26
  namespace: namespaceParameter,
26
27
  dryRun: dryRunParameter,
@@ -39,6 +40,15 @@ export async function kubectlApply(k8sManager, input) {
39
40
  if (!input.manifest && !input.filename) {
40
41
  throw new McpError(ErrorCode.InvalidRequest, "Either manifest or filename must be provided");
41
42
  }
43
+ // Reject server-side filesystem reads on remote transports. Over SSE /
44
+ // Streamable HTTP the path resolves on the MCP server host, not the
45
+ // client, so `filename` (-f) would let any client that can reach the
46
+ // endpoint read arbitrary server files (kubeconfig, service-account
47
+ // token, /proc/self/environ, etc.) via kubectl's parse errors. Clients
48
+ // on these transports must pass content inline via `manifest` instead.
49
+ if (isRemoteTransport() && input.filename) {
50
+ throw new McpError(ErrorCode.InvalidRequest, "The 'filename' parameter reads a file from the MCP server's filesystem and is disabled on remote (SSE/Streamable HTTP) transports. Pass the file contents via 'manifest' instead.");
51
+ }
42
52
  const namespace = input.namespace || "default";
43
53
  const dryRun = input.dryRun || false;
44
54
  const force = input.force || false;
@@ -31,7 +31,7 @@ export declare const kubectlDeleteSchema: {
31
31
  };
32
32
  readonly filename: {
33
33
  readonly type: "string";
34
- readonly description: "Path to a YAML file to delete resources from (optional)";
34
+ readonly description: "Path to a YAML file to delete resources from (optional). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.";
35
35
  };
36
36
  readonly allNamespaces: {
37
37
  readonly type: "boolean";
@@ -5,6 +5,7 @@ import * as path from "path";
5
5
  import * as os from "os";
6
6
  import { getSpawnMaxBuffer } from "../config/max-buffer.js";
7
7
  import { contextParameter, namespaceParameter, } from "../models/common-parameters.js";
8
+ import { isRemoteTransport } from "../security/transport.js";
8
9
  export const kubectlDeleteSchema = {
9
10
  name: "kubectl_delete",
10
11
  description: "Delete Kubernetes resources by resource type, name, labels, or from a manifest file",
@@ -33,7 +34,7 @@ export const kubectlDeleteSchema = {
33
34
  },
34
35
  filename: {
35
36
  type: "string",
36
- description: "Path to a YAML file to delete resources from (optional)",
37
+ description: "Path to a YAML file to delete resources from (optional). The path is read on the machine running the MCP server, so it is rejected when the server runs over a remote (SSE/Streamable HTTP) transport; use 'manifest' to pass the file's contents instead.",
37
38
  },
38
39
  allNamespaces: {
39
40
  type: "boolean",
@@ -64,6 +65,15 @@ export async function kubectlDelete(k8sManager, input) {
64
65
  if (input.resourceType && !input.name && !input.labelSelector) {
65
66
  throw new McpError(ErrorCode.InvalidRequest, "When using resourceType, either name or labelSelector must be provided");
66
67
  }
68
+ // Reject server-side filesystem reads on remote transports. Over SSE /
69
+ // Streamable HTTP the path resolves on the MCP server host, not the
70
+ // client, so `filename` (-f) would let any client that can reach the
71
+ // endpoint read arbitrary server files (kubeconfig, service-account
72
+ // token, /proc/self/environ, etc.) via kubectl's parse errors. Clients
73
+ // on these transports must pass content inline via `manifest` instead.
74
+ if (isRemoteTransport() && input.filename) {
75
+ throw new McpError(ErrorCode.InvalidRequest, "The 'filename' parameter reads a file from the MCP server's filesystem and is disabled on remote (SSE/Streamable HTTP) transports. Pass the file contents via 'manifest' instead.");
76
+ }
67
77
  const namespace = input.namespace || "default";
68
78
  const allNamespaces = input.allNamespaces || false;
69
79
  const force = input.force || false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "4.0.4",
3
+ "version": "4.0.5",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",