mcp-server-kubernetes 0.3.2 → 1.0.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 +24 -63
- package/dist/config/container-templates.d.ts +102 -1
- package/dist/config/container-templates.js +56 -0
- package/dist/config/deployment-config.d.ts +90 -1
- package/dist/config/deployment-config.js +56 -1
- package/dist/index.js +35 -2
- package/dist/models/response-schemas.d.ts +66 -0
- package/dist/models/response-schemas.js +12 -0
- package/dist/tools/create_deployment.d.ts +135 -0
- package/dist/tools/create_deployment.js +162 -0
- package/dist/tools/create_namespace.d.ts +22 -0
- package/dist/tools/create_namespace.js +48 -0
- package/dist/tools/create_pod.d.ts +92 -1
- package/dist/tools/create_pod.js +92 -10
- package/dist/tools/delete_deployment.d.ts +31 -0
- package/dist/tools/delete_deployment.js +47 -0
- package/dist/tools/describe_deployment.d.ts +26 -0
- package/dist/tools/describe_deployment.js +40 -0
- package/dist/tools/port_forward.d.ts +55 -0
- package/dist/tools/port_forward.js +110 -0
- package/dist/utils/sse.d.ts +2 -0
- package/dist/utils/sse.js +27 -0
- package/package.json +12 -10
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const describeDeploymentSchema: {
|
|
3
|
+
readonly name: "describe_deployment";
|
|
4
|
+
readonly description: "Get details about a Kubernetes deployment";
|
|
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
|
+
};
|
|
14
|
+
};
|
|
15
|
+
readonly required: readonly ["name", "namespace"];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function describeDeployment(k8sManager: KubernetesManager, input: {
|
|
19
|
+
name: string;
|
|
20
|
+
namespace: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
content: {
|
|
23
|
+
type: string;
|
|
24
|
+
text: string;
|
|
25
|
+
}[];
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const describeDeploymentSchema = {
|
|
2
|
+
name: "describe_deployment",
|
|
3
|
+
description: "Get details about a Kubernetes deployment",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
name: { type: "string" },
|
|
8
|
+
namespace: { type: "string" },
|
|
9
|
+
},
|
|
10
|
+
required: ["name", "namespace"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export async function describeDeployment(k8sManager, input) {
|
|
14
|
+
const { body } = await k8sManager
|
|
15
|
+
.getAppsApi()
|
|
16
|
+
.readNamespacedDeployment(input.name, input.namespace)
|
|
17
|
+
.catch((error) => {
|
|
18
|
+
console.error("Deployment description error:", {
|
|
19
|
+
status: error.response?.statusCode,
|
|
20
|
+
message: error.response?.body?.message || error.message,
|
|
21
|
+
details: error.response?.body,
|
|
22
|
+
});
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: JSON.stringify({
|
|
30
|
+
name: body.metadata?.name,
|
|
31
|
+
namespace: body.metadata?.namespace,
|
|
32
|
+
replicas: body.spec?.replicas,
|
|
33
|
+
availableReplicas: body.status?.availableReplicas,
|
|
34
|
+
spec: body.spec,
|
|
35
|
+
status: body.status,
|
|
36
|
+
}, null, 2),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { KubernetesManager } from "../utils/kubernetes-manager.js";
|
|
2
|
+
export declare const PortForwardSchema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
resourceType: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
resourceName: {
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
localPort: {
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
17
|
+
targetPort: {
|
|
18
|
+
type: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
required: string[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare function startPortForward(k8sManager: KubernetesManager, input: {
|
|
25
|
+
resourceType: string;
|
|
26
|
+
resourceName: string;
|
|
27
|
+
localPort: number;
|
|
28
|
+
targetPort: number;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
content: {
|
|
31
|
+
success: boolean;
|
|
32
|
+
message: string;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
35
|
+
export declare const StopPortForwardSchema: {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: string;
|
|
40
|
+
properties: {
|
|
41
|
+
id: {
|
|
42
|
+
type: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
required: string[];
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export declare function stopPortForward(k8sManager: KubernetesManager, input: {
|
|
49
|
+
id: string;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
content: {
|
|
52
|
+
success: boolean;
|
|
53
|
+
message: string;
|
|
54
|
+
}[];
|
|
55
|
+
}>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
// Use spawn instead of exec because port-forward is a long-running process
|
|
3
|
+
async function executeKubectlCommandAsync(command) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const [cmd, ...args] = command.split(" ");
|
|
6
|
+
const process = spawn(cmd, args);
|
|
7
|
+
let output = "";
|
|
8
|
+
let errorOutput = "";
|
|
9
|
+
process.stdout.on("data", (data) => {
|
|
10
|
+
output += data.toString();
|
|
11
|
+
if (output.includes("Forwarding from")) {
|
|
12
|
+
resolve({
|
|
13
|
+
success: true,
|
|
14
|
+
message: "port-forwarding was successful",
|
|
15
|
+
pid: process.pid,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
process.stderr.on("data", (data) => {
|
|
20
|
+
errorOutput += data.toString();
|
|
21
|
+
});
|
|
22
|
+
process.on("error", (error) => {
|
|
23
|
+
reject(new Error(`Failed to execute port-forward: ${error.message}`));
|
|
24
|
+
});
|
|
25
|
+
process.on("close", (code) => {
|
|
26
|
+
if (code !== 0) {
|
|
27
|
+
reject(new Error(`Port-forward process exited with code ${code}. Error: ${errorOutput}`));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// Set a timeout to reject if we don't see the success message
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
if (!output.includes("Forwarding from")) {
|
|
33
|
+
reject(new Error("port-forwarding failed - no success message received"));
|
|
34
|
+
}
|
|
35
|
+
}, 5000);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export const PortForwardSchema = {
|
|
39
|
+
name: "port_forward",
|
|
40
|
+
description: "Forward a local port to a port on a Kubernetes resource",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
resourceType: { type: "string" },
|
|
45
|
+
resourceName: { type: "string" },
|
|
46
|
+
localPort: { type: "number" },
|
|
47
|
+
targetPort: { type: "number" },
|
|
48
|
+
},
|
|
49
|
+
required: ["resourceType", "resourceName", "localPort", "targetPort"],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
export async function startPortForward(k8sManager, input) {
|
|
53
|
+
let command = `kubectl port-forward ${input.resourceType}/${input.resourceName} ${input.localPort}:${input.targetPort}`;
|
|
54
|
+
try {
|
|
55
|
+
const result = await executeKubectlCommandAsync(command);
|
|
56
|
+
// Track the port-forward process
|
|
57
|
+
k8sManager.trackPortForward({
|
|
58
|
+
id: `${input.resourceType}-${input.resourceName}-${input.localPort}`,
|
|
59
|
+
server: {
|
|
60
|
+
stop: async () => {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(result.pid);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`Failed to stop port-forward process ${result.pid}:`, error);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
resourceType: input.resourceType,
|
|
70
|
+
name: input.resourceName,
|
|
71
|
+
namespace: "default", // TODO: Make namespace configurable
|
|
72
|
+
ports: [{ local: input.localPort, remote: input.targetPort }],
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
content: [{ success: result.success, message: result.message }],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
throw new Error(`Failed to execute port-forward: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export const StopPortForwardSchema = {
|
|
83
|
+
name: "stop_port_forward",
|
|
84
|
+
description: "Stop a port-forward process",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
id: { type: "string" },
|
|
89
|
+
},
|
|
90
|
+
required: ["id"],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export async function stopPortForward(k8sManager, input) {
|
|
94
|
+
const portForward = k8sManager.getPortForward(input.id);
|
|
95
|
+
if (!portForward) {
|
|
96
|
+
throw new Error(`Port-forward with id ${input.id} not found`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await portForward.server.stop();
|
|
100
|
+
k8sManager.removePortForward(input.id);
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{ success: true, message: "port-forward stopped successfully" },
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw new Error(`Failed to stop port-forward: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
3
|
+
export function startSSEServer(server) {
|
|
4
|
+
const app = express();
|
|
5
|
+
// Currently just copying from docs & allowing for multiple transport connections: https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse
|
|
6
|
+
// TODO: If exposed to web, then this will enable any client to connect to the server via http - so marked as UNSAFE until mcp has a proper auth solution.
|
|
7
|
+
let transports = [];
|
|
8
|
+
app.get("/sse", async (req, res) => {
|
|
9
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
10
|
+
transports.push(transport);
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
});
|
|
13
|
+
app.post("/messages", (req, res) => {
|
|
14
|
+
const transport = transports.find((t) => t.sessionId === req.query.sessionId);
|
|
15
|
+
if (transport) {
|
|
16
|
+
transport.handlePostMessage(req, res);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
res
|
|
20
|
+
.status(404)
|
|
21
|
+
.send("Not found. Must pass valid sessionId as query param.");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const port = process.env.PORT || 3000;
|
|
25
|
+
app.listen(port);
|
|
26
|
+
console.log(`mcp-kubernetes-server is listening on port ${port}\nUse the following url to connect to the server:\n\http://localhost:${port}/sse`);
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-server-kubernetes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "MCP server for interacting with Kubernetes clusters via kubectl",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,17 +35,19 @@
|
|
|
35
35
|
"node": ">=18"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@kubernetes/client-node": "
|
|
39
|
-
"@modelcontextprotocol/sdk": "1.0
|
|
40
|
-
"js-yaml": "
|
|
41
|
-
"yaml": "
|
|
42
|
-
"zod": "
|
|
38
|
+
"@kubernetes/client-node": "0.20.0",
|
|
39
|
+
"@modelcontextprotocol/sdk": "1.7.0",
|
|
40
|
+
"js-yaml": "4.1.0",
|
|
41
|
+
"yaml": "2.7.0",
|
|
42
|
+
"zod": "3.23.8",
|
|
43
|
+
"express": "4.21.2"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@types/
|
|
46
|
-
"@types/
|
|
47
|
-
"
|
|
48
|
-
"
|
|
46
|
+
"@types/express": "5.0.1",
|
|
47
|
+
"@types/js-yaml": "4.0.9",
|
|
48
|
+
"@types/node": "22.9.3",
|
|
49
|
+
"shx": "0.3.4",
|
|
50
|
+
"typescript": "5.6.2",
|
|
49
51
|
"vitest": "2.1.9"
|
|
50
52
|
}
|
|
51
53
|
}
|