mcp-server-kubernetes 2.4.2 → 2.4.3
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 +6 -5
- package/dist/config/container-templates.d.ts +2 -2
- package/dist/index.d.ts +8 -37
- package/dist/index.js +11 -5
- package/dist/models/response-schemas.d.ts +22 -0
- package/dist/models/response-schemas.js +3 -0
- package/dist/models/tool-models.d.ts +1 -0
- package/dist/models/tool-models.js +1 -0
- package/dist/prompts/index.js +6 -6
- package/dist/tools/exec_in_pod.d.ts +78 -0
- package/dist/tools/exec_in_pod.js +161 -0
- package/dist/tools/ping.d.ts +10 -0
- package/dist/tools/ping.js +12 -0
- package/package.json +1 -1
- package/dist/tools/kubectl-list.d.ts +0 -59
- package/dist/tools/kubectl-list.js +0 -187
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
71
71
|
- [x] Unified kubectl API for managing resources
|
|
72
72
|
- Get or list resources with `kubectl_get`
|
|
73
73
|
- Describe resources with `kubectl_describe`
|
|
74
|
-
- List resources with `
|
|
74
|
+
- List resources with `kubectl_get`
|
|
75
75
|
- Create resources with `kubectl_create`
|
|
76
76
|
- Apply YAML manifests with `kubectl_apply`
|
|
77
77
|
- Delete resources with `kubectl_delete`
|
|
@@ -83,21 +83,22 @@ npx mcp-chat --config "%APPDATA%\Claude\claude_desktop_config.json"
|
|
|
83
83
|
- Update field(s) of a resource with `kubectl_patch`
|
|
84
84
|
- Manage deployment rollouts with `kubectl_rollout`
|
|
85
85
|
- Execute any kubectl command with `kubectl_generic`
|
|
86
|
+
- Verify connection with `ping`
|
|
86
87
|
- [x] Advanced operations
|
|
87
88
|
- Scale deployments with `kubectl_scale` (replaces legacy `scale_deployment`)
|
|
88
89
|
- Port forward to pods and services with `port_forward`
|
|
89
90
|
- Run Helm operations
|
|
90
91
|
- Install, upgrade, and uninstall charts
|
|
91
92
|
- Support for custom values, repositories, and versions
|
|
92
|
-
- [x] Troubleshooting Prompt (`k8s-
|
|
93
|
+
- [x] Troubleshooting Prompt (`k8s-diagnose`)
|
|
93
94
|
- Guides through a systematic Kubernetes troubleshooting flow for pods based on a keyword and optional namespace.
|
|
94
95
|
- [x] Non-destructive mode for read and create/update-only access to clusters
|
|
95
96
|
|
|
96
97
|
## Prompts
|
|
97
98
|
|
|
98
|
-
The MCP Kubernetes server includes specialized prompts to assist with common operations.
|
|
99
|
+
The MCP Kubernetes server includes specialized prompts to assist with common diagnostic operations.
|
|
99
100
|
|
|
100
|
-
### k8s-
|
|
101
|
+
### k8s-diagnose Prompt
|
|
101
102
|
|
|
102
103
|
This prompt provides a systematic troubleshooting flow for Kubernetes pods. It accepts a `keyword` to identify relevant pods and an optional `namespace` to narrow the search.
|
|
103
104
|
The prompt's output will guide you through an autonomous troubleshooting flow, providing instructions for identifying issues, collecting evidence, and suggesting remediation steps.
|
|
@@ -192,7 +193,7 @@ For Claude Desktop configuration with non-destructive mode:
|
|
|
192
193
|
|
|
193
194
|
All read-only and resource creation/update operations remain available:
|
|
194
195
|
|
|
195
|
-
- Resource Information: `kubectl_get`, `kubectl_describe`, `
|
|
196
|
+
- Resource Information: `kubectl_get`, `kubectl_describe`, `kubectl_logs`, `explain_resource`, `list_api_resources`
|
|
196
197
|
- Resource Creation/Modification: `kubectl_apply`, `kubectl_create`, `kubectl_scale`, `kubectl_patch`, `kubectl_rollout`
|
|
197
198
|
- Helm Operations: `install_helm_chart`, `upgrade_helm_chart`
|
|
198
199
|
- Connectivity: `port_forward`, `stop_port_forward`
|
|
@@ -61,8 +61,8 @@ export declare const CustomContainerConfig: z.ZodObject<{
|
|
|
61
61
|
limits?: Record<string, string> | undefined;
|
|
62
62
|
requests?: Record<string, string> | undefined;
|
|
63
63
|
} | undefined;
|
|
64
|
-
args?: string[] | undefined;
|
|
65
64
|
command?: string[] | undefined;
|
|
65
|
+
args?: string[] | undefined;
|
|
66
66
|
ports?: {
|
|
67
67
|
containerPort: number;
|
|
68
68
|
name?: string | undefined;
|
|
@@ -84,8 +84,8 @@ export declare const CustomContainerConfig: z.ZodObject<{
|
|
|
84
84
|
limits?: Record<string, string> | undefined;
|
|
85
85
|
requests?: Record<string, string> | undefined;
|
|
86
86
|
} | undefined;
|
|
87
|
-
args?: string[] | undefined;
|
|
88
87
|
command?: string[] | undefined;
|
|
88
|
+
args?: string[] | undefined;
|
|
89
89
|
ports?: {
|
|
90
90
|
containerPort: number;
|
|
91
91
|
name?: string | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -296,43 +296,6 @@ declare const allTools: ({
|
|
|
296
296
|
};
|
|
297
297
|
readonly required: readonly ["resourceType", "name"];
|
|
298
298
|
};
|
|
299
|
-
} | {
|
|
300
|
-
readonly name: "kubectl_list";
|
|
301
|
-
readonly description: "List Kubernetes resources by resource type and optionally namespace";
|
|
302
|
-
readonly inputSchema: {
|
|
303
|
-
readonly type: "object";
|
|
304
|
-
readonly properties: {
|
|
305
|
-
readonly resourceType: {
|
|
306
|
-
readonly type: "string";
|
|
307
|
-
readonly description: "Type of resource to list (e.g., pods, deployments, services, configmaps, etc.)";
|
|
308
|
-
};
|
|
309
|
-
readonly namespace: {
|
|
310
|
-
readonly type: "string";
|
|
311
|
-
readonly description: "Namespace of the resources (optional - defaults to 'default' for namespaced resources)";
|
|
312
|
-
readonly default: "default";
|
|
313
|
-
};
|
|
314
|
-
readonly output: {
|
|
315
|
-
readonly type: "string";
|
|
316
|
-
readonly enum: readonly ["json", "yaml", "wide", "name", "custom", "formatted"];
|
|
317
|
-
readonly description: "Output format - 'formatted' uses a resource-specific format with key information";
|
|
318
|
-
readonly default: "formatted";
|
|
319
|
-
};
|
|
320
|
-
readonly allNamespaces: {
|
|
321
|
-
readonly type: "boolean";
|
|
322
|
-
readonly description: "If true, list resources across all namespaces";
|
|
323
|
-
readonly default: false;
|
|
324
|
-
};
|
|
325
|
-
readonly labelSelector: {
|
|
326
|
-
readonly type: "string";
|
|
327
|
-
readonly description: "Filter resources by label selector (e.g. 'app=nginx')";
|
|
328
|
-
};
|
|
329
|
-
readonly fieldSelector: {
|
|
330
|
-
readonly type: "string";
|
|
331
|
-
readonly description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')";
|
|
332
|
-
};
|
|
333
|
-
};
|
|
334
|
-
readonly required: readonly ["resourceType", "namespace"];
|
|
335
|
-
};
|
|
336
299
|
} | {
|
|
337
300
|
readonly name: "kubectl_apply";
|
|
338
301
|
readonly description: "Apply a Kubernetes YAML manifest from a string or file";
|
|
@@ -591,5 +554,13 @@ declare const allTools: ({
|
|
|
591
554
|
};
|
|
592
555
|
readonly required: readonly ["resourceType", "name", "namespace"];
|
|
593
556
|
};
|
|
557
|
+
} | {
|
|
558
|
+
name: string;
|
|
559
|
+
description: string;
|
|
560
|
+
inputSchema: {
|
|
561
|
+
type: string;
|
|
562
|
+
properties: {};
|
|
563
|
+
required: never[];
|
|
564
|
+
};
|
|
594
565
|
})[];
|
|
595
566
|
export { allTools, destructiveTools };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { installHelmChart, installHelmChartSchema, upgradeHelmChart, upgradeHelmChartSchema, uninstallHelmChart, uninstallHelmChartSchema, } from "./tools/helm-operations.js";
|
|
5
5
|
import { explainResource, explainResourceSchema, listApiResources, listApiResourcesSchema, } from "./tools/kubectl-operations.js";
|
|
6
|
+
import { execInPod, execInPodSchema } from "./tools/exec_in_pod.js";
|
|
6
7
|
import { getResourceHandlers } from "./resources/handlers.js";
|
|
7
8
|
import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
8
9
|
import { KubernetesManager } from "./types.js";
|
|
@@ -14,7 +15,6 @@ import { kubectlScale, kubectlScaleSchema } from "./tools/kubectl-scale.js";
|
|
|
14
15
|
import { kubectlContext, kubectlContextSchema, } from "./tools/kubectl-context.js";
|
|
15
16
|
import { kubectlGet, kubectlGetSchema } from "./tools/kubectl-get.js";
|
|
16
17
|
import { kubectlDescribe, kubectlDescribeSchema, } from "./tools/kubectl-describe.js";
|
|
17
|
-
import { kubectlList, kubectlListSchema } from "./tools/kubectl-list.js";
|
|
18
18
|
import { kubectlApply, kubectlApplySchema } from "./tools/kubectl-apply.js";
|
|
19
19
|
import { kubectlDelete, kubectlDeleteSchema } from "./tools/kubectl-delete.js";
|
|
20
20
|
import { kubectlCreate, kubectlCreateSchema } from "./tools/kubectl-create.js";
|
|
@@ -23,6 +23,7 @@ import { kubectlGeneric, kubectlGenericSchema, } from "./tools/kubectl-generic.j
|
|
|
23
23
|
import { kubectlPatch, kubectlPatchSchema } from "./tools/kubectl-patch.js";
|
|
24
24
|
import { kubectlRollout, kubectlRolloutSchema, } from "./tools/kubectl-rollout.js";
|
|
25
25
|
import { registerPromptHandlers } from "./prompts/index.js";
|
|
26
|
+
import { ping, pingSchema } from "./tools/ping.js";
|
|
26
27
|
// Check if non-destructive tools only mode is enabled
|
|
27
28
|
const nonDestructiveTools = process.env.ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS === "true";
|
|
28
29
|
// Define destructive tools (delete and uninstall operations)
|
|
@@ -39,7 +40,6 @@ const allTools = [
|
|
|
39
40
|
// Unified kubectl-style tools - these replace many specific tools
|
|
40
41
|
kubectlGetSchema,
|
|
41
42
|
kubectlDescribeSchema,
|
|
42
|
-
kubectlListSchema,
|
|
43
43
|
kubectlApplySchema,
|
|
44
44
|
kubectlDeleteSchema,
|
|
45
45
|
kubectlCreateSchema,
|
|
@@ -58,10 +58,13 @@ const allTools = [
|
|
|
58
58
|
// Port forwarding
|
|
59
59
|
PortForwardSchema,
|
|
60
60
|
StopPortForwardSchema,
|
|
61
|
+
execInPodSchema,
|
|
61
62
|
// API resource operations
|
|
62
63
|
listApiResourcesSchema,
|
|
63
64
|
// Generic kubectl command
|
|
64
65
|
kubectlGenericSchema,
|
|
66
|
+
// Ping utility
|
|
67
|
+
pingSchema,
|
|
65
68
|
];
|
|
66
69
|
const k8sManager = new KubernetesManager();
|
|
67
70
|
const server = new Server({
|
|
@@ -101,9 +104,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
101
104
|
if (name === "kubectl_describe") {
|
|
102
105
|
return await kubectlDescribe(k8sManager, input);
|
|
103
106
|
}
|
|
104
|
-
if (name === "kubectl_list") {
|
|
105
|
-
return await kubectlList(k8sManager, input);
|
|
106
|
-
}
|
|
107
107
|
if (name === "kubectl_apply") {
|
|
108
108
|
return await kubectlApply(k8sManager, input);
|
|
109
109
|
}
|
|
@@ -174,6 +174,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
174
174
|
case "kubectl_scale": {
|
|
175
175
|
return await kubectlScale(k8sManager, input);
|
|
176
176
|
}
|
|
177
|
+
case "ping": {
|
|
178
|
+
return await ping();
|
|
179
|
+
}
|
|
180
|
+
case "exec_in_pod": {
|
|
181
|
+
return await execInPod(k8sManager, input);
|
|
182
|
+
}
|
|
177
183
|
default:
|
|
178
184
|
throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`);
|
|
179
185
|
}
|
|
@@ -664,3 +664,25 @@ export declare const DescribeNodeResponseSchema: z.ZodObject<{
|
|
|
664
664
|
text: string;
|
|
665
665
|
}[];
|
|
666
666
|
}>;
|
|
667
|
+
export declare const ExecInPodResponseSchema: z.ZodObject<{
|
|
668
|
+
content: z.ZodArray<z.ZodObject<{
|
|
669
|
+
type: z.ZodLiteral<"text">;
|
|
670
|
+
text: z.ZodString;
|
|
671
|
+
}, "strip", z.ZodTypeAny, {
|
|
672
|
+
type: "text";
|
|
673
|
+
text: string;
|
|
674
|
+
}, {
|
|
675
|
+
type: "text";
|
|
676
|
+
text: string;
|
|
677
|
+
}>, "many">;
|
|
678
|
+
}, "strip", z.ZodTypeAny, {
|
|
679
|
+
content: {
|
|
680
|
+
type: "text";
|
|
681
|
+
text: string;
|
|
682
|
+
}[];
|
|
683
|
+
}, {
|
|
684
|
+
content: {
|
|
685
|
+
type: "text";
|
|
686
|
+
text: string;
|
|
687
|
+
}[];
|
|
688
|
+
}>;
|
|
@@ -116,3 +116,6 @@ export const SetCurrentContextResponseSchema = z.object({
|
|
|
116
116
|
export const DescribeNodeResponseSchema = z.object({
|
|
117
117
|
content: z.array(ToolResponseContent),
|
|
118
118
|
});
|
|
119
|
+
export const ExecInPodResponseSchema = z.object({
|
|
120
|
+
content: z.array(ToolResponseContent),
|
|
121
|
+
});
|
package/dist/prompts/index.js
CHANGED
|
@@ -5,8 +5,8 @@ export function registerPromptHandlers(server, k8sManager) {
|
|
|
5
5
|
return {
|
|
6
6
|
prompts: [
|
|
7
7
|
{
|
|
8
|
-
name: "k8s-
|
|
9
|
-
description: "
|
|
8
|
+
name: "k8s-diagnose",
|
|
9
|
+
description: "Diagnose Kubernetes Resources.",
|
|
10
10
|
arguments: [
|
|
11
11
|
{
|
|
12
12
|
name: "keyword",
|
|
@@ -27,16 +27,16 @@ export function registerPromptHandlers(server, k8sManager) {
|
|
|
27
27
|
// Register prompt handler
|
|
28
28
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
29
29
|
const { name, arguments: args } = request.params;
|
|
30
|
-
if (name === "k8s-
|
|
30
|
+
if (name === "k8s-diagnose") {
|
|
31
31
|
const keyword = args?.keyword;
|
|
32
32
|
const namespace = args?.namespace;
|
|
33
33
|
if (!keyword) {
|
|
34
|
-
throw new Error("Keyword parameter is required for k8s-
|
|
34
|
+
throw new Error("Keyword parameter is required for k8s-diagnose prompt");
|
|
35
35
|
}
|
|
36
36
|
const actualNamespace = namespace || "all";
|
|
37
|
-
const message = `
|
|
37
|
+
const message = `Diagnose Kubernetes resources (pods, nodes, etc.) containing keyword "${keyword}" in their names within namespace "${actualNamespace}" (or across all namespaces if specified) for this investigation:
|
|
38
38
|
|
|
39
|
-
**Autonomous Kubernetes
|
|
39
|
+
**Autonomous Kubernetes Diagnosis Flow**
|
|
40
40
|
|
|
41
41
|
0. **Perform Quick Health Checks / Golden Signals Analysis**
|
|
42
42
|
- Assess latency, errors, and resource utilization. If a clear issue is identified (e.g., node not ready, network partition), streamline or deprioritize subsequent detailed steps.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: exec_in_pod
|
|
3
|
+
* Execute a command in a Kubernetes pod or container and return the output.
|
|
4
|
+
* Uses the official Kubernetes client-node Exec API for native execution.
|
|
5
|
+
* Supports both string and array command formats, and optional container targeting.
|
|
6
|
+
*/
|
|
7
|
+
import { KubernetesManager } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Schema for exec_in_pod tool.
|
|
10
|
+
* - name: Pod name
|
|
11
|
+
* - namespace: Namespace (default: "default")
|
|
12
|
+
* - command: Command to execute (string or array of args)
|
|
13
|
+
* - container: (Optional) Container name
|
|
14
|
+
*/
|
|
15
|
+
export declare const execInPodSchema: {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: string;
|
|
20
|
+
properties: {
|
|
21
|
+
name: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
namespace: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
default: string;
|
|
29
|
+
};
|
|
30
|
+
command: {
|
|
31
|
+
anyOf: ({
|
|
32
|
+
type: string;
|
|
33
|
+
items?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
type: string;
|
|
36
|
+
items: {
|
|
37
|
+
type: string;
|
|
38
|
+
};
|
|
39
|
+
})[];
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
container: {
|
|
43
|
+
type: string;
|
|
44
|
+
description: string;
|
|
45
|
+
optional: boolean;
|
|
46
|
+
};
|
|
47
|
+
shell: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
optional: boolean;
|
|
51
|
+
};
|
|
52
|
+
timeout: {
|
|
53
|
+
type: string;
|
|
54
|
+
description: string;
|
|
55
|
+
optional: boolean;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
required: string[];
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Execute a command in a Kubernetes pod or container using the Kubernetes client-node Exec API.
|
|
63
|
+
* Returns the stdout output as a text response.
|
|
64
|
+
* Throws McpError on failure.
|
|
65
|
+
*/
|
|
66
|
+
export declare function execInPod(k8sManager: KubernetesManager, input: {
|
|
67
|
+
name: string;
|
|
68
|
+
namespace?: string;
|
|
69
|
+
command: string | string[];
|
|
70
|
+
container?: string;
|
|
71
|
+
shell?: string;
|
|
72
|
+
timeout?: number;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
content: {
|
|
75
|
+
type: string;
|
|
76
|
+
text: string;
|
|
77
|
+
}[];
|
|
78
|
+
}>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: exec_in_pod
|
|
3
|
+
* Execute a command in a Kubernetes pod or container and return the output.
|
|
4
|
+
* Uses the official Kubernetes client-node Exec API for native execution.
|
|
5
|
+
* Supports both string and array command formats, and optional container targeting.
|
|
6
|
+
*/
|
|
7
|
+
import * as k8s from "@kubernetes/client-node";
|
|
8
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { Writable } from "stream";
|
|
10
|
+
/**
|
|
11
|
+
* Schema for exec_in_pod tool.
|
|
12
|
+
* - name: Pod name
|
|
13
|
+
* - namespace: Namespace (default: "default")
|
|
14
|
+
* - command: Command to execute (string or array of args)
|
|
15
|
+
* - container: (Optional) Container name
|
|
16
|
+
*/
|
|
17
|
+
export const execInPodSchema = {
|
|
18
|
+
name: "exec_in_pod",
|
|
19
|
+
description: "Execute a command in a Kubernetes pod or container and return the output",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
name: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Name of the pod to execute the command in",
|
|
26
|
+
},
|
|
27
|
+
namespace: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Kubernetes namespace where the pod is located",
|
|
30
|
+
default: "default",
|
|
31
|
+
},
|
|
32
|
+
command: {
|
|
33
|
+
anyOf: [
|
|
34
|
+
{ type: "string" },
|
|
35
|
+
{ type: "array", items: { type: "string" } }
|
|
36
|
+
],
|
|
37
|
+
description: "Command to execute in the pod (string or array of args)",
|
|
38
|
+
},
|
|
39
|
+
container: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Container name (required when pod has multiple containers)",
|
|
42
|
+
optional: true,
|
|
43
|
+
},
|
|
44
|
+
shell: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Shell to use for command execution (e.g. '/bin/sh', '/bin/bash'). If not provided, will use command as-is.",
|
|
47
|
+
optional: true,
|
|
48
|
+
},
|
|
49
|
+
timeout: {
|
|
50
|
+
type: "number",
|
|
51
|
+
description: "Timeout for command - 60000 milliseconds if not specified",
|
|
52
|
+
optional: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ["name", "command"],
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Execute a command in a Kubernetes pod or container using the Kubernetes client-node Exec API.
|
|
60
|
+
* Returns the stdout output as a text response.
|
|
61
|
+
* Throws McpError on failure.
|
|
62
|
+
*/
|
|
63
|
+
export async function execInPod(k8sManager, input) {
|
|
64
|
+
const namespace = input.namespace || "default";
|
|
65
|
+
// Convert command to array of strings for the Exec API
|
|
66
|
+
let commandArr;
|
|
67
|
+
if (Array.isArray(input.command)) {
|
|
68
|
+
commandArr = input.command;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Always wrap string commands in a shell for correct parsing
|
|
72
|
+
const shell = input.shell || "/bin/sh";
|
|
73
|
+
commandArr = [shell, "-c", input.command];
|
|
74
|
+
console.log("[exec_in_pod] Using shell:", shell, "Command array:", commandArr);
|
|
75
|
+
}
|
|
76
|
+
// Prepare buffers to capture stdout and stderr
|
|
77
|
+
let stdout = "";
|
|
78
|
+
let stderr = "";
|
|
79
|
+
// Use Node.js Writable streams to collect output
|
|
80
|
+
const stdoutStream = new Writable({
|
|
81
|
+
write(chunk, _encoding, callback) {
|
|
82
|
+
stdout += chunk.toString();
|
|
83
|
+
callback();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const stderrStream = new Writable({
|
|
87
|
+
write(chunk, _encoding, callback) {
|
|
88
|
+
stderr += chunk.toString();
|
|
89
|
+
callback();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// Add a dummy stdin stream
|
|
93
|
+
const stdinStream = new Writable({
|
|
94
|
+
write(_chunk, _encoding, callback) {
|
|
95
|
+
callback();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
// Use the Kubernetes client-node Exec API for native exec
|
|
100
|
+
const kc = k8sManager.getKubeConfig();
|
|
101
|
+
const exec = new k8s.Exec(kc);
|
|
102
|
+
// Add a timeout to avoid hanging forever if exec never returns
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
let finished = false;
|
|
105
|
+
const timeoutMs = input.timeout || 60000;
|
|
106
|
+
const timeout = setTimeout(() => {
|
|
107
|
+
if (!finished) {
|
|
108
|
+
finished = true;
|
|
109
|
+
reject(new McpError(ErrorCode.InternalError, "Exec operation timed out (possible networking, RBAC, or cluster issue)"));
|
|
110
|
+
}
|
|
111
|
+
}, timeoutMs);
|
|
112
|
+
console.log("[exec_in_pod] Calling exec.exec with params:", {
|
|
113
|
+
namespace,
|
|
114
|
+
pod: input.name,
|
|
115
|
+
container: input.container ?? "",
|
|
116
|
+
commandArr,
|
|
117
|
+
stdoutStreamType: typeof stdoutStream,
|
|
118
|
+
stderrStreamType: typeof stderrStream,
|
|
119
|
+
});
|
|
120
|
+
exec.exec(namespace, input.name, input.container ?? "", commandArr, stdoutStream, stderrStream, stdinStream, // use dummy stdin
|
|
121
|
+
true, // set tty to true
|
|
122
|
+
(status) => {
|
|
123
|
+
console.log("[exec_in_pod] exec.exec callback called. Status:", status);
|
|
124
|
+
if (finished)
|
|
125
|
+
return;
|
|
126
|
+
finished = true;
|
|
127
|
+
clearTimeout(timeout);
|
|
128
|
+
// Always resolve; handle errors based on stderr or thrown errors
|
|
129
|
+
resolve();
|
|
130
|
+
}).catch((err) => {
|
|
131
|
+
console.log("[exec_in_pod] exec.exec threw error:", err);
|
|
132
|
+
if (!finished) {
|
|
133
|
+
finished = true;
|
|
134
|
+
clearTimeout(timeout);
|
|
135
|
+
reject(new McpError(ErrorCode.InternalError, `Exec threw error: ${err?.message || err}`));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
// Return the collected stdout as the result
|
|
140
|
+
// If there is stderr output or no output at all, treat as error
|
|
141
|
+
if (stderr || (!stdout && !stderr)) {
|
|
142
|
+
throw new McpError(ErrorCode.InternalError, `Failed to execute command in pod: ${stderr || "No output"}`);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: stdout,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
// Collect error message and stderr output if available
|
|
155
|
+
let message = error.message || "Unknown error";
|
|
156
|
+
if (stderr) {
|
|
157
|
+
message += "\n" + stderr;
|
|
158
|
+
}
|
|
159
|
+
throw new McpError(ErrorCode.InternalError, `Failed to execute command in pod: ${message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const pingSchema = {
|
|
2
|
+
name: "ping",
|
|
3
|
+
description: "Verify that the counterpart is still responsive and the connection is alive.",
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
properties: {},
|
|
7
|
+
required: [],
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export async function ping() {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { KubernetesManager } from "../types.js";
|
|
2
|
-
export declare const kubectlListSchema: {
|
|
3
|
-
readonly name: "kubectl_list";
|
|
4
|
-
readonly description: "List Kubernetes resources by resource type and optionally namespace";
|
|
5
|
-
readonly inputSchema: {
|
|
6
|
-
readonly type: "object";
|
|
7
|
-
readonly properties: {
|
|
8
|
-
readonly resourceType: {
|
|
9
|
-
readonly type: "string";
|
|
10
|
-
readonly description: "Type of resource to list (e.g., pods, deployments, services, configmaps, etc.)";
|
|
11
|
-
};
|
|
12
|
-
readonly namespace: {
|
|
13
|
-
readonly type: "string";
|
|
14
|
-
readonly description: "Namespace of the resources (optional - defaults to 'default' for namespaced resources)";
|
|
15
|
-
readonly default: "default";
|
|
16
|
-
};
|
|
17
|
-
readonly output: {
|
|
18
|
-
readonly type: "string";
|
|
19
|
-
readonly enum: readonly ["json", "yaml", "wide", "name", "custom", "formatted"];
|
|
20
|
-
readonly description: "Output format - 'formatted' uses a resource-specific format with key information";
|
|
21
|
-
readonly default: "formatted";
|
|
22
|
-
};
|
|
23
|
-
readonly allNamespaces: {
|
|
24
|
-
readonly type: "boolean";
|
|
25
|
-
readonly description: "If true, list resources across all namespaces";
|
|
26
|
-
readonly default: false;
|
|
27
|
-
};
|
|
28
|
-
readonly labelSelector: {
|
|
29
|
-
readonly type: "string";
|
|
30
|
-
readonly description: "Filter resources by label selector (e.g. 'app=nginx')";
|
|
31
|
-
};
|
|
32
|
-
readonly fieldSelector: {
|
|
33
|
-
readonly type: "string";
|
|
34
|
-
readonly description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')";
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
readonly required: readonly ["resourceType", "namespace"];
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
export declare function kubectlList(k8sManager: KubernetesManager, input: {
|
|
41
|
-
resourceType: string;
|
|
42
|
-
namespace?: string;
|
|
43
|
-
output?: string;
|
|
44
|
-
allNamespaces?: boolean;
|
|
45
|
-
labelSelector?: string;
|
|
46
|
-
fieldSelector?: string;
|
|
47
|
-
}): Promise<{
|
|
48
|
-
content: {
|
|
49
|
-
type: string;
|
|
50
|
-
text: string;
|
|
51
|
-
}[];
|
|
52
|
-
isError?: undefined;
|
|
53
|
-
} | {
|
|
54
|
-
content: {
|
|
55
|
-
type: string;
|
|
56
|
-
text: string;
|
|
57
|
-
}[];
|
|
58
|
-
isError: boolean;
|
|
59
|
-
}>;
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { kubectlGet } from "./kubectl-get.js";
|
|
2
|
-
import { execSync } from "child_process";
|
|
3
|
-
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
-
export const kubectlListSchema = {
|
|
5
|
-
name: "kubectl_list",
|
|
6
|
-
description: "List Kubernetes resources by resource type and optionally namespace",
|
|
7
|
-
inputSchema: {
|
|
8
|
-
type: "object",
|
|
9
|
-
properties: {
|
|
10
|
-
resourceType: {
|
|
11
|
-
type: "string",
|
|
12
|
-
description: "Type of resource to list (e.g., pods, deployments, services, configmaps, etc.)"
|
|
13
|
-
},
|
|
14
|
-
namespace: {
|
|
15
|
-
type: "string",
|
|
16
|
-
description: "Namespace of the resources (optional - defaults to 'default' for namespaced resources)",
|
|
17
|
-
default: "default"
|
|
18
|
-
},
|
|
19
|
-
output: {
|
|
20
|
-
type: "string",
|
|
21
|
-
enum: ["json", "yaml", "wide", "name", "custom", "formatted"],
|
|
22
|
-
description: "Output format - 'formatted' uses a resource-specific format with key information",
|
|
23
|
-
default: "formatted"
|
|
24
|
-
},
|
|
25
|
-
allNamespaces: {
|
|
26
|
-
type: "boolean",
|
|
27
|
-
description: "If true, list resources across all namespaces",
|
|
28
|
-
default: false
|
|
29
|
-
},
|
|
30
|
-
labelSelector: {
|
|
31
|
-
type: "string",
|
|
32
|
-
description: "Filter resources by label selector (e.g. 'app=nginx')"
|
|
33
|
-
},
|
|
34
|
-
fieldSelector: {
|
|
35
|
-
type: "string",
|
|
36
|
-
description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
required: ["resourceType", "namespace"],
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
export async function kubectlList(k8sManager, input) {
|
|
43
|
-
try {
|
|
44
|
-
const resourceType = input.resourceType.toLowerCase();
|
|
45
|
-
const namespace = input.namespace || "default";
|
|
46
|
-
const output = input.output || "formatted";
|
|
47
|
-
const allNamespaces = input.allNamespaces || false;
|
|
48
|
-
const labelSelector = input.labelSelector || "";
|
|
49
|
-
const fieldSelector = input.fieldSelector || "";
|
|
50
|
-
// If not using formatted output, delegate to kubectl_get
|
|
51
|
-
if (output !== "formatted") {
|
|
52
|
-
return await kubectlGet(k8sManager, {
|
|
53
|
-
resourceType: input.resourceType,
|
|
54
|
-
namespace: input.namespace,
|
|
55
|
-
output: input.output,
|
|
56
|
-
allNamespaces: input.allNamespaces,
|
|
57
|
-
labelSelector: input.labelSelector,
|
|
58
|
-
fieldSelector: input.fieldSelector
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
// For formatted output, we'll use resource-specific custom columns
|
|
62
|
-
let customColumns = "";
|
|
63
|
-
switch (resourceType) {
|
|
64
|
-
case "pods":
|
|
65
|
-
case "pod":
|
|
66
|
-
case "po":
|
|
67
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,NODE:.spec.nodeName,IP:.status.podIP,AGE:.metadata.creationTimestamp";
|
|
68
|
-
break;
|
|
69
|
-
case "deployments":
|
|
70
|
-
case "deployment":
|
|
71
|
-
case "deploy":
|
|
72
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,READY:.status.readyReplicas/.status.replicas,UP-TO-DATE:.status.updatedReplicas,AVAILABLE:.status.availableReplicas,AGE:.metadata.creationTimestamp";
|
|
73
|
-
break;
|
|
74
|
-
case "services":
|
|
75
|
-
case "service":
|
|
76
|
-
case "svc":
|
|
77
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,EXTERNAL-IP:.status.loadBalancer.ingress[0].ip,PORTS:.spec.ports[*].port,AGE:.metadata.creationTimestamp";
|
|
78
|
-
break;
|
|
79
|
-
case "nodes":
|
|
80
|
-
case "node":
|
|
81
|
-
case "no":
|
|
82
|
-
customColumns = "NAME:.metadata.name,STATUS:.status.conditions[?(@.type==\"Ready\")].status,ROLES:.metadata.labels.kubernetes\\.io/role,VERSION:.status.nodeInfo.kubeletVersion,INTERNAL-IP:.status.addresses[?(@.type==\"InternalIP\")].address,OS-IMAGE:.status.nodeInfo.osImage,KERNEL-VERSION:.status.nodeInfo.kernelVersion,CONTAINER-RUNTIME:.status.nodeInfo.containerRuntimeVersion";
|
|
83
|
-
break;
|
|
84
|
-
case "namespaces":
|
|
85
|
-
case "namespace":
|
|
86
|
-
case "ns":
|
|
87
|
-
customColumns = "NAME:.metadata.name,STATUS:.status.phase,AGE:.metadata.creationTimestamp";
|
|
88
|
-
break;
|
|
89
|
-
case "persistentvolumes":
|
|
90
|
-
case "pv":
|
|
91
|
-
customColumns = "NAME:.metadata.name,CAPACITY:.spec.capacity.storage,ACCESS_MODES:.spec.accessModes,RECLAIM_POLICY:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase,CLAIM:.spec.claimRef.name,STORAGECLASS:.spec.storageClassName,AGE:.metadata.creationTimestamp";
|
|
92
|
-
break;
|
|
93
|
-
case "persistentvolumeclaims":
|
|
94
|
-
case "pvc":
|
|
95
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,VOLUME:.spec.volumeName,CAPACITY:.status.capacity.storage,ACCESS_MODES:.spec.accessModes,STORAGECLASS:.spec.storageClassName,AGE:.metadata.creationTimestamp";
|
|
96
|
-
break;
|
|
97
|
-
case "configmaps":
|
|
98
|
-
case "configmap":
|
|
99
|
-
case "cm":
|
|
100
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,DATA:.data,AGE:.metadata.creationTimestamp";
|
|
101
|
-
break;
|
|
102
|
-
case "secrets":
|
|
103
|
-
case "secret":
|
|
104
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,TYPE:.type,DATA:.data,AGE:.metadata.creationTimestamp";
|
|
105
|
-
break;
|
|
106
|
-
case "jobs":
|
|
107
|
-
case "job":
|
|
108
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,COMPLETIONS:.status.succeeded/.spec.completions,DURATION:.status.completionTime-(.status.startTime),AGE:.metadata.creationTimestamp";
|
|
109
|
-
break;
|
|
110
|
-
case "cronjobs":
|
|
111
|
-
case "cronjob":
|
|
112
|
-
case "cj":
|
|
113
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,ACTIVE:.status.active,LAST_SCHEDULE:.status.lastScheduleTime,AGE:.metadata.creationTimestamp";
|
|
114
|
-
break;
|
|
115
|
-
default:
|
|
116
|
-
// For unknown resource types, fall back to a generic format
|
|
117
|
-
customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,KIND:.kind,AGE:.metadata.creationTimestamp";
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
// Build the kubectl command
|
|
121
|
-
let command = "kubectl get ";
|
|
122
|
-
// Add resource type
|
|
123
|
-
command += resourceType;
|
|
124
|
-
// Add namespace flag unless all namespaces is specified
|
|
125
|
-
if (allNamespaces) {
|
|
126
|
-
command += " --all-namespaces";
|
|
127
|
-
}
|
|
128
|
-
else if (namespace && !isNonNamespacedResource(resourceType)) {
|
|
129
|
-
command += ` -n ${namespace}`;
|
|
130
|
-
}
|
|
131
|
-
// Add label selector if provided
|
|
132
|
-
if (labelSelector) {
|
|
133
|
-
command += ` -l ${labelSelector}`;
|
|
134
|
-
}
|
|
135
|
-
// Add field selector if provided
|
|
136
|
-
if (fieldSelector) {
|
|
137
|
-
command += ` --field-selector=${fieldSelector}`;
|
|
138
|
-
}
|
|
139
|
-
// Add custom columns format
|
|
140
|
-
command += ` -o custom-columns="${customColumns}"`;
|
|
141
|
-
// Execute the command
|
|
142
|
-
try {
|
|
143
|
-
const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
|
|
144
|
-
return {
|
|
145
|
-
content: [
|
|
146
|
-
{
|
|
147
|
-
type: "text",
|
|
148
|
-
text: result,
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
if (error.status === 404 || error.message.includes("not found")) {
|
|
155
|
-
return {
|
|
156
|
-
content: [
|
|
157
|
-
{
|
|
158
|
-
type: "text",
|
|
159
|
-
text: JSON.stringify({
|
|
160
|
-
error: `Resource type ${resourceType} not found or no resources exist`,
|
|
161
|
-
status: "not_found",
|
|
162
|
-
}, null, 2),
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
isError: true,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
throw new McpError(ErrorCode.InternalError, `Failed to list resources: ${error.message}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
throw new McpError(ErrorCode.InternalError, `Failed to execute kubectl list command: ${error.message}`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Helper function to determine if a resource is non-namespaced
|
|
176
|
-
function isNonNamespacedResource(resourceType) {
|
|
177
|
-
const nonNamespacedResources = [
|
|
178
|
-
"nodes", "node", "no",
|
|
179
|
-
"namespaces", "namespace", "ns",
|
|
180
|
-
"persistentvolumes", "pv",
|
|
181
|
-
"storageclasses", "sc",
|
|
182
|
-
"clusterroles",
|
|
183
|
-
"clusterrolebindings",
|
|
184
|
-
"customresourcedefinitions", "crd", "crds"
|
|
185
|
-
];
|
|
186
|
-
return nonNamespacedResources.includes(resourceType.toLowerCase());
|
|
187
|
-
}
|