mcp-server-kubernetes 2.2.1 → 2.3.1

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,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import * as k8s from "@kubernetes/client-node";
2
+ import type { V1Container } from "@kubernetes/client-node";
3
3
  export declare const ContainerTemplate: z.ZodEnum<["ubuntu", "nginx", "busybox", "alpine", "custom"]>;
4
4
  export type ContainerTemplateName = z.infer<typeof ContainerTemplate>;
5
5
  export declare const CustomContainerConfig: z.ZodObject<{
@@ -103,4 +103,4 @@ export declare const CustomContainerConfig: z.ZodObject<{
103
103
  }[] | undefined;
104
104
  }>;
105
105
  export type CustomContainerConfigType = z.infer<typeof CustomContainerConfig>;
106
- export declare const containerTemplates: Record<string, k8s.V1Container>;
106
+ export declare const containerTemplates: Record<string, V1Container>;
package/dist/index.js CHANGED
@@ -11,22 +11,22 @@ import { cleanupSchema } from "./config/cleanup-config.js";
11
11
  import { startSSEServer } from "./utils/sse.js";
12
12
  import { startPortForward, PortForwardSchema, stopPortForward, StopPortForwardSchema, } from "./tools/port_forward.js";
13
13
  import { kubectlScale, kubectlScaleSchema } from "./tools/kubectl-scale.js";
14
- import { kubectlContext, kubectlContextSchema } from "./tools/kubectl-context.js";
14
+ import { kubectlContext, kubectlContextSchema, } from "./tools/kubectl-context.js";
15
15
  import { kubectlGet, kubectlGetSchema } from "./tools/kubectl-get.js";
16
- import { kubectlDescribe, kubectlDescribeSchema } from "./tools/kubectl-describe.js";
16
+ import { kubectlDescribe, kubectlDescribeSchema, } from "./tools/kubectl-describe.js";
17
17
  import { kubectlList, kubectlListSchema } from "./tools/kubectl-list.js";
18
18
  import { kubectlApply, kubectlApplySchema } from "./tools/kubectl-apply.js";
19
19
  import { kubectlDelete, kubectlDeleteSchema } from "./tools/kubectl-delete.js";
20
20
  import { kubectlCreate, kubectlCreateSchema } from "./tools/kubectl-create.js";
21
21
  import { kubectlLogs, kubectlLogsSchema } from "./tools/kubectl-logs.js";
22
- import { kubectlGeneric, kubectlGenericSchema } from "./tools/kubectl-generic.js";
22
+ import { kubectlGeneric, kubectlGenericSchema, } from "./tools/kubectl-generic.js";
23
23
  import { kubectlPatch, kubectlPatchSchema } from "./tools/kubectl-patch.js";
24
- import { kubectlRollout, kubectlRolloutSchema } from "./tools/kubectl-rollout.js";
24
+ import { kubectlRollout, kubectlRolloutSchema, } from "./tools/kubectl-rollout.js";
25
25
  // Check if non-destructive tools only mode is enabled
26
26
  const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === "true";
27
27
  // Define destructive tools (delete and uninstall operations)
28
28
  const destructiveTools = [
29
- kubectlDeleteSchema, // This replaces all individual delete operations
29
+ kubectlDeleteSchema, // This replaces all individual delete operations
30
30
  uninstallHelmChartSchema,
31
31
  cleanupSchema, // Cleanup is also destructive as it deletes resources
32
32
  kubectlGenericSchema, // Generic kubectl command can perform destructive operations
@@ -123,7 +123,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
123
123
  fieldSelector: input.fieldSelector,
124
124
  labelSelector: input.labelSelector,
125
125
  sortBy: input.sortBy,
126
- output: input.output
126
+ output: input.output,
127
127
  });
128
128
  }
129
129
  // Handle specific non-kubectl operations
