mcp-server-kubernetes 0.2.5 → 0.3.1

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
@@ -6,6 +6,8 @@ https://github.com/user-attachments/assets/f25f8f4e-4d04-479b-9ae0-5dac452dd2ed
6
6
 
7
7
  <a href="https://glama.ai/mcp/servers/w71ieamqrt"><img width="380" height="200" src="https://glama.ai/mcp/servers/w71ieamqrt/badge" /></a>
8
8
 
9
+ [![smithery badge](https://smithery.ai/badge/mcp-server-kubernetes)](https://smithery.ai/server/mcp-server-kubernetes)
10
+
9
11
  ## Usage with Claude Desktop
10
12
 
11
13
  ```json
@@ -30,6 +32,28 @@ You can verify your connection by asking Claude to list your pods or create a te
30
32
 
31
33
  If you have errors open up a standard terminal and run `kubectl get pods` to see if you can connect to your cluster without credentials issues.
32
34
 
35
+ ## Usage with mcp-chat
36
+
37
+ [mcp-chat](https://github.com/Flux159/mcp-chat) is a CLI chat client for MCP servers. You can use it to interact with the Kubernetes server.
38
+
39
+ ```shell
40
+ npx mcp-chat --server "npx mcp-server-kubernetes"
41
+ ```
42
+
43
+ Alternatively, pass it your existing Claude Desktop configuration file from above (Linux should pass the correct path to config):
44
+
45
+ Mac:
46
+
47
+ ```shell
48
+ npx mcp-chat --config "~/Library/Application Support/Claude/claude_desktop_config.json"
49
+ ```
50
+
51
+ Windows:
52
+
53
+ ```shell
54
+ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
55
+ ```
56
+
33
57
  ## Features
34
58
 
35
59
  - [x] Connect to a Kubernetes cluster
@@ -49,6 +73,8 @@ If you have errors open up a standard terminal and run `kubectl get pods` to see
49
73
  - Support for namespaces
50
74
  - Support for version specification
51
75
  - Support for custom repositories
76
+ - [x] kubectl explain and kubectl api-resources support
77
+ - [x] Get Kubernetes events from the cluster
52
78
  - [ ] Port forward to a pod
53
79
  - [ ] Choose namespace for next commands (memory)
54
80
 
@@ -87,6 +113,12 @@ npx @modelcontextprotocol/inspector node build/index.js
87
113
  # Follow further instructions on terminal for Inspector link
88
114
  ```
89
115
 
116
+ 5. Local testing with [mcp-chat](https://github.com/Flux159/mcp-chat)
117
+
118
+ ```bash
119
+ npm run chat
120
+ ```
121
+
90
122
  ### Project Structure
91
123
 
92
124
  ```
package/dist/index.js CHANGED
@@ -4,12 +4,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { listPods, listPodsSchema } from "./tools/list_pods.js";
5
5
  import { listNodes, listNodesSchema } from "./tools/list_nodes.js";
6
6
  import { listServices, listServicesSchema } from "./tools/list_services.js";
7
- import { listDeployments, listDeploymentsSchema } from "./tools/list_deployments.js";
7
+ import { listDeployments, listDeploymentsSchema, } from "./tools/list_deployments.js";
8
8
  import { installHelmChart, installHelmChartSchema, upgradeHelmChart, upgradeHelmChartSchema, uninstallHelmChart, uninstallHelmChartSchema, } from "./tools/helm-operations.js";
9
+ import { explainResource, explainResourceSchema, listApiResources, listApiResourcesSchema, } from "./tools/kubectl-operations.js";
9
10
  import { createPod, createPodSchema } from "./tools/create_pod.js";
10
11
  import { deletePod, deletePodSchema } from "./tools/delete_pod.js";
11
12
  import { describePod, describePodSchema } from "./tools/describe_pod.js";
12
13
  import { getLogs, getLogsSchema } from "./tools/get_logs.js";
14
+ import { getEvents, getEventsSchema } from "./tools/get_events.js";
13
15
  import { getResourceHandlers } from "./resources/handlers.js";
14
16
  import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
15
17
  import { KubernetesManager } from "./types.js";
@@ -26,20 +28,23 @@ const server = new Server({
26
28
  server.setRequestHandler(ListToolsRequestSchema, async () => {
27
29
  return {
28
30
  tools: [
29
- listPodsSchema,
30
- listDeploymentsSchema,
31
- listServicesSchema,
32
- listNamespacesSchema,
33
- createPodSchema,
31
+ cleanupSchema,
34
32
  createDeploymentSchema,
33
+ createPodSchema,
35
34
  deletePodSchema,
36
35
  describePodSchema,
37
- cleanupSchema,
38
- listNodesSchema,
36
+ explainResourceSchema,
37
+ getEventsSchema,
39
38
  getLogsSchema,
40
39
  installHelmChartSchema,
41
- upgradeHelmChartSchema,
40
+ listApiResourcesSchema,
41
+ listDeploymentsSchema,
42
+ listNamespacesSchema,
43
+ listNodesSchema,
44
+ listPodsSchema,
45
+ listServicesSchema,
42
46
  uninstallHelmChartSchema,
47
+ upgradeHelmChartSchema,
43
48
  ],
44
49
  };
45
50
  });
@@ -47,27 +52,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
52
  try {
48
53
  const { name, arguments: input = {} } = request.params;
49
54
  switch (name) {
50
- case "list_pods": {
51
- return await listPods(k8sManager, input);
52
- }
53
- case "list_deployments": {
54
- return await listDeployments(k8sManager, input);
55
- }
56
- case "list_services": {
57
- return await listServices(k8sManager, input);
58
- }
59
- case "list_namespaces": {
60
- const { body } = await k8sManager.getCoreApi().listNamespace();
61
- const namespaces = body.items.map((ns) => ({
62
- name: ns.metadata?.name || "",
63
- status: ns.status?.phase || "",
64
- createdAt: ns.metadata?.creationTimestamp,
65
- }));
55
+ case "cleanup": {
56
+ await k8sManager.cleanup();
66
57
  return {
67
58
  content: [
68
59
  {
69
60
  type: "text",
70
- text: JSON.stringify({ namespaces }, null, 2),
61
+ text: JSON.stringify({
62
+ success: true,
63
+ }, null, 2),
71
64
  },
72
65
  ],
73
66
  };
@@ -81,15 +74,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
74
  case "describe_pod": {
82
75
  return await describePod(k8sManager, input);
83
76
  }
84
- case "cleanup": {
85
- await k8sManager.cleanup();
77
+ case "explain_resource": {
78
+ return await explainResource(input);
79
+ }
80
+ case "get_events": {
81
+ return await getEvents(k8sManager, input);
82
+ }
83
+ case "get_logs": {
84
+ return await getLogs(k8sManager, input);
85
+ }
86
+ case "install_helm_chart": {
87
+ return await installHelmChart(input);
88
+ }
89
+ case "list_api_resources": {
90
+ return await listApiResources(input);
91
+ }
92
+ case "list_deployments": {
93
+ return await listDeployments(k8sManager, input);
94
+ }
95
+ case "list_namespaces": {
96
+ const { body } = await k8sManager.getCoreApi().listNamespace();
97
+ const namespaces = body.items.map((ns) => ({
98
+ name: ns.metadata?.name || "",
99
+ status: ns.status?.phase || "",
100
+ createdAt: ns.metadata?.creationTimestamp,
101
+ }));
86
102
  return {
87
103
  content: [
88
104
  {
89
105
  type: "text",
90
- text: JSON.stringify({
91
- success: true,
92
- }, null, 2),
106
+ text: JSON.stringify({ namespaces }, null, 2),
93
107
  },
94
108
  ],
95
109
  };
@@ -97,18 +111,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
97
111
  case "list_nodes": {
98
112
  return await listNodes(k8sManager);
99
113
  }
100
- case "get_logs": {
101
- return await getLogs(k8sManager, input);
102
- }
103
- case "install_helm_chart": {
104
- return await installHelmChart(input);
114
+ case "list_pods": {
115
+ return await listPods(k8sManager, input);
105
116
  }
106
- case "upgrade_helm_chart": {
107
- return await upgradeHelmChart(input);
117
+ case "list_services": {
118
+ return await listServices(k8sManager, input);
108
119
  }
109
120
  case "uninstall_helm_chart": {
110
121
  return await uninstallHelmChart(input);
111
122
  }
123
+ case "upgrade_helm_chart": {
124
+ return await upgradeHelmChart(input);
125
+ }
112
126
  default:
113
127
  throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
114
128
  }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ export declare const KubectlResponseSchema: z.ZodObject<{
3
+ content: z.ZodArray<z.ZodObject<{
4
+ type: z.ZodLiteral<"text">;
5
+ text: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ type: "text";
8
+ text: string;
9
+ }, {
10
+ type: "text";
11
+ text: string;
12
+ }>, "many">;
13
+ }, "strip", z.ZodTypeAny, {
14
+ content: {
15
+ type: "text";
16
+ text: string;
17
+ }[];
18
+ }, {
19
+ content: {
20
+ type: "text";
21
+ text: string;
22
+ }[];
23
+ }>;
24
+ export interface ExplainResourceParams {
25
+ resource: string;
26
+ apiVersion?: string;
27
+ recursive?: boolean;
28
+ output?: "plaintext" | "plaintext-openapiv2";
29
+ }
30
+ export interface ListApiResourcesParams {
31
+ apiGroup?: string;
32
+ namespaced?: boolean;
33
+ verbs?: string[];
34
+ output?: "wide" | "name" | "no-headers";
35
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export const KubectlResponseSchema = z.object({
3
+ content: z.array(z.object({
4
+ type: z.literal("text"),
5
+ text: z.string(),
6
+ })),
7
+ });
@@ -219,3 +219,25 @@ export declare const GetLogsResponseSchema: z.ZodObject<{
219
219
  text: string;
220
220
  }[];
221
221
  }>;
222
+ export declare const GetEventsResponseSchema: z.ZodObject<{
223
+ content: z.ZodArray<z.ZodObject<{
224
+ type: z.ZodLiteral<"text">;
225
+ text: z.ZodString;
226
+ }, "strip", z.ZodTypeAny, {
227
+ type: "text";
228
+ text: string;
229
+ }, {
230
+ type: "text";
231
+ text: string;
232
+ }>, "many">;
233
+ }, "strip", z.ZodTypeAny, {
234
+ content: {
235
+ type: "text";
236
+ text: string;
237
+ }[];
238
+ }, {
239
+ content: {
240
+ type: "text";
241
+ text: string;
242
+ }[];
243
+ }>;
@@ -34,3 +34,6 @@ export const ListNodesResponseSchema = z.object({
34
34
  export const GetLogsResponseSchema = z.object({
35
35
  content: z.array(ToolResponseContent),
36
36
  });
37
+ export const GetEventsResponseSchema = z.object({
38
+ content: z.array(ToolResponseContent),
39
+ });
@@ -0,0 +1,28 @@
1
+ import { KubernetesManager } from "../types.js";
2
+ export declare const getEventsSchema: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: string;
7
+ properties: {
8
+ namespace: {
9
+ type: string;
10
+ description: string;
11
+ };
12
+ fieldSelector: {
13
+ type: string;
14
+ description: string;
15
+ };
16
+ };
17
+ required: never[];
18
+ };
19
+ };
20
+ export declare function getEvents(k8sManager: KubernetesManager, params: {
21
+ namespace?: string;
22
+ fieldSelector?: string;
23
+ }): Promise<{
24
+ content: {
25
+ type: string;
26
+ text: string;
27
+ }[];
28
+ }>;
@@ -0,0 +1,66 @@
1
+ export const getEventsSchema = {
2
+ name: "get_events",
3
+ description: "Get Kubernetes events from the cluster",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ namespace: {
8
+ type: "string",
9
+ description: "Namespace to get events from. If not specified, gets events from all namespaces",
10
+ },
11
+ fieldSelector: {
12
+ type: "string",
13
+ description: "Field selector to filter events",
14
+ },
15
+ },
16
+ required: [],
17
+ },
18
+ };
19
+ export async function getEvents(k8sManager, params) {
20
+ const { namespace, fieldSelector } = params;
21
+ const api = k8sManager.getCoreApi();
22
+ let events;
23
+ if (namespace) {
24
+ const { body } = await api.listNamespacedEvent(namespace, undefined, // pretty
25
+ undefined, // allowWatchBookmarks
26
+ undefined, // _continue
27
+ undefined, // fieldSelector
28
+ fieldSelector // fieldSelector
29
+ );
30
+ events = body;
31
+ }
32
+ else {
33
+ const { body } = await api.listEventForAllNamespaces(undefined, // allowWatchBookmarks
34
+ undefined, // _continue
35
+ fieldSelector, // fieldSelector
36
+ undefined, // labelSelector
37
+ undefined, // limit
38
+ undefined, // pretty
39
+ undefined, // resourceVersion
40
+ undefined, // resourceVersionMatch
41
+ undefined // timeoutSeconds
42
+ );
43
+ events = body;
44
+ }
45
+ const formattedEvents = events.items.map((event) => ({
46
+ type: event.type || "",
47
+ reason: event.reason || "",
48
+ message: event.message || "",
49
+ involvedObject: {
50
+ kind: event.involvedObject.kind || "",
51
+ name: event.involvedObject.name || "",
52
+ namespace: event.involvedObject.namespace || "",
53
+ },
54
+ firstTimestamp: event.firstTimestamp || "",
55
+ lastTimestamp: event.lastTimestamp || "",
56
+ count: event.count || 0,
57
+ }));
58
+ return {
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: JSON.stringify({ events: formattedEvents }, null, 2),
63
+ },
64
+ ],
65
+ };
66
+ }
@@ -0,0 +1,72 @@
1
+ import { ExplainResourceParams, ListApiResourcesParams } from "../models/kubectl-models.js";
2
+ export declare const explainResourceSchema: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: string;
7
+ properties: {
8
+ resource: {
9
+ type: string;
10
+ description: string;
11
+ };
12
+ apiVersion: {
13
+ type: string;
14
+ description: string;
15
+ };
16
+ recursive: {
17
+ type: string;
18
+ description: string;
19
+ default: boolean;
20
+ };
21
+ output: {
22
+ type: string;
23
+ description: string;
24
+ enum: string[];
25
+ default: string;
26
+ };
27
+ };
28
+ required: string[];
29
+ };
30
+ };
31
+ export declare const listApiResourcesSchema: {
32
+ name: string;
33
+ description: string;
34
+ inputSchema: {
35
+ type: string;
36
+ properties: {
37
+ apiGroup: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ namespaced: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ verbs: {
46
+ type: string;
47
+ items: {
48
+ type: string;
49
+ };
50
+ description: string;
51
+ };
52
+ output: {
53
+ type: string;
54
+ description: string;
55
+ enum: string[];
56
+ default: string;
57
+ };
58
+ };
59
+ };
60
+ };
61
+ export declare function explainResource(params: ExplainResourceParams): Promise<{
62
+ content: {
63
+ type: string;
64
+ text: string;
65
+ }[];
66
+ }>;
67
+ export declare function listApiResources(params: ListApiResourcesParams): Promise<{
68
+ content: {
69
+ type: string;
70
+ text: string;
71
+ }[];
72
+ }>;
@@ -0,0 +1,124 @@
1
+ import { execSync } from "child_process";
2
+ export const explainResourceSchema = {
3
+ name: "explain_resource",
4
+ description: "Get documentation for a Kubernetes resource or field",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ resource: {
9
+ type: "string",
10
+ description: "Resource name or field path (e.g. 'pods' or 'pods.spec.containers')",
11
+ },
12
+ apiVersion: {
13
+ type: "string",
14
+ description: "API version to use (e.g. 'apps/v1')",
15
+ },
16
+ recursive: {
17
+ type: "boolean",
18
+ description: "Print the fields of fields recursively",
19
+ default: false,
20
+ },
21
+ output: {
22
+ type: "string",
23
+ description: "Output format (plaintext or plaintext-openapiv2)",
24
+ enum: ["plaintext", "plaintext-openapiv2"],
25
+ default: "plaintext",
26
+ },
27
+ },
28
+ required: ["resource"],
29
+ },
30
+ };
31
+ export const listApiResourcesSchema = {
32
+ name: "list_api_resources",
33
+ description: "List the API resources available in the cluster",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {
37
+ apiGroup: {
38
+ type: "string",
39
+ description: "API group to filter by",
40
+ },
41
+ namespaced: {
42
+ type: "boolean",
43
+ description: "If true, only show namespaced resources",
44
+ },
45
+ verbs: {
46
+ type: "array",
47
+ items: {
48
+ type: "string",
49
+ },
50
+ description: "List of verbs to filter by",
51
+ },
52
+ output: {
53
+ type: "string",
54
+ description: "Output format (wide, name, or no-headers)",
55
+ enum: ["wide", "name", "no-headers"],
56
+ default: "wide",
57
+ },
58
+ },
59
+ },
60
+ };
61
+ const executeKubectlCommand = (command) => {
62
+ try {
63
+ return execSync(command, { encoding: "utf8" });
64
+ }
65
+ catch (error) {
66
+ throw new Error(`Kubectl command failed: ${error.message}`);
67
+ }
68
+ };
69
+ export async function explainResource(params) {
70
+ try {
71
+ let command = "kubectl explain";
72
+ if (params.apiVersion) {
73
+ command += ` --api-version=${params.apiVersion}`;
74
+ }
75
+ if (params.recursive) {
76
+ command += " --recursive";
77
+ }
78
+ if (params.output) {
79
+ command += ` --output=${params.output}`;
80
+ }
81
+ command += ` ${params.resource}`;
82
+ const result = executeKubectlCommand(command);
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: result,
88
+ },
89
+ ],
90
+ };
91
+ }
92
+ catch (error) {
93
+ throw new Error(`Failed to explain resource: ${error.message}`);
94
+ }
95
+ }
96
+ export async function listApiResources(params) {
97
+ try {
98
+ let command = "kubectl api-resources";
99
+ if (params.apiGroup) {
100
+ command += ` --api-group=${params.apiGroup}`;
101
+ }
102
+ if (params.namespaced !== undefined) {
103
+ command += ` --namespaced=${params.namespaced}`;
104
+ }
105
+ if (params.verbs && params.verbs.length > 0) {
106
+ command += ` --verbs=${params.verbs.join(",")}`;
107
+ }
108
+ if (params.output) {
109
+ command += ` -o ${params.output}`;
110
+ }
111
+ const result = executeKubectlCommand(command);
112
+ return {
113
+ content: [
114
+ {
115
+ type: "text",
116
+ text: result,
117
+ },
118
+ ],
119
+ };
120
+ }
121
+ catch (error) {
122
+ throw new Error(`Failed to list API resources: ${error.message}`);
123
+ }
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-kubernetes",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "MCP server for interacting with Kubernetes clusters via kubectl",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,7 +21,8 @@
21
21
  "start": "node dist/index.js",
22
22
  "test": "vitest run",
23
23
  "prepublishOnly": "npm run build",
24
- "dockerbuild": "docker buildx build -t flux159/mcp-server-kubernetes --platform linux/amd64,linux/arm64 --push ."
24
+ "dockerbuild": "docker buildx build -t flux159/mcp-server-kubernetes --platform linux/amd64,linux/arm64 --push .",
25
+ "chat": "npx mcp-chat --server \"./dist/index.js\""
25
26
  },
26
27
  "keywords": [
27
28
  "mcp",