mcp-server-kubernetes 1.3.1 → 1.4.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/README.md CHANGED
@@ -70,6 +70,7 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
70
70
  - [x] Create, describe, delete a pod
71
71
  - [x] List all namespaces, create a namespace
72
72
  - [x] Create custom pod & deployment configs, update deployment replicas
73
+ - [x] Create services
73
74
  - [x] Get logs from a pod for debugging (supports pods, deployments, jobs, and label selectors)
74
75
  - [x] Support Helm v3 for installing charts
75
76
  - Install charts with custom values
@@ -82,6 +83,7 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
82
83
  - [x] Get Kubernetes events from the cluster
83
84
  - [x] Port forward to a pod or service
84
85
  - [x] Create, list, and decribe cronjobs
86
+ - [x] Non-destructive mode for read and create/update-only access to clusters
85
87
 
86
88
  ## Local Development
87
89
 
@@ -143,7 +145,9 @@ See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details.
143
145
 
144
146
  ## Advanced
145
147
 
146
- For more advanced information like using SSE transport, see the [ADVANCED_README.md](ADVANCED_README.md).
148
+ ### Additional Advanced Features
149
+
150
+ For more advanced information like using SSE transport, Non-destructive mode with `ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS`, see the [ADVANCED_README.md](ADVANCED_README.md).
147
151
 
148
152
  ## Architecture
149
153
 
package/dist/index.js CHANGED
@@ -33,49 +33,74 @@ import { deleteDeployment, deleteDeploymentSchema } from "./tools/delete_deploym
33
33
  import { createDeployment } from "./tools/create_deployment.js";
34
34
  import { scaleDeployment, scaleDeploymentSchema } from "./tools/scale_deployment.js";
35
35
  import { describeDeployment, describeDeploymentSchema, } from "./tools/describe_deployment.js";
36
+ import { updateDeployment, updateDeploymentSchema } from "./tools/update_deployment.js";
36
37
  import { createConfigMap, CreateConfigMapSchema } from "./tools/create_configmap.js";
38
+ import { createService, createServiceSchema } from "./tools/create_service.js";
39
+ import { listContexts, listContextsSchema } from "./tools/list_contexts.js";
40
+ import { getCurrentContext, getCurrentContextSchema } from "./tools/get_current_context.js";
41
+ import { setCurrentContext, setCurrentContextSchema } from "./tools/set_current_context.js";
42
+ // Check if non-destructive tools only mode is enabled
43
+ const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === 'true';
37
44
  const k8sManager = new KubernetesManager();
38
45
  const server = new Server({
39
46
  name: serverConfig.name,
40
47
  version: serverConfig.version,
41
48
  }, serverConfig);
49
+ // Define destructive tools (delete and uninstall operations)
50
+ const destructiveTools = [
51
+ deletePodSchema,
52
+ deleteDeploymentSchema,
53
+ deleteNamespaceSchema,
54
+ uninstallHelmChartSchema,
55
+ DeleteCronJobSchema,
56
+ cleanupSchema, // Cleanup is also destructive as it deletes resources
57
+ ];
42
58
  // Tools handlers
