mcp-server-kubernetes 1.4.0 → 1.5.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 -2
- package/dist/index.js +32 -11
- package/dist/models/response-schemas.d.ts +22 -0
- package/dist/models/response-schemas.js +3 -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/update_service.d.ts +72 -0
- package/dist/tools/update_service.js +125 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,10 +67,11 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
67
67
|
|
|
68
68
|
- [x] Connect to a Kubernetes cluster
|
|
69
69
|
- [x] List all pods, services, deployments, nodes
|
|
70
|
+
- [x] 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
|
|
74
75
|
- [x] Get logs from a pod for debugging (supports pods, deployments, jobs, and label selectors)
|
|
75
76
|
- [x] Support Helm v3 for installing charts
|
|
76
77
|
- Install charts with custom values
|
|
@@ -87,6 +88,8 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
87
88
|
|
|
88
89
|
## Local Development
|
|
89
90
|
|
|
91
|
+
Make sure that you have [bun installed](https://bun.sh/docs/installation). Clone the repo & install dependencies:
|
|
92
|
+
|
|
90
93
|
```bash
|
|
91
94
|
git clone https://github.com/Flux159/mcp-server-kubernetes.git
|
|
92
95
|
cd mcp-server-kubernetes
|
|
@@ -136,7 +139,7 @@ npx @modelcontextprotocol/inspector node dist/index.js
|
|
|
136
139
|
6. Local testing with [mcp-chat](https://github.com/Flux159/mcp-chat)
|
|
137
140
|
|
|
138
141
|
```bash
|
|
139
|
-
|
|
142
|
+
bun run chat
|
|
140
143
|
```
|
|
141
144
|
|
|
142
145
|
## 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,25 @@ 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 { createService, createServiceSchema } from "./tools/create_service.js";
|
|
37
|
+
import { updateDeployment, updateDeploymentSchema, } from "./tools/update_deployment.js";
|
|
38
|
+
import { createConfigMap, CreateConfigMapSchema, } from "./tools/create_configmap.js";
|
|
39
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";
|
|
40
|
+
import { getCurrentContext, getCurrentContextSchema, } from "./tools/get_current_context.js";
|
|
41
|
+
import { setCurrentContext, setCurrentContextSchema, } from "./tools/set_current_context.js";
|
|
42
|
+
import { createService, createServiceSchema } from "./tools/create_service.js";
|
|
43
|
+
import { describeService, describeServiceSchema, } from "./tools/describe_service.js";
|
|
44
|
+
import { updateService, updateServiceSchema } from "./tools/update_service.js";
|
|
45
|
+
import { deleteService, deleteServiceSchema } from "./tools/delete_service.js";
|
|
42
46
|
// Check if non-destructive tools only mode is enabled
|
|
43
|
-
const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS ===
|
|
47
|
+
const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === "true";
|
|
44
48
|
const k8sManager = new KubernetesManager();
|
|
45
49
|
const server = new Server({
|
|
46
50
|
name: serverConfig.name,
|
|
@@ -49,6 +53,7 @@ const server = new Server({
|
|
|
49
53
|
// Define destructive tools (delete and uninstall operations)
|
|
50
54
|
const destructiveTools = [
|
|
51
55
|
deletePodSchema,
|
|
56
|
+
deleteServiceSchema,
|
|
52
57
|
deleteDeploymentSchema,
|
|
53
58
|
deleteNamespaceSchema,
|
|
54
59
|
uninstallHelmChartSchema,
|
|
@@ -68,9 +73,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
68
73
|
deletePodSchema,
|
|
69
74
|
deleteDeploymentSchema,
|
|
70
75
|
deleteNamespaceSchema,
|
|
76
|
+
deleteServiceSchema,
|
|
71
77
|
describeCronJobSchema,
|
|
72
78
|
describePodSchema,
|
|
79
|
+
describeNodeSchema,
|
|
73
80
|
describeDeploymentSchema,
|
|
81
|
+
describeServiceSchema,
|
|
74
82
|
explainResourceSchema,
|
|
75
83
|
getEventsSchema,
|
|
76
84
|
getJobLogsSchema,
|
|
@@ -95,10 +103,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
95
103
|
scaleDeploymentSchema,
|
|
96
104
|
DeleteCronJobSchema,
|
|
97
105
|
CreateConfigMapSchema,
|
|
106
|
+
updateServiceSchema,
|
|
98
107
|
];
|
|
99
108
|
// Filter out destructive tools if ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS is set to 'true'
|
|
100
109
|
const tools = nonDestructiveTools
|
|
101
|
-
? allTools.filter(tool => !destructiveTools.some(dt => dt.name === tool.name))
|
|
110
|
+
? allTools.filter((tool) => !destructiveTools.some((dt) => dt.name === tool.name))
|
|
102
111
|
: allTools;
|
|
103
112
|
return { tools };
|
|
104
113
|
});
|
|
@@ -137,6 +146,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
137
146
|
case "describe_pod": {
|
|
138
147
|
return await describePod(k8sManager, input);
|
|
139
148
|
}
|
|
149
|
+
case "describe_node": {
|
|
150
|
+
return await describeNode(k8sManager, input);
|
|
151
|
+
}
|
|
140
152
|
case "explain_resource": {
|
|
141
153
|
return await explainResource(input);
|
|
142
154
|
}
|
|
@@ -237,6 +249,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
237
249
|
case "create_service": {
|
|
238
250
|
return await createService(k8sManager, input);
|
|
239
251
|
}
|
|
252
|
+
case "update_service": {
|
|
253
|
+
return await updateService(k8sManager, input);
|
|
254
|
+
}
|
|
255
|
+
case "delete_service": {
|
|
256
|
+
return await deleteService(k8sManager, input);
|
|
257
|
+
}
|
|
258
|
+
case "describe_service": {
|
|
259
|
+
return await describeService(k8sManager, input);
|
|
260
|
+
}
|
|
240
261
|
default:
|
|
241
262
|
throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
|
|
242
263
|
}
|
|
@@ -571,3 +571,25 @@ export declare const SetCurrentContextResponseSchema: z.ZodObject<{
|
|
|
571
571
|
text: string;
|
|
572
572
|
}[];
|
|
573
573
|
}>;
|
|
574
|
+
export declare const DescribeNodeResponseSchema: z.ZodObject<{
|
|
575
|
+
content: z.ZodArray<z.ZodObject<{
|
|
576
|
+
type: z.ZodLiteral<"text">;
|
|
577
|
+
text: z.ZodString;
|
|
578
|
+
}, "strip", z.ZodTypeAny, {
|
|
579
|
+
type: "text";
|
|
580
|
+
text: string;
|
|
581
|
+
}, {
|
|
582
|
+
type: "text";
|
|
583
|
+
text: string;
|
|
584
|
+
}>, "many">;
|
|
585
|
+
}, "strip", z.ZodTypeAny, {
|
|
586
|
+
content: {
|
|
587
|
+
type: "text";
|
|
588
|
+
text: string;
|
|
589
|
+
}[];
|
|
590
|
+
}, {
|
|
591
|
+
content: {
|
|
592
|
+
type: "text";
|
|
593
|
+
text: string;
|
|
594
|
+
}[];
|
|
595
|
+
}>;
|
|
@@ -94,3 +94,6 @@ export const GetCurrentContextResponseSchema = z.object({
|
|
|
94
94
|
export const SetCurrentContextResponseSchema = z.object({
|
|
95
95
|
content: z.array(ToolResponseContent),
|
|
96
96
|
});
|
|
97
|
+
export const DescribeNodeResponseSchema = z.object({
|
|
98
|
+
content: z.array(ToolResponseContent),
|
|
99
|
+
});
|
|
@@ -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,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
|
+
}
|