mcp-server-kubernetes 1.3.2 → 1.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/README.md CHANGED
@@ -67,9 +67,11 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
67
67
 
68
68
  - [x] Connect to a Kubernetes cluster
69
69
  - [x] List all pods, services, deployments, nodes
70
+ - [x] Describe nodes
70
71
  - [x] Create, describe, delete a pod
71
72
  - [x] List all namespaces, create a namespace
72
73
  - [x] Create custom pod & deployment configs, update deployment replicas
74
+ - [x] Create, describe, delete, update a service
73
75
  - [x] Get logs from a pod for debugging (supports pods, deployments, jobs, and label selectors)
74
76
  - [x] Support Helm v3 for installing charts
75
77
  - Install charts with custom values
@@ -82,9 +84,12 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
82
84
  - [x] Get Kubernetes events from the cluster
83
85
  - [x] Port forward to a pod or service
84
86
  - [x] Create, list, and decribe cronjobs
87
+ - [x] Non-destructive mode for read and create/update-only access to clusters
85
88
 
86
89
  ## Local Development
87
90
 
91
+ Make sure that you have [bun installed](https://bun.sh/docs/installation). Clone the repo & install dependencies:
92
+
88
93
  ```bash
89
94
  git clone https://github.com/Flux159/mcp-server-kubernetes.git
90
95
  cd mcp-server-kubernetes
@@ -134,7 +139,7 @@ npx @modelcontextprotocol/inspector node dist/index.js
134
139
  6. Local testing with [mcp-chat](https://github.com/Flux159/mcp-chat)
135
140
 
136
141
  ```bash
137
- npm run chat
142
+ bun run chat
138
143
  ```
139
144
 
140
145
  ## Contributing
@@ -143,7 +148,9 @@ See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details.
143
148
 
144
149
  ## Advanced
145
150
 
146
- For more advanced information like using SSE transport, see the [ADVANCED_README.md](ADVANCED_README.md).
151
+ ### Additional Advanced Features
152
+
153
+ 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
154
 
148
155
  ## Architecture
149
156
 
package/dist/index.js CHANGED
@@ -6,9 +6,10 @@ import { listNodes, listNodesSchema } from "./tools/list_nodes.js";
6
6
  import { listServices, listServicesSchema } from "./tools/list_services.js";
7
7
  import { listDeployments, listDeploymentsSchema, } from "./tools/list_deployments.js";
8
8
  import { listCronJobs, listCronJobsSchema } from "./tools/list_cronjobs.js";
9
- import { describeCronJob, describeCronJobSchema } from "./tools/describe_cronjob.js";
9
+ import { describeCronJob, describeCronJobSchema, } from "./tools/describe_cronjob.js";
10
10
  import { listJobs, listJobsSchema } from "./tools/list_jobs.js";
11
11
  import { getJobLogs, getJobLogsSchema } from "./tools/get_job_logs.js";
12
+ import { describeNode, describeNodeSchema } from "./tools/describe_node.js";
12
13
  import { installHelmChart, installHelmChartSchema, upgradeHelmChart, upgradeHelmChartSchema, uninstallHelmChart, uninstallHelmChartSchema, } from "./tools/helm-operations.js";
13
14
  import { explainResource, explainResourceSchema, listApiResources, listApiResourcesSchema, } from "./tools/kubectl-operations.js";
14
15
  import { createNamespace, createNamespaceSchema, } from "./tools/create_namespace.js";
@@ -25,57 +26,90 @@ import { KubernetesManager } from "./types.js";
25
26
  import { serverConfig } from "./config/server-config.js";
26
27
  import { createDeploymentSchema } from "./config/deployment-config.js";
27
28
  import { listNamespacesSchema } from "./config/namespace-config.js";
28
- import { deleteNamespace, deleteNamespaceSchema } from "./tools/delete_namespace.js";
29
+ import { deleteNamespace, deleteNamespaceSchema, } from "./tools/delete_namespace.js";
29
30
  import { cleanupSchema } from "./config/cleanup-config.js";
30
31
  import { startSSEServer } from "./utils/sse.js";
31
32
  import { startPortForward, PortForwardSchema, stopPortForward, StopPortForwardSchema, } from "./tools/port_forward.js";
32
- import { deleteDeployment, deleteDeploymentSchema } from "./tools/delete_deployment.js";
33
+ import { deleteDeployment, deleteDeploymentSchema, } from "./tools/delete_deployment.js";
33
34
  import { createDeployment } from "./tools/create_deployment.js";
34
- import { scaleDeployment, scaleDeploymentSchema } from "./tools/scale_deployment.js";
35
+ import { scaleDeployment, scaleDeploymentSchema, } from "./tools/scale_deployment.js";
35
36
  import { describeDeployment, describeDeploymentSchema, } from "./tools/describe_deployment.js";
36
- import { createConfigMap, CreateConfigMapSchema } from "./tools/create_configmap.js";
37
+ import { updateDeployment, updateDeploymentSchema, } from "./tools/update_deployment.js";
38
+ import { createConfigMap, CreateConfigMapSchema, } from "./tools/create_configmap.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
+ import { createService, createServiceSchema } from "./tools/create_service.js";
43
+ import { describeService, describeServiceSchema, } from "./tools/describe_service.js";
44
+ import { updateService, updateServiceSchema } from "./tools/update_service.js";
45
+ import { deleteService, deleteServiceSchema } from "./tools/delete_service.js";
46
+ // Check if non-destructive tools only mode is enabled
47
+ const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === "true";
37
48
  const k8sManager = new KubernetesManager();
38
49
  const server = new Server({
39
50
  name: serverConfig.name,
40
51
  version: serverConfig.version,
41
52
  }, serverConfig);
53
+ // Define destructive tools (delete and uninstall operations)
54
+ const destructiveTools = [
55
+ deletePodSchema,
56
+ deleteServiceSchema,
57
+ deleteDeploymentSchema,
58
+ deleteNamespaceSchema,
59
+ uninstallHelmChartSchema,
60
+ DeleteCronJobSchema,
61
+ cleanupSchema, // Cleanup is also destructive as it deletes resources
62
+ ];
42
63
  // Tools handlers
43
64
  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
- };
65
+ // Get all available tools
66
+ const allTools = [
67
+ cleanupSchema,
68
+ createDeploymentSchema,
69
+ createNamespaceSchema,
70
+ createPodSchema,
71
+ createCronJobSchema,
72
+ createServiceSchema,
73
+ deletePodSchema,
74
+ deleteDeploymentSchema,
75
+ deleteNamespaceSchema,
76
+ deleteServiceSchema,
77
+ describeCronJobSchema,
78
+ describePodSchema,
79
+ describeNodeSchema,
80
+ describeDeploymentSchema,
81
+ describeServiceSchema,
82
+ explainResourceSchema,
83
+ getEventsSchema,
84
+ getJobLogsSchema,
85
+ getLogsSchema,
86
+ installHelmChartSchema,
87
+ listApiResourcesSchema,
88
+ listCronJobsSchema,
89
+ listContextsSchema,
90
+ getCurrentContextSchema,
91
+ setCurrentContextSchema,
92
+ listDeploymentsSchema,
93
+ listJobsSchema,
94
+ listNamespacesSchema,
95
+ listNodesSchema,
96
+ listPodsSchema,
97
+ listServicesSchema,
98
+ uninstallHelmChartSchema,
99
+ updateDeploymentSchema,
100
+ upgradeHelmChartSchema,
101
+ PortForwardSchema,
102
+ StopPortForwardSchema,
103
+ scaleDeploymentSchema,
104
+ DeleteCronJobSchema,
105
+ CreateConfigMapSchema,
106
+ updateServiceSchema,
107
+ ];
108
+ // Filter out destructive tools if ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS is set to 'true'
109
+ const tools = nonDestructiveTools
110
+ ? allTools.filter((tool) => !destructiveTools.some((dt) => dt.name === tool.name))
111
+ : allTools;
112
+ return { tools };
79
113
  });
80
114
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
115
  try {
@@ -112,6 +146,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
112
146
  case "describe_pod": {
113
147
  return await describePod(k8sManager, input);
114
148
  }
149
+ case "describe_node": {
150
+ return await describeNode(k8sManager, input);
151
+ }
115
152
  case "explain_resource": {
116
153
  return await explainResource(input);
117
154
  }
@@ -158,6 +195,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
158
195
  case "list_cronjobs": {
159
196
  return await listCronJobs(k8sManager, input);
160
197
  }
198
+ case "list_contexts": {
199
+ return await listContexts(k8sManager, input);
200
+ }
201
+ case "get_current_context": {
202
+ return await getCurrentContext(k8sManager, input);
203
+ }
204
+ case "set_current_context": {
205
+ return await setCurrentContext(k8sManager, input);
206
+ }
161
207
  case "describe_cronjob": {
162
208
  return await describeCronJob(k8sManager, input);
163
209
  }
@@ -188,6 +234,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
188
234
  case "create_deployment": {
189
235
  return await createDeployment(k8sManager, input);
190
236
  }
237
+ case "update_deployment": {
238
+ return await updateDeployment(k8sManager, input);
239
+ }
191
240
  case "describe_deployment": {
192
241
  return await describeDeployment(k8sManager, input);
193
242
  }
@@ -197,6 +246,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
197
246
  case "create_configmap": {
198
247
  return await createConfigMap(k8sManager, input);
199
248
  }
249
+ case "create_service": {
250
+ return await createService(k8sManager, input);
251
+ }
252
+ case "update_service": {
253
+ return await updateService(k8sManager, input);
254
+ }
255
+ case "delete_service": {
256
+ return await deleteService(k8sManager, input);
257
+ }
258
+ case "describe_service": {
259
+ return await describeService(k8sManager, input);
260
+ }
200
261
  default:
201
262
  throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
202
263
  }
@@ -505,3 +505,91 @@ 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
+ }>;
574
+ export declare const DescribeNodeResponseSchema: z.ZodObject<{
575
+ content: z.ZodArray<z.ZodObject<{
576
+ type: z.ZodLiteral<"text">;
577
+ text: z.ZodString;
578
+ }, "strip", z.ZodTypeAny, {
579
+ type: "text";
580
+ text: string;
581
+ }, {
582
+ type: "text";
583
+ text: string;
584
+ }>, "many">;
585
+ }, "strip", z.ZodTypeAny, {
586
+ content: {
587
+ type: "text";
588
+ text: string;
589
+ }[];
590
+ }, {
591
+ content: {
592
+ type: "text";
593
+ text: string;
594
+ }[];
595
+ }>;
@@ -85,3 +85,15 @@ 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
+ });
97
+ export const DescribeNodeResponseSchema = z.object({
98
+ content: z.array(ToolResponseContent),
99
+ });
@@ -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,32 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const deleteServiceSchema: {
3
+ readonly name: "delete_service";
4
+ readonly description: "Delete a 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 ignoreNotFound: {
16
+ readonly type: "boolean";
17
+ readonly default: false;
18
+ };
19
+ };
20
+ readonly required: readonly ["name"];
21
+ };
22
+ };
23
+ export declare function deleteService(k8sManager: KubernetesManager, input: {
24
+ name: string;
25
+ namespace?: string;
26
+ ignoreNotFound?: boolean;
27
+ }): Promise<{
28
+ content: {
29
+ type: string;
30
+ text: string;
31
+ }[];
32
+ }>;
@@ -0,0 +1,46 @@
1
+ export const deleteServiceSchema = {
2
+ name: "delete_service",
3
+ description: "Delete a Kubernetes service",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ name: { type: "string" },
8
+ namespace: { type: "string", default: "default" },
9
+ ignoreNotFound: { type: "boolean", default: false },
10
+ },
11
+ required: ["name"],
12
+ },
13
+ };
14
+ export async function deleteService(k8sManager, input) {
15
+ const namespace = input.namespace || "default";
16
+ try {
17
+ await k8sManager.getCoreApi().deleteNamespacedService(input.name, namespace);
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text",
22
+ text: JSON.stringify({
23
+ success: true,
24
+ status: "deleted",
25
+ }, null, 2),
26
+ },
27
+ ],
28
+ };
29
+ }
30
+ catch (error) {
31
+ if (input.ignoreNotFound && error.response?.statusCode === 404) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: JSON.stringify({
37
+ success: true,
38
+ status: "not_found",
39
+ }, null, 2),
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ throw error;
45
+ }
46
+ }
@@ -0,0 +1,22 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const describeNodeSchema: {
3
+ readonly name: "describe_node";
4
+ readonly description: "Describe a Kubernetes node (read details like status, capacity, conditions, etc.)";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly name: {
9
+ readonly type: "string";
10
+ };
11
+ };
12
+ readonly required: readonly ["name"];
13
+ };
14
+ };
15
+ export declare function describeNode(k8sManager: KubernetesManager, input: {
16
+ name: string;
17
+ }): Promise<{
18
+ content: {
19
+ type: string;
20
+ text: string;
21
+ }[];
22
+ }>;