43
59
  server.setRequestHandler(ListToolsRequestSchema, async () => {
44
- return {
45
- tools: [
46
- cleanupSchema,
47
- createDeploymentSchema,
48
- createNamespaceSchema,
49
- createPodSchema,
50
- createCronJobSchema,
51
- deletePodSchema,
52
- deleteDeploymentSchema,
53
- deleteNamespaceSchema,
54
- describeCronJobSchema,
55
- describePodSchema,
56
- describeDeploymentSchema,
57
- explainResourceSchema,
58
- getEventsSchema,
59
- getJobLogsSchema,
60
- getLogsSchema,
61
- installHelmChartSchema,
62
- listApiResourcesSchema,
63
- listCronJobsSchema,
64
- listDeploymentsSchema,
65
- listJobsSchema,
66
- listNamespacesSchema,
67
- listNodesSchema,
68
- listPodsSchema,
69
- listServicesSchema,
70
- uninstallHelmChartSchema,
71
- upgradeHelmChartSchema,
72
- PortForwardSchema,
73
- StopPortForwardSchema,
74
- scaleDeploymentSchema,
75
- DeleteCronJobSchema,
76
- CreateConfigMapSchema,
77
- ],
78
- };
60
+ // Get all available tools
61
+ const allTools = [
62
+ cleanupSchema,
63
+ createDeploymentSchema,
64
+ createNamespaceSchema,
65
+ createPodSchema,
66
+ createCronJobSchema,
67
+ createServiceSchema,
68
+ deletePodSchema,
69
+ deleteDeploymentSchema,
70
+ deleteNamespaceSchema,
71
+ describeCronJobSchema,
72
+ describePodSchema,
73
+ describeDeploymentSchema,
74
+ explainResourceSchema,
75
+ getEventsSchema,
76
+ getJobLogsSchema,
77
+ getLogsSchema,
78
+ installHelmChartSchema,
79
+ listApiResourcesSchema,
80
+ listCronJobsSchema,
81
+ listContextsSchema,
82
+ getCurrentContextSchema,
83
+ setCurrentContextSchema,
84
+ listDeploymentsSchema,
85
+ listJobsSchema,
86
+ listNamespacesSchema,
87
+ listNodesSchema,
88
+ listPodsSchema,
89
+ listServicesSchema,
90
+ uninstallHelmChartSchema,
91
+ updateDeploymentSchema,
92
+ upgradeHelmChartSchema,
93
+ PortForwardSchema,
94
+ StopPortForwardSchema,
95
+ scaleDeploymentSchema,
96
+ DeleteCronJobSchema,
97
+ CreateConfigMapSchema,
98
+ ];
99
+ // Filter out destructive tools if ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS is set to 'true'
100
+ const tools = nonDestructiveTools
101
+ ? allTools.filter(tool => !destructiveTools.some(dt => dt.name === tool.name))
102
+ : allTools;
103
+ return { tools };
79
104
  });
80
105
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
106
  try {
@@ -158,6 +183,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
158
183
  case "list_cronjobs": {
159
184
  return await listCronJobs(k8sManager, input);
160
185
  }
186
+ case "list_contexts": {
187
+ return await listContexts(k8sManager, input);
188
+ }
189
+ case "get_current_context": {
190
+ return await getCurrentContext(k8sManager, input);
191
+ }
192
+ case "set_current_context": {
193
+ return await setCurrentContext(k8sManager, input);
194
+ }
161
195
  case "describe_cronjob": {
162
196
  return await describeCronJob(k8sManager, input);
163
197
  }
@@ -188,6 +222,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
188
222
  case "create_deployment": {
189
223
  return await createDeployment(k8sManager, input);
190
224
  }
225
+ case "update_deployment": {
226
+ return await updateDeployment(k8sManager, input);
227
+ }
191
228
  case "describe_deployment": {
192
229
  return await describeDeployment(k8sManager, input);
193
230
  }
@@ -197,6 +234,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
197
234
  case "create_configmap": {
198
235
  return await createConfigMap(k8sManager, input);
199
236
  }
237
+ case "create_service": {
238
+ return await createService(k8sManager, input);
239
+ }
200
240
  default:
201
241
  throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
202
242
  }
@@ -505,3 +505,69 @@ export declare const CreateConfigMapResponseSchema: z.ZodObject<{
505
505
  success: boolean;
506
506
  }[];
507
507
  }>;
508
+ export declare const ListContextsResponseSchema: z.ZodObject<{
509
+ content: z.ZodArray<z.ZodObject<{
510
+ type: z.ZodLiteral<"text">;
511
+ text: z.ZodString;
512
+ }, "strip", z.ZodTypeAny, {
513
+ type: "text";
514
+ text: string;
515
+ }, {
516
+ type: "text";
517
+ text: string;
518
+ }>, "many">;
519
+ }, "strip", z.ZodTypeAny, {
520
+ content: {
521
+ type: "text";
522
+ text: string;
523
+ }[];
524
+ }, {
525
+ content: {
526
+ type: "text";
527
+ text: string;
528
+ }[];
529
+ }>;
530
+ export declare const GetCurrentContextResponseSchema: z.ZodObject<{
531
+ content: z.ZodArray<z.ZodObject<{
532
+ type: z.ZodLiteral<"text">;
533
+ text: z.ZodString;
534
+ }, "strip", z.ZodTypeAny, {
535
+ type: "text";
536
+ text: string;
537
+ }, {
538
+ type: "text";
539
+ text: string;
540
+ }>, "many">;
541
+ }, "strip", z.ZodTypeAny, {
542
+ content: {
543
+ type: "text";
544
+ text: string;
545
+ }[];
546
+ }, {
547
+ content: {
548
+ type: "text";
549
+ text: string;
550
+ }[];
551
+ }>;
552
+ export declare const SetCurrentContextResponseSchema: z.ZodObject<{
553
+ content: z.ZodArray<z.ZodObject<{
554
+ type: z.ZodLiteral<"text">;
555
+ text: z.ZodString;
556
+ }, "strip", z.ZodTypeAny, {
557
+ type: "text";
558
+ text: string;
559
+ }, {
560
+ type: "text";
561
+ text: string;
562
+ }>, "many">;
563
+ }, "strip", z.ZodTypeAny, {
564
+ content: {
565
+ type: "text";
566
+ text: string;
567
+ }[];
568
+ }, {
569
+ content: {
570
+ type: "text";
571
+ text: string;
572
+ }[];
573
+ }>;
@@ -85,3 +85,12 @@ export const CreateConfigMapResponseSchema = z.object({
85
85
  message: z.string(),
86
86
  })),
