mcp-server-kubernetes 0.1.2 → 0.1.3

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
@@ -35,6 +35,7 @@ If you have errors, open up a standard terminal and run `kubectl get pods` to se
35
35
  - [x] List all pods
36
36
  - [x] List all services
37
37
  - [x] List all deployments
38
+ - [x] List all nodes
38
39
  - [x] Create a pod
39
40
  - [x] Delete a pod
40
41
  - [x] Describe a pod
package/dist/index.js CHANGED
@@ -19,15 +19,6 @@ class KubernetesManager {
19
19
  // process.on("SIGTERM", () => this.cleanup());
20
20
  }
21
21
  async cleanup() {
22
- console.log("Cleaning up resources...");
23
- // Stop port forwards
24
- // for (const pf of this.portForwards) {
25
- // try {
26
- // await pf.server.stop();
27
- // } catch (error) {
28
- // console.error(`Failed to close port-forward ${pf.id}:`, error);
29
- // }
30
- // }
31
22
  // Stop watches
32
23
  for (const watch of this.watches) {
33
24
  watch.abort.abort();
@@ -41,6 +32,7 @@ class KubernetesManager {
41
32
  console.error(`Failed to delete ${resource.kind} ${resource.name}:`, error);
42
33
  }
43
34
  }
35
+ // TODO: Cleanup port forwards when implemented
44
36
  }
45
37
  trackResource(kind, name, namespace) {
46
38
  this.resources.push({ kind, name, namespace, createdAt: new Date() });
@@ -83,6 +75,7 @@ class KubernetesManager {
83
75
  }
84
76
  const k8sManager = new KubernetesManager();
85
77
  // Template configurations with health checks and resource limits
78
+ // TODO: Update create_pod to accept custom images and custom template files
86
79
  const containerTemplates = {
87
80
  ubuntu: {
88
81
  name: "main",
@@ -238,6 +231,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
238
231
  },
239
232
  },
240
233
  {
234
+ // TODO: Add support for custom images and templates (see above in containerTemplates definition)
241
235
  name: "create_pod",
242
236
  description: "Create a new Kubernetes pod",
243
237
  inputSchema: {
@@ -259,6 +253,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
259
253
  },
260
254
  },
261
255
  {
256
+ // TODO: Support for custom deployments (see above)
262
257
  name: "create_deployment",
263
258
  description: "Create a new Kubernetes deployment",
264
259
  inputSchema: {
@@ -313,6 +308,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
313
308
  properties: {},
314
309
  },
315
310
  },
311
+ {
312
+ name: "list_nodes",
313
+ description: "List all nodes in the cluster",
314
+ inputSchema: {
315
+ type: "object",
316
+ properties: {},
317
+ },
318
+ },
316
319
  ],
317
320
  };
318
321
  });
@@ -404,9 +407,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
404
407
  };
405
408
  }
406
409
  case "create_pod": {
407
- console.error("calling create_pod");
408
- console.error(input);
409
- console.error(request);
410
410
  const createPodInput = input;
411
411
  const templateConfig = containerTemplates[createPodInput.template];
412
412
  const pod = {
@@ -496,7 +496,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
496
496
  type: "text",
497
497
  text: JSON.stringify({
498
498
  error: "Pod not found",
499
- status: "not_found"
499
+ status: "not_found",
500
500
  }, null, 2),
501
501
  },
502
502
  ],
@@ -513,7 +513,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
513
513
  labels: body.metadata?.labels,
514
514
  },
515
515
  spec: {
516
- containers: body.spec?.containers.map(container => ({
516
+ containers: body.spec?.containers.map((container) => ({
517
517
  name: container.name,
518
518
  image: container.image,
519
519
  ports: container.ports,
@@ -525,7 +525,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
525
525
  phase: body.status?.phase,
526
526
  conditions: body.status?.conditions,
527
527
  containerStatuses: body.status?.containerStatuses,
528
- }
528
+ },
529
529
  };
530
530
  return {
531
531
  content: [
@@ -544,7 +544,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
544
544
  type: "text",
545
545
  text: JSON.stringify({
546
546
  error: "Pod not found",
547
- status: "not_found"
547
+ status: "not_found",
548
548
  }, null, 2),
549
549
  },
550
550
  ],
@@ -567,6 +567,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
567
567
  ],
568
568
  };
569
569
  }
570
+ case "list_nodes": {
571
+ const { body } = await k8sManager.getCoreApi().listNode();
572
+ return {
573
+ content: [
574
+ {
575
+ type: "text",
576
+ text: JSON.stringify({
577
+ nodes: body.items,
578
+ }, null, 2),
579
+ },
580
+ ],
581
+ };
582
+ }
570
583
  default:
571
584
  throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
572
585
  }
@@ -605,6 +618,12 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
605
618
  mimeType: "application/json",
606
619
  description: "List of all namespaces",
607
620
  },
