mcp-server-kubernetes 0.1.5 → 0.2.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.
Files changed (49) hide show
  1. package/README.md +29 -9
  2. package/dist/config/cleanup-config.d.ts +8 -0
  3. package/dist/config/cleanup-config.js +8 -0
  4. package/dist/config/container-templates.d.ts +5 -0
  5. package/dist/config/container-templates.js +109 -0
  6. package/dist/config/deployment-config.d.ts +31 -0
  7. package/dist/config/deployment-config.js +23 -0
  8. package/dist/config/namespace-config.d.ts +8 -0
  9. package/dist/config/namespace-config.js +8 -0
  10. package/dist/config/server-config.d.ts +8 -0
  11. package/dist/config/server-config.js +8 -0
  12. package/dist/index.js +49 -928
  13. package/dist/models/helm-models.d.ts +39 -0
  14. package/dist/models/helm-models.js +8 -0
  15. package/dist/models/resource-models.d.ts +94 -0
  16. package/dist/models/resource-models.js +17 -0
  17. package/dist/models/response-schemas.d.ts +221 -0
  18. package/dist/models/response-schemas.js +36 -0
  19. package/dist/models/tool-models.d.ts +42 -0
  20. package/dist/models/tool-models.js +10 -0
  21. package/dist/resources/handlers.d.ts +22 -0
  22. package/dist/resources/handlers.js +112 -0
  23. package/dist/tools/create_pod.d.ts +39 -0
  24. package/dist/tools/create_pod.js +71 -0
  25. package/dist/tools/delete_pod.d.ts +31 -0
  26. package/dist/tools/delete_pod.js +45 -0
  27. package/dist/tools/describe_pod.d.ts +33 -0
  28. package/dist/tools/describe_pod.js +81 -0
  29. package/dist/tools/get_logs.d.ts +67 -0
  30. package/dist/tools/get_logs.js +150 -0
  31. package/dist/tools/helm-operations.d.ts +99 -0
  32. package/dist/tools/helm-operations.js +198 -0
  33. package/dist/tools/list_deployments.d.ts +23 -0
  34. package/dist/tools/list_deployments.js +30 -0
  35. package/dist/tools/list_nodes.d.ts +15 -0
  36. package/dist/tools/list_nodes.js +21 -0
  37. package/dist/tools/list_pods.d.ts +23 -0
  38. package/dist/tools/list_pods.js +29 -0
  39. package/dist/tools/list_services.d.ts +23 -0
  40. package/dist/tools/list_services.js +31 -0
  41. package/dist/types.d.ts +4 -433
  42. package/dist/types.js +6 -120
  43. package/dist/utils/kubernetes-manager.d.ts +21 -0
  44. package/dist/utils/kubernetes-manager.js +68 -0
  45. package/package.json +3 -2
  46. package/dist/helm.test.d.ts +0 -1
  47. package/dist/helm.test.js +0 -208
  48. package/dist/unit.test.d.ts +0 -1
  49. package/dist/unit.test.js +0 -293