87
87
  });
88
+ export const ListContextsResponseSchema = z.object({
89
+ content: z.array(ToolResponseContent),
90
+ });
91
+ export const GetCurrentContextResponseSchema = z.object({
92
+ content: z.array(ToolResponseContent),
93
+ });
94
+ export const SetCurrentContextResponseSchema = z.object({
95
+ content: z.array(ToolResponseContent),
96
+ });
@@ -0,0 +1,74 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const createServiceSchema: {
3
+ readonly name: "create_service";
4
+ readonly description: "Create a new Kubernetes service";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ };
11
+ readonly namespace: {
12
+ readonly type: "string";
13
+ readonly default: "default";
14
+ };
15
+ readonly type: {
16
+ readonly type: "string";
17
+ readonly enum: readonly ["ClusterIP", "NodePort", "LoadBalancer"];
18
+ readonly default: "ClusterIP";
19
+ };
20
+ readonly selector: {
21
+ readonly type: "object";
22
+ readonly additionalProperties: {
23
+ readonly type: "string";
24
+ };
25
+ readonly default: {};
26
+ };
27
+ readonly ports: {
28
+ readonly type: "array";
29
+ readonly items: {
30
+ readonly type: "object";
31
+ readonly properties: {
32
+ readonly port: {
33
+ readonly type: "number";
34
+ };
35
+ readonly targetPort: {
36
+ readonly type: "number";
37
+ };
38
+ readonly protocol: {
39
+ readonly type: "string";
40
+ readonly enum: readonly ["TCP", "UDP"];
41
+ readonly default: "TCP";
42
+ };
43
+ readonly name: {
44
+ readonly type: "string";
45
+ };
46
+ readonly nodePort: {
47
+ readonly type: "number";
48
+ };
49
+ };
50
+ readonly required: readonly ["port"];
51
+ };
52
+ };
53
+ };
54
+ readonly required: readonly ["name", "ports"];
55
+ };
56
+ };
57
+ export declare function createService(k8sManager: KubernetesManager, input: {
58
+ name: string;
59
+ namespace?: string;
60
+ type?: "ClusterIP" | "NodePort" | "LoadBalancer";
61
+ selector?: Record<string, string>;
62
+ ports: Array<{
63
+ port: number;
64
+ targetPort?: number;
65
+ protocol?: string;
66
+ name?: string;
67
+ nodePort?: number;
68
+ }>;
69
+ }): Promise<{
70
+ content: {
71
+ type: string;
72
+ text: string;
73
+ }[];
74
+ }>;
@@ -0,0 +1,102 @@
1
+ export const createServiceSchema = {
2
+ name: "create_service",
3
+ description: "Create a new Kubernetes service",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ namespace: { type: "string", default: "default" },
9
+ type: {
10
+ type: "string",
11
+ enum: ["ClusterIP", "NodePort", "LoadBalancer"],
12
+ default: "ClusterIP"
13
+ },
14
+ selector: {
15
+ type: "object",
16
+ additionalProperties: { type: "string" },
17
+ default: {}
18
+ },
19
+ ports: {
20
+ type: "array",
21
+ items: {
22
+ type: "object",
23
+ properties: {
24
+ port: { type: "number" },
25
+ targetPort: { type: "number" },
26
+ protocol: {
27
+ type: "string",
28
+ enum: ["TCP", "UDP"],
29
+ default: "TCP"
30
+ },
31
+ name: { type: "string" },
32
+ nodePort: { type: "number" }
33
+ },
34
+ required: ["port"]
35
+ }
36
+ }
37
+ },
38
+ required: ["name", "ports"],
39
+ },
40
+ };
41
+ export async function createService(k8sManager, input) {
42
+ const namespace = input.namespace || "default";
43
+ const serviceType = input.type || "ClusterIP";
44
+ // Convert ports to k8s.V1ServicePort format
45
+ const servicePorts = input.ports.map((portConfig, index) => {
46
+ return {
47
+ port: portConfig.port,
48
+ targetPort: portConfig.targetPort !== undefined ? portConfig.targetPort : portConfig.port,
49
+ protocol: portConfig.protocol || "TCP",
50
+ name: portConfig.name || `port-${index}`,
51
+ ...(serviceType === "NodePort" && portConfig.nodePort ? { nodePort: portConfig.nodePort } : {})
52
+ };
53
+ });
54
+ // Default selector
55
+ const selector = input.selector || { app: input.name };
56
+ const service = {
57
+ apiVersion: "v1",
58
+ kind: "Service",
59
+ metadata: {
60
+ name: input.name,
61
+ namespace: namespace,
62
+ labels: {
63
+ "mcp-managed": "true",
64
+ app: input.name,
65
+ },
66
+ },
67
+ spec: {
68
+ type: serviceType,
69
+ selector: selector,
70
+ ports: servicePorts
71
+ }
72
+ };
73
+ try {
74
+ const response = await k8sManager
75
+ .getCoreApi()
76
+ .createNamespacedService(namespace, service);
77
+ k8sManager.trackResource("Service", input.name, namespace);
78
+ return {
79
+ content: [
80
+ {
81
+ type: "text",
82
+ text: JSON.stringify({
83
+ serviceName: response.body.metadata.name,
84
+ namespace: response.body.metadata.namespace,
85
+ type: response.body.spec.type,
86
+ clusterIP: response.body.spec.clusterIP,
87
+ ports: response.body.spec.ports,
88
+ status: "created",
89
+ }, null, 2),
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ catch (error) {
95
+ console.error("Service creation error:", {
96
+ status: error.response?.statusCode,
97
+ message: error.response?.body?.message || error.message,
98
+ details: error.response?.body,
99
+ });
100
+ throw error;
101
+ }
102
+ }
@@ -0,0 +1,23 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const getCurrentContextSchema: {
3
+ readonly name: "get_current_context";
4
+ readonly description: "Get the current Kubernetes context";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly detailed: {
9
+ readonly type: "boolean";
10
+ readonly description: "Include detailed information about the current context";
11
+ readonly default: false;
12
+ };
13
+ };
14
+ };
15
+ };
16
+ export declare function getCurrentContext(k8sManager: KubernetesManager, input: {
17
+ detailed?: boolean;
18
+ }): Promise<{
19
+ content: {
20
+ type: string;
21
+ text: string;
22
+ }[];
23
+ }>;
@@ -0,0 +1,55 @@
1
+ export const getCurrentContextSchema = {
2
+ name: "get_current_context",
3
+ description: "Get the current Kubernetes context",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ detailed: {
8
+ type: "boolean",
9
+ description: "Include detailed information about the current context",
10
+ default: false
11
+ }
12
+ }
13
+ },
14
+ };
15
+ export async function getCurrentContext(k8sManager, input) {
16
+ try {
17
+ // Get the KubeConfig from the KubernetesManager
18
+ const kc = k8sManager.getKubeConfig();
19
+ // Get the current context name
20
+ const currentContextName = kc.getCurrentContext();
21
+ // If detailed is true, get more information about the context
22
+ if (input.detailed) {
23
+ const contexts = kc.getContexts();
24
+ const currentContext = contexts.find(context => context.name === currentContextName);
25
+ if (!currentContext) {
26
+ throw new Error(`Current context '${currentContextName}' not found in available contexts`);
27
+ }
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text",
32
+ text: JSON.stringify({
33
+ name: currentContextName,
34
+ cluster: currentContext.cluster,
35
+ user: currentContext.user,
36
+ namespace: currentContext.namespace || "default"
37
+ }, null, 2),
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ // Simple response with just the context name
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: JSON.stringify({ currentContext: currentContextName }, null, 2),
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ catch (error) {
53
+ throw new Error(`Failed to get current context: ${error.message}`);
54
+ }
55
+ }
@@ -85,7 +85,11 @@ export const uninstallHelmChartSchema = {
85
85
  };
86
86
  const executeHelmCommand = (command) => {
87
87
  try {
88
- return execSync(command, { encoding: "utf8" });
88
+ // Add a generous timeout of 60 seconds for Helm operations
89
+ return execSync(command, {
90
+ encoding: "utf8",
91
+ timeout: 60000 // 60 seconds timeout
92
+ });
89
93
  }
90
94
  catch (error) {
91
95
  throw new Error(`Helm command failed: ${error.message}`);
@@ -0,0 +1,23 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const listContextsSchema: {
3
+ readonly name: "list_contexts";
4
+ readonly description: "List all available Kubernetes contexts";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly showCurrent: {
9
+ readonly type: "boolean";
10
+ readonly description: "Show which context is currently active";
11
+ readonly default: true;
12
+ };
13
+ };
14
+ };
15
+ };
16
+ export declare function listContexts(k8sManager: KubernetesManager, input: {
17
+ showCurrent?: boolean;
18
+ }): Promise<{
19
+ content: {
20
+ type: string;
21
+ text: string;
22
+ }[];
23
+ }>;
@@ -0,0 +1,39 @@
1
+ export const listContextsSchema = {
2
+ name: "list_contexts",
3
+ description: "List all available Kubernetes contexts",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ showCurrent: {
8
+ type: "boolean",
9
+ description: "Show which context is currently active",
10
+ default: true
11
+ }
12
+ }
13
+ },
14
+ };
15
+ export async function listContexts(k8sManager, input) {
16
+ try {
17
+ // Get the KubeConfig from the KubernetesManager
18
+ const kc = k8sManager.getKubeConfig();
19
+ const contexts = kc.getContexts();
20
+ const currentContext = input.showCurrent ? kc.getCurrentContext() : undefined;
21
+ const contextList = contexts.map(context => ({
22
+ name: context.name,
23
+ cluster: context.cluster,
24
+ user: context.user,
25
+ isCurrent: context.name === currentContext
26
+ }));
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: JSON.stringify({ contexts: contextList }, null, 2),
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ catch (error) {
37
+ throw new Error(`Failed to list contexts: ${error.message}`);
38
+ }
39
+ }
@@ -0,0 +1,23 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const setCurrentContextSchema: {
3
+ readonly name: "set_current_context";
4
+ readonly description: "Set the current Kubernetes context";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ readonly description: "Name of the context to set as current";
11
+ };
12
+ };
13
+ readonly required: readonly ["name"];
14
+ };
15
+ };
16
+ export declare function setCurrentContext(k8sManager: KubernetesManager, input: {
17
+ name: string;
18
+ }): Promise<{
19
+ content: {
20
+ type: string;
21
+ text: string;
22
+ }[];
23
+ }>;
@@ -0,0 +1,35 @@
1
+ export const setCurrentContextSchema = {
2
+ name: "set_current_context",
3
+ description: "Set the current Kubernetes context",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: {
8
+ type: "string",
9
+ description: "Name of the context to set as current"
10
+ }
11
+ },
12
+ required: ["name"],
13
+ },
14
+ };
15
+ export async function setCurrentContext(k8sManager, input) {
16
+ try {
17
+ // Set the current context
18
+ k8sManager.setCurrentContext(input.name);
19
+ return {
20
+ content: [
21
+ {
22
+ type: "text",
23
+ text: JSON.stringify({
24
+ success: true,
25
+ message: `Current context set to '${input.name}'`,
26
+ context: input.name
27
+ }, null, 2),
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ catch (error) {
33
+ throw new Error(`Failed to set current context: ${error.message}`);
34
+ }
35
+ }
@@ -0,0 +1,113 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ import { CustomContainerConfigType } from "../config/container-templates.js";
3
+ export declare const updateDeploymentSchema: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ type: string;
8
+ required: string[];
9
+ properties: {
10
+ name: {
11
+ type: string;
12
+ };
13
+ namespace: {
14
+ type: string;
15
+ };
16
+ template: {
17
+ type: string;
18
+ enum: ["ubuntu", "nginx", "busybox", "alpine", "custom"];
19
+ };
20
+ containerName: {
21
+ type: string;
22
+ description: string;
23
+ };
24
+ replicas: {
25
+ type: string;
26
+ };
27
+ customConfig: {
28
+ type: string;
29
+ properties: {
30
+ image: {
31
+ type: string;
32
+ };
33
+ command: {
34
+ type: string;
35
+ items: {
36
+ type: string;
37
+ };
38
+ };
39
+ args: {
40
+ type: string;
41
+ items: {
42
+ type: string;
43
+ };
44
+ };
45
+ ports: {
46
+ type: string;
47
+ items: {
48
+ type: string;
49
+ properties: {
50
+ containerPort: {
51
+ type: string;
52
+ };
53
+ name: {
54
+ type: string;
55
+ };
56
+ protocol: {
57
+ type: string;
58
+ };
59
+ };
60
+ };
61
+ };
62
+ resources: {
63
+ type: string;
64
+ properties: {
65
+ limits: {
66
+ type: string;
67
+ additionalProperties: {
68
+ type: string;
69
+ };
70
+ };
71
+ requests: {
72
+ type: string;
73
+ additionalProperties: {
74
+ type: string;
75
+ };
76
+ };
77
+ };
78
+ };
79
+ env: {
80
+ type: string;
81
+ items: {
82
+ type: string;
83
+ properties: {
84
+ name: {
85
+ type: string;
86
+ };
87
+ value: {
88
+ type: string;
89
+ };
90
+ valueFrom: {
91
+ type: string;
92
+ };
93
+ };
94
+ };
95
+ };
96
+ };
97
+ };
98
+ };
99
+ };
100
+ };
101
+ export declare function updateDeployment(k8sManager: KubernetesManager, params: {
102
+ name: string;
103
+ namespace: string;
104
+ template: string;
105
+ containerName?: string;
106
+ replicas?: number;
107
+ customConfig?: CustomContainerConfigType;
108
+ }): Promise<{
109
+ content: {
110
+ type: string;
111
+ text: string;
112
+ }[];
113
+ }>;
@@ -0,0 +1,155 @@
1
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2
+ import { ContainerTemplate, containerTemplates, CustomContainerConfig, } from "../config/container-templates.js";
3
+ export const updateDeploymentSchema = {
4
+ name: "update_deployment",
5
+ description: "Update an existing kubernetes deployment in cluster",
6
+ inputSchema: {
7
+ type: "object",
8
+ required: ["name", "namespace", "template"],
9
+ properties: {
10
+ name: { type: "string" },
11
+ namespace: { type: "string" },
12
+ template: {
13
+ type: "string",
14
+ enum: ContainerTemplate.options,
15
+ },
16
+ containerName: {
17
+ type: "string",
18
+ description: "Name of the container to update",
19
+ },
20
+ replicas: { type: "number" },
21
+ customConfig: {
22
+ type: "object",
23
+ properties: {
24
+ image: { type: "string" },
25
+ command: { type: "array", items: { type: "string" } },
26
+ args: { type: "array", items: { type: "string" } },
27
+ ports: {
28
+ type: "array",
29
+ items: {
30
+ type: "object",
31
+ properties: {
32
+ containerPort: { type: "number" },
33
+ name: { type: "string" },
34
+ protocol: { type: "string" },
35
+ },
36
+ },
37
+ },
38
+ resources: {
39
+ type: "object",
40
+ properties: {
41
+ limits: {
42
+ type: "object",
43
+ additionalProperties: { type: "string" },
44
+ },
45
+ requests: {
46
+ type: "object",
47
+ additionalProperties: { type: "string" },
48
+ },
49
+ },
50
+ },
51
+ env: {
52
+ type: "array",
53
+ items: {
54
+ type: "object",
55
+ properties: {
56
+ name: { type: "string" },
57
+ value: { type: "string" },
58
+ valueFrom: { type: "object" },
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ },
65
+ },
66
+ };
67
+ export async function updateDeployment(k8sManager, params) {
68
+ // Get existing deployment
69
+ const { body: existingDeployment } = await k8sManager
70
+ .getAppsApi()
71
+ .readNamespacedDeployment(params.name, params.namespace)
72
+ .catch((error) => {
73
+ console.error("Deployment read error:", {
74
+ status: error.response?.statusCode,
75
+ message: error.response?.body?.message || error.message,
76
+ details: error.response?.body,
77
+ });
78
+ throw error;
79
+ });
80
+ // Find target container
81
+ const containers = existingDeployment.spec.template.spec.containers;
82
+ let targetContainerIndex = params.containerName
83
+ ? containers.findIndex(c => c.name === params.containerName)
84
+ : 0;
85
+ if (targetContainerIndex === -1) {
86
+ throw new McpError(ErrorCode.InvalidRequest, `Container '${params.containerName}' not found in deployment`);
87
+ }
88
+ // Prepare container config
89
+ const templateConfig = containerTemplates[params.template];
90
+ let containerConfig;
91
+ if (params.template === "custom") {
92
+ if (!params.customConfig) {
93
+ throw new McpError(ErrorCode.InvalidRequest, "Custom container configuration is required when using 'custom' template");
94
+ }
95
+ const validatedConfig = CustomContainerConfig.parse(params.customConfig);
96
+ containerConfig = {
97
+ ...containers[targetContainerIndex],
98
+ ...templateConfig,
99
+ image: validatedConfig.image,
100
+ command: validatedConfig.command,
101
+ args: validatedConfig.args,
102
+ ports: validatedConfig.ports,
103
+ resources: validatedConfig.resources,
104
+ env: validatedConfig.env,
105
+ };
106
+ }
107
+ else {
108
+ containerConfig = {
109
+ ...containers[targetContainerIndex],
110
+ ...templateConfig,
111
+ };
112
+ }
113
+ // Update deployment
114
+ const updatedContainers = [...containers];
115
+ updatedContainers[targetContainerIndex] = containerConfig;
116
+ const updatedDeployment = {
117
+ ...existingDeployment,
118
+ spec: {
119
+ ...existingDeployment.spec,
120
+ replicas: params.replicas ?? existingDeployment.spec.replicas,
121
+ template: {
122
+ ...existingDeployment.spec.template,
123
+ spec: {
124
+ ...existingDeployment.spec.template.spec,
125
+ containers: updatedContainers,
126
+ },
127
+ },
128
+ },
129
+ };
130
+ const { body } = await k8sManager
131
+ .getAppsApi()
132
+ .replaceNamespacedDeployment(params.name, params.namespace, updatedDeployment)
133
+ .catch((error) => {
134
+ if (error instanceof McpError)
135
+ throw error;
136
+ throw new McpError(ErrorCode.InternalError, `Failed to update deployment: ${error}`);
137
+ });
138
+ return {
139
+ content: [
140
+ {
141
+ type: "text",
142
+ text: JSON.stringify({
143
+ message: "Deployment updated successfully",
144
+ deployment: {
145
+ name: body.metadata?.name,
146
+ namespace: body.metadata?.namespace,
147
+ replicas: body.spec?.replicas,
148
+ image: body.spec?.template.spec?.containers[targetContainerIndex].image,
149
+ containerName: body.spec?.template.spec?.containers[targetContainerIndex].name,
150
+ },
151
+ }, null, 2),
152
+ },
153
+ ],
154
+ };
155
+ }
@@ -9,6 +9,12 @@ export declare class KubernetesManager {
9
9
  private k8sAppsApi;
10
10
  private k8sBatchApi;
11
11
  constructor();
12
+ /**
13
+ * Set the current context to the desired context name.
14
+ *
15
+ * @param contextName
16
+ */
17
+ setCurrentContext(contextName: string): void;
12
18
  cleanup(): Promise<void>;
13
19
  trackResource(kind: string, name: string, namespace: string): void;
14
20
  deleteResource(kind: string, name: string, namespace: string): Promise<void>;
@@ -14,6 +14,25 @@ export class KubernetesManager {
14
14
  this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
15
15
  this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
16
16
  }
17
+ /**
18
+ * Set the current context to the desired context name.
19
+ *
20
+ * @param contextName
21
+ */
22
+ setCurrentContext(contextName) {
23
+ // Get all available contexts
24
+ const contexts = this.kc.getContexts();
25
+ const contextNames = contexts.map(context => context.name);
26
+ // Check if the requested context exists
27
+ if (!contextNames.includes(contextName)) {
28
+ throw new Error(`Context '${contextName}' not found. Available contexts: ${contextNames.join(', ')}`);
29
+ }
30
+ // Set the current context
31
+ this.kc.setCurrentContext(contextName);
32
+ this.k8sApi = this.kc.makeApiClient(k8s.CoreV1Api);
33
+ this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
34
+ this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
35
+ }
17
36
  async cleanup() {
18
37
  // Stop watches
19
38
  for (const watch of this.watches) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",