@@ -44,13 +44,13 @@ export const getResourceHandlers = (k8sManager) => ({
44
44
  const isNodes = parts[0] === "nodes";
45
45
  if ((isNamespaces || isNodes) && parts.length === 1) {
46
46
  const fn = isNodes ? "listNode" : "listNamespace";
47
- const { body } = await k8sManager.getCoreApi()[fn]();
47
+ const { items } = await k8sManager.getCoreApi()[fn]();
48
48
  return {
49
49
  contents: [
50
50
  {
51
51
  uri: request.params.uri,
52
52
  mimeType: "application/json",
53
- text: JSON.stringify(body.items, null, 2),
53
+ text: JSON.stringify(items, null, 2),
54
54
  },
55
55
  ],
56
56
  };
@@ -58,43 +58,43 @@ export const getResourceHandlers = (k8sManager) => ({
58
58
  const [namespace, resourceType] = parts;
59
59
  switch (resourceType) {
60
60
  case "pods": {
61
- const { body } = await k8sManager
61
+ const { items } = await k8sManager
62
62
  .getCoreApi()
63
- .listNamespacedPod(namespace);
63
+ .listNamespacedPod({ namespace });
64
64
  return {
65
65
  contents: [
66
66
  {
67
67
  uri: request.params.uri,
68
68
  mimeType: "application/json",
69
- text: JSON.stringify(body.items, null, 2),
69
+ text: JSON.stringify(items, null, 2),
70
70
  },
71
71
  ],
72
72
  };
73
73
  }
74
74
  case "deployments": {
75
- const { body } = await k8sManager
75
+ const { items } = await k8sManager
76
76
  .getAppsApi()
77
- .listNamespacedDeployment(namespace);
77
+ .listNamespacedDeployment({ namespace });
78
78
  return {
79
79
  contents: [
80
80
  {
81
81
  uri: request.params.uri,
82
82
  mimeType: "application/json",
83
- text: JSON.stringify(body.items, null, 2),
83
+ text: JSON.stringify(items, null, 2),
84
84
  },
85
85
  ],
86
86
  };
87
87
  }
88
88
  case "services": {
89
- const { body } = await k8sManager
89
+ const { items } = await k8sManager
90
90
  .getCoreApi()
91
- .listNamespacedService(namespace);
91
+ .listNamespacedService({ namespace });
92
92
  return {
93
93
  contents: [
94
94
  {
95
95
  uri: request.params.uri,
96
96
  mimeType: "application/json",
97
- text: JSON.stringify(body.items, null, 2),
97
+ text: JSON.stringify(items, null, 2),
98
98
  },
99
99
  ],
100
100
  };
@@ -88,7 +88,8 @@ const executeHelmCommand = (command) => {
88
88
  // Add a generous timeout of 60 seconds for Helm operations
89
89
  return execSync(command, {
90
90
  encoding: "utf8",
91
- timeout: 60000 // 60 seconds timeout
91
+ timeout: 60000, // 60 seconds timeout
92
+ env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG }
92
93
  });
93
94
  }
94
95
  catch (error) {
@@ -69,7 +69,7 @@ export async function kubectlApply(k8sManager, input) {
69
69
  }
70
70
  // Execute the command
71
71
  try {
72
- const result = execSync(command, { encoding: "utf8" });
72
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
73
73
  // Clean up temp file if created
74
74
  if (tempFile) {
75
75
  try {
@@ -52,7 +52,7 @@ export async function kubectlContext(k8sManager, input) {
52
52
  }
53
53
  else if (output === "custom" || output === "json") {
54
54
  // For custom or JSON output, we'll format it ourselves
55
- const rawResult = execSync(command, { encoding: "utf8" });
55
+ const rawResult = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
56
56
  // Parse the tabular output from kubectl
57
57
  const lines = rawResult.trim().split("\n");
58
58
  const headers = lines[0].trim().split(/\s+/);
@@ -83,17 +83,17 @@ export async function kubectlContext(k8sManager, input) {
83
83
  };
84
84
  }
85
85
  // Execute the command for non-json outputs
86
- result = execSync(command, { encoding: "utf8" });
86
+ result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
87
87
  break;
88
88
  case "get":
89
89
  // Build command to get current context
90
90
  command = "kubectl config current-context";
91
91
  // Execute the command
92
92
  try {
93
- const currentContext = execSync(command, { encoding: "utf8" }).trim();
93
+ const currentContext = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } }).trim();
94
94
  if (detailed) {
95
95
  // For detailed context info, we need to use get-contexts and filter
96
- const allContextsOutput = execSync("kubectl config get-contexts", { encoding: "utf8" });
96
+ const allContextsOutput = execSync("kubectl config get-contexts", { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
97
97
  // Parse the tabular output from kubectl
98
98
  const lines = allContextsOutput.trim().split("\n");
99
99
  const headers = lines[0].trim().split(/\s+/);
@@ -169,7 +169,7 @@ export async function kubectlContext(k8sManager, input) {
169
169
  }
170
170
  // First check if the context exists
171
171
  try {
172
- const allContextsOutput = execSync("kubectl config get-contexts -o name", { encoding: "utf8" });
172
+ const allContextsOutput = execSync("kubectl config get-contexts -o name", { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
173
173
  const availableContexts = allContextsOutput.trim().split("\n");
174
174
  // Extract the short name from the ARN if needed
175
175
  let contextName = name;
@@ -186,7 +186,7 @@ export async function kubectlContext(k8sManager, input) {
186
186
  // Build command to set context
187
187
  command = `kubectl config use-context "${contextName}"`;
188
188
  // Execute the command
189
- result = execSync(command, { encoding: "utf8" });
189
+ result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
190
190
  // For tests to pass, we need to return the original name format that was passed in
191
191
  return {
192
192
  content: [
@@ -280,7 +280,7 @@ export async function kubectlCreate(k8sManager, input) {
280
280
  command += ` -o ${output}`;
281
281
  // Execute the command
282
282
  try {
283
- const result = execSync(command, { encoding: "utf8" });
283
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
284
284
  // Clean up temp file if created
285
285
  if (tempFile) {
286
286
  try {
@@ -109,7 +109,7 @@ export async function kubectlDelete(k8sManager, input) {
109
109
  }
110
110
  // Execute the command
111
111
  try {
112
- const result = execSync(command, { encoding: "utf8" });
112
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
113
113
  // Clean up temp file if created
114
114
  if (tempFile) {
115
115
  try {
@@ -49,7 +49,7 @@ export async function kubectlDescribe(k8sManager, input) {
49
49
  }
50
50
  // Execute the command
51
51
  try {
52
- const result = execSync(command, { encoding: "utf8" });
52
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
53
53
  return {
54
54
  content: [
55
55
  {
@@ -98,7 +98,7 @@ export async function kubectlGeneric(k8sManager, input) {
98
98
  const command = cmdArgs.slice(1).join(' ');
99
99
  try {
100
100
  console.error(`Executing: kubectl ${command}`);
101
- const result = execSync(`kubectl ${command}`, { encoding: "utf8" });
101
+ const result = execSync(`kubectl ${command}`, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
102
102
  return {
103
103
  content: [
104
104
  {
@@ -8,43 +8,43 @@ export const kubectlGetSchema = {
8
8
  properties: {
9
9
  resourceType: {
10
10
  type: "string",
11
- description: "Type of resource to get (e.g., pods, deployments, services, configmaps, events, etc.)"
11
+ description: "Type of resource to get (e.g., pods, deployments, services, configmaps, events, etc.)",
12
12
  },
13
13
  name: {
14
14
  type: "string",
15
- description: "Name of the resource (optional - if not provided, lists all resources of the specified type)"
15
+ description: "Name of the resource (optional - if not provided, lists all resources of the specified type)",
16
16
  },
17
17
  namespace: {
18
18
  type: "string",
19
19
  description: "Namespace of the resource (optional - defaults to 'default' for namespaced resources)",
20
- default: "default"
20
+ default: "default",
21
21
  },
22
22
  output: {
23
23
  type: "string",
24
24
  enum: ["json", "yaml", "wide", "name", "custom"],
25
25
  description: "Output format",
26
- default: "json"
26
+ default: "json",
27
27
  },
28
28
  allNamespaces: {
29
29
  type: "boolean",
30
30
  description: "If true, list resources across all namespaces",
31
- default: false
31
+ default: false,
32
32
  },
33
33
  labelSelector: {
34
34
  type: "string",
35
35
  description: "Filter resources by label selector (e.g. 'app=nginx')",
36
- optional: true
36
+ optional: true,
37
37
  },
38
38
  fieldSelector: {
39
39
  type: "string",
40
40
  description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')",
41
- optional: true
41
+ optional: true,
42
42
  },
43
43
  sortBy: {
44
44
  type: "string",
45
45
  description: "Sort events by a field (default: lastTimestamp). Only applicable for events.",
46
- optional: true
47
- }
46
+ optional: true,
47
+ },
48
48
  },
49
49
  required: ["resourceType"],
50
50
  },
@@ -68,8 +68,11 @@ export async function kubectlGet(k8sManager, input) {
68
68
  command += ` ${name}`;
69
69
  }
70
70
  // For events, default to all namespaces unless explicitly specified
71
- const shouldShowAllNamespaces = resourceType === "events" ?
72
- (input.namespace ? false : true) : allNamespaces;
71
+ const shouldShowAllNamespaces = resourceType === "events"
72
+ ? input.namespace
73
+ ? false
74
+ : true
75
+ : allNamespaces;
73
76
  // Add namespace flag unless all namespaces is specified
74
77
  if (shouldShowAllNamespaces) {
75
78
  command += " --all-namespaces";
@@ -115,7 +118,10 @@ export async function kubectlGet(k8sManager, input) {
115
118
  }
116
119
  // Execute the command
117
120
  try {
118
- const result = execSync(command, { encoding: "utf8" });
121
+ const result = execSync(command, {
122
+ encoding: "utf8",
123
+ env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
124
+ });
119
125
  // Format the results for better readability
120
126
  const isListOperation = !name;
121
127
  if (isListOperation && output === "json") {
@@ -152,7 +158,7 @@ export async function kubectlGet(k8sManager, input) {
152
158
  namespace: item.metadata?.namespace || "",
153
159
  kind: item.kind || resourceType,
154
160
  status: getResourceStatus(item),
155
- createdAt: item.metadata?.creationTimestamp
161
+ createdAt: item.metadata?.creationTimestamp,
156
162
  }));
157
163
  return {
158
164
  content: [
@@ -239,13 +245,21 @@ function getResourceStatus(resource) {
239
245
  // Helper function to determine if a resource is non-namespaced
240
246
  function isNonNamespacedResource(resourceType) {
241
247
  const nonNamespacedResources = [
242
- "nodes", "node", "no",
243
- "namespaces", "namespace", "ns",
244
- "persistentvolumes", "pv",
245
- "storageclasses", "sc",
248
+ "nodes",
249
+ "node",
250
+ "no",
251
+ "namespaces",
252
+ "namespace",
253
+ "ns",
254
+ "persistentvolumes",
255
+ "pv",
256
+ "storageclasses",
257
+ "sc",
246
258
  "clusterroles",
247
259
  "clusterrolebindings",
248
- "customresourcedefinitions", "crd", "crds"
260
+ "customresourcedefinitions",
261
+ "crd",
262
+ "crds",
249
263
  ];
250
264
  return nonNamespacedResources.includes(resourceType.toLowerCase());
251
265
  }
@@ -142,7 +142,7 @@ export async function kubectlList(k8sManager, input) {
142
142
  command += ` -o custom-columns="${customColumns}"`;
143
143
  // Execute the command
144
144
  try {
145
- const result = execSync(command, { encoding: "utf8" });
145
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
146
146
  return {
147
147
  content: [
148
148
  {
@@ -83,7 +83,7 @@ export async function kubectlLogs(k8sManager, input) {
83
83
  baseCommand = addLogOptions(baseCommand, input);
84
84
  // Execute the command
85
85
  try {
86
- const result = execSync(baseCommand, { encoding: "utf8" });
86
+ const result = execSync(baseCommand, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
87
87
  return formatLogOutput(name, result);
88
88
  }
89
89
  catch (error) {
@@ -104,7 +104,7 @@ export async function kubectlLogs(k8sManager, input) {
104
104
  // For cronjobs, it's more complex - need to find the job first
105
105
  const jobsCommand = `kubectl -n ${namespace} get jobs --selector=job-name=${name} -o jsonpath='{.items[*].metadata.name}'`;
106
106
  try {
107
- const jobs = execSync(jobsCommand, { encoding: "utf8" }).trim().split(' ');
107
+ const jobs = execSync(jobsCommand, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } }).trim().split(' ');
108
108
  if (jobs.length === 0 || (jobs.length === 1 && jobs[0] === '')) {
109
109
  return {
110
110
  content: [
@@ -148,7 +148,7 @@ export async function kubectlLogs(k8sManager, input) {
148
148
  if (!selectorCommand) {
149
149
  throw new Error("Selector command is undefined");
150
150
  }
151
- const selectorJson = execSync(selectorCommand, { encoding: "utf8" }).trim();
151
+ const selectorJson = execSync(selectorCommand, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } }).trim();
152
152
  const selector = JSON.parse(selectorJson.replace(/'/g, '"'));
153
153
  // Convert to label selector format
154
154
  const labelSelector = Object.entries(selector)
@@ -216,7 +216,7 @@ async function getLabelSelectorLogs(labelSelector, namespace, input) {
216
216
  try {
217
217
  // First, find all pods matching the label selector
218
218
  const podsCommand = `kubectl -n ${namespace} get pods --selector=${labelSelector} -o jsonpath='{.items[*].metadata.name}'`;
219
- const pods = execSync(podsCommand, { encoding: "utf8" }).trim().split(' ');
219
+ const pods = execSync(podsCommand, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } }).trim().split(' ');
220
220
  if (pods.length === 0 || (pods.length === 1 && pods[0] === '')) {
221
221
  return {
222
222
  content: [
@@ -243,7 +243,7 @@ async function getLabelSelectorLogs(labelSelector, namespace, input) {
243
243
  // Add other options
244
244
  podCommand = addLogOptions(podCommand, input);
245
245
  try {
246
- const logs = execSync(podCommand, { encoding: "utf8" });
246
+ const logs = execSync(podCommand, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
247
247
  logsMap[pod] = logs;
248
248
  }
249
249
  catch (error) {
@@ -60,7 +60,7 @@ export const listApiResourcesSchema = {
60
60
  };
61
61
  const executeKubectlCommand = (command) => {
62
62
  try {
63
- return execSync(command, { encoding: "utf8" });
63
+ return execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
64
64
  }
65
65
  catch (error) {
66
66
  throw new Error(`Kubectl command failed: ${error.message}`);
@@ -87,7 +87,7 @@ export async function kubectlPatch(k8sManager, input) {
87
87
  }
88
88
  // Execute the command
89
89
  try {
90
- const result = execSync(command, { encoding: "utf8" });
90
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
91
91
  // Clean up temp file if created
92
92
  if (tempFile) {
93
93
  try {
@@ -79,7 +79,8 @@ export async function kubectlRollout(k8sManager, input) {
79
79
  // and capture the output until that point
80
80
  const result = execSync(command, {
81
81
  encoding: "utf8",
82
- timeout: 15000 // Reduced from 30 seconds to 15 seconds
82
+ timeout: 15000, // Reduced from 30 seconds to 15 seconds
83
+ env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG }
83
84
  });
84
85
  return {
85
86
  content: [
@@ -91,7 +92,7 @@ export async function kubectlRollout(k8sManager, input) {
91
92
  };
92
93
  }
93
94
  else {
94
- const result = execSync(command, { encoding: "utf8" });
95
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
95
96
  return {
96
97
  content: [
97
98
  {
@@ -36,7 +36,7 @@ export async function kubectlScale(k8sManager, input) {
36
36
  let command = `kubectl scale ${resourceType} ${input.name} --replicas=${input.replicas} --namespace=${namespace}`;
37
37
  // Execute the command
38
38
  try {
39
- const result = execSync(command, { encoding: "utf8" });
39
+ const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
40
40
  return {
41
41
  content: [
42
42
  {
@@ -45,6 +45,7 @@ export declare class KubernetesManager {
45
45
  * Check if KUBECONFIG_PATH environment variable is available
46
46
  */
47
47
  private hasEnvKubeconfigPath;
48
+ private hasEnvKubeconfig;
48
49
  /**
49
50
  * Set the current context to the desired context name.
50
51
  *
@@ -67,4 +68,9 @@ export declare class KubernetesManager {
67
68
  * Uses K8S_NAMESPACE environment variable if set, otherwise defaults to "default"
68
69
  */
69
70
  getDefaultNamespace(): string;
71
+ /**
72
+ * Create temporary kubeconfig file from YAML content for kubectl commands
73
+ * @param kubeconfigYaml YAML content of the kubeconfig
74
+ */
75
+ private createTempKubeconfigFromYaml;
70
76
  }
@@ -1,5 +1,7 @@
1
- import * as k8s from "@kubernetes/client-node";
2
1
  import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import * as k8s from "@kubernetes/client-node";
3
5
  export class KubernetesManager {
4
6
  resources = [];
5
7
  portForwards = [];
@@ -10,48 +12,61 @@ export class KubernetesManager {
10
12
  k8sBatchApi;
11
13
  constructor() {
12
14
  this.kc = new k8s.KubeConfig();
13
- if (this.isRunningInCluster()) {
14
- // Priority 1: In-cluster configuration (existing)
15
- this.kc.loadFromCluster();
16
- }
17
- else if (this.hasEnvKubeconfigYaml()) {
18
- // Priority 2: Full kubeconfig as YAML string
15
+ if (this.hasEnvKubeconfigYaml()) {
16
+ // Priority 1: Full kubeconfig as YAML string
19
17
  try {
20
18
  this.loadEnvKubeconfigYaml();
19
+ this.createTempKubeconfigFromYaml(process.env.KUBECONFIG_YAML);
21
20
  }
22
21
  catch (error) {
23
- throw new Error(`Failed to parse KUBECONFIG_YAML: ${error instanceof Error ? error.message : 'Unknown error'}`);
22
+ throw new Error(`Failed to parse KUBECONFIG_YAML: ${error instanceof Error ? error.message : "Unknown error"}`);
24
23
  }
25
24
  }
25
+ else if (this.isRunningInCluster()) {
26
+ // Priority 2: Check if running in cluster
27
+ this.kc.loadFromCluster();
28
+ }
26
29
  else if (this.hasEnvKubeconfigJson()) {
27
30
  // Priority 3: Full kubeconfig as JSON string
28
31
  try {
29
32
  this.loadEnvKubeconfigJson();
33
+ // Create temp kubeconfig file for kubectl commands from JSON
34
+ const yamlConfig = this.kc.exportConfig();
35
+ this.createTempKubeconfigFromYaml(yamlConfig);
30
36
  }
31
37
  catch (error) {
32
- throw new Error(`Failed to parse KUBECONFIG_JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
38
+ throw new Error(`Failed to parse KUBECONFIG_JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
33
39
  }
34
40
  }
35
41
  else if (this.hasEnvMinimalKubeconfig()) {
36
42
  // Priority 4: Minimal config with individual environment variables
37
43
  try {
38
44
  this.loadEnvMinimalKubeconfig();
45
+ // Create temp kubeconfig file for kubectl commands from minimal config
46
+ const yamlConfig = this.kc.exportConfig();
47
+ this.createTempKubeconfigFromYaml(yamlConfig);
39
48
  }
40
49
  catch (error) {
41
- throw new Error(`Failed to create kubeconfig from K8S_SERVER and K8S_TOKEN: ${error instanceof Error ? error.message : 'Unknown error'}`);
50
+ throw new Error(`Failed to create kubeconfig from K8S_SERVER and K8S_TOKEN: ${error instanceof Error ? error.message : "Unknown error"}`);
42
51
  }
43
52
  }
44
53
  else if (this.hasEnvKubeconfigPath()) {
45
- // Priority 5: Custom kubeconfig file path
54
+ // Priority 5: Custom kubeconfig file path using KUBECONFIG_PATH
46
55
  try {
47
56
  this.loadEnvKubeconfigPath();
57
+ // Set KUBECONFIG environment variable to the custom path for kubectl commands
58
+ process.env.KUBECONFIG = process.env.KUBECONFIG_PATH;
48
59
  }
49
60
  catch (error) {
50
- throw new Error(`Failed to load kubeconfig from KUBECONFIG_PATH: ${error instanceof Error ? error.message : 'Unknown error'}`);
61
+ throw new Error(`Failed to load kubeconfig from KUBECONFIG_PATH: ${error instanceof Error ? error.message : "Unknown error"}`);
51
62
  }
52
63
  }
64
+ else if (this.hasEnvKubeconfig()) {
65
+ // Load from KUBECONFIG
66
+ this.kc.loadFromFile(process.env.KUBECONFIG);
67
+ }
53
68
  else {
54
- // Priority 6: Default file-based configuration (existing fallback)
69
+ // Priority 7: Default file-based configuration (existing fallback)
55
70
  this.kc.loadFromDefault();
56
71
  }
57
72
  // Apply context override if specified
@@ -60,7 +75,7 @@ export class KubernetesManager {
60
75
  this.setCurrentContext(process.env.K8S_CONTEXT);
61
76
  }
62
77
  catch (error) {
63
- console.warn(`Warning: Could not set context to ${process.env.K8S_CONTEXT}: ${error instanceof Error ? error.message : 'Unknown error'}`);
78
+ console.warn(`Warning: Could not set context to ${process.env.K8S_CONTEXT}: ${error instanceof Error ? error.message : "Unknown error"}`);
64
79
  }
65
80
  }
66
81
  // Initialize API clients
@@ -111,6 +126,10 @@ export class KubernetesManager {
111
126
  * Load kubeconfig from KUBECONFIG_YAML environment variable (YAML format)
112
127
  */
113
128
  loadEnvKubeconfigYaml() {
129
+ if (!process.env.KUBECONFIG_YAML) {
130
+ throw new Error("KUBECONFIG_YAML environment variable is not set");
131
+ }
132
+ // Load the config into the JavaScript client
114
133
  this.kc.loadFromString(process.env.KUBECONFIG_YAML);
115
134
  }
116
135
  /**
@@ -125,28 +144,29 @@ export class KubernetesManager {
125
144
  */
126
145
  loadEnvMinimalKubeconfig() {
127
146
  if (!process.env.K8S_SERVER || !process.env.K8S_TOKEN) {
128
- throw new Error('K8S_SERVER and K8S_TOKEN environment variables are required');
147
+ throw new Error("K8S_SERVER and K8S_TOKEN environment variables are required");
129
148
  }
130
149
  const cluster = {
131
- name: 'env-cluster',
150
+ name: "env-cluster",
132
151
  server: process.env.K8S_SERVER,
133
- skipTLSVerify: process.env.K8S_SKIP_TLS_VERIFY === 'true'
152
+ skipTLSVerify: process.env.K8S_SKIP_TLS_VERIFY === "true",
134
153
  };
135
154
  const user = {
136
- name: 'env-user',
137
- token: process.env.K8S_TOKEN
155
+ name: "env-user",
156
+ token: process.env.K8S_TOKEN,
138
157
  };
139
158
  const context = {
140
- name: 'env-context',
159
+ name: "env-context",
141
160
  user: user.name,
142
- cluster: cluster.name
161
+ cluster: cluster.name,
143
162
  };
144
- this.kc.loadFromOptions({
163
+ const kubeconfigContent = {
145
164
  clusters: [cluster],
146
165
  users: [user],
147
166
  contexts: [context],
148
- currentContext: context.name
149
- });
167
+ currentContext: context.name,
168
+ };
169
+ this.kc.loadFromOptions(kubeconfigContent);
150
170
  }
151
171
  /**
152
172
  * Check if KUBECONFIG_PATH environment variable is available
@@ -154,6 +174,9 @@ export class KubernetesManager {
154
174
  hasEnvKubeconfigPath() {
155
175
  return !!(process.env.KUBECONFIG_PATH && process.env.KUBECONFIG_PATH.trim());
156
176
  }
177
+ hasEnvKubeconfig() {
178
+ return !!(process.env.KUBECONFIG && process.env.KUBECONFIG.trim());
179
+ }
157
180
  /**
158
181
  * Set the current context to the desired context name.
159
182
  *
@@ -184,7 +207,7 @@ export class KubernetesManager {
184
207
  await this.deleteResource(resource.kind, resource.name, resource.namespace);
185
208
  }
186
209
  catch (error) {
187
- console.error(`Failed to delete ${resource.kind} ${resource.name}:`, error);
210
+ process.stderr.write(`Failed to delete ${resource.kind} ${resource.name}: ${error}\n`);
188
211
  }
189
212
  }
190
213
  }
@@ -194,16 +217,16 @@ export class KubernetesManager {
194
217
  async deleteResource(kind, name, namespace) {
195
218
  switch (kind.toLowerCase()) {
196
219
  case "pod":
197
- await this.k8sApi.deleteNamespacedPod(name, namespace);
220
+ await this.k8sApi.deleteNamespacedPod({ name, namespace });
198
221
  break;
199
222
  case "deployment":
200
- await this.k8sAppsApi.deleteNamespacedDeployment(name, namespace);
223
+ await this.k8sAppsApi.deleteNamespacedDeployment({ name, namespace });
201
224
  break;
202
225
  case "service":
203
- await this.k8sApi.deleteNamespacedService(name, namespace);
226
+ await this.k8sApi.deleteNamespacedService({ name, namespace });
204
227
  break;
205
228
  case "cronjob":
206
- await this.k8sBatchApi.deleteNamespacedCronJob(name, namespace);
229
+ await this.k8sBatchApi.deleteNamespacedCronJob({ name, namespace });
207
230
  break;
208
231
  }
209
232
  this.resources = this.resources.filter((r) => !(r.kind === kind && r.name === name && r.namespace === namespace));
@@ -237,6 +260,56 @@ export class KubernetesManager {
237
260
  * Uses K8S_NAMESPACE environment variable if set, otherwise defaults to "default"
238
261
  */
239
262
  getDefaultNamespace() {
240
- return process.env.K8S_NAMESPACE || 'default';
263
+ return process.env.K8S_NAMESPACE || "default";
264
+ }
265
+ /**
266
+ * Create temporary kubeconfig file from YAML content for kubectl commands
267
+ * @param kubeconfigYaml YAML content of the kubeconfig
268
+ */
269
+ createTempKubeconfigFromYaml(kubeconfigYaml) {
270
+ try {
271
+ if (!kubeconfigYaml || typeof kubeconfigYaml !== "string") {
272
+ throw new Error(`Invalid kubeconfigYaml: ${typeof kubeconfigYaml}`);
273
+ }
274
+ const tempDir = os.tmpdir();
275
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
276
+ const randomString = Math.random().toString(36).substring(2);
277
+ const tempKubeconfigPath = path.join(tempDir, `kubeconfig-${timestamp}-${randomString}`);
278
+ // Write temporary kubeconfig file
279
+ fs.writeFileSync(tempKubeconfigPath, kubeconfigYaml, {
280
+ mode: 0o600,
281
+ encoding: "utf8",
282
+ });
283
+ // Set KUBECONFIG environment variable for kubectl commands
284
+ process.env.KUBECONFIG = tempKubeconfigPath;
285
+ // Function to clean up the temporary file
286
+ const cleanupTempFile = () => {
287
+ try {
288
+ if (fs.existsSync(tempKubeconfigPath)) {
289
+ fs.unlinkSync(tempKubeconfigPath);
290
+ }
291
+ }
292
+ catch (cleanupError) {
293
+ // Ignore cleanup errors
294
+ }
295
+ };
296
+ // Schedule cleanup of temporary file when process exits
297
+ process.on("exit", cleanupTempFile);
298
+ // Also clean up on SIGINT and SIGTERM (common in Docker containers)
299
+ ["SIGINT", "SIGTERM"].forEach((signal) => {
300
+ process.on(signal, () => {
301
+ cleanupTempFile();
302
+ process.exit(0);
303
+ });
304
+ });
305
+ // Additional cleanup for Docker container lifecycle
306
+ ["SIGUSR1", "SIGUSR2"].forEach((signal) => {
307
+ process.on(signal, cleanupTempFile);
308
+ });
309
+ }
310
+ catch (error) {
311
+ // Continue without temporary file - kubectl commands may fail but JavaScript client will work
312
+ throw error;
313
+ }
241
314
  }
242
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "2.2.1",
3
+ "version": "2.3.1",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "dependencies": {
38
- "@kubernetes/client-node": "0.20.0",
38
+ "@kubernetes/client-node": "1.3.0",
39
39
  "@modelcontextprotocol/sdk": "1.7.0",
40
40
  "express": "4.21.2",
41
41
  "js-yaml": "4.1.0",
@@ -49,10 +49,5 @@
49
49
  "shx": "0.3.4",
50
50
  "typescript": "5.6.2",
51
51
  "vitest": "2.1.9"
52
- },
53
- "overrides": {
54
- "@kubernetes/client-node": {
55
- "jsonpath-plus": "^10.3.0"
56
- }
57
52
  }
58
53
  }