package/dist/helm.test.js DELETED
@@ -1,208 +0,0 @@
1
- import { expect, test, describe, beforeEach, afterEach } from "vitest";
2
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
- import { HelmResponseSchema } from "./types.js";
5
- import * as fs from "fs";
6
- async function sleep(ms) {
7
- return new Promise((resolve) => setTimeout(resolve, ms));
8
- }
9
- describe("helm operations", () => {
10
- let transport;
11
- let client;
12
- const testReleaseName = "test-nginx";
13
- const testNamespace = "default";
14
- beforeEach(async () => {
15
- try {
16
- transport = new StdioClientTransport({
17
- command: "bun",
18
- args: ["src/index.ts"],
19
- stderr: "pipe",
20
- });
21
- client = new Client({
22
- name: "test-client",
23
- version: "1.0.0",
24
- }, {
25
- capabilities: {},
26
- });
27
- await client.connect(transport);
28
- await sleep(1000);
29
- }
30
- catch (e) {
31
- console.error("Error in beforeEach:", e);
32
- throw e;
33
- }
34
- });
35
- afterEach(async () => {
36
- try {
37
- // Cleanup: Uninstall the test release if it exists
38
- await client
39
- .request({
40
- method: "tools/call",
41
- params: {
42
- name: "uninstall_helm_chart",
43
- arguments: {
44
- name: testReleaseName,
45
- namespace: testNamespace,
46
- },
47
- },
48
- }, HelmResponseSchema)
49
- .catch(() => { }); // Ignore errors if release doesn't exist
50
- await transport.close();
51
- await sleep(1000);
52
- // Cleanup generated values files
53
- if (fs.existsSync("test-nginx-values.yaml")) {
54
- fs.unlinkSync("test-nginx-values.yaml");
55
- }
56
- }
57
- catch (e) {
58
- console.error("Error during cleanup:", e);
59
- }
60
- });
61
- test("install helm chart", async () => {
62
- const installResult = await client.request({
63
- method: "tools/call",
64
- params: {
65
- name: "install_helm_chart",
66
- arguments: {
67
- name: testReleaseName,
68
- chart: "nginx",
69
- repo: "https://charts.bitnami.com/bitnami",
70
- namespace: testNamespace,
71
- values: {
72
- service: {
73
- type: "ClusterIP",
74
- },
75
- resources: {
76
- limits: {
77
- cpu: "100m",
78
- memory: "128Mi",
79
- },
80
- requests: {
81
- cpu: "50m",
82
- memory: "64Mi",
83
- },
84
- },
85
- },
86
- },
87
- },
88
- }, HelmResponseSchema);
89
- expect(installResult.content[0].type).toBe("text");
90
- const result = JSON.parse(installResult.content[0].text);
91
- expect(result.status).toBe("installed");
92
- // Wait for the deployment to be ready
93
- await sleep(5000);
94
- // Verify the deployment exists
95
- const deploymentResult = await client.request({
96
- method: "tools/call",
97
- params: {
98
- name: "list_deployments",
99
- arguments: {
100
- namespace: testNamespace,
101
- },
102
- },
103
- }, HelmResponseSchema);
104
- const deployments = JSON.parse(deploymentResult.content[0].text);
105
- expect(deployments.deployments.some((d) => d.name.startsWith(testReleaseName))).toBe(true);
106
- }, 30000); // Increase timeout to 30s for chart installation
107
- test("upgrade helm chart values", async () => {
108
- // First install the chart
109
- await client.request({
110
- method: "tools/call",
111
- params: {
112
- name: "install_helm_chart",
113
- arguments: {
114
- name: testReleaseName,
115
- chart: "nginx",
116
- repo: "https://charts.bitnami.com/bitnami",
117
- namespace: testNamespace,
118
- },
119
- },
120
- }, HelmResponseSchema);
121
- await sleep(5000);
122
- // Then upgrade it with new values
123
- const upgradeResult = await client.request({
124
- method: "tools/call",
125
- params: {
126
- name: "upgrade_helm_chart",
127
- arguments: {
128
- name: testReleaseName,
129
- chart: "nginx",
130
- repo: "https://charts.bitnami.com/bitnami",
131
- namespace: testNamespace,
132
- values: {
133
- replicaCount: 2,
134
- resources: {
135
- limits: {
136
- cpu: "200m",
137
- memory: "256Mi",
138
- },
139
- },
140
- },
141
- },
142
- },
143
- }, HelmResponseSchema);
144
- expect(upgradeResult.content[0].type).toBe("text");
145
- const result = JSON.parse(upgradeResult.content[0].text);
146
- expect(result.status).toBe("upgraded");
147
- // Wait for the upgrade to take effect
148
- await sleep(5000);
149
- // Verify the deployment was updated
150
- const deploymentResult = await client.request({
151
- method: "tools/call",
152
- params: {
153
- name: "list_deployments",
154
- arguments: {
155
- namespace: testNamespace,
156
- },
157
- },
158
- }, HelmResponseSchema);
159
- const deployments = JSON.parse(deploymentResult.content[0].text);
160
- const nginxDeployment = deployments.deployments.find((d) => d.name.startsWith(testReleaseName));
161
- expect(nginxDeployment).toBeDefined();
162
- expect(nginxDeployment.replicas).toBe(2);
163
- }, 60000); // Increase timeout to 60s for install + upgrade
164
- test("uninstall helm chart", async () => {
165
- // First install the chart
166
- await client.request({
167
- method: "tools/call",
168
- params: {
169
- name: "install_helm_chart",
170
- arguments: {
171
- name: testReleaseName,
172
- chart: "nginx",
173
- repo: "https://charts.bitnami.com/bitnami",
174
- namespace: testNamespace,
175
- },
176
- },
177
- }, HelmResponseSchema);
178
- await sleep(5000);
179
- // Then uninstall it
180
- const uninstallResult = await client.request({
181
- method: "tools/call",
182
- params: {
183
- name: "uninstall_helm_chart",
184
- arguments: {
185
- name: testReleaseName,
186
- namespace: testNamespace,
187
- },
188
- },
189
- }, HelmResponseSchema);
190
- expect(uninstallResult.content[0].type).toBe("text");
191
- const result = JSON.parse(uninstallResult.content[0].text);
192
- expect(result.status).toBe("uninstalled");
193
- // Wait for resources to be cleaned up
194
- await sleep(5000);
195
- // Verify the deployment is gone
196
- const deploymentResult = await client.request({
197
- method: "tools/call",
198
- params: {
199
- name: "list_deployments",
200
- arguments: {
201
- namespace: testNamespace,
202
- },
203
- },
204
- }, HelmResponseSchema);
205
- const deployments = JSON.parse(deploymentResult.content[0].text);
206
- expect(deployments.deployments.every((d) => !d.name.startsWith(testReleaseName))).toBe(true);
207
- }, 60000); // Increase timeout to 60s for install + uninstall
208
- });
@@ -1 +0,0 @@
1
- export {};
package/dist/unit.test.js DELETED
@@ -1,293 +0,0 @@
1
- // Import required test frameworks and SDK components
2
- import { expect, test, describe, beforeEach, afterEach, } from "vitest";
3
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5
- import { ListToolsResponseSchema, ListPodsResponseSchema, ListNamespacesResponseSchema, ListNodesResponseSchema, CreatePodResponseSchema, DeletePodResponseSchema, } from "./types.js";
6
- /**
7
- * Utility function to create a promise that resolves after specified milliseconds
8
- * Useful for waiting between operations or ensuring async operations complete
9
- */
10
- async function sleep(ms) {
11
- return new Promise((resolve) => setTimeout(resolve, ms));
12
- }
13
- /**
14
- * Generates a random SHA-like string for unique resource naming
15
- * Used to avoid naming conflicts when creating test resources
16
- */
17
- function generateRandomSHA() {
18
- return Math.random().toString(36).substring(2, 15);
19
- }
20
- /**
21
- * Test suite for kubernetes server operations
22
- * Tests the core functionality of kubernetes operations including:
23
- * - Listing available tools
24
- * - Namespace and node operations
25
- * - Pod lifecycle management (create, monitor, delete)
26
- */
27
- describe("kubernetes server operations", () => {
28
- let transport;
29
- let client;
30
- /**
31
- * Set up before each test:
32
- * - Creates a new StdioClientTransport instance
33
- * - Initializes and connects the MCP client
34
- * - Waits for connection to be established
35
- */
36
- beforeEach(async () => {
37
- try {
38
- transport = new StdioClientTransport({
39
- command: "bun",
40
- args: ["src/index.ts"],
41
- stderr: "pipe",
42
- });
43
- client = new Client({
44
- name: "test-client",
45
- version: "1.0.0",
46
- }, {
47
- capabilities: {},
48
- });
49
- await client.connect(transport);
50
- // Wait for connection to be fully established
51
- await sleep(1000);
52
- }
53
- catch (e) {
54
- console.error("Error in beforeEach:", e);
55
- throw e;
56
- }
57
- });
58
- /**
59
- * Clean up after each test:
60
- * - Closes the transport connection
61
- * - Waits to ensure clean shutdown
62
- */
63
- afterEach(async () => {
64
- try {
65
- await transport.close();
66
- await sleep(1000);
67
- }
68
- catch (e) {
69
- console.error("Error during cleanup:", e);
70
- }
71
- });
72
- /**
73
- * Test case: Verify the availability of kubernetes tools
74
- * Ensures that the server exposes the expected kubernetes operations
75
- */
76
- test("list available tools", async () => {
77
- // List available tools stays the same
78
- console.log("Listing available tools...");
79
- const toolsList = await client.request({
80
- method: "tools/list",
81
- }, ListToolsResponseSchema);
82
- expect(toolsList.tools).toBeDefined();
83
- expect(toolsList.tools.length).toBeGreaterThan(0);
84
- });
85
- /**
86
- * Test case: Verify namespace and node listing functionality
87
- * Tests both namespace and node listing operations in sequence
88
- */
89
- test("list namespaces and nodes", async () => {
90
- // List namespaces
91
- console.log("Listing namespaces...");
92
- const namespacesResult = await client.request({
93
- method: "tools/call",
94
- params: {
95
- name: "list_namespaces",
96
- arguments: {},
97
- },
98
- }, ListNamespacesResponseSchema);
99
- expect(namespacesResult.content[0].type).toBe("text");
100
- const namespaces = JSON.parse(namespacesResult.content[0].text);
101
- expect(namespaces.namespaces).toBeDefined();
102
- // List nodes
103
- console.log("Listing nodes...");
104
- const listNodesResult = await client.request({
105
- method: "tools/call",
106
- params: {
107
- name: "list_nodes",
108
- arguments: {},
109
- },
110
- }, ListNodesResponseSchema);
111
- expect(listNodesResult.content[0].type).toBe("text");
112
- const nodes = JSON.parse(listNodesResult.content[0].text);
113
- expect(nodes.nodes).toBeDefined();
114
- expect(Array.isArray(nodes.nodes)).toBe(true);
115
- });
116
- /**
117
- * Test case: Complete pod lifecycle management
118
- * Tests the full lifecycle of a pod including:
119
- * 1. Cleanup of existing test pods
120
- * 2. Creation of new test pod
121
- * 3. Monitoring pod until running state
122
- * 4. Verification of pod logs
123
- * 5. Pod deletion and termination verification
124
- *
125
- * Note: Test timeout is set to 120 seconds to accommodate all operations via vitest.config.ts
126
- */
127
- test("pod lifecycle management", async () => {
128
- const podBaseName = "unit-test";
129
- const podName = `${podBaseName}-${generateRandomSHA()}`;
130
- // Step 1: Check if pods with unit-test prefix exist and terminate them if found
131
- const existingPods = await client.request({
132
- method: "tools/call",
133
- params: {
134
- name: "list_pods",
135
- arguments: {
136
- namespace: "default",
137
- },
138
- },
139
- }, ListPodsResponseSchema);
140
- const podsResponse = JSON.parse(existingPods.content[0].text);
141
- const existingTestPods = podsResponse.items?.filter((pod) => pod.metadata?.name?.startsWith(podBaseName)) || [];
142
- // Terminate existing test pods if found
143
- for (const pod of existingTestPods) {
144
- await client.request({
145
- method: "tools/call",
146
- params: {
147
- name: "delete_pod",
148
- arguments: {
149
- name: pod.metadata.name,
150
- namespace: "default",
151
- ignoreNotFound: true,
152
- },
153
- },
154
- }, DeletePodResponseSchema);
155
- // Wait for pod to be fully terminated
156
- let podDeleted = false;
157
- const terminationStartTime = Date.now();
158
- while (!podDeleted && Date.now() - terminationStartTime < 10000) {
159
- try {
160
- await client.request({
161
- method: "tools/call",
162
- params: {
163
- name: "describe_pod",
164
- arguments: {
165
- name: pod.metadata.name,
166
- namespace: "default",
167
- },
168
- },
169
- }, ListPodsResponseSchema);
170
- await sleep(500);
171
- }
172
- catch (error) {
173
- // If we get an error, it might be because the pod is gone (404)
174
- podDeleted = true;
175
- }
176
- }
177
- }
178
- // Create new pod with random SHA name
179
- const createPodResult = await client.request({
180
- method: "tools/call",
181
- params: {
182
- name: "create_pod",
183
- arguments: {
184
- name: podName,
185
- namespace: "default",
186
- template: "busybox",
187
- command: [
188
- "/bin/sh",
189
- "-c",
190
- "echo Pod is running && sleep infinity",
191
- ],
192
- },
193
- },
194
- }, CreatePodResponseSchema);
195
- expect(createPodResult.content[0].type).toBe("text");
196
- const podResult = JSON.parse(createPodResult.content[0].text);
197
- expect(podResult.podName).toBe(podName);
198
- // Step 2: Wait for Running state (up to 60 seconds)
199
- let podRunning = false;
200
- const startTime = Date.now();
201
- while (!podRunning && Date.now() - startTime < 60000) {
202
- const podStatus = await client.request({
203
- method: "tools/call",
204
- params: {
205
- name: "describe_pod",
206
- arguments: {
207
- name: podName,
208
- namespace: "default",
209
- },
210
- },
211
- }, ListPodsResponseSchema);
212
- const status = JSON.parse(podStatus.content[0].text);
213
- if (status.status?.phase === "Running") {
214
- podRunning = true;
215
- console.log(`Pod ${podName} is running. Checking logs...`);
216
- // Check pod logs once running
217
- const logsResult = await client.request({
218
- method: "tools/call",
219
- params: {
220
- name: "get_logs",
221
- arguments: {
222
- resourceType: "pod",
223
- name: podName,
224
- namespace: "default",
225
- },
226
- },
227
- }, ListPodsResponseSchema);
228
- expect(logsResult.content[0].type).toBe("text");
229
- const logs = JSON.parse(logsResult.content[0].text);
230
- expect(logs.logs[podName]).toContain("Pod is running");
231
- break;
232
- }
233
- await sleep(1000);
234
- }
235
- expect(podRunning).toBe(true);
236
- // Step 3: Terminate pod and verify termination (wait up to 10 seconds)
237
- const deletePodResult = await client.request({
238
- method: "tools/call",
239
- params: {
240
- name: "delete_pod",
241
- arguments: {
242
- name: podName,
243
- namespace: "default",
244
- },
245
- },
246
- }, DeletePodResponseSchema);
247
- expect(deletePodResult.content[0].type).toBe("text");
248
- const deleteResult = JSON.parse(deletePodResult.content[0].text);
249
- expect(deleteResult.status).toBe("deleted");
250
- // Try to verify pod termination, but don't fail the test if we can't confirm it
251
- try {
252
- let podTerminated = false;
253
- const terminationStartTime = Date.now();
254
- while (!podTerminated && Date.now() - terminationStartTime < 10000) {
255
- try {
256
- const podStatus = await client.request({
257
- method: "tools/call",
258
- params: {
259
- name: "describe_pod",
260
- arguments: {
261
- name: podName,
262
- namespace: "default",
263
- },
264
- },
265
- }, ListPodsResponseSchema);
266
- // Pod still exists, check if it's in Terminating state
267
- const status = JSON.parse(podStatus.content[0].text);
268
- if (status.status?.phase === "Terminating") {
269
- podTerminated = true;
270
- break;
271
- }
272
- await sleep(500);
273
- }
274
- catch (error) {
275
- // If we get an error (404), the pod is gone which also means it's terminated
276
- podTerminated = true;
277
- break;
278
- }
279
- }
280
- // Log termination status but don't fail the test
281
- if (podTerminated) {
282
- console.log(`Pod ${podName} termination confirmed`);
283
- }
284
- else {
285
- console.log(`Pod ${podName} termination could not be confirmed within timeout, but deletion was initiated`);
286
- }
287
- }
288
- catch (error) {
289
- // Ignore any errors during termination check
290
- console.log(`Error checking pod termination status: ${error}`);
291
- }
292
- }, { timeout: 120000 });
293
- });