mcp-server-kubernetes 1.3.1 → 1.4.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 +5 -1
- package/dist/index.js +75 -35
- package/dist/models/response-schemas.d.ts +66 -0
- package/dist/models/response-schemas.js +9 -0
- package/dist/tools/create_service.d.ts +74 -0
- package/dist/tools/create_service.js +102 -0
- package/dist/tools/get_current_context.d.ts +23 -0
- package/dist/tools/get_current_context.js +55 -0
- package/dist/tools/helm-operations.js +5 -1
- package/dist/tools/list_contexts.d.ts +23 -0
- package/dist/tools/list_contexts.js +39 -0
- package/dist/tools/set_current_context.d.ts +23 -0
- package/dist/tools/set_current_context.js +35 -0
- package/dist/tools/update_deployment.d.ts +113 -0
- package/dist/tools/update_deployment.js +155 -0
- package/dist/utils/kubernetes-manager.d.ts +6 -0
- package/dist/utils/kubernetes-manager.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,7 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
70
70
|
- [x] Create, describe, delete a pod
|
|
71
71
|
- [x] List all namespaces, create a namespace
|
|
72
72
|
- [x] Create custom pod & deployment configs, update deployment replicas
|
|
73
|
+
- [x] Create services
|
|
73
74
|
- [x] Get logs from a pod for debugging (supports pods, deployments, jobs, and label selectors)
|
|
74
75
|
- [x] Support Helm v3 for installing charts
|
|
75
76
|
- Install charts with custom values
|
|
@@ -82,6 +83,7 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
82
83
|
- [x] Get Kubernetes events from the cluster
|
|
83
84
|
- [x] Port forward to a pod or service
|
|
84
85
|
- [x] Create, list, and decribe cronjobs
|
|
86
|
+
- [x] Non-destructive mode for read and create/update-only access to clusters
|
|
85
87
|
|
|
86
88
|
## Local Development
|
|
87
89
|
|
|
@@ -143,7 +145,9 @@ See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details.
|
|
|
143
145
|
|
|
144
146
|
## Advanced
|
|
145
147
|
|
|
146
|
-
|
|
148
|
+
### Additional Advanced Features
|
|
149
|
+
|
|
150
|
+
For more advanced information like using SSE transport, Non-destructive mode with `ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS`, see the [ADVANCED_README.md](ADVANCED_README.md).
|
|
147
151
|
|
|
148
152
|
## Architecture
|
|
149
153
|
|
package/dist/index.js
CHANGED
|
@@ -33,49 +33,74 @@ import { deleteDeployment, deleteDeploymentSchema } from "./tools/delete_deploym
|
|
|
33
33
|
import { createDeployment } from "./tools/create_deployment.js";
|
|
34
34
|
import { scaleDeployment, scaleDeploymentSchema } from "./tools/scale_deployment.js";
|
|
35
35
|
import { describeDeployment, describeDeploymentSchema, } from "./tools/describe_deployment.js";
|
|
36
|
+
import { updateDeployment, updateDeploymentSchema } from "./tools/update_deployment.js";
|
|
36
37
|
import { createConfigMap, CreateConfigMapSchema } from "./tools/create_configmap.js";
|
|
38
|
+
import { createService, createServiceSchema } from "./tools/create_service.js";
|
|
39
|
+
import { listContexts, listContextsSchema } from "./tools/list_contexts.js";
|
|
40
|
+
import { getCurrentContext, getCurrentContextSchema } from "./tools/get_current_context.js";
|
|
41
|
+
import { setCurrentContext, setCurrentContextSchema } from "./tools/set_current_context.js";
|
|
42
|
+
// Check if non-destructive tools only mode is enabled
|
|
43
|
+
const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === 'true';
|
|
37
44
|
const k8sManager = new KubernetesManager();
|
|
38
45
|
const server = new Server({
|
|
39
46
|
name: serverConfig.name,
|
|
40
47
|
version: serverConfig.version,
|
|
41
48
|
}, serverConfig);
|
|
49
|
+
// Define destructive tools (delete and uninstall operations)
|
|
50
|
+
const destructiveTools = [
|
|
51
|
+
deletePodSchema,
|
|
52
|
+
deleteDeploymentSchema,
|
|
53
|
+
deleteNamespaceSchema,
|
|
54
|
+
uninstallHelmChartSchema,
|
|
55
|
+
DeleteCronJobSchema,
|
|
56
|
+
cleanupSchema, // Cleanup is also destructive as it deletes resources
|
|
57
|
+
];
|
|
42
58
|
// Tools handlers
|
|
43
59
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
60
|
+
// Get all available tools
|
|
61
|
+
const allTools = [
|
|
62
|
+
cleanupSchema,
|
|
63
|
+
createDeploymentSchema,
|
|
64
|
+
createNamespaceSchema,
|
|
65
|
+
createPodSchema,
|
|
66
|
+
createCronJobSchema,
|
|
67
|
+
createServiceSchema,
|
|
68
|
+
deletePodSchema,
|
|
69
|
+
deleteDeploymentSchema,
|
|
70
|
+
deleteNamespaceSchema,
|
|
71
|
+
describeCronJobSchema,
|
|
72
|
+
describePodSchema,
|
|
73
|
+
describeDeploymentSchema,
|
|
74
|
+
explainResourceSchema,
|
|
75
|
+
getEventsSchema,
|
|
76
|
+
getJobLogsSchema,
|
|
77
|
+
getLogsSchema,
|
|
78
|
+
installHelmChartSchema,
|
|
79
|
+
listApiResourcesSchema,
|
|
80
|
+
listCronJobsSchema,
|
|
81
|
+
listContextsSchema,
|
|
82
|
+
getCurrentContextSchema,
|
|
83
|
+
setCurrentContextSchema,
|
|
84
|
+
listDeploymentsSchema,
|
|
85
|
+
listJobsSchema,
|
|
86
|
+
listNamespacesSchema,
|
|
87
|
+
listNodesSchema,
|
|
88
|
+
listPodsSchema,
|
|
89
|
+
listServicesSchema,
|
|
90
|
+
uninstallHelmChartSchema,
|
|
91
|
+
updateDeploymentSchema,
|
|
92
|
+
upgradeHelmChartSchema,
|
|
93
|
+
PortForwardSchema,
|
|
94
|
+
StopPortForwardSchema,
|
|
95
|
+
scaleDeploymentSchema,
|
|
96
|
+
DeleteCronJobSchema,
|
|
97
|
+
CreateConfigMapSchema,
|
|
98
|
+
];
|
|
99
|
+
// Filter out destructive tools if ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS is set to 'true'
|
|
100
|
+
const tools = nonDestructiveTools
|
|
101
|
+
? allTools.filter(tool => !destructiveTools.some(dt => dt.name === tool.name))
|
|
102
|
+
: allTools;
|
|
103
|
+
return { tools };
|
|
79
104
|
});
|
|
80
105
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
81
106
|
try {
|
|
@@ -158,6 +183,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
158
183
|
case "list_cronjobs": {
|
|
159
184
|
return await listCronJobs(k8sManager, input);
|
|
160
185
|
}
|
|
186
|
+
case "list_contexts": {
|
|
187
|
+
return await listContexts(k8sManager, input);
|
|
188
|
+
}
|
|
189
|
+
case "get_current_context": {
|
|
190
|
+
return await getCurrentContext(k8sManager, input);
|
|
191
|
+
}
|
|
192
|
+
case "set_current_context": {
|
|
193
|
+
return await setCurrentContext(k8sManager, input);
|
|
194
|
+
}
|
|
161
195
|
case "describe_cronjob": {
|
|
162
196
|
return await describeCronJob(k8sManager, input);
|
|
163
197
|
}
|
|
@@ -188,6 +222,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
188
222
|
case "create_deployment": {
|
|
189
223
|
return await createDeployment(k8sManager, input);
|
|
190
224
|
}
|
|
225
|
+
case "update_deployment": {
|
|
226
|
+
return await updateDeployment(k8sManager, input);
|
|
227
|
+
}
|
|
191
228
|
case "describe_deployment": {
|
|
192
229
|
return await describeDeployment(k8sManager, input);
|
|
193
230
|
}
|
|
@@ -197,6 +234,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
197
234
|
case "create_configmap": {
|
|
198
235
|
return await createConfigMap(k8sManager, input);
|
|
199
236
|
}
|
|
237
|
+
case "create_service": {
|
|
238
|
+
return await createService(k8sManager, input);
|
|
239
|
+
}
|
|
200
240
|
default:
|
|
201
241
|
throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
|
|
202
242
|
}
|
|
@@ -505,3 +505,69 @@ export declare const CreateConfigMapResponseSchema: z.ZodObject<{
|
|
|
505
505
|
success: boolean;
|
|
506
506
|
}[];
|
|
507
507
|
}>;
|
|
508
|
+
export declare const ListContextsResponseSchema: z.ZodObject<{
|
|
509
|
+
content: z.ZodArray<z.ZodObject<{
|
|
510
|
+
type: z.ZodLiteral<"text">;
|
|
511
|
+
text: z.ZodString;
|
|
512
|
+
}, "strip", z.ZodTypeAny, {
|
|
513
|
+
type: "text";
|
|
514
|
+
text: string;
|
|
515
|
+
}, {
|
|
516
|
+
type: "text";
|
|
517
|
+
text: string;
|
|
518
|
+
}>, "many">;
|
|
519
|
+
}, "strip", z.ZodTypeAny, {
|
|
520
|
+
content: {
|
|
521
|
+
type: "text";
|
|
522
|
+
text: string;
|
|
523
|
+
}[];
|
|
524
|
+
}, {
|
|
525
|
+
content: {
|
|
526
|
+
type: "text";
|
|
527
|
+
text: string;
|
|
528
|
+
}[];
|
|
529
|
+
}>;
|
|
530
|
+
export declare const GetCurrentContextResponseSchema: z.ZodObject<{
|
|
531
|
+
content: z.ZodArray<z.ZodObject<{
|
|
532
|
+
type: z.ZodLiteral<"text">;
|
|
533
|
+
text: z.ZodString;
|
|
534
|
+
}, "strip", z.ZodTypeAny, {
|
|
535
|
+
type: "text";
|
|
536
|
+
text: string;
|
|
537
|
+
}, {
|
|
538
|
+
type: "text";
|
|
539
|
+
text: string;
|
|
540
|
+
}>, "many">;
|
|
541
|
+
}, "strip", z.ZodTypeAny, {
|
|
542
|
+
content: {
|
|
543
|
+
type: "text";
|
|
544
|
+
text: string;
|
|
545
|
+
}[];
|
|
546
|
+
}, {
|
|
547
|
+
content: {
|
|
548
|
+
type: "text";
|
|
549
|
+
text: string;
|
|
550
|
+
}[];
|
|
551
|
+
}>;
|
|
552
|
+
export declare const SetCurrentContextResponseSchema: z.ZodObject<{
|
|
553
|
+
content: z.ZodArray<z.ZodObject<{
|
|
554
|
+
type: z.ZodLiteral<"text">;
|
|
555
|
+
text: z.ZodString;
|
|
556
|
+
}, "strip", z.ZodTypeAny, {
|
|
557
|
+
type: "text";
|
|
558
|
+
text: string;
|
|
559
|
+
}, {
|
|
560
|
+
type: "text";
|
|
561
|
+
text: string;
|
|
562
|
+
}>, "many">;
|
|
563
|
+
}, "strip", z.ZodTypeAny, {
|
|
564
|
+
content: {
|
|
565
|
+
type: "text";
|
|
566
|
+
text: string;
|
|
567
|
+
}[];
|
|
568
|
+
}, {
|
|
569
|
+
content: {
|
|
570
|
+
type: "text";
|
|
571
|
+
text: string;
|
|
572
|
+
}[];
|
|
573
|
+
}>;
|
|
@@ -85,3 +85,12 @@ export const CreateConfigMapResponseSchema = z.object({
|
|
|
85
85
|
message: z.string(),
|
|
86
86
|
})),
|
|
87
87
|
});
|
|
88
|
+
export const ListContextsResponseSchema = z.object({
|
|
89
|
+
content: z.array(ToolResponseContent),
|
|
90
|
+
});
|
|
91
|
+
export const GetCurrentContextResponseSchema = z.object({
|
|
92
|
+
content: z.array(ToolResponseContent),
|
|
93
|
+
});
|
|
94
|
+
export const SetCurrentContextResponseSchema = z.object({
|
|
95
|
+
content: z.array(ToolResponseContent),
|
|
96
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const createServiceSchema: {
|
|
3
|
+
readonly name: "create_service";
|
|
4
|
+
readonly description: "Create a new Kubernetes service";
|
|
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
|
+
readonly default: "default";
|
|
14
|
+
};
|
|
15
|
+
readonly type: {
|
|
16
|
+
readonly type: "string";
|
|
17
|
+
readonly enum: readonly ["ClusterIP", "NodePort", "LoadBalancer"];
|
|
18
|
+
readonly default: "ClusterIP";
|
|
19
|
+
};
|
|
20
|
+
readonly selector: {
|
|
21
|
+
readonly type: "object";
|
|
22
|
+
readonly additionalProperties: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
};
|
|
25
|
+
readonly default: {};
|
|
26
|
+
};
|
|
27
|
+
readonly ports: {
|
|
28
|
+
readonly type: "array";
|
|
29
|
+
readonly items: {
|
|
30
|
+
readonly type: "object";
|
|
31
|
+
readonly properties: {
|
|
32
|
+
readonly port: {
|
|
33
|
+
readonly type: "number";
|
|
34
|
+
};
|
|
35
|
+
readonly targetPort: {
|
|
36
|
+
readonly type: "number";
|
|
37
|
+
};
|
|
38
|
+
readonly protocol: {
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
readonly enum: readonly ["TCP", "UDP"];
|
|
41
|
+
readonly default: "TCP";
|
|
42
|
+
};
|
|
43
|
+
readonly name: {
|
|
44
|
+
readonly type: "string";
|
|
45
|
+
};
|
|
46
|
+
readonly nodePort: {
|
|
47
|
+
readonly type: "number";
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
readonly required: readonly ["port"];
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
readonly required: readonly ["name", "ports"];
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export declare function createService(k8sManager: KubernetesManager, input: {
|
|
58
|
+
name: string;
|
|
59
|
+
namespace?: string;
|
|
60
|
+
type?: "ClusterIP" | "NodePort" | "LoadBalancer";
|
|
61
|
+
selector?: Record<string, string>;
|
|
62
|
+
ports: Array<{
|
|
63
|
+
port: number;
|
|
64
|
+
targetPort?: number;
|
|
65
|
+
protocol?: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
nodePort?: number;
|
|
68
|
+
}>;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
content: {
|
|
71
|
+
type: string;
|
|
72
|
+
text: string;
|
|
73
|
+
}[];
|
|
74
|
+
}>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export const createServiceSchema = {
|
|
2
|
+
name: "create_service",
|
|
3
|
+
description: "Create a new Kubernetes service",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
name: { type: "string" },
|
|
8
|
+
namespace: { type: "string", default: "default" },
|
|
9
|
+
type: {
|
|
10
|
+
type: "string",
|
|
11
|
+
enum: ["ClusterIP", "NodePort", "LoadBalancer"],
|
|
12
|
+
default: "ClusterIP"
|
|
13
|
+
},
|
|
14
|
+
selector: {
|
|
15
|
+
type: "object",
|
|
16
|
+
additionalProperties: { type: "string" },
|
|
17
|
+
default: {}
|
|
18
|
+
},
|
|
19
|
+
ports: {
|
|
20
|
+
type: "array",
|
|
21
|
+
items: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
port: { type: "number" },
|
|
25
|
+
targetPort: { type: "number" },
|
|
26
|
+
protocol: {
|
|
27
|
+
type: "string",
|
|
28
|
+
enum: ["TCP", "UDP"],
|
|
29
|
+
default: "TCP"
|
|
30
|
+
},
|
|
31
|
+
name: { type: "string" },
|
|
32
|
+
nodePort: { type: "number" }
|
|
33
|
+
},
|
|
34
|
+
required: ["port"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: ["name", "ports"],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export async function createService(k8sManager, input) {
|
|
42
|
+
const namespace = input.namespace || "default";
|
|
43
|
+
const serviceType = input.type || "ClusterIP";
|
|
44
|
+
// Convert ports to k8s.V1ServicePort format
|
|
45
|
+
const servicePorts = input.ports.map((portConfig, index) => {
|
|
46
|
+
return {
|
|
47
|
+
port: portConfig.port,
|
|
48
|
+
targetPort: portConfig.targetPort !== undefined ? portConfig.targetPort : portConfig.port,
|
|
49
|
+
protocol: portConfig.protocol || "TCP",
|
|
50
|
+
name: portConfig.name || `port-${index}`,
|
|
51
|
+
...(serviceType === "NodePort" && portConfig.nodePort ? { nodePort: portConfig.nodePort } : {})
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
// Default selector
|
|
55
|
+
const selector = input.selector || { app: input.name };
|
|
56
|
+
const service = {
|
|
57
|
+
apiVersion: "v1",
|
|
58
|
+
kind: "Service",
|
|
59
|
+
metadata: {
|
|
60
|
+
name: input.name,
|
|
61
|
+
namespace: namespace,
|
|
62
|
+
labels: {
|
|
63
|
+
"mcp-managed": "true",
|
|
64
|
+
app: input.name,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
spec: {
|
|
68
|
+
type: serviceType,
|
|
69
|
+
selector: selector,
|
|
70
|
+
ports: servicePorts
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
const response = await k8sManager
|
|
75
|
+
.getCoreApi()
|
|
76
|
+
.createNamespacedService(namespace, service);
|
|
77
|
+
k8sManager.trackResource("Service", input.name, namespace);
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: JSON.stringify({
|
|
83
|
+
serviceName: response.body.metadata.name,
|
|
84
|
+
namespace: response.body.metadata.namespace,
|
|
85
|
+
type: response.body.spec.type,
|
|
86
|
+
clusterIP: response.body.spec.clusterIP,
|
|
87
|
+
ports: response.body.spec.ports,
|
|
88
|
+
status: "created",
|
|
89
|
+
}, null, 2),
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error("Service creation error:", {
|
|
96
|
+
status: error.response?.statusCode,
|
|
97
|
+
message: error.response?.body?.message || error.message,
|
|
98
|
+
details: error.response?.body,
|
|
99
|
+
});
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const getCurrentContextSchema: {
|
|
3
|
+
readonly name: "get_current_context";
|
|
4
|
+
readonly description: "Get the current Kubernetes context";
|
|
5
|
+
readonly inputSchema: {
|
|
6
|
+
readonly type: "object";
|
|
7
|
+
readonly properties: {
|
|
8
|
+
readonly detailed: {
|
|
9
|
+
readonly type: "boolean";
|
|
10
|
+
readonly description: "Include detailed information about the current context";
|
|
11
|
+
readonly default: false;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function getCurrentContext(k8sManager: KubernetesManager, input: {
|
|
17
|
+
detailed?: boolean;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const getCurrentContextSchema = {
|
|
2
|
+
name: "get_current_context",
|
|
3
|
+
description: "Get the current Kubernetes context",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
detailed: {
|
|
8
|
+
type: "boolean",
|
|
9
|
+
description: "Include detailed information about the current context",
|
|
10
|
+
default: false
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export async function getCurrentContext(k8sManager, input) {
|
|
16
|
+
try {
|
|
17
|
+
// Get the KubeConfig from the KubernetesManager
|
|
18
|
+
const kc = k8sManager.getKubeConfig();
|
|
19
|
+
// Get the current context name
|
|
20
|
+
const currentContextName = kc.getCurrentContext();
|
|
21
|
+
// If detailed is true, get more information about the context
|
|
22
|
+
if (input.detailed) {
|
|
23
|
+
const contexts = kc.getContexts();
|
|
24
|
+
const currentContext = contexts.find(context => context.name === currentContextName);
|
|
25
|
+
if (!currentContext) {
|
|
26
|
+
throw new Error(`Current context '${currentContextName}' not found in available contexts`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: JSON.stringify({
|
|
33
|
+
name: currentContextName,
|
|
34
|
+
cluster: currentContext.cluster,
|
|
35
|
+
user: currentContext.user,
|
|
36
|
+
namespace: currentContext.namespace || "default"
|
|
37
|
+
}, null, 2),
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Simple response with just the context name
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: JSON.stringify({ currentContext: currentContextName }, null, 2),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new Error(`Failed to get current context: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -85,7 +85,11 @@ export const uninstallHelmChartSchema = {
|
|
|
85
85
|
};
|
|
86
86
|
const executeHelmCommand = (command) => {
|
|
87
87
|
try {
|
|
88
|
-
|
|
88
|
+
// Add a generous timeout of 60 seconds for Helm operations
|
|
89
|
+
return execSync(command, {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
timeout: 60000 // 60 seconds timeout
|
|
92
|
+
});
|
|
89
93
|
}
|
|
90
94
|
catch (error) {
|
|
91
95
|
throw new Error(`Helm command failed: ${error.message}`);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const listContextsSchema: {
|
|
3
|
+
readonly name: "list_contexts";
|
|
4
|
+
readonly description: "List all available Kubernetes contexts";
|
|
5
|
+
readonly inputSchema: {
|
|
6
|
+
readonly type: "object";
|
|
7
|
+
readonly properties: {
|
|
8
|
+
readonly showCurrent: {
|
|
9
|
+
readonly type: "boolean";
|
|
10
|
+
readonly description: "Show which context is currently active";
|
|
11
|
+
readonly default: true;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function listContexts(k8sManager: KubernetesManager, input: {
|
|
17
|
+
showCurrent?: boolean;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const listContextsSchema = {
|
|
2
|
+
name: "list_contexts",
|
|
3
|
+
description: "List all available Kubernetes contexts",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
showCurrent: {
|
|
8
|
+
type: "boolean",
|
|
9
|
+
description: "Show which context is currently active",
|
|
10
|
+
default: true
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export async function listContexts(k8sManager, input) {
|
|
16
|
+
try {
|
|
17
|
+
// Get the KubeConfig from the KubernetesManager
|
|
18
|
+
const kc = k8sManager.getKubeConfig();
|
|
19
|
+
const contexts = kc.getContexts();
|
|
20
|
+
const currentContext = input.showCurrent ? kc.getCurrentContext() : undefined;
|
|
21
|
+
const contextList = contexts.map(context => ({
|
|
22
|
+
name: context.name,
|
|
23
|
+
cluster: context.cluster,
|
|
24
|
+
user: context.user,
|
|
25
|
+
isCurrent: context.name === currentContext
|
|
26
|
+
}));
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: JSON.stringify({ contexts: contextList }, null, 2),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Failed to list contexts: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const setCurrentContextSchema: {
|
|
3
|
+
readonly name: "set_current_context";
|
|
4
|
+
readonly description: "Set the current Kubernetes context";
|
|
5
|
+
readonly inputSchema: {
|
|
6
|
+
readonly type: "object";
|
|
7
|
+
readonly properties: {
|
|
8
|
+
readonly name: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly description: "Name of the context to set as current";
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
readonly required: readonly ["name"];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function setCurrentContext(k8sManager: KubernetesManager, input: {
|
|
17
|
+
name: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const setCurrentContextSchema = {
|
|
2
|
+
name: "set_current_context",
|
|
3
|
+
description: "Set the current Kubernetes context",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
name: {
|
|
8
|
+
type: "string",
|
|
9
|
+
description: "Name of the context to set as current"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
required: ["name"],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export async function setCurrentContext(k8sManager, input) {
|
|
16
|
+
try {
|
|
17
|
+
// Set the current context
|
|
18
|
+
k8sManager.setCurrentContext(input.name);
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
success: true,
|
|
25
|
+
message: `Current context set to '${input.name}'`,
|
|
26
|
+
context: input.name
|
|
27
|
+
}, null, 2),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Failed to set current context: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
import { CustomContainerConfigType } from "../config/container-templates.js";
|
|
3
|
+
export declare const updateDeploymentSchema: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
required: string[];
|
|
9
|
+
properties: {
|
|
10
|
+
name: {
|
|
11
|
+
type: string;
|
|
12
|
+
};
|
|
13
|
+
namespace: {
|
|
14
|
+
type: string;
|
|
15
|
+
};
|
|
16
|
+
template: {
|
|
17
|
+
type: string;
|
|
18
|
+
enum: ["ubuntu", "nginx", "busybox", "alpine", "custom"];
|
|
19
|
+
};
|
|
20
|
+
containerName: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
replicas: {
|
|
25
|
+
type: string;
|
|
26
|
+
};
|
|
27
|
+
customConfig: {
|
|
28
|
+
type: string;
|
|
29
|
+
properties: {
|
|
30
|
+
image: {
|
|
31
|
+
type: string;
|
|
32
|
+
};
|
|
33
|
+
command: {
|
|
34
|
+
type: string;
|
|
35
|
+
items: {
|
|
36
|
+
type: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
args: {
|
|
40
|
+
type: string;
|
|
41
|
+
items: {
|
|
42
|
+
type: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
ports: {
|
|
46
|
+
type: string;
|
|
47
|
+
items: {
|
|
48
|
+
type: string;
|
|
49
|
+
properties: {
|
|
50
|
+
containerPort: {
|
|
51
|
+
type: string;
|
|
52
|
+
};
|
|
53
|
+
name: {
|
|
54
|
+
type: string;
|
|
55
|
+
};
|
|
56
|
+
protocol: {
|
|
57
|
+
type: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
resources: {
|
|
63
|
+
type: string;
|
|
64
|
+
properties: {
|
|
65
|
+
limits: {
|
|
66
|
+
type: string;
|
|
67
|
+
additionalProperties: {
|
|
68
|
+
type: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
requests: {
|
|
72
|
+
type: string;
|
|
73
|
+
additionalProperties: {
|
|
74
|
+
type: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
env: {
|
|
80
|
+
type: string;
|
|
81
|
+
items: {
|
|
82
|
+
type: string;
|
|
83
|
+
properties: {
|
|
84
|
+
name: {
|
|
85
|
+
type: string;
|
|
86
|
+
};
|
|
87
|
+
value: {
|
|
88
|
+
type: string;
|
|
89
|
+
};
|
|
90
|
+
valueFrom: {
|
|
91
|
+
type: string;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
export declare function updateDeployment(k8sManager: KubernetesManager, params: {
|
|
102
|
+
name: string;
|
|
103
|
+
namespace: string;
|
|
104
|
+
template: string;
|
|
105
|
+
containerName?: string;
|
|
106
|
+
replicas?: number;
|
|
107
|
+
customConfig?: CustomContainerConfigType;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
content: {
|
|
110
|
+
type: string;
|
|
111
|
+
text: string;
|
|
112
|
+
}[];
|
|
113
|
+
}>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { ContainerTemplate, containerTemplates, CustomContainerConfig, } from "../config/container-templates.js";
|
|
3
|
+
export const updateDeploymentSchema = {
|
|
4
|
+
name: "update_deployment",
|
|
5
|
+
description: "Update an existing kubernetes deployment in cluster",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
required: ["name", "namespace", "template"],
|
|
9
|
+
properties: {
|
|
10
|
+
name: { type: "string" },
|
|
11
|
+
namespace: { type: "string" },
|
|
12
|
+
template: {
|
|
13
|
+
type: "string",
|
|
14
|
+
enum: ContainerTemplate.options,
|
|
15
|
+
},
|
|
16
|
+
containerName: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Name of the container to update",
|
|
19
|
+
},
|
|
20
|
+
replicas: { type: "number" },
|
|
21
|
+
customConfig: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
image: { type: "string" },
|
|
25
|
+
command: { type: "array", items: { type: "string" } },
|
|
26
|
+
args: { type: "array", items: { type: "string" } },
|
|
27
|
+
ports: {
|
|
28
|
+
type: "array",
|
|
29
|
+
items: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
containerPort: { type: "number" },
|
|
33
|
+
name: { type: "string" },
|
|
34
|
+
protocol: { type: "string" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
resources: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
limits: {
|
|
42
|
+
type: "object",
|
|
43
|
+
additionalProperties: { type: "string" },
|
|
44
|
+
},
|
|
45
|
+
requests: {
|
|
46
|
+
type: "object",
|
|
47
|
+
additionalProperties: { type: "string" },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
env: {
|
|
52
|
+
type: "array",
|
|
53
|
+
items: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
name: { type: "string" },
|
|
57
|
+
value: { type: "string" },
|
|
58
|
+
valueFrom: { type: "object" },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export async function updateDeployment(k8sManager, params) {
|
|
68
|
+
// Get existing deployment
|
|
69
|
+
const { body: existingDeployment } = await k8sManager
|
|
70
|
+
.getAppsApi()
|
|
71
|
+
.readNamespacedDeployment(params.name, params.namespace)
|
|
72
|
+
.catch((error) => {
|
|
73
|
+
console.error("Deployment read error:", {
|
|
74
|
+
status: error.response?.statusCode,
|
|
75
|
+
message: error.response?.body?.message || error.message,
|
|
76
|
+
details: error.response?.body,
|
|
77
|
+
});
|
|
78
|
+
throw error;
|
|
79
|
+
});
|
|
80
|
+
// Find target container
|
|
81
|
+
const containers = existingDeployment.spec.template.spec.containers;
|
|
82
|
+
let targetContainerIndex = params.containerName
|
|
83
|
+
? containers.findIndex(c => c.name === params.containerName)
|
|
84
|
+
: 0;
|
|
85
|
+
if (targetContainerIndex === -1) {
|
|
86
|
+
throw new McpError(ErrorCode.InvalidRequest, `Container '${params.containerName}' not found in deployment`);
|
|
87
|
+
}
|
|
88
|
+
// Prepare container config
|
|
89
|
+
const templateConfig = containerTemplates[params.template];
|
|
90
|
+
let containerConfig;
|
|
91
|
+
if (params.template === "custom") {
|
|
92
|
+
if (!params.customConfig) {
|
|
93
|
+
throw new McpError(ErrorCode.InvalidRequest, "Custom container configuration is required when using 'custom' template");
|
|
94
|
+
}
|
|
95
|
+
const validatedConfig = CustomContainerConfig.parse(params.customConfig);
|
|
96
|
+
containerConfig = {
|
|
97
|
+
...containers[targetContainerIndex],
|
|
98
|
+
...templateConfig,
|
|
99
|
+
image: validatedConfig.image,
|
|
100
|
+
command: validatedConfig.command,
|
|
101
|
+
args: validatedConfig.args,
|
|
102
|
+
ports: validatedConfig.ports,
|
|
103
|
+
resources: validatedConfig.resources,
|
|
104
|
+
env: validatedConfig.env,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
containerConfig = {
|
|
109
|
+
...containers[targetContainerIndex],
|
|
110
|
+
...templateConfig,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Update deployment
|
|
114
|
+
const updatedContainers = [...containers];
|
|
115
|
+
updatedContainers[targetContainerIndex] = containerConfig;
|
|
116
|
+
const updatedDeployment = {
|
|
117
|
+
...existingDeployment,
|
|
118
|
+
spec: {
|
|
119
|
+
...existingDeployment.spec,
|
|
120
|
+
replicas: params.replicas ?? existingDeployment.spec.replicas,
|
|
121
|
+
template: {
|
|
122
|
+
...existingDeployment.spec.template,
|
|
123
|
+
spec: {
|
|
124
|
+
...existingDeployment.spec.template.spec,
|
|
125
|
+
containers: updatedContainers,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
const { body } = await k8sManager
|
|
131
|
+
.getAppsApi()
|
|
132
|
+
.replaceNamespacedDeployment(params.name, params.namespace, updatedDeployment)
|
|
133
|
+
.catch((error) => {
|
|
134
|
+
if (error instanceof McpError)
|
|
135
|
+
throw error;
|
|
136
|
+
throw new McpError(ErrorCode.InternalError, `Failed to update deployment: ${error}`);
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: JSON.stringify({
|
|
143
|
+
message: "Deployment updated successfully",
|
|
144
|
+
deployment: {
|
|
145
|
+
name: body.metadata?.name,
|
|
146
|
+
namespace: body.metadata?.namespace,
|
|
147
|
+
replicas: body.spec?.replicas,
|
|
148
|
+
image: body.spec?.template.spec?.containers[targetContainerIndex].image,
|
|
149
|
+
containerName: body.spec?.template.spec?.containers[targetContainerIndex].name,
|
|
150
|
+
},
|
|
151
|
+
}, null, 2),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -9,6 +9,12 @@ export declare class KubernetesManager {
|
|
|
9
9
|
private k8sAppsApi;
|
|
10
10
|
private k8sBatchApi;
|
|
11
11
|
constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Set the current context to the desired context name.
|
|
14
|
+
*
|
|
15
|
+
* @param contextName
|
|
16
|
+
*/
|
|
17
|
+
setCurrentContext(contextName: string): void;
|
|
12
18
|
cleanup(): Promise<void>;
|
|
13
19
|
trackResource(kind: string, name: string, namespace: string): void;
|
|
14
20
|
deleteResource(kind: string, name: string, namespace: string): Promise<void>;
|
|
@@ -14,6 +14,25 @@ export class KubernetesManager {
|
|
|
14
14
|
this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
|
|
15
15
|
this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Set the current context to the desired context name.
|
|
19
|
+
*
|
|
20
|
+
* @param contextName
|
|
21
|
+
*/
|
|
22
|
+
setCurrentContext(contextName) {
|
|
23
|
+
// Get all available contexts
|
|
24
|
+
const contexts = this.kc.getContexts();
|
|
25
|
+
const contextNames = contexts.map(context => context.name);
|
|
26
|
+
// Check if the requested context exists
|
|
27
|
+
if (!contextNames.includes(contextName)) {
|
|
28
|
+
throw new Error(`Context '${contextName}' not found. Available contexts: ${contextNames.join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
// Set the current context
|
|
31
|
+
this.kc.setCurrentContext(contextName);
|
|
32
|
+
this.k8sApi = this.kc.makeApiClient(k8s.CoreV1Api);
|
|
33
|
+
this.k8sAppsApi = this.kc.makeApiClient(k8s.AppsV1Api);
|
|
34
|
+
this.k8sBatchApi = this.kc.makeApiClient(k8s.BatchV1Api);
|
|
35
|
+
}
|
|
17
36
|
async cleanup() {
|
|
18
37
|
// Stop watches
|
|
19
38
|
for (const watch of this.watches) {
|