mcp-server-kubernetes 0.1.1 → 0.1.2
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 +98 -5
- package/dist/index.js +90 -0
- package/dist/unit.test.js +18 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ MCP Server that can connect to a Kubernetes cluster and manage it.
|
|
|
4
4
|
|
|
5
5
|
https://github.com/user-attachments/assets/f25f8f4e-4d04-479b-9ae0-5dac452dd2ed
|
|
6
6
|
|
|
7
|
+
<a href="https://glama.ai/mcp/servers/w71ieamqrt"><img width="380" height="200" src="https://glama.ai/mcp/servers/w71ieamqrt/badge" /></a>
|
|
8
|
+
|
|
7
9
|
## Usage with Claude Desktop
|
|
8
10
|
|
|
9
11
|
```json
|
|
@@ -35,21 +37,112 @@ If you have errors, open up a standard terminal and run `kubectl get pods` to se
|
|
|
35
37
|
- [x] List all deployments
|
|
36
38
|
- [x] Create a pod
|
|
37
39
|
- [x] Delete a pod
|
|
40
|
+
- [x] Describe a pod
|
|
38
41
|
- [x] List all namespaces
|
|
39
|
-
- [] Port forward to a pod
|
|
40
|
-
- [] Get logs from a pod for debugging
|
|
41
|
-
- [] Choose namespace for next commands (memory)
|
|
42
|
-
- [] Support Helm for installing charts
|
|
42
|
+
- [ ] Port forward to a pod
|
|
43
|
+
- [ ] Get logs from a pod for debugging
|
|
44
|
+
- [ ] Choose namespace for next commands (memory)
|
|
45
|
+
- [ ] Support Helm for installing charts
|
|
46
|
+
|
|
47
|
+
## In Progress
|
|
48
|
+
|
|
49
|
+
- [ ] [Docker support](https://github.com/Flux159/mcp-server-kubernetes/pull/9)
|
|
43
50
|
|
|
44
|
-
## Development
|
|
51
|
+
## Local Development
|
|
45
52
|
|
|
46
53
|
```bash
|
|
47
54
|
git clone https://github.com/Flux159/mcp-server-kubernetes.git
|
|
48
55
|
cd mcp-server-kubernetes
|
|
49
56
|
bun install
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Development Workflow
|
|
60
|
+
|
|
61
|
+
1. Start the server in development mode (watches for file changes):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. Run unit tests:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
50
70
|
bun run test
|
|
51
71
|
```
|
|
52
72
|
|
|
73
|
+
3. Build the project:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bun run build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. Local Testing with [Inspector](https://github.com/modelcontextprotocol/inspector)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx @modelcontextprotocol/inspector node build/index.js
|
|
83
|
+
# Follow further instructions on terminal for Inspector link
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Project Structure
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
src/
|
|
90
|
+
├── index.ts # Main server implementation
|
|
91
|
+
├── types.ts # TypeScript type definitions
|
|
92
|
+
└── unit.test.ts # Unit tests
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Contributing
|
|
96
|
+
|
|
97
|
+
1. Fork the repository
|
|
98
|
+
2. Create a feature branch
|
|
99
|
+
3. Make your changes
|
|
100
|
+
4. Add tests for new functionality
|
|
101
|
+
5. Ensure all tests pass
|
|
102
|
+
6. Submit a pull request
|
|
103
|
+
|
|
104
|
+
For bigger changes, please open an issue first to discuss the proposed changes.
|
|
105
|
+
|
|
106
|
+
## Architecture
|
|
107
|
+
|
|
108
|
+
This section describes the high-level architecture of the MCP Kubernetes server.
|
|
109
|
+
|
|
110
|
+
### Request Flow
|
|
111
|
+
|
|
112
|
+
The sequence diagram below illustrates how requests flow through the system:
|
|
113
|
+
|
|
114
|
+
```mermaid
|
|
115
|
+
sequenceDiagram
|
|
116
|
+
participant Client
|
|
117
|
+
participant Transport as StdioTransport
|
|
118
|
+
participant Server as MCP Server
|
|
119
|
+
participant Handler as Request Handler
|
|
120
|
+
participant K8sManager as KubernetesManager
|
|
121
|
+
participant K8s as Kubernetes API
|
|
122
|
+
|
|
123
|
+
Client->>Transport: Send Request via STDIO
|
|
124
|
+
Transport->>Server: Forward Request
|
|
125
|
+
|
|
126
|
+
alt Tools Request
|
|
127
|
+
Server->>Handler: Route to tools handler
|
|
128
|
+
Handler->>K8sManager: Execute tool operation
|
|
129
|
+
K8sManager->>K8s: Make API call
|
|
130
|
+
K8s-->>K8sManager: Return result
|
|
131
|
+
K8sManager-->>Handler: Process response
|
|
132
|
+
Handler-->>Server: Return tool result
|
|
133
|
+
else Resource Request
|
|
134
|
+
Server->>Handler: Route to resource handler
|
|
135
|
+
Handler->>K8sManager: Get resource data
|
|
136
|
+
K8sManager->>K8s: Query API
|
|
137
|
+
K8s-->>K8sManager: Return data
|
|
138
|
+
K8sManager-->>Handler: Format response
|
|
139
|
+
Handler-->>Server: Return resource data
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
Server-->>Transport: Send Response
|
|
143
|
+
Transport-->>Client: Return Final Response
|
|
144
|
+
```
|
|
145
|
+
|
|
53
146
|
## Not planned
|
|
54
147
|
|
|
55
148
|
Authentication / adding clusters to kubectx.
|
package/dist/index.js
CHANGED
|
@@ -293,6 +293,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
293
293
|
required: ["name", "namespace"],
|
|
294
294
|
},
|
|
295
295
|
},
|
|
296
|
+
{
|
|
297
|
+
name: "describe_pod",
|
|
298
|
+
description: "Describe a Kubernetes pod (read details like status, containers, etc.)",
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: "object",
|
|
301
|
+
properties: {
|
|
302
|
+
name: { type: "string" },
|
|
303
|
+
namespace: { type: "string" },
|
|
304
|
+
},
|
|
305
|
+
required: ["name", "namespace"],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
296
308
|
{
|
|
297
309
|
name: "cleanup",
|
|
298
310
|
description: "Cleanup all managed resources",
|
|
@@ -471,6 +483,77 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
471
483
|
throw error;
|
|
472
484
|
}
|
|
473
485
|
}
|
|
486
|
+
case "describe_pod": {
|
|
487
|
+
const describePodInput = input;
|
|
488
|
+
try {
|
|
489
|
+
const { body } = await k8sManager
|
|
490
|
+
.getCoreApi()
|
|
491
|
+
.readNamespacedPod(describePodInput.name, describePodInput.namespace);
|
|
492
|
+
if (!body) {
|
|
493
|
+
return {
|
|
494
|
+
content: [
|
|
495
|
+
{
|
|
496
|
+
type: "text",
|
|
497
|
+
text: JSON.stringify({
|
|
498
|
+
error: "Pod not found",
|
|
499
|
+
status: "not_found"
|
|
500
|
+
}, null, 2),
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
isError: true,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
// Format the pod details for better readability
|
|
507
|
+
const podDetails = {
|
|
508
|
+
kind: body.kind,
|
|
509
|
+
metadata: {
|
|
510
|
+
name: body.metadata?.name,
|
|
511
|
+
namespace: body.metadata?.namespace,
|
|
512
|
+
creationTimestamp: body.metadata?.creationTimestamp,
|
|
513
|
+
labels: body.metadata?.labels,
|
|
514
|
+
},
|
|
515
|
+
spec: {
|
|
516
|
+
containers: body.spec?.containers.map(container => ({
|
|
517
|
+
name: container.name,
|
|
518
|
+
image: container.image,
|
|
519
|
+
ports: container.ports,
|
|
520
|
+
resources: container.resources,
|
|
521
|
+
})),
|
|
522
|
+
nodeName: body.spec?.nodeName,
|
|
523
|
+
},
|
|
524
|
+
status: {
|
|
525
|
+
phase: body.status?.phase,
|
|
526
|
+
conditions: body.status?.conditions,
|
|
527
|
+
containerStatuses: body.status?.containerStatuses,
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
return {
|
|
531
|
+
content: [
|
|
532
|
+
{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: JSON.stringify(podDetails, null, 2),
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
if (error.response?.statusCode === 404) {
|
|
541
|
+
return {
|
|
542
|
+
content: [
|
|
543
|
+
{
|
|
544
|
+
type: "text",
|
|
545
|
+
text: JSON.stringify({
|
|
546
|
+
error: "Pod not found",
|
|
547
|
+
status: "not_found"
|
|
548
|
+
}, null, 2),
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
isError: true,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
throw new McpError(ErrorCode.InternalError, `Failed to describe pod: ${error.response?.body?.message || error.message}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
474
557
|
case "cleanup": {
|
|
475
558
|
await k8sManager.cleanup();
|
|
476
559
|
return {
|
|
@@ -597,3 +680,10 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
597
680
|
});
|
|
598
681
|
const transport = new StdioServerTransport();
|
|
599
682
|
await server.connect(transport);
|
|
683
|
+
["SIGINT", "SIGTERM"].forEach((signal) => {
|
|
684
|
+
process.on(signal, async () => {
|
|
685
|
+
console.log(`Received ${signal}, shutting down...`);
|
|
686
|
+
await server.close();
|
|
687
|
+
process.exit(0);
|
|
688
|
+
});
|
|
689
|
+
});
|
package/dist/unit.test.js
CHANGED
|
@@ -74,6 +74,24 @@ test("kubernetes server operations", async () => {
|
|
|
74
74
|
const createResult = JSON.parse(createPodResult.content[0].text);
|
|
75
75
|
expect(createResult.podName).toBe("test-pod");
|
|
76
76
|
expect(createResult.status).toBe("created");
|
|
77
|
+
// Describe the pod
|
|
78
|
+
console.log("Describing test pod...");
|
|
79
|
+
const describePodResult = await client.request({
|
|
80
|
+
method: "tools/call",
|
|
81
|
+
params: {
|
|
82
|
+
name: "describe_pod",
|
|
83
|
+
arguments: {
|
|
84
|
+
name: "test-pod",
|
|
85
|
+
namespace: "default",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}, CreatePodResponseSchema // Reusing existing schema since response format is similar
|
|
89
|
+
);
|
|
90
|
+
expect(describePodResult.content[0].type).toBe("text");
|
|
91
|
+
const podDescription = JSON.parse(describePodResult.content[0].text);
|
|
92
|
+
expect(podDescription.metadata.name).toBe("test-pod");
|
|
93
|
+
expect(podDescription.metadata.namespace).toBe("default");
|
|
94
|
+
expect(podDescription.kind).toBe("Pod");
|
|
77
95
|
// List pods to verify creation
|
|
78
96
|
console.log("Listing pods...");
|
|
79
97
|
const listPodsResult = 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.2",
|
|
4
4
|
"description": "MCP server for interacting with Kubernetes clusters via kubectl",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
"@types/node": "^22.9.3",
|
|
42
42
|
"shx": "^0.3.4",
|
|
43
43
|
"typescript": "^5.6.2",
|
|
44
|
-
"vitest": "2.1.
|
|
44
|
+
"vitest": "2.1.9"
|
|
45
45
|
}
|
|
46
46
|
}
|