kubectl-mcp-server 1.13.0__py3-none-any.whl → 1.15.0__py3-none-any.whl
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.
- kubectl_mcp_server-1.15.0.dist-info/METADATA +1026 -0
- kubectl_mcp_server-1.15.0.dist-info/RECORD +49 -0
- kubectl_mcp_tool/__init__.py +1 -1
- kubectl_mcp_tool/cli/__init__.py +53 -1
- kubectl_mcp_tool/cli/cli.py +476 -17
- kubectl_mcp_tool/cli/errors.py +262 -0
- kubectl_mcp_tool/cli/output.py +377 -0
- kubectl_mcp_tool/k8s_config.py +285 -63
- kubectl_mcp_tool/tools/browser.py +316 -28
- kubectl_mcp_tool/tools/cluster.py +395 -121
- kubectl_mcp_tool/tools/core.py +157 -60
- kubectl_mcp_tool/tools/cost.py +97 -41
- kubectl_mcp_tool/tools/deployments.py +173 -56
- kubectl_mcp_tool/tools/diagnostics.py +40 -13
- kubectl_mcp_tool/tools/helm.py +133 -46
- kubectl_mcp_tool/tools/networking.py +106 -32
- kubectl_mcp_tool/tools/operations.py +176 -50
- kubectl_mcp_tool/tools/pods.py +162 -50
- kubectl_mcp_tool/tools/security.py +89 -36
- kubectl_mcp_tool/tools/storage.py +35 -16
- tests/test_browser.py +167 -5
- tests/test_cli.py +299 -0
- tests/test_tools.py +10 -9
- kubectl_mcp_server-1.13.0.dist-info/METADATA +0 -780
- kubectl_mcp_server-1.13.0.dist-info/RECORD +0 -46
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/WHEEL +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/top_level.txt +0 -0
|
@@ -10,6 +10,13 @@ from mcp.types import ToolAnnotations
|
|
|
10
10
|
logger = logging.getLogger("mcp-server")
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _get_kubectl_context_args(context: str) -> List[str]:
|
|
14
|
+
"""Get kubectl context arguments if context is specified."""
|
|
15
|
+
if context:
|
|
16
|
+
return ["--context", context]
|
|
17
|
+
return []
|
|
18
|
+
|
|
19
|
+
|
|
13
20
|
def register_operations_tools(server, non_destructive: bool):
|
|
14
21
|
"""Register kubectl operations tools (apply, describe, patch, etc.)."""
|
|
15
22
|
|
|
@@ -25,8 +32,14 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
25
32
|
destructiveHint=True,
|
|
26
33
|
),
|
|
27
34
|
)
|
|
28
|
-
def kubectl_apply(manifest: str, namespace: Optional[str] = "default") -> Dict[str, Any]:
|
|
29
|
-
"""Apply a YAML manifest to the cluster.
|
|
35
|
+
def kubectl_apply(manifest: str, namespace: Optional[str] = "default", context: str = "") -> Dict[str, Any]:
|
|
36
|
+
"""Apply a YAML manifest to the cluster.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
manifest: YAML manifest content to apply
|
|
40
|
+
namespace: Target namespace
|
|
41
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
42
|
+
"""
|
|
30
43
|
blocked = check_destructive()
|
|
31
44
|
if blocked:
|
|
32
45
|
return blocked
|
|
@@ -35,13 +48,13 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
35
48
|
f.write(manifest)
|
|
36
49
|
temp_path = f.name
|
|
37
50
|
|
|
38
|
-
cmd = ["kubectl"
|
|
51
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["apply", "-f", temp_path, "-n", namespace]
|
|
39
52
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
40
53
|
|
|
41
54
|
os.unlink(temp_path)
|
|
42
55
|
|
|
43
56
|
if result.returncode == 0:
|
|
44
|
-
return {"success": True, "output": result.stdout.strip()}
|
|
57
|
+
return {"success": True, "context": context or "current", "output": result.stdout.strip()}
|
|
45
58
|
else:
|
|
46
59
|
return {"success": False, "error": result.stderr.strip()}
|
|
47
60
|
except Exception as e:
|
|
@@ -54,14 +67,21 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
54
67
|
readOnlyHint=True,
|
|
55
68
|
),
|
|
56
69
|
)
|
|
57
|
-
def kubectl_describe(resource_type: str, name: str, namespace: Optional[str] = "default") -> Dict[str, Any]:
|
|
58
|
-
"""Describe a Kubernetes resource in detail.
|
|
70
|
+
def kubectl_describe(resource_type: str, name: str, namespace: Optional[str] = "default", context: str = "") -> Dict[str, Any]:
|
|
71
|
+
"""Describe a Kubernetes resource in detail.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
resource_type: Type of resource (e.g., pod, deployment, service)
|
|
75
|
+
name: Name of the resource
|
|
76
|
+
namespace: Target namespace
|
|
77
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
78
|
+
"""
|
|
59
79
|
try:
|
|
60
|
-
cmd = ["kubectl"
|
|
80
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["describe", resource_type, name, "-n", namespace]
|
|
61
81
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
62
82
|
|
|
63
83
|
if result.returncode == 0:
|
|
64
|
-
return {"success": True, "description": result.stdout}
|
|
84
|
+
return {"success": True, "context": context or "current", "description": result.stdout}
|
|
65
85
|
else:
|
|
66
86
|
return {"success": False, "error": result.stderr.strip()}
|
|
67
87
|
except Exception as e:
|
|
@@ -74,8 +94,13 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
74
94
|
readOnlyHint=True,
|
|
75
95
|
),
|
|
76
96
|
)
|
|
77
|
-
def kubectl_generic(command: str) -> Dict[str, Any]:
|
|
78
|
-
"""Execute any kubectl command. Use with caution.
|
|
97
|
+
def kubectl_generic(command: str, context: str = "") -> Dict[str, Any]:
|
|
98
|
+
"""Execute any kubectl command. Use with caution.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
command: kubectl command to execute (without kubectl prefix)
|
|
102
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
103
|
+
"""
|
|
79
104
|
try:
|
|
80
105
|
# Security: validate command starts with allowed operations
|
|
81
106
|
allowed_prefixes = [
|
|
@@ -96,11 +121,12 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
96
121
|
"error": f"Command not allowed. Allowed: {', '.join(allowed_prefixes)}"
|
|
97
122
|
}
|
|
98
123
|
|
|
99
|
-
full_cmd = ["kubectl"] + cmd_parts
|
|
124
|
+
full_cmd = ["kubectl"] + _get_kubectl_context_args(context) + cmd_parts
|
|
100
125
|
result = subprocess.run(full_cmd, capture_output=True, text=True, timeout=60)
|
|
101
126
|
|
|
102
127
|
return {
|
|
103
128
|
"success": result.returncode == 0,
|
|
129
|
+
"context": context or "current",
|
|
104
130
|
"output": result.stdout,
|
|
105
131
|
"error": result.stderr if result.returncode != 0 else None
|
|
106
132
|
}
|
|
@@ -114,8 +140,17 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
114
140
|
destructiveHint=True,
|
|
115
141
|
),
|
|
116
142
|
)
|
|
117
|
-
def kubectl_patch(resource_type: str, name: str, patch: str, patch_type: str = "strategic", namespace: Optional[str] = "default") -> Dict[str, Any]:
|
|
118
|
-
"""Patch a Kubernetes resource.
|
|
143
|
+
def kubectl_patch(resource_type: str, name: str, patch: str, patch_type: str = "strategic", namespace: Optional[str] = "default", context: str = "") -> Dict[str, Any]:
|
|
144
|
+
"""Patch a Kubernetes resource.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
resource_type: Type of resource to patch
|
|
148
|
+
name: Name of the resource
|
|
149
|
+
patch: JSON patch content
|
|
150
|
+
patch_type: Type of patch (strategic, merge, json)
|
|
151
|
+
namespace: Target namespace
|
|
152
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
153
|
+
"""
|
|
119
154
|
blocked = check_destructive()
|
|
120
155
|
if blocked:
|
|
121
156
|
return blocked
|
|
@@ -126,8 +161,8 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
126
161
|
"json": "json"
|
|
127
162
|
}.get(patch_type, "strategic")
|
|
128
163
|
|
|
129
|
-
cmd = [
|
|
130
|
-
"
|
|
164
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + [
|
|
165
|
+
"patch", resource_type, name,
|
|
131
166
|
"-n", namespace,
|
|
132
167
|
"--type", type_flag,
|
|
133
168
|
"-p", patch
|
|
@@ -135,7 +170,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
135
170
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
136
171
|
|
|
137
172
|
if result.returncode == 0:
|
|
138
|
-
return {"success": True, "output": result.stdout.strip()}
|
|
173
|
+
return {"success": True, "context": context or "current", "output": result.stdout.strip()}
|
|
139
174
|
else:
|
|
140
175
|
return {"success": False, "error": result.stderr.strip()}
|
|
141
176
|
except Exception as e:
|
|
@@ -148,8 +183,16 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
148
183
|
destructiveHint=True,
|
|
149
184
|
),
|
|
150
185
|
)
|
|
151
|
-
def kubectl_rollout(action: str, resource_type: str, name: str, namespace: Optional[str] = "default") -> Dict[str, Any]:
|
|
152
|
-
"""Manage rollouts (restart, status, history, undo, pause, resume).
|
|
186
|
+
def kubectl_rollout(action: str, resource_type: str, name: str, namespace: Optional[str] = "default", context: str = "") -> Dict[str, Any]:
|
|
187
|
+
"""Manage rollouts (restart, status, history, undo, pause, resume).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
action: Rollout action (status, history, restart, undo, pause, resume)
|
|
191
|
+
resource_type: Type of resource (deployment, statefulset, daemonset)
|
|
192
|
+
name: Name of the resource
|
|
193
|
+
namespace: Target namespace
|
|
194
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
195
|
+
"""
|
|
153
196
|
try:
|
|
154
197
|
allowed_actions = ["status", "history", "restart", "undo", "pause", "resume"]
|
|
155
198
|
if action not in allowed_actions:
|
|
@@ -161,11 +204,11 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
161
204
|
if blocked:
|
|
162
205
|
return blocked
|
|
163
206
|
|
|
164
|
-
cmd = ["kubectl"
|
|
207
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["rollout", action, f"{resource_type}/{name}", "-n", namespace]
|
|
165
208
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
166
209
|
|
|
167
210
|
if result.returncode == 0:
|
|
168
|
-
return {"success": True, "output": result.stdout.strip()}
|
|
211
|
+
return {"success": True, "context": context or "current", "output": result.stdout.strip()}
|
|
169
212
|
else:
|
|
170
213
|
return {"success": False, "error": result.stderr.strip()}
|
|
171
214
|
except Exception as e:
|
|
@@ -178,18 +221,26 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
178
221
|
destructiveHint=True,
|
|
179
222
|
),
|
|
180
223
|
)
|
|
181
|
-
def kubectl_create(resource_type: str, name: str, namespace: Optional[str] = "default", image: Optional[str] = None) -> Dict[str, Any]:
|
|
182
|
-
"""Create a Kubernetes resource.
|
|
224
|
+
def kubectl_create(resource_type: str, name: str, namespace: Optional[str] = "default", image: Optional[str] = None, context: str = "") -> Dict[str, Any]:
|
|
225
|
+
"""Create a Kubernetes resource.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
resource_type: Type of resource to create
|
|
229
|
+
name: Name of the resource
|
|
230
|
+
namespace: Target namespace
|
|
231
|
+
image: Container image (for deployment/pod)
|
|
232
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
233
|
+
"""
|
|
183
234
|
blocked = check_destructive()
|
|
184
235
|
if blocked:
|
|
185
236
|
return blocked
|
|
186
237
|
try:
|
|
187
|
-
cmd = ["kubectl"
|
|
238
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["create", resource_type, name, "-n", namespace]
|
|
188
239
|
if image and resource_type in ["deployment", "pod"]:
|
|
189
240
|
cmd.extend(["--image", image])
|
|
190
241
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
191
242
|
if result.returncode == 0:
|
|
192
|
-
return {"success": True, "output": result.stdout.strip()}
|
|
243
|
+
return {"success": True, "context": context or "current", "output": result.stdout.strip()}
|
|
193
244
|
else:
|
|
194
245
|
return {"success": False, "error": result.stderr.strip()}
|
|
195
246
|
except Exception as e:
|
|
@@ -202,16 +253,23 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
202
253
|
destructiveHint=True,
|
|
203
254
|
),
|
|
204
255
|
)
|
|
205
|
-
def delete_resource(resource_type: str, name: str, namespace: Optional[str] = "default") -> Dict[str, Any]:
|
|
206
|
-
"""Delete a Kubernetes resource.
|
|
256
|
+
def delete_resource(resource_type: str, name: str, namespace: Optional[str] = "default", context: str = "") -> Dict[str, Any]:
|
|
257
|
+
"""Delete a Kubernetes resource.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
resource_type: Type of resource to delete
|
|
261
|
+
name: Name of the resource
|
|
262
|
+
namespace: Target namespace
|
|
263
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
264
|
+
"""
|
|
207
265
|
blocked = check_destructive()
|
|
208
266
|
if blocked:
|
|
209
267
|
return blocked
|
|
210
268
|
try:
|
|
211
|
-
cmd = ["kubectl"
|
|
269
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["delete", resource_type, name, "-n", namespace]
|
|
212
270
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
213
271
|
if result.returncode == 0:
|
|
214
|
-
return {"success": True, "message": f"Deleted {resource_type}/{name}"}
|
|
272
|
+
return {"success": True, "context": context or "current", "message": f"Deleted {resource_type}/{name}"}
|
|
215
273
|
else:
|
|
216
274
|
return {"success": False, "error": result.stderr.strip()}
|
|
217
275
|
except Exception as e:
|
|
@@ -224,23 +282,30 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
224
282
|
destructiveHint=True,
|
|
225
283
|
),
|
|
226
284
|
)
|
|
227
|
-
def kubectl_cp(source: str, destination: str, namespace: str = "default", container: Optional[str] = None) -> Dict[str, Any]:
|
|
285
|
+
def kubectl_cp(source: str, destination: str, namespace: str = "default", container: Optional[str] = None, context: str = "") -> Dict[str, Any]:
|
|
228
286
|
"""Copy files between local filesystem and pods.
|
|
229
287
|
|
|
230
288
|
Use pod:path format for pod paths, e.g.:
|
|
231
289
|
- Local to pod: kubectl_cp("/tmp/file.txt", "mypod:/tmp/file.txt")
|
|
232
290
|
- Pod to local: kubectl_cp("mypod:/tmp/file.txt", "/tmp/file.txt")
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
source: Source path (local path or pod:path)
|
|
294
|
+
destination: Destination path (local path or pod:path)
|
|
295
|
+
namespace: Target namespace
|
|
296
|
+
container: Container name (optional)
|
|
297
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
233
298
|
"""
|
|
234
299
|
blocked = check_destructive()
|
|
235
300
|
if blocked:
|
|
236
301
|
return blocked
|
|
237
302
|
try:
|
|
238
|
-
cmd = ["kubectl"
|
|
303
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["cp", source, destination, "-n", namespace]
|
|
239
304
|
if container:
|
|
240
305
|
cmd.extend(["-c", container])
|
|
241
306
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
242
307
|
if result.returncode == 0:
|
|
243
|
-
return {"success": True, "message": f"Copied {source} to {destination}"}
|
|
308
|
+
return {"success": True, "context": context or "current", "message": f"Copied {source} to {destination}"}
|
|
244
309
|
else:
|
|
245
310
|
return {"success": False, "error": result.stderr.strip()}
|
|
246
311
|
except Exception as e:
|
|
@@ -253,10 +318,17 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
253
318
|
readOnlyHint=True,
|
|
254
319
|
),
|
|
255
320
|
)
|
|
256
|
-
def backup_resource(resource_type: str, name: str, namespace: Optional[str] = None) -> Dict[str, Any]:
|
|
257
|
-
"""Export a resource as YAML for backup or migration.
|
|
321
|
+
def backup_resource(resource_type: str, name: str, namespace: Optional[str] = None, context: str = "") -> Dict[str, Any]:
|
|
322
|
+
"""Export a resource as YAML for backup or migration.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
resource_type: Type of resource to export
|
|
326
|
+
name: Name of the resource
|
|
327
|
+
namespace: Target namespace (optional)
|
|
328
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
329
|
+
"""
|
|
258
330
|
try:
|
|
259
|
-
cmd = ["kubectl"
|
|
331
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["get", resource_type, name, "-o", "yaml"]
|
|
260
332
|
if namespace:
|
|
261
333
|
cmd.extend(["-n", namespace])
|
|
262
334
|
|
|
@@ -267,6 +339,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
267
339
|
|
|
268
340
|
return {
|
|
269
341
|
"success": True,
|
|
342
|
+
"context": context or "current",
|
|
270
343
|
"resource": {
|
|
271
344
|
"type": resource_type,
|
|
272
345
|
"name": name,
|
|
@@ -290,14 +363,24 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
290
363
|
name: str,
|
|
291
364
|
labels: Dict[str, str],
|
|
292
365
|
namespace: Optional[str] = None,
|
|
293
|
-
overwrite: bool = False
|
|
366
|
+
overwrite: bool = False,
|
|
367
|
+
context: str = ""
|
|
294
368
|
) -> Dict[str, Any]:
|
|
295
|
-
"""Add or update labels on a resource.
|
|
369
|
+
"""Add or update labels on a resource.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
resource_type: Type of resource to label
|
|
373
|
+
name: Name of the resource
|
|
374
|
+
labels: Labels to apply (use None value to remove a label)
|
|
375
|
+
namespace: Target namespace (optional)
|
|
376
|
+
overwrite: Overwrite existing labels
|
|
377
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
378
|
+
"""
|
|
296
379
|
blocked = check_destructive()
|
|
297
380
|
if blocked:
|
|
298
381
|
return blocked
|
|
299
382
|
try:
|
|
300
|
-
cmd = ["kubectl"
|
|
383
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["label", resource_type, name]
|
|
301
384
|
if namespace:
|
|
302
385
|
cmd.extend(["-n", namespace])
|
|
303
386
|
|
|
@@ -317,6 +400,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
317
400
|
|
|
318
401
|
return {
|
|
319
402
|
"success": True,
|
|
403
|
+
"context": context or "current",
|
|
320
404
|
"message": result.stdout.strip(),
|
|
321
405
|
"resource": {"type": resource_type, "name": name, "namespace": namespace},
|
|
322
406
|
"appliedLabels": labels
|
|
@@ -336,14 +420,24 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
336
420
|
name: str,
|
|
337
421
|
annotations: Dict[str, str],
|
|
338
422
|
namespace: Optional[str] = None,
|
|
339
|
-
overwrite: bool = False
|
|
423
|
+
overwrite: bool = False,
|
|
424
|
+
context: str = ""
|
|
340
425
|
) -> Dict[str, Any]:
|
|
341
|
-
"""Add or update annotations on a resource.
|
|
426
|
+
"""Add or update annotations on a resource.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
resource_type: Type of resource to annotate
|
|
430
|
+
name: Name of the resource
|
|
431
|
+
annotations: Annotations to apply (use None value to remove)
|
|
432
|
+
namespace: Target namespace (optional)
|
|
433
|
+
overwrite: Overwrite existing annotations
|
|
434
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
435
|
+
"""
|
|
342
436
|
blocked = check_destructive()
|
|
343
437
|
if blocked:
|
|
344
438
|
return blocked
|
|
345
439
|
try:
|
|
346
|
-
cmd = ["kubectl"
|
|
440
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["annotate", resource_type, name]
|
|
347
441
|
if namespace:
|
|
348
442
|
cmd.extend(["-n", namespace])
|
|
349
443
|
|
|
@@ -363,6 +457,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
363
457
|
|
|
364
458
|
return {
|
|
365
459
|
"success": True,
|
|
460
|
+
"context": context or "current",
|
|
366
461
|
"message": result.stdout.strip(),
|
|
367
462
|
"resource": {"type": resource_type, "name": name, "namespace": namespace},
|
|
368
463
|
"appliedAnnotations": annotations
|
|
@@ -382,9 +477,19 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
382
477
|
key: str,
|
|
383
478
|
value: Optional[str] = None,
|
|
384
479
|
effect: str = "NoSchedule",
|
|
385
|
-
remove: bool = False
|
|
480
|
+
remove: bool = False,
|
|
481
|
+
context: str = ""
|
|
386
482
|
) -> Dict[str, Any]:
|
|
387
|
-
"""Add or remove taints on a node.
|
|
483
|
+
"""Add or remove taints on a node.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
node_name: Name of the node
|
|
487
|
+
key: Taint key
|
|
488
|
+
value: Taint value (optional)
|
|
489
|
+
effect: Taint effect (NoSchedule, PreferNoSchedule, NoExecute)
|
|
490
|
+
remove: Remove the taint instead of adding
|
|
491
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
492
|
+
"""
|
|
388
493
|
blocked = check_destructive()
|
|
389
494
|
if blocked:
|
|
390
495
|
return blocked
|
|
@@ -392,7 +497,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
392
497
|
if effect not in ["NoSchedule", "PreferNoSchedule", "NoExecute"]:
|
|
393
498
|
return {"success": False, "error": f"Invalid effect: {effect}. Must be NoSchedule, PreferNoSchedule, or NoExecute"}
|
|
394
499
|
|
|
395
|
-
cmd = ["kubectl"
|
|
500
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["taint", "nodes", node_name]
|
|
396
501
|
|
|
397
502
|
if remove:
|
|
398
503
|
taint_str = f"{key}:{effect}-"
|
|
@@ -411,6 +516,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
411
516
|
|
|
412
517
|
return {
|
|
413
518
|
"success": True,
|
|
519
|
+
"context": context or "current",
|
|
414
520
|
"message": result.stdout.strip(),
|
|
415
521
|
"node": node_name,
|
|
416
522
|
"action": "removed" if remove else "added",
|
|
@@ -431,11 +537,21 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
431
537
|
name: str,
|
|
432
538
|
condition: str,
|
|
433
539
|
namespace: Optional[str] = None,
|
|
434
|
-
timeout: int = 60
|
|
540
|
+
timeout: int = 60,
|
|
541
|
+
context: str = ""
|
|
435
542
|
) -> Dict[str, Any]:
|
|
436
|
-
"""Wait for a resource to reach a specific condition.
|
|
543
|
+
"""Wait for a resource to reach a specific condition.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
resource_type: Type of resource to wait for
|
|
547
|
+
name: Name of the resource
|
|
548
|
+
condition: Condition to wait for (e.g., condition=Ready, delete)
|
|
549
|
+
namespace: Target namespace (optional)
|
|
550
|
+
timeout: Timeout in seconds
|
|
551
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
552
|
+
"""
|
|
437
553
|
try:
|
|
438
|
-
cmd = ["kubectl"
|
|
554
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["wait", f"{resource_type}/{name}", f"--for={condition}", f"--timeout={timeout}s"]
|
|
439
555
|
if namespace:
|
|
440
556
|
cmd.extend(["-n", namespace])
|
|
441
557
|
|
|
@@ -446,6 +562,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
446
562
|
"success": False,
|
|
447
563
|
"conditionMet": False,
|
|
448
564
|
"error": result.stderr.strip(),
|
|
565
|
+
"context": context or "current",
|
|
449
566
|
"resource": {"type": resource_type, "name": name, "namespace": namespace},
|
|
450
567
|
"condition": condition
|
|
451
568
|
}
|
|
@@ -453,6 +570,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
453
570
|
return {
|
|
454
571
|
"success": True,
|
|
455
572
|
"conditionMet": True,
|
|
573
|
+
"context": context or "current",
|
|
456
574
|
"message": result.stdout.strip(),
|
|
457
575
|
"resource": {"type": resource_type, "name": name, "namespace": namespace},
|
|
458
576
|
"condition": condition
|
|
@@ -462,6 +580,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
462
580
|
"success": False,
|
|
463
581
|
"conditionMet": False,
|
|
464
582
|
"error": f"Timeout waiting for condition '{condition}' after {timeout}s",
|
|
583
|
+
"context": context or "current",
|
|
465
584
|
"resource": {"type": resource_type, "name": name, "namespace": namespace}
|
|
466
585
|
}
|
|
467
586
|
except Exception as e:
|
|
@@ -474,8 +593,15 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
474
593
|
destructiveHint=True,
|
|
475
594
|
),
|
|
476
595
|
)
|
|
477
|
-
def node_management(action: str, node_name: str, force: bool = False) -> Dict[str, Any]:
|
|
478
|
-
"""Manage nodes: cordon, uncordon, or drain.
|
|
596
|
+
def node_management(action: str, node_name: str, force: bool = False, context: str = "") -> Dict[str, Any]:
|
|
597
|
+
"""Manage nodes: cordon, uncordon, or drain.
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
action: Action to perform (cordon, uncordon, drain)
|
|
601
|
+
node_name: Name of the node
|
|
602
|
+
force: Force drain (for drain action)
|
|
603
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
604
|
+
"""
|
|
479
605
|
blocked = check_destructive()
|
|
480
606
|
if blocked:
|
|
481
607
|
return blocked
|
|
@@ -484,7 +610,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
484
610
|
if action not in allowed_actions:
|
|
485
611
|
return {"success": False, "error": f"Invalid action. Allowed: {', '.join(allowed_actions)}"}
|
|
486
612
|
|
|
487
|
-
cmd = ["kubectl"
|
|
613
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + [action, node_name]
|
|
488
614
|
if action == "drain":
|
|
489
615
|
cmd.extend(["--ignore-daemonsets", "--delete-emptydir-data"])
|
|
490
616
|
if force:
|
|
@@ -493,7 +619,7 @@ def register_operations_tools(server, non_destructive: bool):
|
|
|
493
619
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
494
620
|
|
|
495
621
|
if result.returncode == 0:
|
|
496
|
-
return {"success": True, "output": result.stdout.strip()}
|
|
622
|
+
return {"success": True, "context": context or "current", "output": result.stdout.strip()}
|
|
497
623
|
else:
|
|
498
624
|
return {"success": False, "error": result.stderr.strip()}
|
|
499
625
|
except Exception as e:
|