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.
@@ -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", "apply", "-f", temp_path, "-n", namespace]
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", "describe", resource_type, name, "-n", namespace]
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
- "kubectl", "patch", resource_type, name,
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", "rollout", action, f"{resource_type}/{name}", "-n", namespace]
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", "create", resource_type, name, "-n", namespace]
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", "delete", resource_type, name, "-n", namespace]
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", "cp", source, destination, "-n", namespace]
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", "get", resource_type, name, "-o", "yaml"]
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", "label", resource_type, name]
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", "annotate", resource_type, name]
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", "taint", "nodes", node_name]
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", "wait", f"{resource_type}/{name}", f"--for={condition}", f"--timeout={timeout}s"]
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", action, node_name]
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: