mcp-server-kubernetes 0.1.1 → 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 +99 -5
- package/dist/index.js +126 -14
- package/dist/types.d.ts +22 -0
- package/dist/types.js +6 -0
- package/dist/unit.test.js +32 -1
- package/package.json +4 -3
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
|
|
@@ -33,23 +35,115 @@ If you have errors, open up a standard terminal and run `kubectl get pods` to se
|
|
|
33
35
|
- [x] List all pods
|
|
34
36
|
- [x] List all services
|
|
35
37
|
- [x] List all deployments
|
|
38
|
+
- [x] List all nodes
|
|
36
39
|
- [x] Create a pod
|
|
37
40
|
- [x] Delete a pod
|
|
41
|
+
- [x] Describe a pod
|
|
38
42
|
- [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
|
|
43
|
+
- [ ] Port forward to a pod
|
|
44
|
+
- [ ] Get logs from a pod for debugging
|
|
45
|
+
- [ ] Choose namespace for next commands (memory)
|
|
46
|
+
- [ ] Support Helm for installing charts
|
|
47
|
+
|
|
48
|
+
## In Progress
|
|
49
|
+
|
|
50
|
+
- [ ] [Docker support](https://github.com/Flux159/mcp-server-kubernetes/pull/9)
|
|
43
51
|
|
|
44
|
-
## Development
|
|
52
|
+
## Local Development
|
|
45
53
|
|
|
46
54
|
```bash
|
|
47
55
|
git clone https://github.com/Flux159/mcp-server-kubernetes.git
|
|
48
56
|
cd mcp-server-kubernetes
|
|
49
57
|
bun install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Development Workflow
|
|
61
|
+
|
|
62
|
+
1. Start the server in development mode (watches for file changes):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bun run dev
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. Run unit tests:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
50
71
|
bun run test
|
|
51
72
|
```
|
|
52
73
|
|
|
74
|
+
3. Build the project:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
bun run build
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
4. Local Testing with [Inspector](https://github.com/modelcontextprotocol/inspector)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @modelcontextprotocol/inspector node build/index.js
|
|
84
|
+
# Follow further instructions on terminal for Inspector link
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Project Structure
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
src/
|
|
91
|
+
├── index.ts # Main server implementation
|
|
92
|
+
├── types.ts # TypeScript type definitions
|
|
93
|
+
└── unit.test.ts # Unit tests
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Contributing
|
|
97
|
+
|
|
98
|
+
1. Fork the repository
|
|
99
|
+
2. Create a feature branch
|
|
100
|
+
3. Make your changes
|
|
101
|
+
4. Add tests for new functionality
|
|
102
|
+
5. Ensure all tests pass
|
|
103
|
+
6. Submit a pull request
|
|
104
|
+
|
|
105
|
+
For bigger changes, please open an issue first to discuss the proposed changes.
|
|
106
|
+
|
|
107
|
+
## Architecture
|
|
108
|
+
|
|
109
|
+
This section describes the high-level architecture of the MCP Kubernetes server.
|
|
110
|
+
|
|
111
|
+
### Request Flow
|
|
112
|
+
|
|
113
|
+
The sequence diagram below illustrates how requests flow through the system:
|
|
114
|
+
|
|
115
|
+
```mermaid
|
|
116
|
+
sequenceDiagram
|
|
117
|
+
participant Client
|
|
118
|
+
participant Transport as StdioTransport
|
|
119
|
+
participant Server as MCP Server
|
|
120
|
+
participant Handler as Request Handler
|
|
121
|
+
participant K8sManager as KubernetesManager
|
|
122
|
+
participant K8s as Kubernetes API
|
|
123
|
+
|
|
124
|
+
Client->>Transport: Send Request via STDIO
|
|
125
|
+
Transport->>Server: Forward Request
|
|
126
|
+
|
|
127
|
+
alt Tools Request
|
|
128
|
+
Server->>Handler: Route to tools handler
|
|
129
|
+
Handler->>K8sManager: Execute tool operation
|
|
130
|
+
K8sManager->>K8s: Make API call
|
|
131
|
+
K8s-->>K8sManager: Return result
|
|
132
|
+
K8sManager-->>Handler: Process response
|
|
133
|
+
Handler-->>Server: Return tool result
|
|
134
|
+
else Resource Request
|
|
135
|
+
Server->>Handler: Route to resource handler
|
|
136
|
+
Handler->>K8sManager: Get resource data
|
|
137
|
+
K8sManager->>K8s: Query API
|
|
138
|
+
K8s-->>K8sManager: Return data
|
|
139
|
+
K8sManager-->>Handler: Format response
|
|
140
|
+
Handler-->>Server: Return resource data
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
Server-->>Transport: Send Response
|
|
144
|
+
Transport-->>Client: Return Final Response
|
|
145
|
+
```
|
|
146
|
+
|
|
53
147
|
## Not planned
|
|
54
148
|
|
|
55
149
|
Authentication / adding clusters to kubectx.
|
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: {
|
|
@@ -293,6 +288,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
293
288
|
required: ["name", "namespace"],
|
|
294
289
|
},
|
|
295
290
|
},
|
|
291
|
+
{
|
|
292
|
+
name: "describe_pod",
|
|
293
|
+
description: "Describe a Kubernetes pod (read details like status, containers, etc.)",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
name: { type: "string" },
|
|
298
|
+
namespace: { type: "string" },
|
|
299
|
+
},
|
|
300
|
+
required: ["name", "namespace"],
|
|
301
|
+
},
|
|
302
|
+
},
|
|
296
303
|
{
|
|
297
304
|
name: "cleanup",
|
|
298
305
|
description: "Cleanup all managed resources",
|
|
@@ -301,6 +308,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
301
308
|
properties: {},
|
|
302
309
|
},
|
|
303
310
|
},
|
|
311
|
+
{
|
|
312
|
+
name: "list_nodes",
|
|
313
|
+
description: "List all nodes in the cluster",
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: "object",
|
|
316
|
+
properties: {},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
304
319
|
],
|
|
305
320
|
};
|
|
306
321
|
});
|
|
@@ -392,9 +407,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
392
407
|
};
|
|
393
408
|
}
|
|
394
409
|
case "create_pod": {
|
|
395
|
-
console.error("calling create_pod");
|
|
396
|
-
console.error(input);
|
|
397
|
-
console.error(request);
|
|
398
410
|
const createPodInput = input;
|
|
399
411
|
const templateConfig = containerTemplates[createPodInput.template];
|
|
400
412
|
const pod = {
|
|
@@ -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 {
|
|
@@ -484,6 +567,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
484
567
|
],
|
|
485
568
|
};
|
|
486
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
|
+
}
|
|
487
583
|
default:
|
|
488
584
|
throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
|
|
489
585
|
}
|
|
@@ -522,6 +618,12 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
522
618
|
mimeType: "application/json",
|
|
523
619
|
description: "List of all namespaces",
|
|
524
620
|
},
|
|
621
|
+
{
|
|
622
|
+
uri: "k8s://nodes",
|
|
623
|
+
name: "Kubernetes Nodes",
|
|
624
|
+
mimeType: "application/json",
|
|
625
|
+
description: "List of all nodes in the cluster",
|
|
626
|
+
},
|
|
525
627
|
],
|
|
526
628
|
};
|
|
527
629
|
});
|
|
@@ -529,8 +631,11 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
529
631
|
try {
|
|
530
632
|
const uri = request.params.uri;
|
|
531
633
|
const parts = uri.replace("k8s://", "").split("/");
|
|
532
|
-
|
|
533
|
-
|
|
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]();
|
|
534
639
|
return {
|
|
535
640
|
contents: [
|
|
536
641
|
{
|
|
@@ -597,3 +702,10 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
597
702
|
});
|
|
598
703
|
const transport = new StdioServerTransport();
|
|
599
704
|
await server.connect(transport);
|
|
705
|
+
["SIGINT", "SIGTERM"].forEach((signal) => {
|
|
706
|
+
process.on(signal, async () => {
|
|
707
|
+
console.log(`Received ${signal}, shutting down...`);
|
|
708
|
+
await server.close();
|
|
709
|
+
process.exit(0);
|
|
710
|
+
});
|
|
711
|
+
});
|
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({
|
|
@@ -74,6 +87,24 @@ test("kubernetes server operations", async () => {
|
|
|
74
87
|
const createResult = JSON.parse(createPodResult.content[0].text);
|
|
75
88
|
expect(createResult.podName).toBe("test-pod");
|
|
76
89
|
expect(createResult.status).toBe("created");
|
|
90
|
+
// Describe the pod
|
|
91
|
+
console.log("Describing test pod...");
|
|
92
|
+
const describePodResult = await client.request({
|
|
93
|
+
method: "tools/call",
|
|
94
|
+
params: {
|
|
95
|
+
name: "describe_pod",
|
|
96
|
+
arguments: {
|
|
97
|
+
name: "test-pod",
|
|
98
|
+
namespace: "default",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}, CreatePodResponseSchema // Reusing existing schema since response format is similar
|
|
102
|
+
);
|
|
103
|
+
expect(describePodResult.content[0].type).toBe("text");
|
|
104
|
+
const podDescription = JSON.parse(describePodResult.content[0].text);
|
|
105
|
+
expect(podDescription.metadata.name).toBe("test-pod");
|
|
106
|
+
expect(podDescription.metadata.namespace).toBe("default");
|
|
107
|
+
expect(podDescription.kind).toBe("Pod");
|
|
77
108
|
// List pods to verify creation
|
|
78
109
|
console.log("Listing pods...");
|
|
79
110
|
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.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",
|
|
@@ -41,6 +42,6 @@
|
|
|
41
42
|
"@types/node": "^22.9.3",
|
|
42
43
|
"shx": "^0.3.4",
|
|
43
44
|
"typescript": "^5.6.2",
|
|
44
|
-
"vitest": "2.1.
|
|
45
|
+
"vitest": "2.1.9"
|
|
45
46
|
}
|
|
46
47
|
}
|