mcp-server-kubernetes 2.4.9 → 2.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/dist/config/max-buffer.d.ts +1 -0
- package/dist/config/max-buffer.js +3 -0
- package/dist/index.js +27 -5
- package/dist/tools/exec_in_pod.d.ts +0 -3
- package/dist/tools/exec_in_pod.js +0 -3
- package/dist/tools/helm-operations.js +39 -17
- package/dist/tools/kubectl-apply.js +20 -14
- package/dist/tools/kubectl-context.js +53 -24
- package/dist/tools/kubectl-create.js +78 -59
- package/dist/tools/kubectl-delete.js +44 -28
- package/dist/tools/kubectl-describe.js +29 -19
- package/dist/tools/kubectl-generic.js +22 -17
- package/dist/tools/kubectl-get.js +22 -21
- package/dist/tools/kubectl-logs.js +99 -43
- package/dist/tools/kubectl-operations.js +22 -15
- package/dist/tools/kubectl-patch.js +25 -20
- package/dist/tools/kubectl-rollout.js +35 -22
- package/dist/tools/kubectl-scale.js +31 -20
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
2
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { getSpawnMaxBuffer } from "../config/max-buffer.js";
|
|
3
4
|
export const kubectlGenericSchema = {
|
|
4
5
|
name: "kubectl_generic",
|
|
5
6
|
description: "Execute any kubectl command with the provided arguments and flags",
|
|
@@ -8,48 +9,49 @@ export const kubectlGenericSchema = {
|
|
|
8
9
|
properties: {
|
|
9
10
|
command: {
|
|
10
11
|
type: "string",
|
|
11
|
-
description: "The kubectl command to execute (e.g. patch, rollout, top)"
|
|
12
|
+
description: "The kubectl command to execute (e.g. patch, rollout, top)",
|
|
12
13
|
},
|
|
13
14
|
subCommand: {
|
|
14
15
|
type: "string",
|
|
15
|
-
description: "Subcommand if applicable (e.g. 'history' for rollout)"
|
|
16
|
+
description: "Subcommand if applicable (e.g. 'history' for rollout)",
|
|
16
17
|
},
|
|
17
18
|
resourceType: {
|
|
18
19
|
type: "string",
|
|
19
|
-
description: "Resource type (e.g. pod, deployment)"
|
|
20
|
+
description: "Resource type (e.g. pod, deployment)",
|
|
20
21
|
},
|
|
21
22
|
name: {
|
|
22
23
|
type: "string",
|
|
23
|
-
description: "Resource name"
|
|
24
|
+
description: "Resource name",
|
|
24
25
|
},
|
|
25
26
|
namespace: {
|
|
26
27
|
type: "string",
|
|
27
28
|
description: "Namespace",
|
|
28
|
-
default: "default"
|
|
29
|
+
default: "default",
|
|
29
30
|
},
|
|
30
31
|
outputFormat: {
|
|
31
32
|
type: "string",
|
|
32
33
|
description: "Output format (e.g. json, yaml, wide)",
|
|
33
|
-
enum: ["json", "yaml", "wide", "name", "custom"]
|
|
34
|
+
enum: ["json", "yaml", "wide", "name", "custom"],
|
|
34
35
|
},
|
|
35
36
|
flags: {
|
|
36
37
|
type: "object",
|
|
37
38
|
description: "Command flags as key-value pairs",
|
|
38
|
-
additionalProperties: true
|
|
39
|
+
additionalProperties: true,
|
|
39
40
|
},
|
|
40
41
|
args: {
|
|
41
42
|
type: "array",
|
|
42
43
|
items: { type: "string" },
|
|
43
|
-
description: "Additional command arguments"
|
|
44
|
-
}
|
|
44
|
+
description: "Additional command arguments",
|
|
45
|
+
},
|
|
45
46
|
},
|
|
46
|
-
required: ["command"]
|
|
47
|
-
}
|
|
47
|
+
required: ["command"],
|
|
48
|
+
},
|
|
48
49
|
};
|
|
49
50
|
export async function kubectlGeneric(k8sManager, input) {
|
|
50
51
|
try {
|
|
51
52
|
// Start building the kubectl command
|
|
52
|
-
|
|
53
|
+
const command = "kubectl";
|
|
54
|
+
const cmdArgs = [input.command];
|
|
53
55
|
// Add subcommand if provided
|
|
54
56
|
if (input.subCommand) {
|
|
55
57
|
cmdArgs.push(input.subCommand);
|
|
@@ -87,11 +89,14 @@ export async function kubectlGeneric(k8sManager, input) {
|
|
|
87
89
|
if (input.args && input.args.length > 0) {
|
|
88
90
|
cmdArgs.push(...input.args);
|
|
89
91
|
}
|
|
90
|
-
// Execute the command
|
|
91
|
-
const command = cmdArgs.slice(1).join(' ');
|
|
92
|
+
// Execute the command
|
|
92
93
|
try {
|
|
93
|
-
console.error(`Executing: kubectl ${
|
|
94
|
-
const result =
|
|
94
|
+
console.error(`Executing: kubectl ${cmdArgs.join(" ")}`);
|
|
95
|
+
const result = execFileSync(command, cmdArgs, {
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
98
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
99
|
+
});
|
|
95
100
|
return {
|
|
96
101
|
content: [
|
|
97
102
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
2
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { getSpawnMaxBuffer } from "../config/max-buffer.js";
|
|
3
4
|
export const kubectlGetSchema = {
|
|
4
5
|
name: "kubectl_get",
|
|
5
6
|
description: "Get or list Kubernetes resources by resource type, name, and optionally namespace",
|
|
@@ -32,15 +33,15 @@ export const kubectlGetSchema = {
|
|
|
32
33
|
},
|
|
33
34
|
labelSelector: {
|
|
34
35
|
type: "string",
|
|
35
|
-
description: "Filter resources by label selector (e.g. 'app=nginx')"
|
|
36
|
+
description: "Filter resources by label selector (e.g. 'app=nginx')",
|
|
36
37
|
},
|
|
37
38
|
fieldSelector: {
|
|
38
39
|
type: "string",
|
|
39
|
-
description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')"
|
|
40
|
+
description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')",
|
|
40
41
|
},
|
|
41
42
|
sortBy: {
|
|
42
43
|
type: "string",
|
|
43
|
-
description: "Sort events by a field (default: lastTimestamp). Only applicable for events."
|
|
44
|
+
description: "Sort events by a field (default: lastTimestamp). Only applicable for events.",
|
|
44
45
|
},
|
|
45
46
|
},
|
|
46
47
|
required: ["resourceType", "name", "namespace"],
|
|
@@ -57,12 +58,11 @@ export async function kubectlGet(k8sManager, input) {
|
|
|
57
58
|
const fieldSelector = input.fieldSelector || "";
|
|
58
59
|
const sortBy = input.sortBy;
|
|
59
60
|
// Build the kubectl command
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
command += resourceType;
|
|
61
|
+
const command = "kubectl";
|
|
62
|
+
const args = ["get", resourceType];
|
|
63
63
|
// Add name if provided
|
|
64
64
|
if (name) {
|
|
65
|
-
|
|
65
|
+
args.push(name);
|
|
66
66
|
}
|
|
67
67
|
// For events, default to all namespaces unless explicitly specified
|
|
68
68
|
const shouldShowAllNamespaces = resourceType === "events"
|
|
@@ -72,51 +72,52 @@ export async function kubectlGet(k8sManager, input) {
|
|
|
72
72
|
: allNamespaces;
|
|
73
73
|
// Add namespace flag unless all namespaces is specified
|
|
74
74
|
if (shouldShowAllNamespaces) {
|
|
75
|
-
|
|
75
|
+
args.push("--all-namespaces");
|
|
76
76
|
}
|
|
77
77
|
else if (namespace && !isNonNamespacedResource(resourceType)) {
|
|
78
|
-
|
|
78
|
+
args.push("-n", namespace);
|
|
79
79
|
}
|
|
80
80
|
// Add label selector if provided
|
|
81
81
|
if (labelSelector) {
|
|
82
|
-
|
|
82
|
+
args.push("-l", labelSelector);
|
|
83
83
|
}
|
|
84
84
|
// Add field selector if provided
|
|
85
85
|
if (fieldSelector) {
|
|
86
|
-
|
|
86
|
+
args.push(`--field-selector=${fieldSelector}`);
|
|
87
87
|
}
|
|
88
88
|
// Add sort-by for events
|
|
89
89
|
if (resourceType === "events" && sortBy) {
|
|
90
|
-
|
|
90
|
+
args.push(`--sort-by=.${sortBy}`);
|
|
91
91
|
}
|
|
92
92
|
else if (resourceType === "events") {
|
|
93
|
-
|
|
93
|
+
args.push(`--sort-by=.lastTimestamp`);
|
|
94
94
|
}
|
|
95
95
|
// Add output format
|
|
96
96
|
if (output === "json") {
|
|
97
|
-
|
|
97
|
+
args.push("-o", "json");
|
|
98
98
|
}
|
|
99
99
|
else if (output === "yaml") {
|
|
100
|
-
|
|
100
|
+
args.push("-o", "yaml");
|
|
101
101
|
}
|
|
102
102
|
else if (output === "wide") {
|
|
103
|
-
|
|
103
|
+
args.push("-o", "wide");
|
|
104
104
|
}
|
|
105
105
|
else if (output === "name") {
|
|
106
|
-
|
|
106
|
+
args.push("-o", "name");
|
|
107
107
|
}
|
|
108
108
|
else if (output === "custom") {
|
|
109
109
|
if (resourceType === "events") {
|
|
110
|
-
|
|
110
|
+
args.push("-o", "'custom-columns=LASTSEEN:.lastTimestamp,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.name,MESSAGE:.message'");
|
|
111
111
|
}
|
|
112
112
|
else {
|
|
113
|
-
|
|
113
|
+
args.push("-o", "'custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,AGE:.metadata.creationTimestamp'");
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
// Execute the command
|
|
117
117
|
try {
|
|
118
|
-
const result =
|
|
118
|
+
const result = execFileSync(command, args, {
|
|
119
119
|
encoding: "utf8",
|
|
120
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
120
121
|
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
121
122
|
});
|
|
122
123
|
// Format the results for better readability
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
2
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { getSpawnMaxBuffer } from "../config/max-buffer.js";
|
|
3
4
|
export const kubectlLogsSchema = {
|
|
4
5
|
name: "kubectl_logs",
|
|
5
6
|
description: "Get logs from Kubernetes resources like pods, deployments, or jobs",
|
|
@@ -22,19 +23,19 @@ export const kubectlLogsSchema = {
|
|
|
22
23
|
},
|
|
23
24
|
container: {
|
|
24
25
|
type: "string",
|
|
25
|
-
description: "Container name (required when pod has multiple containers)"
|
|
26
|
+
description: "Container name (required when pod has multiple containers)",
|
|
26
27
|
},
|
|
27
28
|
tail: {
|
|
28
29
|
type: "number",
|
|
29
|
-
description: "Number of lines to show from end of logs"
|
|
30
|
+
description: "Number of lines to show from end of logs",
|
|
30
31
|
},
|
|
31
32
|
since: {
|
|
32
33
|
type: "string",
|
|
33
|
-
description: "Show logs since relative time (e.g. '5s', '2m', '3h')"
|
|
34
|
+
description: "Show logs since relative time (e.g. '5s', '2m', '3h')",
|
|
34
35
|
},
|
|
35
36
|
sinceTime: {
|
|
36
37
|
type: "string",
|
|
37
|
-
description: "Show logs since absolute time (RFC3339)"
|
|
38
|
+
description: "Show logs since absolute time (RFC3339)",
|
|
38
39
|
},
|
|
39
40
|
timestamps: {
|
|
40
41
|
type: "boolean",
|
|
@@ -53,8 +54,8 @@ export const kubectlLogsSchema = {
|
|
|
53
54
|
},
|
|
54
55
|
labelSelector: {
|
|
55
56
|
type: "string",
|
|
56
|
-
description: "Filter resources by label selector"
|
|
57
|
-
}
|
|
57
|
+
description: "Filter resources by label selector",
|
|
58
|
+
},
|
|
58
59
|
},
|
|
59
60
|
required: ["resourceType", "name", "namespace"],
|
|
60
61
|
},
|
|
@@ -64,32 +65,45 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
64
65
|
const resourceType = input.resourceType.toLowerCase();
|
|
65
66
|
const name = input.name;
|
|
66
67
|
const namespace = input.namespace || "default";
|
|
67
|
-
|
|
68
|
-
let baseCommand = `kubectl -n ${namespace}`;
|
|
68
|
+
const command = "kubectl";
|
|
69
69
|
// Handle different resource types
|
|
70
70
|
if (resourceType === "pod") {
|
|
71
71
|
// Direct pod logs
|
|
72
|
-
|
|
72
|
+
let args = ["-n", namespace, "logs", name];
|
|
73
73
|
// If container is specified, add it
|
|
74
74
|
if (input.container) {
|
|
75
|
-
|
|
75
|
+
args.push(`-c`, input.container);
|
|
76
76
|
}
|
|
77
77
|
// Add options
|
|
78
|
-
|
|
78
|
+
args = addLogOptions(args, input);
|
|
79
79
|
// Execute the command
|
|
80
80
|
try {
|
|
81
|
-
const result =
|
|
81
|
+
const result = execFileSync(command, args, {
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
84
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
85
|
+
});
|
|
82
86
|
return formatLogOutput(name, result);
|
|
83
87
|
}
|
|
84
88
|
catch (error) {
|
|
85
89
|
return handleCommandError(error, `pod ${name}`);
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
|
-
else if (resourceType === "deployment" ||
|
|
92
|
+
else if (resourceType === "deployment" ||
|
|
93
|
+
resourceType === "job" ||
|
|
94
|
+
resourceType === "cronjob") {
|
|
89
95
|
// For deployments, jobs and cronjobs we need to find the pods first
|
|
90
|
-
let
|
|
96
|
+
let selectorArgs;
|
|
91
97
|
if (resourceType === "deployment") {
|
|
92
|
-
|
|
98
|
+
selectorArgs = [
|
|
99
|
+
"-n",
|
|
100
|
+
namespace,
|
|
101
|
+
"get",
|
|
102
|
+
"deployment",
|
|
103
|
+
name,
|
|
104
|
+
"-o",
|
|
105
|
+
"jsonpath='{.spec.selector.matchLabels}'",
|
|
106
|
+
];
|
|
93
107
|
}
|
|
94
108
|
else if (resourceType === "job") {
|
|
95
109
|
// For jobs, we use the job-name label
|
|
@@ -97,10 +111,24 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
97
111
|
}
|
|
98
112
|
else if (resourceType === "cronjob") {
|
|
99
113
|
// For cronjobs, it's more complex - need to find the job first
|
|
100
|
-
const
|
|
114
|
+
const jobsArgs = [
|
|
115
|
+
"-n",
|
|
116
|
+
namespace,
|
|
117
|
+
"get",
|
|
118
|
+
"jobs",
|
|
119
|
+
"--selector=job-name=" + name,
|
|
120
|
+
"-o",
|
|
121
|
+
"jsonpath='{.items[*].metadata.name}'",
|
|
122
|
+
];
|
|
101
123
|
try {
|
|
102
|
-
const jobs =
|
|
103
|
-
|
|
124
|
+
const jobs = execFileSync(command, jobsArgs, {
|
|
125
|
+
encoding: "utf8",
|
|
126
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
127
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
128
|
+
})
|
|
129
|
+
.trim()
|
|
130
|
+
.split(" ");
|
|
131
|
+
if (jobs.length === 0 || (jobs.length === 1 && jobs[0] === "")) {
|
|
104
132
|
return {
|
|
105
133
|
content: [
|
|
106
134
|
{
|
|
@@ -140,15 +168,19 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
140
168
|
try {
|
|
141
169
|
if (resourceType === "deployment") {
|
|
142
170
|
// Get the deployment's selector
|
|
143
|
-
if (!
|
|
171
|
+
if (!selectorArgs) {
|
|
144
172
|
throw new Error("Selector command is undefined");
|
|
145
173
|
}
|
|
146
|
-
const selectorJson =
|
|
174
|
+
const selectorJson = execFileSync(command, selectorArgs, {
|
|
175
|
+
encoding: "utf8",
|
|
176
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
177
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
178
|
+
}).trim();
|
|
147
179
|
const selector = JSON.parse(selectorJson.replace(/'/g, '"'));
|
|
148
180
|
// Convert to label selector format
|
|
149
181
|
const labelSelector = Object.entries(selector)
|
|
150
182
|
.map(([key, value]) => `${key}=${value}`)
|
|
151
|
-
.join(
|
|
183
|
+
.join(",");
|
|
152
184
|
return getLabelSelectorLogs(labelSelector, namespace, input);
|
|
153
185
|
}
|
|
154
186
|
// For jobs and cronjobs, the logic is handled above
|
|
@@ -183,36 +215,50 @@ export async function kubectlLogs(k8sManager, input) {
|
|
|
183
215
|
}
|
|
184
216
|
}
|
|
185
217
|
// Helper function to add log options to the kubectl command
|
|
186
|
-
function addLogOptions(
|
|
187
|
-
let command = baseCommand;
|
|
218
|
+
function addLogOptions(args, input) {
|
|
188
219
|
// Add options based on inputs
|
|
189
220
|
if (input.tail !== undefined) {
|
|
190
|
-
|
|
221
|
+
args.push(`--tail=${input.tail}`);
|
|
191
222
|
}
|
|
192
223
|
if (input.since) {
|
|
193
|
-
|
|
224
|
+
args.push(`--since=${input.since}`);
|
|
194
225
|
}
|
|
195
226
|
if (input.sinceTime) {
|
|
196
|
-
|
|
227
|
+
args.push(`--since-time=${input.sinceTime}`);
|
|
197
228
|
}
|
|
198
229
|
if (input.timestamps) {
|
|
199
|
-
|
|
230
|
+
args.push(`--timestamps`);
|
|
200
231
|
}
|
|
201
232
|
if (input.previous) {
|
|
202
|
-
|
|
233
|
+
args.push(`--previous`);
|
|
203
234
|
}
|
|
204
235
|
if (input.follow) {
|
|
205
|
-
|
|
236
|
+
args.push(`--follow`);
|
|
206
237
|
}
|
|
207
|
-
return
|
|
238
|
+
return args;
|
|
208
239
|
}
|
|
209
240
|
// Helper function to get logs for resources selected by labels
|
|
210
241
|
async function getLabelSelectorLogs(labelSelector, namespace, input) {
|
|
211
242
|
try {
|
|
243
|
+
const command = "kubectl";
|
|
212
244
|
// First, find all pods matching the label selector
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
245
|
+
const podsArgs = [
|
|
246
|
+
"-n",
|
|
247
|
+
namespace,
|
|
248
|
+
"get",
|
|
249
|
+
"pods",
|
|
250
|
+
`--selector=${labelSelector}`,
|
|
251
|
+
"-o",
|
|
252
|
+
"jsonpath='{.items[*].metadata.name}'",
|
|
253
|
+
];
|
|
254
|
+
const pods = execFileSync(command, podsArgs, {
|
|
255
|
+
encoding: "utf8",
|
|
256
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
257
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
258
|
+
})
|
|
259
|
+
.trim()
|
|
260
|
+
.split(" ");
|
|
261
|
+
if (pods.length === 0 || (pods.length === 1 && pods[0] === "")) {
|
|
216
262
|
return {
|
|
217
263
|
content: [
|
|
218
264
|
{
|
|
@@ -230,15 +276,19 @@ async function getLabelSelectorLogs(labelSelector, namespace, input) {
|
|
|
230
276
|
// Skip empty pod names
|
|
231
277
|
if (!pod)
|
|
232
278
|
continue;
|
|
233
|
-
let
|
|
279
|
+
let podArgs = ["-n", namespace, "logs", pod];
|
|
234
280
|
// Add container if specified
|
|
235
281
|
if (input.container) {
|
|
236
|
-
|
|
282
|
+
podArgs.push(`-c`, input.container);
|
|
237
283
|
}
|
|
238
284
|
// Add other options
|
|
239
|
-
|
|
285
|
+
podArgs = addLogOptions(podArgs, input);
|
|
240
286
|
try {
|
|
241
|
-
const logs =
|
|
287
|
+
const logs = execFileSync(command, podArgs, {
|
|
288
|
+
encoding: "utf8",
|
|
289
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
290
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
291
|
+
});
|
|
242
292
|
logsMap[pod] = logs;
|
|
243
293
|
}
|
|
244
294
|
catch (error) {
|
|
@@ -299,9 +349,13 @@ function handleCommandError(error, resourceDescription) {
|
|
|
299
349
|
const podNameMatch = error.message.match(/for pod ([^,]+)/);
|
|
300
350
|
const containersMatch = error.message.match(/choose one of: \[([^\]]+)\]/);
|
|
301
351
|
const initContainersMatch = error.message.match(/or one of the init containers: \[([^\]]+)\]/);
|
|
302
|
-
const podName = podNameMatch ? podNameMatch[1] :
|
|
303
|
-
const containers = containersMatch
|
|
304
|
-
|
|
352
|
+
const podName = podNameMatch ? podNameMatch[1] : "unknown";
|
|
353
|
+
const containers = containersMatch
|
|
354
|
+
? containersMatch[1].split(" ").map((c) => c.trim())
|
|
355
|
+
: [];
|
|
356
|
+
const initContainers = initContainersMatch
|
|
357
|
+
? initContainersMatch[1].split(" ").map((c) => c.trim())
|
|
358
|
+
: [];
|
|
305
359
|
// Generate structured context for the MCP client to make decisions
|
|
306
360
|
const context = {
|
|
307
361
|
error: "Multi-container pod requires container specification",
|
|
@@ -309,7 +363,9 @@ function handleCommandError(error, resourceDescription) {
|
|
|
309
363
|
pod_name: podName,
|
|
310
364
|
available_containers: containers,
|
|
311
365
|
init_containers: initContainers,
|
|
312
|
-
suggestion: `Please specify a container name using the 'container' parameter. Available containers: ${containers.join(
|
|
366
|
+
suggestion: `Please specify a container name using the 'container' parameter. Available containers: ${containers.join(", ")}${initContainers.length > 0
|
|
367
|
+
? `. Init containers: ${initContainers.join(", ")}`
|
|
368
|
+
: ""}`,
|
|
313
369
|
};
|
|
314
370
|
return {
|
|
315
371
|
content: [
|
|
@@ -328,7 +384,7 @@ function handleCommandError(error, resourceDescription) {
|
|
|
328
384
|
text: JSON.stringify({
|
|
329
385
|
error: `Failed to get logs for ${resourceDescription}: ${error.message}`,
|
|
330
386
|
status: "general_error",
|
|
331
|
-
original_error: error.message
|
|
387
|
+
original_error: error.message,
|
|
332
388
|
}, null, 2),
|
|
333
389
|
},
|
|
334
390
|
],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import { getSpawnMaxBuffer } from "../config/max-buffer.js";
|
|
2
3
|
export const explainResourceSchema = {
|
|
3
4
|
name: "explain_resource",
|
|
4
5
|
description: "Get documentation for a Kubernetes resource or field",
|
|
@@ -58,9 +59,13 @@ export const listApiResourcesSchema = {
|
|
|
58
59
|
},
|
|
59
60
|
},
|
|
60
61
|
};
|
|
61
|
-
const executeKubectlCommand = (command) => {
|
|
62
|
+
const executeKubectlCommand = (command, args) => {
|
|
62
63
|
try {
|
|
63
|
-
return
|
|
64
|
+
return execFileSync(command, args, {
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
67
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
68
|
+
});
|
|
64
69
|
}
|
|
65
70
|
catch (error) {
|
|
66
71
|
throw new Error(`Kubectl command failed: ${error.message}`);
|
|
@@ -68,18 +73,19 @@ const executeKubectlCommand = (command) => {
|
|
|
68
73
|
};
|
|
69
74
|
export async function explainResource(params) {
|
|
70
75
|
try {
|
|
71
|
-
|
|
76
|
+
const command = "kubectl";
|
|
77
|
+
const args = ["explain"];
|
|
72
78
|
if (params.apiVersion) {
|
|
73
|
-
|
|
79
|
+
args.push(`--api-version=${params.apiVersion}`);
|
|
74
80
|
}
|
|
75
81
|
if (params.recursive) {
|
|
76
|
-
|
|
82
|
+
args.push("--recursive");
|
|
77
83
|
}
|
|
78
84
|
if (params.output) {
|
|
79
|
-
|
|
85
|
+
args.push(`--output=${params.output}`);
|
|
80
86
|
}
|
|
81
|
-
|
|
82
|
-
const result = executeKubectlCommand(command);
|
|
87
|
+
args.push(params.resource);
|
|
88
|
+
const result = executeKubectlCommand(command, args);
|
|
83
89
|
return {
|
|
84
90
|
content: [
|
|
85
91
|
{
|
|
@@ -95,20 +101,21 @@ export async function explainResource(params) {
|
|
|
95
101
|
}
|
|
96
102
|
export async function listApiResources(params) {
|
|
97
103
|
try {
|
|
98
|
-
|
|
104
|
+
const command = "kubectl";
|
|
105
|
+
const args = ["api-resources"];
|
|
99
106
|
if (params.apiGroup) {
|
|
100
|
-
|
|
107
|
+
args.push(`--api-group=${params.apiGroup}`);
|
|
101
108
|
}
|
|
102
109
|
if (params.namespaced !== undefined) {
|
|
103
|
-
|
|
110
|
+
args.push(`--namespaced=${params.namespaced}`);
|
|
104
111
|
}
|
|
105
112
|
if (params.verbs && params.verbs.length > 0) {
|
|
106
|
-
|
|
113
|
+
args.push(`--verbs=${params.verbs.join(",")}`);
|
|
107
114
|
}
|
|
108
115
|
if (params.output) {
|
|
109
|
-
|
|
116
|
+
args.push(`-o`, params.output);
|
|
110
117
|
}
|
|
111
|
-
const result = executeKubectlCommand(command);
|
|
118
|
+
const result = executeKubectlCommand(command, args);
|
|
112
119
|
return {
|
|
113
120
|
content: [
|
|
114
121
|
{
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
2
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import * as os from "os";
|
|
6
|
+
import { getSpawnMaxBuffer } from "../config/max-buffer.js";
|
|
6
7
|
export const kubectlPatchSchema = {
|
|
7
8
|
name: "kubectl_patch",
|
|
8
9
|
description: "Update field(s) of a resource using strategic merge patch, JSON merge patch, or JSON patch",
|
|
@@ -11,39 +12,39 @@ export const kubectlPatchSchema = {
|
|
|
11
12
|
properties: {
|
|
12
13
|
resourceType: {
|
|
13
14
|
type: "string",
|
|
14
|
-
description: "Type of resource to patch (e.g., pods, deployments, services)"
|
|
15
|
+
description: "Type of resource to patch (e.g., pods, deployments, services)",
|
|
15
16
|
},
|
|
16
17
|
name: {
|
|
17
18
|
type: "string",
|
|
18
|
-
description: "Name of the resource to patch"
|
|
19
|
+
description: "Name of the resource to patch",
|
|
19
20
|
},
|
|
20
21
|
namespace: {
|
|
21
22
|
type: "string",
|
|
22
23
|
description: "Namespace of the resource",
|
|
23
|
-
default: "default"
|
|
24
|
+
default: "default",
|
|
24
25
|
},
|
|
25
26
|
patchType: {
|
|
26
27
|
type: "string",
|
|
27
28
|
description: "Type of patch to apply",
|
|
28
29
|
enum: ["strategic", "merge", "json"],
|
|
29
|
-
default: "strategic"
|
|
30
|
+
default: "strategic",
|
|
30
31
|
},
|
|
31
32
|
patchData: {
|
|
32
33
|
type: "object",
|
|
33
|
-
description: "Patch data as a JSON object"
|
|
34
|
+
description: "Patch data as a JSON object",
|
|
34
35
|
},
|
|
35
36
|
patchFile: {
|
|
36
37
|
type: "string",
|
|
37
|
-
description: "Path to a file containing the patch data (alternative to patchData)"
|
|
38
|
+
description: "Path to a file containing the patch data (alternative to patchData)",
|
|
38
39
|
},
|
|
39
40
|
dryRun: {
|
|
40
41
|
type: "boolean",
|
|
41
42
|
description: "If true, only print the object that would be sent, without sending it",
|
|
42
|
-
default: false
|
|
43
|
-
}
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
44
45
|
},
|
|
45
46
|
required: ["resourceType", "name"],
|
|
46
|
-
}
|
|
47
|
+
},
|
|
47
48
|
};
|
|
48
49
|
export async function kubectlPatch(k8sManager, input) {
|
|
49
50
|
try {
|
|
@@ -54,21 +55,21 @@ export async function kubectlPatch(k8sManager, input) {
|
|
|
54
55
|
const patchType = input.patchType || "strategic";
|
|
55
56
|
const dryRun = input.dryRun || false;
|
|
56
57
|
let tempFile = null;
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const command = "kubectl";
|
|
59
|
+
const args = ["patch", input.resourceType, input.name, "-n", namespace];
|
|
59
60
|
// Add patch type flag
|
|
60
61
|
switch (patchType) {
|
|
61
62
|
case "strategic":
|
|
62
|
-
|
|
63
|
+
args.push("--type", "strategic");
|
|
63
64
|
break;
|
|
64
65
|
case "merge":
|
|
65
|
-
|
|
66
|
+
args.push("--type", "merge");
|
|
66
67
|
break;
|
|
67
68
|
case "json":
|
|
68
|
-
|
|
69
|
+
args.push("--type", "json");
|
|
69
70
|
break;
|
|
70
71
|
default:
|
|
71
|
-
|
|
72
|
+
args.push("--type", "strategic");
|
|
72
73
|
}
|
|
73
74
|
// Handle patch data
|
|
74
75
|
if (input.patchData) {
|
|
@@ -76,18 +77,22 @@ export async function kubectlPatch(k8sManager, input) {
|
|
|
76
77
|
const tmpDir = os.tmpdir();
|
|
77
78
|
tempFile = path.join(tmpDir, `patch-${Date.now()}.json`);
|
|
78
79
|
fs.writeFileSync(tempFile, JSON.stringify(input.patchData));
|
|
79
|
-
|
|
80
|
+
args.push("--patch-file", tempFile);
|
|
80
81
|
}
|
|
81
82
|
else if (input.patchFile) {
|
|
82
|
-
|
|
83
|
+
args.push("--patch-file", input.patchFile);
|
|
83
84
|
}
|
|
84
85
|
// Add dry-run flag if requested
|
|
85
86
|
if (dryRun) {
|
|
86
|
-
|
|
87
|
+
args.push("--dry-run=client");
|
|
87
88
|
}
|
|
88
89
|
// Execute the command
|
|
89
90
|
try {
|
|
90
|
-
const result =
|
|
91
|
+
const result = execFileSync(command, args, {
|
|
92
|
+
encoding: "utf8",
|
|
93
|
+
maxBuffer: getSpawnMaxBuffer(),
|
|
94
|
+
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
|
|
95
|
+
});
|
|
91
96
|
// Clean up temp file if created
|
|
92
97
|
if (tempFile) {
|
|
93
98
|
try {
|