621
+ {
622
+ uri: "k8s://nodes",
623
+ name: "Kubernetes Nodes",
624
+ mimeType: "application/json",
625
+ description: "List of all nodes in the cluster",
626
+ },
608
627
  ],
609
628
  };
610
629
  });
@@ -612,8 +631,11 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
612
631
  try {
613
632
  const uri = request.params.uri;
614
633
  const parts = uri.replace("k8s://", "").split("/");
615
- if (parts[0] === "namespaces" && parts.length === 1) {
616
- const { body } = await k8sManager.getCoreApi().listNamespace();
634
+ const isNamespaces = parts[0] === "namespaces";
635
+ const isNodes = parts[0] === "nodes";
636
+ if ((isNamespaces || isNodes) && parts.length === 1) {
637
+ const fn = isNodes ? "listNode" : "listNamespace";
638
+ const { body } = await k8sManager.getCoreApi()[fn]();
617
639
  return {
618
640
  contents: [
619
641
  {
package/dist/types.d.ts CHANGED
@@ -229,6 +229,28 @@ export declare const ListNamespacesResponseSchema: z.ZodObject<{
229
229
  text: string;
230
230
  }[];
231
231
  }>;
232
+ export declare const ListNodesResponseSchema: z.ZodObject<{
233
+ content: z.ZodArray<z.ZodObject<{
234
+ type: z.ZodLiteral<"text">;
235
+ text: z.ZodString;
236
+ }, "strip", z.ZodTypeAny, {
237
+ type: "text";
238
+ text: string;
239
+ }, {
240
+ type: "text";
241
+ text: string;
242
+ }>, "many">;
243
+ }, "strip", z.ZodTypeAny, {
244
+ content: {
245
+ type: "text";
246
+ text: string;
247
+ }[];
248
+ }, {
249
+ content: {
250
+ type: "text";
251
+ text: string;
252
+ }[];
253
+ }>;
232
254
  export declare const ListResourcesResponseSchema: z.ZodObject<{
233
255
  resources: z.ZodArray<z.ZodObject<{
234
256
  uri: z.ZodString;
package/dist/types.js CHANGED
@@ -70,6 +70,12 @@ export const ListNamespacesResponseSchema = z.object({
70
70
  text: z.string(),
71
71
  })),
72
72
  });
73
+ export const ListNodesResponseSchema = z.object({
74
+ content: z.array(z.object({
75
+ type: z.literal("text"),
76
+ text: z.string(),
77
+ })),
78
+ });
73
79
  export const ListResourcesResponseSchema = z.object({
74
80
  resources: z.array(ResourceSchema),
75
81
  });
package/dist/unit.test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { expect, test } from "vitest";
2
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
3
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
- import { ListToolsResponseSchema, ListPodsResponseSchema, ListDeploymentsResponseSchema, ListNamespacesResponseSchema, CreatePodResponseSchema, DeletePodResponseSchema, CleanupResponseSchema, } from "./types.js";
4
+ import { ListToolsResponseSchema, ListPodsResponseSchema, ListDeploymentsResponseSchema, ListNamespacesResponseSchema, ListNodesResponseSchema, CreatePodResponseSchema, DeletePodResponseSchema, CleanupResponseSchema, } from "./types.js";
5
5
  async function sleep(ms) {
6
6
  return new Promise((resolve) => setTimeout(resolve, ms));
7
7
  }
@@ -38,6 +38,19 @@ test("kubernetes server operations", async () => {
38
38
  expect(namespacesResult.content[0].type).toBe("text");
39
39
  const namespaces = JSON.parse(namespacesResult.content[0].text);
40
40
  expect(namespaces.namespaces).toBeDefined();
41
+ // List nodes
42
+ console.log("Listing nodes...");
43
+ const listNodesResult = await client.request({
44
+ method: "tools/call",
45
+ params: {
46
+ name: "list_nodes",
47
+ arguments: {},
48
+ },
49
+ }, ListNodesResponseSchema);
50
+ expect(listNodesResult.content[0].type).toBe("text");
51
+ const nodes = JSON.parse(listNodesResult.content[0].text);
52
+ expect(nodes.nodes).toBeDefined();
53
+ expect(Array.isArray(nodes.nodes)).toBe(true);
41
54
  // Delete test pod if it exists
42
55
  console.log("Deleting test pod if exists...");
43
56
  const deletePodResult = await client.request({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,7 +20,8 @@
20
20
  "dev": "tsc --watch",
21
21
  "start": "node dist/index.js",
22
22
  "test": "vitest --testTimeout=20000",
23
- "prepublishOnly": "npm run build"
23
+ "prepublishOnly": "npm run build",
24
+ "dockerbuild": "docker buildx build -t flux159/mcp-server-kubernetes --platform linux/amd64,linux/arm64 --push ."
24
25
  },
25
26
  "keywords": [
26
27
  "mcp",