mcp-server-kubernetes 1.4.0 → 1.6.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 +7 -3
- package/dist/index.js +47 -11
- package/dist/models/response-schemas.d.ts +93 -0
- package/dist/models/response-schemas.js +22 -0
- package/dist/tools/delete_configmap.d.ts +26 -0
- package/dist/tools/delete_configmap.js +49 -0
- package/dist/tools/delete_service.d.ts +32 -0
- package/dist/tools/delete_service.js +46 -0
- package/dist/tools/describe_node.d.ts +22 -0
- package/dist/tools/describe_node.js +84 -0
- package/dist/tools/describe_service.d.ts +34 -0
- package/dist/tools/describe_service.js +85 -0
- package/dist/tools/get_configmap.d.ts +27 -0
- package/dist/tools/get_configmap.js +48 -0
- package/dist/tools/update_configmap.d.ts +33 -0
- package/dist/tools/update_configmap.js +71 -0
- package/dist/tools/update_service.d.ts +72 -0
- package/dist/tools/update_service.js +125 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,11 +66,13 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
66
66
|
## Features
|
|
67
67
|
|
|
68
68
|
- [x] Connect to a Kubernetes cluster
|
|
69
|
-
- [x] List all pods, services, deployments
|
|
69
|
+
- [x] List all pods, services, deployments
|
|
70
|
+
- [x] List, Describe nodes
|
|
70
71
|
- [x] Create, describe, delete a pod
|
|
71
72
|
- [x] List all namespaces, create a namespace
|
|
72
73
|
- [x] Create custom pod & deployment configs, update deployment replicas
|
|
73
|
-
- [x] Create
|
|
74
|
+
- [x] Create, describe, delete, update a service
|
|
75
|
+
- [x] Create, get, update, delete a ConfigMap
|
|
74
76
|
- [x] Get logs from a pod for debugging (supports pods, deployments, jobs, and label selectors)
|
|
75
77
|
- [x] Support Helm v3 for installing charts
|
|
76
78
|
- Install charts with custom values
|
|
@@ -87,6 +89,8 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
87
89
|
|
|
88
90
|
## Local Development
|
|
89
91
|
|
|
92
|
+
Make sure that you have [bun installed](https://bun.sh/docs/installation). Clone the repo & install dependencies:
|
|
93
|
+
|
|
90
94
|
```bash
|
|
91
95
|
git clone https://github.com/Flux159/mcp-server-kubernetes.git
|
|
92
96
|
cd mcp-server-kubernetes
|
|
@@ -136,7 +140,7 @@ npx @modelcontextprotocol/inspector node dist/index.js
|
|
|
136
140
|
6. Local testing with [mcp-chat](https://github.com/Flux159/mcp-chat)
|
|
137
141
|
|
|
138
142
|
```bash
|
|
139
|
-
|
|
143
|
+
bun run chat
|
|
140
144
|
```
|
|
141
145
|
|
|
142
146
|
## Contributing
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,10 @@ import { listNodes, listNodesSchema } from "./tools/list_nodes.js";
|
|
|
6
6
|
import { listServices, listServicesSchema } from "./tools/list_services.js";
|
|
7
7
|
import { listDeployments, listDeploymentsSchema, } from "./tools/list_deployments.js";
|
|
8
8
|
import { listCronJobs, listCronJobsSchema } from "./tools/list_cronjobs.js";
|
|
9
|
-
import { describeCronJob, describeCronJobSchema } from "./tools/describe_cronjob.js";
|
|
9
|
+
import { describeCronJob, describeCronJobSchema, } from "./tools/describe_cronjob.js";
|
|
10
10
|
import { listJobs, listJobsSchema } from "./tools/list_jobs.js";
|
|
11
11
|
import { getJobLogs, getJobLogsSchema } from "./tools/get_job_logs.js";
|
|
12
|
+
import { describeNode, describeNodeSchema } from "./tools/describe_node.js";
|
|
12
13
|
import { installHelmChart, installHelmChartSchema, upgradeHelmChart, upgradeHelmChartSchema, uninstallHelmChart, uninstallHelmChartSchema, } from "./tools/helm-operations.js";
|
|
13
14
|
import { explainResource, explainResourceSchema, listApiResources, listApiResourcesSchema, } from "./tools/kubectl-operations.js";
|
|
14
15
|
import { createNamespace, createNamespaceSchema, } from "./tools/create_namespace.js";
|
|
@@ -25,22 +26,28 @@ import { KubernetesManager } from "./types.js";
|
|
|
25
26
|
import { serverConfig } from "./config/server-config.js";
|
|
26
27
|
import { createDeploymentSchema } from "./config/deployment-config.js";
|
|
27
28
|
import { listNamespacesSchema } from "./config/namespace-config.js";
|
|
28
|
-
import { deleteNamespace, deleteNamespaceSchema } from "./tools/delete_namespace.js";
|
|
29
|
+
import { deleteNamespace, deleteNamespaceSchema, } from "./tools/delete_namespace.js";
|
|
29
30
|
import { cleanupSchema } from "./config/cleanup-config.js";
|
|
30
31
|
import { startSSEServer } from "./utils/sse.js";
|
|
31
32
|
import { startPortForward, PortForwardSchema, stopPortForward, StopPortForwardSchema, } from "./tools/port_forward.js";
|
|
32
|
-
import { deleteDeployment, deleteDeploymentSchema } from "./tools/delete_deployment.js";
|
|
33
|
+
import { deleteDeployment, deleteDeploymentSchema, } from "./tools/delete_deployment.js";
|
|
33
34
|
import { createDeployment } from "./tools/create_deployment.js";
|
|
34
|
-
import { scaleDeployment, scaleDeploymentSchema } from "./tools/scale_deployment.js";
|
|
35
|
+
import { scaleDeployment, scaleDeploymentSchema, } from "./tools/scale_deployment.js";
|
|
35
36
|
import { describeDeployment, describeDeploymentSchema, } from "./tools/describe_deployment.js";
|
|
36
|
-
import { updateDeployment, updateDeploymentSchema } from "./tools/update_deployment.js";
|
|
37
|
-
import { createConfigMap, CreateConfigMapSchema } from "./tools/create_configmap.js";
|
|
38
|
-
import {
|
|
37
|
+
import { updateDeployment, updateDeploymentSchema, } from "./tools/update_deployment.js";
|
|
38
|
+
import { createConfigMap, CreateConfigMapSchema, } from "./tools/create_configmap.js";
|
|
39
|
+
import { getConfigMap, GetConfigMapSchema } from "./tools/get_configmap.js";
|
|
40
|
+
import { updateConfigMap, UpdateConfigMapSchema } from "./tools/update_configmap.js";
|
|
41
|
+
import { deleteConfigMap, DeleteConfigMapSchema } from "./tools/delete_configmap.js";
|
|
39
42
|
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";
|
|
43
|
+
import { getCurrentContext, getCurrentContextSchema, } from "./tools/get_current_context.js";
|
|
44
|
+
import { setCurrentContext, setCurrentContextSchema, } from "./tools/set_current_context.js";
|
|
45
|
+
import { createService, createServiceSchema } from "./tools/create_service.js";
|
|
46
|
+
import { describeService, describeServiceSchema, } from "./tools/describe_service.js";
|
|
47
|
+
import { updateService, updateServiceSchema } from "./tools/update_service.js";
|
|
48
|
+
import { deleteService, deleteServiceSchema } from "./tools/delete_service.js";
|
|
42
49
|
// Check if non-destructive tools only mode is enabled
|
|
43
|
-
const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS ===
|
|
50
|
+
const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === "true";
|
|
44
51
|
const k8sManager = new KubernetesManager();
|
|
45
52
|
const server = new Server({
|
|
46
53
|
name: serverConfig.name,
|
|
@@ -49,6 +56,7 @@ const server = new Server({
|
|
|
49
56
|
// Define destructive tools (delete and uninstall operations)
|
|
50
57
|
const destructiveTools = [
|
|
51
58
|
deletePodSchema,
|
|
59
|
+
deleteServiceSchema,
|
|
52
60
|
deleteDeploymentSchema,
|
|
53
61
|
deleteNamespaceSchema,
|
|
54
62
|
uninstallHelmChartSchema,
|
|
@@ -68,9 +76,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
68
76
|
deletePodSchema,
|
|
69
77
|
deleteDeploymentSchema,
|
|
70
78
|
deleteNamespaceSchema,
|
|
79
|
+
deleteServiceSchema,
|
|
71
80
|
describeCronJobSchema,
|
|
72
81
|
describePodSchema,
|
|
82
|
+
describeNodeSchema,
|
|
73
83
|
describeDeploymentSchema,
|
|
84
|
+
describeServiceSchema,
|
|
74
85
|
explainResourceSchema,
|
|
75
86
|
getEventsSchema,
|
|
76
87
|
getJobLogsSchema,
|
|
@@ -95,10 +106,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
95
106
|
scaleDeploymentSchema,
|
|
96
107
|
DeleteCronJobSchema,
|
|
97
108
|
CreateConfigMapSchema,
|
|
109
|
+
updateServiceSchema,
|
|
110
|
+
GetConfigMapSchema,
|
|
111
|
+
UpdateConfigMapSchema,
|
|
112
|
+
DeleteConfigMapSchema,
|
|
98
113
|
];
|
|
99
114
|
// Filter out destructive tools if ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS is set to 'true'
|
|
100
115
|
const tools = nonDestructiveTools
|
|
101
|
-
? allTools.filter(tool => !destructiveTools.some(dt => dt.name === tool.name))
|
|
116
|
+
? allTools.filter((tool) => !destructiveTools.some((dt) => dt.name === tool.name))
|
|
102
117
|
: allTools;
|
|
103
118
|
return { tools };
|
|
104
119
|
});
|
|
@@ -137,6 +152,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
137
152
|
case "describe_pod": {
|
|
138
153
|
return await describePod(k8sManager, input);
|
|
139
154
|
}
|
|
155
|
+
case "describe_node": {
|
|
156
|
+
return await describeNode(k8sManager, input);
|
|
157
|
+
}
|
|
140
158
|
case "explain_resource": {
|
|
141
159
|
return await explainResource(input);
|
|
142
160
|
}
|
|
@@ -234,9 +252,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
234
252
|
case "create_configmap": {
|
|
235
253
|
return await createConfigMap(k8sManager, input);
|
|
236
254
|
}
|
|
255
|
+
case "get_configmap": {
|
|
256
|
+
return await getConfigMap(k8sManager, input);
|
|
257
|
+
}
|
|
258
|
+
case "update_configmap": {
|
|
259
|
+
return await updateConfigMap(k8sManager, input);
|
|
260
|
+
}
|
|
261
|
+
case "delete_configmap": {
|
|
262
|
+
return await deleteConfigMap(k8sManager, input);
|
|
263
|
+
}
|
|
237
264
|
case "create_service": {
|
|
238
265
|
return await createService(k8sManager, input);
|
|
239
266
|
}
|
|
267
|
+
case "update_service": {
|
|
268
|
+
return await updateService(k8sManager, input);
|
|
269
|
+
}
|
|
270
|
+
case "delete_service": {
|
|
271
|
+
return await deleteService(k8sManager, input);
|
|
272
|
+
}
|
|
273
|
+
case "describe_service": {
|
|
274
|
+
return await describeService(k8sManager, input);
|
|
275
|
+
}
|
|
240
276
|
default:
|
|
241
277
|
throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
|
|
242
278
|
}
|
|
@@ -505,6 +505,77 @@ export declare const CreateConfigMapResponseSchema: z.ZodObject<{
|
|
|
505
505
|
success: boolean;
|
|
506
506
|
}[];
|
|
507
507
|
}>;
|
|
508
|
+
export declare const GetConfigMapResponseSchema: z.ZodObject<{
|
|
509
|
+
content: z.ZodArray<z.ZodObject<{
|
|
510
|
+
success: z.ZodBoolean;
|
|
511
|
+
message: z.ZodString;
|
|
512
|
+
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
513
|
+
}, "strip", z.ZodTypeAny, {
|
|
514
|
+
message: string;
|
|
515
|
+
success: boolean;
|
|
516
|
+
data?: Record<string, string> | undefined;
|
|
517
|
+
}, {
|
|
518
|
+
message: string;
|
|
519
|
+
success: boolean;
|
|
520
|
+
data?: Record<string, string> | undefined;
|
|
521
|
+
}>, "many">;
|
|
522
|
+
}, "strip", z.ZodTypeAny, {
|
|
523
|
+
content: {
|
|
524
|
+
message: string;
|
|
525
|
+
success: boolean;
|
|
526
|
+
data?: Record<string, string> | undefined;
|
|
527
|
+
}[];
|
|
528
|
+
}, {
|
|
529
|
+
content: {
|
|
530
|
+
message: string;
|
|
531
|
+
success: boolean;
|
|
532
|
+
data?: Record<string, string> | undefined;
|
|
533
|
+
}[];
|
|
534
|
+
}>;
|
|
535
|
+
export declare const UpdateConfigMapResponseSchema: z.ZodObject<{
|
|
536
|
+
content: z.ZodArray<z.ZodObject<{
|
|
537
|
+
success: z.ZodBoolean;
|
|
538
|
+
message: z.ZodString;
|
|
539
|
+
}, "strip", z.ZodTypeAny, {
|
|
540
|
+
message: string;
|
|
541
|
+
success: boolean;
|
|
542
|
+
}, {
|
|
543
|
+
message: string;
|
|
544
|
+
success: boolean;
|
|
545
|
+
}>, "many">;
|
|
546
|
+
}, "strip", z.ZodTypeAny, {
|
|
547
|
+
content: {
|
|
548
|
+
message: string;
|
|
549
|
+
success: boolean;
|
|
550
|
+
}[];
|
|
551
|
+
}, {
|
|
552
|
+
content: {
|
|
553
|
+
message: string;
|
|
554
|
+
success: boolean;
|
|
555
|
+
}[];
|
|
556
|
+
}>;
|
|
557
|
+
export declare const DeleteConfigMapResponseSchema: z.ZodObject<{
|
|
558
|
+
content: z.ZodArray<z.ZodObject<{
|
|
559
|
+
success: z.ZodBoolean;
|
|
560
|
+
message: z.ZodString;
|
|
561
|
+
}, "strip", z.ZodTypeAny, {
|
|
562
|
+
message: string;
|
|
563
|
+
success: boolean;
|
|
564
|
+
}, {
|
|
565
|
+
message: string;
|
|
566
|
+
success: boolean;
|
|
567
|
+
}>, "many">;
|
|
568
|
+
}, "strip", z.ZodTypeAny, {
|
|
569
|
+
content: {
|
|
570
|
+
message: string;
|
|
571
|
+
success: boolean;
|
|
572
|
+
}[];
|
|
573
|
+
}, {
|
|
574
|
+
content: {
|
|
575
|
+
message: string;
|
|
576
|
+
success: boolean;
|
|
577
|
+
}[];
|
|
578
|
+
}>;
|
|
508
579
|
export declare const ListContextsResponseSchema: z.ZodObject<{
|
|
509
580
|
content: z.ZodArray<z.ZodObject<{
|
|
510
581
|
type: z.ZodLiteral<"text">;
|
|
@@ -571,3 +642,25 @@ export declare const SetCurrentContextResponseSchema: z.ZodObject<{
|
|
|
571
642
|
text: string;
|
|
572
643
|
}[];
|
|
573
644
|
}>;
|
|
645
|
+
export declare const DescribeNodeResponseSchema: z.ZodObject<{
|
|
646
|
+
content: z.ZodArray<z.ZodObject<{
|
|
647
|
+
type: z.ZodLiteral<"text">;
|
|
648
|
+
text: z.ZodString;
|
|
649
|
+
}, "strip", z.ZodTypeAny, {
|
|
650
|
+
type: "text";
|
|
651
|
+
text: string;
|
|
652
|
+
}, {
|
|
653
|
+
type: "text";
|
|
654
|
+
text: string;
|
|
655
|
+
}>, "many">;
|
|
656
|
+
}, "strip", z.ZodTypeAny, {
|
|
657
|
+
content: {
|
|
658
|
+
type: "text";
|
|
659
|
+
text: string;
|
|
660
|
+
}[];
|
|
661
|
+
}, {
|
|
662
|
+
content: {
|
|
663
|
+
type: "text";
|
|
664
|
+
text: string;
|
|
665
|
+
}[];
|
|
666
|
+
}>;
|
|
@@ -85,6 +85,25 @@ export const CreateConfigMapResponseSchema = z.object({
|
|
|
85
85
|
message: z.string(),
|
|
86
86
|
})),
|
|
87
87
|
});
|
|
88
|
+
export const GetConfigMapResponseSchema = z.object({
|
|
89
|
+
content: z.array(z.object({
|
|
90
|
+
success: z.boolean(),
|
|
91
|
+
message: z.string(),
|
|
92
|
+
data: z.record(z.string(), z.string()).optional(),
|
|
93
|
+
})),
|
|
94
|
+
});
|
|
95
|
+
export const UpdateConfigMapResponseSchema = z.object({
|
|
96
|
+
content: z.array(z.object({
|
|
97
|
+
success: z.boolean(),
|
|
98
|
+
message: z.string(),
|
|
99
|
+
})),
|
|
100
|
+
});
|
|
101
|
+
export const DeleteConfigMapResponseSchema = z.object({
|
|
102
|
+
content: z.array(z.object({
|
|
103
|
+
success: z.boolean(),
|
|
104
|
+
message: z.string(),
|
|
105
|
+
})),
|
|
106
|
+
});
|
|
88
107
|
export const ListContextsResponseSchema = z.object({
|
|
89
108
|
content: z.array(ToolResponseContent),
|
|
90
109
|
});
|
|
@@ -94,3 +113,6 @@ export const GetCurrentContextResponseSchema = z.object({
|
|
|
94
113
|
export const SetCurrentContextResponseSchema = z.object({
|
|
95
114
|
content: z.array(ToolResponseContent),
|
|
96
115
|
});
|
|
116
|
+
export const DescribeNodeResponseSchema = z.object({
|
|
117
|
+
content: z.array(ToolResponseContent),
|
|
118
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const DeleteConfigMapSchema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
name: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
namespace: {
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
required: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function deleteConfigMap(k8sManager: KubernetesManager, input: {
|
|
19
|
+
name: string;
|
|
20
|
+
namespace: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
content: {
|
|
23
|
+
success: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
}[];
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const DeleteConfigMapSchema = {
|
|
2
|
+
name: "delete_configmap",
|
|
3
|
+
description: "Delete a Kubernetes ConfigMap",
|
|
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 deleteConfigMap(k8sManager, input) {
|
|
14
|
+
try {
|
|
15
|
+
const response = await k8sManager.getCoreApi().deleteNamespacedConfigMap(input.name, input.namespace);
|
|
16
|
+
if (response.response?.statusCode !== undefined &&
|
|
17
|
+
(response.response.statusCode === 200 ||
|
|
18
|
+
response.response.statusCode === 202)) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
success: true,
|
|
23
|
+
message: `Deleted ConfigMap ${input.name} in namespace ${input.namespace}`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
success: false,
|
|
33
|
+
message: `Failed to delete ConfigMap ${input.name} in namespace ${input.namespace}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
success: false,
|
|
44
|
+
message: `Failed to delete ConfigMap ${input.name} in namespace ${input.namespace}. Error: ${error.message}`,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const deleteServiceSchema: {
|
|
3
|
+
readonly name: "delete_service";
|
|
4
|
+
readonly description: "Delete a 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 ignoreNotFound: {
|
|
16
|
+
readonly type: "boolean";
|
|
17
|
+
readonly default: false;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
readonly required: readonly ["name"];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare function deleteService(k8sManager: KubernetesManager, input: {
|
|
24
|
+
name: string;
|
|
25
|
+
namespace?: string;
|
|
26
|
+
ignoreNotFound?: boolean;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
content: {
|
|
29
|
+
type: string;
|
|
30
|
+
text: string;
|
|
31
|
+
}[];
|
|
32
|
+
}>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const deleteServiceSchema = {
|
|
2
|
+
name: "delete_service",
|
|
3
|
+
description: "Delete a Kubernetes service",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
name: { type: "string" },
|
|
8
|
+
namespace: { type: "string", default: "default" },
|
|
9
|
+
ignoreNotFound: { type: "boolean", default: false },
|
|
10
|
+
},
|
|
11
|
+
required: ["name"],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export async function deleteService(k8sManager, input) {
|
|
15
|
+
const namespace = input.namespace || "default";
|
|
16
|
+
try {
|
|
17
|
+
await k8sManager.getCoreApi().deleteNamespacedService(input.name, namespace);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: "text",
|
|
22
|
+
text: JSON.stringify({
|
|
23
|
+
success: true,
|
|
24
|
+
status: "deleted",
|
|
25
|
+
}, null, 2),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (input.ignoreNotFound && error.response?.statusCode === 404) {
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: JSON.stringify({
|
|
37
|
+
success: true,
|
|
38
|
+
status: "not_found",
|
|
39
|
+
}, null, 2),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const describeNodeSchema: {
|
|
3
|
+
readonly name: "describe_node";
|
|
4
|
+
readonly description: "Describe a Kubernetes node (read details like status, capacity, conditions, etc.)";
|
|
5
|
+
readonly inputSchema: {
|
|
6
|
+
readonly type: "object";
|
|
7
|
+
readonly properties: {
|
|
8
|
+
readonly name: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
readonly required: readonly ["name"];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function describeNode(k8sManager: KubernetesManager, input: {
|
|
16
|
+
name: string;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
content: {
|
|
19
|
+
type: string;
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export const describeNodeSchema = {
|
|
3
|
+
name: "describe_node",
|
|
4
|
+
description: "Describe a Kubernetes node (read details like status, capacity, conditions, etc.)",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
name: { type: "string" },
|
|
9
|
+
},
|
|
10
|
+
required: ["name"],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export async function describeNode(k8sManager, input) {
|
|
14
|
+
try {
|
|
15
|
+
const { body } = await k8sManager.getCoreApi().readNode(input.name);
|
|
16
|
+
if (!body) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: JSON.stringify({
|
|
22
|
+
error: "Node not found",
|
|
23
|
+
status: "not_found",
|
|
24
|
+
}, null, 2),
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Format the node details for better readability
|
|
30
|
+
const nodeDetails = {
|
|
31
|
+
kind: body.kind,
|
|
32
|
+
metadata: {
|
|
33
|
+
name: body.metadata?.name,
|
|
34
|
+
creationTimestamp: body.metadata?.creationTimestamp,
|
|
35
|
+
labels: body.metadata?.labels,
|
|
36
|
+
annotations: body.metadata?.annotations,
|
|
37
|
+
},
|
|
38
|
+
spec: {
|
|
39
|
+
podCIDR: body.spec?.podCIDR,
|
|
40
|
+
podCIDRs: body.spec?.podCIDRs,
|
|
41
|
+
taints: body.spec?.taints,
|
|
42
|
+
unschedulable: body.spec?.unschedulable,
|
|
43
|
+
},
|
|
44
|
+
status: {
|
|
45
|
+
capacity: body.status?.capacity,
|
|
46
|
+
allocatable: body.status?.allocatable,
|
|
47
|
+
conditions: body.status?.conditions,
|
|
48
|
+
nodeInfo: {
|
|
49
|
+
architecture: body.status?.nodeInfo?.architecture,
|
|
50
|
+
containerRuntimeVersion: body.status?.nodeInfo?.containerRuntimeVersion,
|
|
51
|
+
kernelVersion: body.status?.nodeInfo?.kernelVersion,
|
|
52
|
+
kubeletVersion: body.status?.nodeInfo?.kubeletVersion,
|
|
53
|
+
operatingSystem: body.status?.nodeInfo?.operatingSystem,
|
|
54
|
+
osImage: body.status?.nodeInfo?.osImage,
|
|
55
|
+
},
|
|
56
|
+
addresses: body.status?.addresses,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: JSON.stringify(nodeDetails, null, 2),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.response?.statusCode === 404) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify({
|
|
75
|
+
error: "Node not found",
|
|
76
|
+
status: "not_found",
|
|
77
|
+
}, null, 2),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
throw new McpError(ErrorCode.InternalError, `Failed to describe node: ${error.response?.body?.message || error.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const describeServiceSchema: {
|
|
3
|
+
readonly name: "describe_service";
|
|
4
|
+
readonly description: "Describe a Kubernetes service (read details like status, ports, selectors, etc.)";
|
|
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
|
+
};
|
|
16
|
+
readonly required: readonly ["name"];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export declare function describeService(k8sManager: KubernetesManager, input: {
|
|
20
|
+
name: string;
|
|
21
|
+
namespace?: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
content: {
|
|
24
|
+
type: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError: boolean;
|
|
28
|
+
} | {
|
|
29
|
+
content: {
|
|
30
|
+
type: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
isError?: undefined;
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export const describeServiceSchema = {
|
|
3
|
+
name: "describe_service",
|
|
4
|
+
description: "Describe a Kubernetes service (read details like status, ports, selectors, etc.)",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
name: { type: "string" },
|
|
9
|
+
namespace: { type: "string", default: "default" },
|
|
10
|
+
},
|
|
11
|
+
required: ["name"],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export async function describeService(k8sManager, input) {
|
|
15
|
+
const namespace = input.namespace || "default";
|
|
16
|
+
try {
|
|
17
|
+
const { body } = await k8sManager.getCoreApi().readNamespacedService(input.name, namespace);
|
|
18
|
+
if (!body) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
error: "Service not found",
|
|
25
|
+
status: "not_found",
|
|
26
|
+
}, null, 2),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
isError: true,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Format service details for better readability
|
|
33
|
+
const serviceDetails = {
|
|
34
|
+
kind: body.kind,
|
|
35
|
+
metadata: {
|
|
36
|
+
name: body.metadata?.name,
|
|
37
|
+
namespace: body.metadata?.namespace,
|
|
38
|
+
creationTimestamp: body.metadata?.creationTimestamp,
|
|
39
|
+
labels: body.metadata?.labels,
|
|
40
|
+
},
|
|
41
|
+
spec: {
|
|
42
|
+
type: body.spec?.type,
|
|
43
|
+
selector: body.spec?.selector,
|
|
44
|
+
ports: body.spec?.ports?.map((port) => ({
|
|
45
|
+
name: port.name,
|
|
46
|
+
protocol: port.protocol,
|
|
47
|
+
port: port.port,
|
|
48
|
+
targetPort: port.targetPort,
|
|
49
|
+
nodePort: port.nodePort,
|
|
50
|
+
})),
|
|
51
|
+
clusterIP: body.spec?.clusterIP,
|
|
52
|
+
externalIPs: body.spec?.externalIPs,
|
|
53
|
+
loadBalancerIP: body.spec?.loadBalancerIP,
|
|
54
|
+
},
|
|
55
|
+
status: {
|
|
56
|
+
loadBalancer: body.status?.loadBalancer,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: JSON.stringify(serviceDetails, null, 2),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.response?.statusCode === 404) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify({
|
|
75
|
+
error: "Service not found",
|
|
76
|
+
status: "not_found",
|
|
77
|
+
}, null, 2),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw new McpError(ErrorCode.InternalError, `Failed to describe service: ${error.response?.body?.message || error.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const GetConfigMapSchema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
name: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
namespace: {
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
required: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function getConfigMap(k8sManager: KubernetesManager, input: {
|
|
19
|
+
name: string;
|
|
20
|
+
namespace: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
content: {
|
|
23
|
+
success: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
data?: Record<string, string>;
|
|
26
|
+
}[];
|
|
27
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const GetConfigMapSchema = {
|
|
2
|
+
name: "get_configmap",
|
|
3
|
+
description: "Get a Kubernetes ConfigMap",
|
|
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 getConfigMap(k8sManager, input) {
|
|
14
|
+
try {
|
|
15
|
+
const response = await k8sManager.getCoreApi().readNamespacedConfigMap(input.name, input.namespace);
|
|
16
|
+
if (response.body && response.body.data) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
success: true,
|
|
21
|
+
message: `Fetched ConfigMap ${input.name} in namespace ${input.namespace}`,
|
|
22
|
+
data: response.body.data,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
success: false,
|
|
32
|
+
message: `ConfigMap ${input.name} in namespace ${input.namespace} not found or has no data.`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
success: false,
|
|
43
|
+
message: `Failed to get ConfigMap ${input.name} in namespace ${input.namespace}. Error: ${error.message}`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const UpdateConfigMapSchema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
name: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
namespace: {
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
data: {
|
|
15
|
+
type: string;
|
|
16
|
+
ConfigData: {
|
|
17
|
+
type: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
required: string[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare function updateConfigMap(k8sManager: KubernetesManager, input: {
|
|
25
|
+
name: string;
|
|
26
|
+
namespace: string;
|
|
27
|
+
data: Record<string, string>;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
content: {
|
|
30
|
+
success: boolean;
|
|
31
|
+
message: string;
|
|
32
|
+
}[];
|
|
33
|
+
}>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const UpdateConfigMapSchema = {
|
|
2
|
+
name: "update_configmap",
|
|
3
|
+
description: "Update an existing Kubernetes ConfigMap",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {
|
|
7
|
+
name: { type: "string" },
|
|
8
|
+
namespace: { type: "string" },
|
|
9
|
+
data: {
|
|
10
|
+
type: "object",
|
|
11
|
+
ConfigData: { type: "string" },
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
required: ["name", "namespace", "data"],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export async function updateConfigMap(k8sManager, input) {
|
|
18
|
+
try {
|
|
19
|
+
// Fetch the existing ConfigMap
|
|
20
|
+
const existing = await k8sManager.getCoreApi().readNamespacedConfigMap(input.name, input.namespace);
|
|
21
|
+
if (!existing.body || !existing.body.metadata) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
success: false,
|
|
26
|
+
message: `ConfigMap ${input.name} in namespace ${input.namespace} not found.`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Update the data
|
|
32
|
+
const updatedConfigMap = {
|
|
33
|
+
...existing.body,
|
|
34
|
+
data: input.data,
|
|
35
|
+
};
|
|
36
|
+
const response = await k8sManager.getCoreApi().replaceNamespacedConfigMap(input.name, input.namespace, updatedConfigMap);
|
|
37
|
+
if (response.response?.statusCode !== undefined &&
|
|
38
|
+
(response.response.statusCode === 200 ||
|
|
39
|
+
response.response.statusCode === 201 ||
|
|
40
|
+
response.response.statusCode === 202)) {
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
success: true,
|
|
45
|
+
message: `Updated ConfigMap ${input.name} in namespace ${input.namespace}`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
success: false,
|
|
55
|
+
message: `Failed to update ConfigMap ${input.name} in namespace ${input.namespace}`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
success: false,
|
|
66
|
+
message: `Failed to update ConfigMap ${input.name} in namespace ${input.namespace}. Error: ${error.message}`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { KubernetesManager } from "../types.js";
|
|
2
|
+
export declare const updateServiceSchema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: string;
|
|
7
|
+
required: string[];
|
|
8
|
+
properties: {
|
|
9
|
+
name: {
|
|
10
|
+
type: string;
|
|
11
|
+
};
|
|
12
|
+
namespace: {
|
|
13
|
+
type: string;
|
|
14
|
+
default: string;
|
|
15
|
+
};
|
|
16
|
+
type: {
|
|
17
|
+
type: string;
|
|
18
|
+
enum: string[];
|
|
19
|
+
};
|
|
20
|
+
selector: {
|
|
21
|
+
type: string;
|
|
22
|
+
additionalProperties: {
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
ports: {
|
|
27
|
+
type: string;
|
|
28
|
+
items: {
|
|
29
|
+
type: string;
|
|
30
|
+
properties: {
|
|
31
|
+
port: {
|
|
32
|
+
type: string;
|
|
33
|
+
};
|
|
34
|
+
targetPort: {
|
|
35
|
+
type: string;
|
|
36
|
+
};
|
|
37
|
+
protocol: {
|
|
38
|
+
type: string;
|
|
39
|
+
enum: string[];
|
|
40
|
+
default: string;
|
|
41
|
+
};
|
|
42
|
+
name: {
|
|
43
|
+
type: string;
|
|
44
|
+
};
|
|
45
|
+
nodePort: {
|
|
46
|
+
type: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
required: string[];
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export declare function updateService(k8sManager: KubernetesManager, params: {
|
|
56
|
+
name: string;
|
|
57
|
+
namespace: string;
|
|
58
|
+
type?: "ClusterIP" | "NodePort" | "LoadBalancer";
|
|
59
|
+
selector?: Record<string, string>;
|
|
60
|
+
ports?: Array<{
|
|
61
|
+
port: number;
|
|
62
|
+
targetPort?: number;
|
|
63
|
+
protocol?: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
nodePort?: number;
|
|
66
|
+
}>;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
content: {
|
|
69
|
+
type: string;
|
|
70
|
+
text: string;
|
|
71
|
+
}[];
|
|
72
|
+
}>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export const updateServiceSchema = {
|
|
3
|
+
name: "update_service",
|
|
4
|
+
description: "Update an existing kubernetes service in cluster",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
required: ["name", "namespace"],
|
|
8
|
+
properties: {
|
|
9
|
+
name: { type: "string" },
|
|
10
|
+
namespace: { type: "string", default: "default" },
|
|
11
|
+
type: {
|
|
12
|
+
type: "string",
|
|
13
|
+
enum: ["ClusterIP", "NodePort", "LoadBalancer"],
|
|
14
|
+
},
|
|
15
|
+
selector: {
|
|
16
|
+
type: "object",
|
|
17
|
+
additionalProperties: { type: "string" },
|
|
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
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export async function updateService(k8sManager, params) {
|
|
41
|
+
// Get existing service
|
|
42
|
+
const { body: existingService } = await k8sManager
|
|
43
|
+
.getCoreApi()
|
|
44
|
+
.readNamespacedService(params.name, params.namespace)
|
|
45
|
+
.catch((error) => {
|
|
46
|
+
console.error("Service read error:", {
|
|
47
|
+
status: error.response?.statusCode,
|
|
48
|
+
message: error.response?.body?.message || error.message,
|
|
49
|
+
details: error.response?.body,
|
|
50
|
+
});
|
|
51
|
+
if (error.response?.statusCode === 404) {
|
|
52
|
+
throw new McpError(ErrorCode.InvalidRequest, `Service '${params.name}' not found in namespace '${params.namespace}'`);
|
|
53
|
+
}
|
|
54
|
+
throw new McpError(ErrorCode.InternalError, `Failed to retrieve service: ${error.response?.body?.message || error.message}`);
|
|
55
|
+
});
|
|
56
|
+
// Process ports if provided
|
|
57
|
+
let servicePorts;
|
|
58
|
+
if (params.ports) {
|
|
59
|
+
servicePorts = params.ports.map((portConfig, index) => {
|
|
60
|
+
const existingPort = existingService.spec?.ports?.[index];
|
|
61
|
+
const name = portConfig.name || (existingPort?.name || `port-${index}`);
|
|
62
|
+
return {
|
|
63
|
+
port: portConfig.port,
|
|
64
|
+
targetPort: portConfig.targetPort !== undefined
|
|
65
|
+
? portConfig.targetPort
|
|
66
|
+
: portConfig.port,
|
|
67
|
+
protocol: portConfig.protocol || "TCP",
|
|
68
|
+
name: name,
|
|
69
|
+
...(existingService.spec?.type === "NodePort" || params.type === "NodePort" ?
|
|
70
|
+
{ nodePort: portConfig.nodePort !== undefined ? portConfig.nodePort : existingPort?.nodePort } : {})
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const updatedService = {
|
|
75
|
+
...existingService,
|
|
76
|
+
spec: {
|
|
77
|
+
...existingService.spec,
|
|
78
|
+
type: params.type || existingService.spec.type,
|
|
79
|
+
selector: params.selector || existingService.spec.selector,
|
|
80
|
+
ports: servicePorts || existingService.spec.ports,
|
|
81
|
+
clusterIP: existingService.spec.clusterIP,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
const { body } = await k8sManager
|
|
86
|
+
.getCoreApi()
|
|
87
|
+
.replaceNamespacedService(params.name, params.namespace, updatedService);
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: JSON.stringify({
|
|
93
|
+
message: "Service updated successfully",
|
|
94
|
+
service: {
|
|
95
|
+
name: body.metadata?.name,
|
|
96
|
+
namespace: body.metadata?.namespace,
|
|
97
|
+
type: body.spec?.type,
|
|
98
|
+
clusterIP: body.spec?.clusterIP,
|
|
99
|
+
ports: body.spec?.ports,
|
|
100
|
+
},
|
|
101
|
+
}, null, 2),
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error("Service update error:", {
|
|
108
|
+
status: error.response?.statusCode,
|
|
109
|
+
message: error.response?.body?.message || error.message,
|
|
110
|
+
details: error.response?.body,
|
|
111
|
+
});
|
|
112
|
+
if (error instanceof McpError)
|
|
113
|
+
throw error;
|
|
114
|
+
// Handle specific Kubernetes API errors
|
|
115
|
+
if (error.response?.body?.message) {
|
|
116
|
+
if (error.response.body.message.includes("field is immutable")) {
|
|
117
|
+
throw new McpError(ErrorCode.InvalidRequest, `Update failed: Attempted to modify immutable field. ${error.response.body.message}`);
|
|
118
|
+
}
|
|
119
|
+
if (error.response.statusCode === 422) {
|
|
120
|
+
throw new McpError(ErrorCode.InvalidRequest, `Invalid service configuration: ${error.response.body.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
throw new McpError(ErrorCode.InternalError, `Failed to update service: ${error.response?.body?.message || error.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|