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