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 +1 -0
- package/dist/index.js +40 -18
- package/dist/types.d.ts +22 -0
- package/dist/types.js +6 -0
- package/dist/unit.test.js +14 -1
- package/package.json +3 -2
package/README.md
CHANGED
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
|
-
|
|
616
|
-
|
|
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.
|
|
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",
|