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 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 services
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
- npm run chat
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 === 'true';
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",