kubectl-mcp-server 1.19.0__py3-none-any.whl → 1.19.1__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.
@@ -1,12 +1,7 @@
1
- """GitOps toolset for kubectl-mcp-server.
1
+ """GitOps toolset for kubectl-mcp-server (Flux and Argo CD)."""
2
2
 
3
- Provides tools for managing Flux and Argo CD applications.
4
- """
5
-
6
- import subprocess
7
3
  import json
8
4
  from typing import Dict, Any, List
9
- from datetime import datetime
10
5
 
11
6
  try:
12
7
  from fastmcp import FastMCP
@@ -15,8 +10,8 @@ except ImportError:
15
10
  from mcp.server.fastmcp import FastMCP
16
11
  from mcp.types import ToolAnnotations
17
12
 
18
- from ..k8s_config import _get_kubectl_context_args
19
13
  from ..crd_detector import crd_exists, require_any_crd
14
+ from .utils import run_kubectl, get_resources
20
15
 
21
16
 
22
17
  FLUX_KUSTOMIZATION_CRD = "kustomizations.kustomize.toolkit.fluxcd.io"
@@ -27,40 +22,6 @@ ARGOCD_APP_CRD = "applications.argoproj.io"
27
22
  ARGOCD_APPSET_CRD = "applicationsets.argoproj.io"
28
23
 
29
24
 
30
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
31
- """Run kubectl command and return result."""
32
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
33
- try:
34
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
35
- if result.returncode == 0:
36
- return {"success": True, "output": result.stdout}
37
- return {"success": False, "error": result.stderr}
38
- except subprocess.TimeoutExpired:
39
- return {"success": False, "error": "Command timed out"}
40
- except Exception as e:
41
- return {"success": False, "error": str(e)}
42
-
43
-
44
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
45
- """Get Kubernetes resources of a specific kind."""
46
- args = ["get", kind, "-o", "json"]
47
- if namespace:
48
- args.extend(["-n", namespace])
49
- else:
50
- args.append("-A")
51
- if label_selector:
52
- args.extend(["-l", label_selector])
53
-
54
- result = _run_kubectl(args, context)
55
- if result["success"]:
56
- try:
57
- data = json.loads(result["output"])
58
- return data.get("items", [])
59
- except json.JSONDecodeError:
60
- return []
61
- return []
62
-
63
-
64
25
  def gitops_apps_list(
65
26
  namespace: str = "",
66
27
  context: str = "",
@@ -82,7 +43,7 @@ def gitops_apps_list(
82
43
 
83
44
  if not kind or kind.lower() == "kustomization":
84
45
  if crd_exists(FLUX_KUSTOMIZATION_CRD, context):
85
- for item in _get_resources("kustomizations.kustomize.toolkit.fluxcd.io", namespace, context, label_selector):
46
+ for item in get_resources("kustomizations.kustomize.toolkit.fluxcd.io", namespace, context, label_selector):
86
47
  status = item.get("status", {})
87
48
  conditions = status.get("conditions", [])
88
49
  ready_cond = next((c for c in conditions if c.get("type") == "Ready"), {})
@@ -101,7 +62,7 @@ def gitops_apps_list(
101
62
 
102
63
  if not kind or kind.lower() == "helmrelease":
103
64
  if crd_exists(FLUX_HELMRELEASE_CRD, context):
104
- for item in _get_resources("helmreleases.helm.toolkit.fluxcd.io", namespace, context, label_selector):
65
+ for item in get_resources("helmreleases.helm.toolkit.fluxcd.io", namespace, context, label_selector):
105
66
  status = item.get("status", {})
106
67
  conditions = status.get("conditions", [])
107
68
  ready_cond = next((c for c in conditions if c.get("type") == "Ready"), {})
@@ -119,7 +80,7 @@ def gitops_apps_list(
119
80
 
120
81
  if not kind or kind.lower() == "application":
121
82
  if crd_exists(ARGOCD_APP_CRD, context):
122
- for item in _get_resources("applications.argoproj.io", namespace, context, label_selector):
83
+ for item in get_resources("applications.argoproj.io", namespace, context, label_selector):
123
84
  status = item.get("status", {})
124
85
  health = status.get("health", {})
125
86
  sync = status.get("sync", {})
@@ -171,7 +132,7 @@ def gitops_app_get(
171
132
  return {"success": False, "error": f"Unknown kind: {kind}"}
172
133
 
173
134
  args = ["get", k8s_kind, name, "-n", namespace, "-o", "json"]
174
- result = _run_kubectl(args, context)
135
+ result = run_kubectl(args, context)
175
136
 
176
137
  if result["success"]:
177
138
  try:
@@ -218,7 +179,7 @@ def gitops_app_sync(
218
179
  name, "-n", namespace,
219
180
  f"{annotation}={timestamp}", "--overwrite"
220
181
  ]
221
- result = _run_kubectl(args, context)
182
+ result = run_kubectl(args, context)
222
183
 
223
184
  if result["success"]:
224
185
  return {
@@ -241,7 +202,7 @@ def gitops_app_sync(
241
202
  name, "-n", namespace,
242
203
  f"{annotation}={timestamp}", "--overwrite"
243
204
  ]
244
- result = _run_kubectl(args, context)
205
+ result = run_kubectl(args, context)
245
206
 
246
207
  if result["success"]:
247
208
  return {
@@ -262,7 +223,7 @@ def gitops_app_sync(
262
223
  name, "-n", namespace,
263
224
  f"{annotation}=hard", "--overwrite"
264
225
  ]
265
- result = _run_kubectl(args, context)
226
+ result = run_kubectl(args, context)
266
227
 
267
228
  if result["success"]:
268
229
  return {
@@ -363,7 +324,7 @@ def gitops_sources_list(
363
324
 
364
325
  if not kind or kind.lower() == "gitrepository":
365
326
  if crd_exists(FLUX_GITREPO_CRD, context):
366
- for item in _get_resources("gitrepositories.source.toolkit.fluxcd.io", namespace, context, label_selector):
327
+ for item in get_resources("gitrepositories.source.toolkit.fluxcd.io", namespace, context, label_selector):
367
328
  status = item.get("status", {})
368
329
  conditions = status.get("conditions", [])
369
330
  ready_cond = next((c for c in conditions if c.get("type") == "Ready"), {})
@@ -382,7 +343,7 @@ def gitops_sources_list(
382
343
 
383
344
  if not kind or kind.lower() == "helmrepository":
384
345
  if crd_exists(FLUX_HELMREPO_CRD, context):
385
- for item in _get_resources("helmrepositories.source.toolkit.fluxcd.io", namespace, context, label_selector):
346
+ for item in get_resources("helmrepositories.source.toolkit.fluxcd.io", namespace, context, label_selector):
386
347
  status = item.get("status", {})
387
348
  conditions = status.get("conditions", [])
388
349
  ready_cond = next((c for c in conditions if c.get("type") == "Ready"), {})
@@ -432,7 +393,7 @@ def gitops_source_get(
432
393
  return {"success": False, "error": f"Unknown kind: {kind}"}
433
394
 
434
395
  args = ["get", k8s_kind, name, "-n", namespace, "-o", "json"]
435
- result = _run_kubectl(args, context)
396
+ result = run_kubectl(args, context)
436
397
 
437
398
  if result["success"]:
438
399
  try:
@@ -1,9 +1,5 @@
1
- """KEDA autoscaling toolset for kubectl-mcp-server.
1
+ """KEDA autoscaling toolset for kubectl-mcp-server."""
2
2
 
3
- Provides tools for managing KEDA ScaledObjects, ScaledJobs, and TriggerAuthentications.
4
- """
5
-
6
- import subprocess
7
3
  import json
8
4
  from typing import Dict, Any, List
9
5
 
@@ -14,8 +10,8 @@ except ImportError:
14
10
  from mcp.server.fastmcp import FastMCP
15
11
  from mcp.types import ToolAnnotations
16
12
 
17
- from ..k8s_config import _get_kubectl_context_args
18
13
  from ..crd_detector import crd_exists
14
+ from .utils import run_kubectl, get_resources
19
15
 
20
16
 
21
17
  SCALEDOBJECT_CRD = "scaledobjects.keda.sh"
@@ -24,40 +20,6 @@ TRIGGERAUTH_CRD = "triggerauthentications.keda.sh"
24
20
  CLUSTERTRIGGERAUTH_CRD = "clustertriggerauthentications.keda.sh"
25
21
 
26
22
 
27
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
28
- """Run kubectl command and return result."""
29
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
30
- try:
31
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
32
- if result.returncode == 0:
33
- return {"success": True, "output": result.stdout}
34
- return {"success": False, "error": result.stderr}
35
- except subprocess.TimeoutExpired:
36
- return {"success": False, "error": "Command timed out"}
37
- except Exception as e:
38
- return {"success": False, "error": str(e)}
39
-
40
-
41
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
42
- """Get Kubernetes resources of a specific kind."""
43
- args = ["get", kind, "-o", "json"]
44
- if namespace:
45
- args.extend(["-n", namespace])
46
- else:
47
- args.append("-A")
48
- if label_selector:
49
- args.extend(["-l", label_selector])
50
-
51
- result = _run_kubectl(args, context)
52
- if result["success"]:
53
- try:
54
- data = json.loads(result["output"])
55
- return data.get("items", [])
56
- except json.JSONDecodeError:
57
- return []
58
- return []
59
-
60
-
61
23
  def keda_scaledobjects_list(
62
24
  namespace: str = "",
63
25
  context: str = "",
@@ -80,7 +42,7 @@ def keda_scaledobjects_list(
80
42
  }
81
43
 
82
44
  objects = []
83
- for item in _get_resources("scaledobjects.keda.sh", namespace, context, label_selector):
45
+ for item in get_resources("scaledobjects.keda.sh", namespace, context, label_selector):
84
46
  status = item.get("status", {})
85
47
  spec = item.get("spec", {})
86
48
  conditions = status.get("conditions", [])
@@ -138,7 +100,7 @@ def keda_scaledobject_get(
138
100
  return {"success": False, "error": "KEDA is not installed"}
139
101
 
140
102
  args = ["get", "scaledobjects.keda.sh", name, "-n", namespace, "-o", "json"]
141
- result = _run_kubectl(args, context)
103
+ result = run_kubectl(args, context)
142
104
 
143
105
  if result["success"]:
144
106
  try:
@@ -176,7 +138,7 @@ def keda_scaledjobs_list(
176
138
  }
177
139
 
178
140
  jobs = []
179
- for item in _get_resources("scaledjobs.keda.sh", namespace, context, label_selector):
141
+ for item in get_resources("scaledjobs.keda.sh", namespace, context, label_selector):
180
142
  status = item.get("status", {})
181
143
  spec = item.get("spec", {})
182
144
  conditions = status.get("conditions", [])
@@ -227,7 +189,7 @@ def keda_triggerauths_list(
227
189
  auths = []
228
190
 
229
191
  if crd_exists(TRIGGERAUTH_CRD, context):
230
- for item in _get_resources("triggerauthentications.keda.sh", namespace, context):
192
+ for item in get_resources("triggerauthentications.keda.sh", namespace, context):
231
193
  spec = item.get("spec", {})
232
194
  secret_refs = spec.get("secretTargetRef", [])
233
195
  env_refs = spec.get("env", [])
@@ -244,7 +206,7 @@ def keda_triggerauths_list(
244
206
  })
245
207
 
246
208
  if include_cluster and crd_exists(CLUSTERTRIGGERAUTH_CRD, context):
247
- for item in _get_resources("clustertriggerauthentications.keda.sh", "", context):
209
+ for item in get_resources("clustertriggerauthentications.keda.sh", "", context):
248
210
  spec = item.get("spec", {})
249
211
  secret_refs = spec.get("secretTargetRef", [])
250
212
  env_refs = spec.get("env", [])
@@ -294,7 +256,7 @@ def keda_triggerauth_get(
294
256
  if not crd_exists(crd, context):
295
257
  return {"success": False, "error": f"{crd} not found"}
296
258
 
297
- result = _run_kubectl(args, context)
259
+ result = run_kubectl(args, context)
298
260
 
299
261
  if result["success"]:
300
262
  try:
@@ -337,7 +299,7 @@ def keda_hpa_list(
337
299
  if selector:
338
300
  args.extend(["-l", selector])
339
301
 
340
- result = _run_kubectl(args, context)
302
+ result = run_kubectl(args, context)
341
303
  if not result["success"]:
342
304
  return {"success": False, "error": result.get("error", "Failed to list HPAs")}
343
305
 
@@ -1,12 +1,9 @@
1
- """Kiali/Istio service mesh observability toolset for kubectl-mcp-server.
2
-
3
- Provides tools for service mesh visualization and Istio configuration inspection.
4
- """
1
+ """Kiali/Istio service mesh observability toolset for kubectl-mcp-server."""
5
2
 
6
3
  import subprocess
7
4
  import json
8
5
  import os
9
- from typing import Dict, Any, List, Optional
6
+ from typing import Dict, Any, List
10
7
 
11
8
  try:
12
9
  from fastmcp import FastMCP
@@ -15,11 +12,10 @@ except ImportError:
15
12
  from mcp.server.fastmcp import FastMCP
16
13
  from mcp.types import ToolAnnotations
17
14
 
18
- from ..k8s_config import _get_kubectl_context_args
19
15
  from ..crd_detector import crd_exists
16
+ from .utils import run_kubectl, get_resources
20
17
 
21
18
 
22
- # Istio CRDs
23
19
  VIRTUALSERVICE_CRD = "virtualservices.networking.istio.io"
24
20
  DESTINATIONRULE_CRD = "destinationrules.networking.istio.io"
25
21
  GATEWAY_CRD = "gateways.networking.istio.io"
@@ -30,40 +26,6 @@ AUTHORIZATIONPOLICY_CRD = "authorizationpolicies.security.istio.io"
30
26
  REQUESTAUTHENTICATION_CRD = "requestauthentications.security.istio.io"
31
27
 
32
28
 
33
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
34
- """Run kubectl command and return result."""
35
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
36
- try:
37
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
38
- if result.returncode == 0:
39
- return {"success": True, "output": result.stdout}
40
- return {"success": False, "error": result.stderr}
41
- except subprocess.TimeoutExpired:
42
- return {"success": False, "error": "Command timed out"}
43
- except Exception as e:
44
- return {"success": False, "error": str(e)}
45
-
46
-
47
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
48
- """Get Kubernetes resources of a specific kind."""
49
- args = ["get", kind, "-o", "json"]
50
- if namespace:
51
- args.extend(["-n", namespace])
52
- else:
53
- args.append("-A")
54
- if label_selector:
55
- args.extend(["-l", label_selector])
56
-
57
- result = _run_kubectl(args, context)
58
- if result["success"]:
59
- try:
60
- data = json.loads(result["output"])
61
- return data.get("items", [])
62
- except json.JSONDecodeError:
63
- return []
64
- return []
65
-
66
-
67
29
  def _istioctl_available() -> bool:
68
30
  """Check if istioctl CLI is available."""
69
31
  try:
@@ -84,8 +46,6 @@ def _get_kiali_config() -> Dict[str, str]:
84
46
  }
85
47
 
86
48
 
87
- # ============== Istio Resource Functions ==============
88
-
89
49
  def istio_virtualservices_list(
90
50
  namespace: str = "",
91
51
  context: str = "",
@@ -108,7 +68,7 @@ def istio_virtualservices_list(
108
68
  }
109
69
 
110
70
  virtualservices = []
111
- for item in _get_resources("virtualservices.networking.istio.io", namespace, context, label_selector):
71
+ for item in get_resources("virtualservices.networking.istio.io", namespace, context, label_selector):
112
72
  spec = item.get("spec", {})
113
73
  hosts = spec.get("hosts", [])
114
74
  gateways = spec.get("gateways", [])
@@ -153,7 +113,7 @@ def istio_virtualservice_get(
153
113
  return {"success": False, "error": "Istio is not installed"}
154
114
 
155
115
  args = ["get", "virtualservices.networking.istio.io", name, "-n", namespace, "-o", "json"]
156
- result = _run_kubectl(args, context)
116
+ result = run_kubectl(args, context)
157
117
 
158
118
  if result["success"]:
159
119
  try:
@@ -191,7 +151,7 @@ def istio_destinationrules_list(
191
151
  }
192
152
 
193
153
  rules = []
194
- for item in _get_resources("destinationrules.networking.istio.io", namespace, context, label_selector):
154
+ for item in get_resources("destinationrules.networking.istio.io", namespace, context, label_selector):
195
155
  spec = item.get("spec", {})
196
156
  traffic_policy = spec.get("trafficPolicy", {})
197
157
  subsets = spec.get("subsets", [])
@@ -238,7 +198,7 @@ def istio_gateways_list(
238
198
  }
239
199
 
240
200
  gateways = []
241
- for item in _get_resources("gateways.networking.istio.io", namespace, context, label_selector):
201
+ for item in get_resources("gateways.networking.istio.io", namespace, context, label_selector):
242
202
  spec = item.get("spec", {})
243
203
  selector = spec.get("selector", {})
244
204
  servers = spec.get("servers", [])
@@ -294,7 +254,7 @@ def istio_peerauthentications_list(
294
254
  }
295
255
 
296
256
  policies = []
297
- for item in _get_resources("peerauthentications.security.istio.io", namespace, context, label_selector):
257
+ for item in get_resources("peerauthentications.security.istio.io", namespace, context, label_selector):
298
258
  spec = item.get("spec", {})
299
259
  selector = spec.get("selector", {})
300
260
  mtls = spec.get("mtls", {})
@@ -337,7 +297,7 @@ def istio_authorizationpolicies_list(
337
297
  }
338
298
 
339
299
  policies = []
340
- for item in _get_resources("authorizationpolicies.security.istio.io", namespace, context, label_selector):
300
+ for item in get_resources("authorizationpolicies.security.istio.io", namespace, context, label_selector):
341
301
  spec = item.get("spec", {})
342
302
  selector = spec.get("selector", {})
343
303
  rules = spec.get("rules", [])
@@ -503,7 +463,7 @@ def istio_sidecar_status(
503
463
  else:
504
464
  args.append("-A")
505
465
 
506
- result = _run_kubectl(args, context)
466
+ result = run_kubectl(args, context)
507
467
  if not result["success"]:
508
468
  return {"success": False, "error": result.get("error", "Failed to list pods")}
509
469
 
@@ -1,7 +1,4 @@
1
- """KubeVirt VM lifecycle toolset for kubectl-mcp-server.
2
-
3
- Provides tools for managing virtual machines on Kubernetes via KubeVirt.
4
- """
1
+ """KubeVirt VM lifecycle toolset for kubectl-mcp-server."""
5
2
 
6
3
  import subprocess
7
4
  import json
@@ -14,11 +11,10 @@ except ImportError:
14
11
  from mcp.server.fastmcp import FastMCP
15
12
  from mcp.types import ToolAnnotations
16
13
 
17
- from ..k8s_config import _get_kubectl_context_args
18
14
  from ..crd_detector import crd_exists
15
+ from .utils import run_kubectl, get_resources
19
16
 
20
17
 
21
- # KubeVirt CRDs
22
18
  VM_CRD = "virtualmachines.kubevirt.io"
23
19
  VMI_CRD = "virtualmachineinstances.kubevirt.io"
24
20
  VMIPRESET_CRD = "virtualmachineinstancepresets.kubevirt.io"
@@ -32,40 +28,6 @@ CLUSTERINSTANCETYPE_CRD = "virtualmachineclusterinstancetypes.instancetype.kubev
32
28
  PREFERENCE_CRD = "virtualmachinepreferences.instancetype.kubevirt.io"
33
29
 
34
30
 
35
- def _run_kubectl(args: List[str], context: str = "") -> Dict[str, Any]:
36
- """Run kubectl command and return result."""
37
- cmd = ["kubectl"] + _get_kubectl_context_args(context) + args
38
- try:
39
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
40
- if result.returncode == 0:
41
- return {"success": True, "output": result.stdout}
42
- return {"success": False, "error": result.stderr}
43
- except subprocess.TimeoutExpired:
44
- return {"success": False, "error": "Command timed out"}
45
- except Exception as e:
46
- return {"success": False, "error": str(e)}
47
-
48
-
49
- def _get_resources(kind: str, namespace: str = "", context: str = "", label_selector: str = "") -> List[Dict]:
50
- """Get Kubernetes resources of a specific kind."""
51
- args = ["get", kind, "-o", "json"]
52
- if namespace:
53
- args.extend(["-n", namespace])
54
- else:
55
- args.append("-A")
56
- if label_selector:
57
- args.extend(["-l", label_selector])
58
-
59
- result = _run_kubectl(args, context)
60
- if result["success"]:
61
- try:
62
- data = json.loads(result["output"])
63
- return data.get("items", [])
64
- except json.JSONDecodeError:
65
- return []
66
- return []
67
-
68
-
69
31
  def _virtctl_available() -> bool:
70
32
  """Check if virtctl CLI is available."""
71
33
  try:
@@ -118,7 +80,7 @@ def kubevirt_vms_list(
118
80
  }
119
81
 
120
82
  vms = []
121
- for item in _get_resources("virtualmachines.kubevirt.io", namespace, context, label_selector):
83
+ for item in get_resources("virtualmachines.kubevirt.io", namespace, context, label_selector):
122
84
  status = item.get("status", {})
123
85
  spec = item.get("spec", {})
124
86
  conditions = status.get("conditions", [])
@@ -182,7 +144,7 @@ def kubevirt_vm_get(
182
144
  return {"success": False, "error": "KubeVirt is not installed"}
183
145
 
184
146
  args = ["get", "virtualmachines.kubevirt.io", name, "-n", namespace, "-o", "json"]
185
- result = _run_kubectl(args, context)
147
+ result = run_kubectl(args, context)
186
148
 
187
149
  if result["success"]:
188
150
  try:
@@ -220,7 +182,7 @@ def kubevirt_vmis_list(
220
182
  }
221
183
 
222
184
  vmis = []
223
- for item in _get_resources("virtualmachineinstances.kubevirt.io", namespace, context, label_selector):
185
+ for item in get_resources("virtualmachineinstances.kubevirt.io", namespace, context, label_selector):
224
186
  status = item.get("status", {})
225
187
  spec = item.get("spec", {})
226
188
  conditions = status.get("conditions", [])
@@ -300,7 +262,7 @@ def kubevirt_vm_start(
300
262
  "--type=merge",
301
263
  "-p", json.dumps(patch)
302
264
  ]
303
- result = _run_kubectl(args, context)
265
+ result = run_kubectl(args, context)
304
266
 
305
267
  if result["success"]:
306
268
  return {
@@ -354,7 +316,7 @@ def kubevirt_vm_stop(
354
316
  "--type=merge",
355
317
  "-p", json.dumps(patch)
356
318
  ]
357
- result = _run_kubectl(args, context)
319
+ result = run_kubectl(args, context)
358
320
 
359
321
  if result["success"]:
360
322
  return {
@@ -516,7 +478,7 @@ def kubevirt_datasources_list(
516
478
  }
517
479
 
518
480
  datasources = []
519
- for item in _get_resources("datasources.cdi.kubevirt.io", namespace, context, label_selector):
481
+ for item in get_resources("datasources.cdi.kubevirt.io", namespace, context, label_selector):
520
482
  spec = item.get("spec", {})
521
483
  status = item.get("status", {})
522
484
  conditions = status.get("conditions", [])
@@ -557,7 +519,7 @@ def kubevirt_instancetypes_list(
557
519
  instancetypes = []
558
520
 
559
521
  if crd_exists(INSTANCETYPE_CRD, context):
560
- for item in _get_resources("virtualmachineinstancetypes.instancetype.kubevirt.io", namespace, context):
522
+ for item in get_resources("virtualmachineinstancetypes.instancetype.kubevirt.io", namespace, context):
561
523
  spec = item.get("spec", {})
562
524
  cpu = spec.get("cpu", {})
563
525
  memory = spec.get("memory", {})
@@ -573,7 +535,7 @@ def kubevirt_instancetypes_list(
573
535
  })
574
536
 
575
537
  if include_cluster and crd_exists(CLUSTERINSTANCETYPE_CRD, context):
576
- for item in _get_resources("virtualmachineclusterinstancetypes.instancetype.kubevirt.io", "", context):
538
+ for item in get_resources("virtualmachineclusterinstancetypes.instancetype.kubevirt.io", "", context):
577
539
  spec = item.get("spec", {})
578
540
  cpu = spec.get("cpu", {})
579
541
  memory = spec.get("memory", {})
@@ -617,7 +579,7 @@ def kubevirt_datavolumes_list(
617
579
  }
618
580
 
619
581
  datavolumes = []
620
- for item in _get_resources("datavolumes.cdi.kubevirt.io", namespace, context, label_selector):
582
+ for item in get_resources("datavolumes.cdi.kubevirt.io", namespace, context, label_selector):
621
583
  spec = item.get("spec", {})
622
584
  status = item.get("status", {})
623
585
  conditions = status.get("conditions", [])
@@ -287,6 +287,99 @@ def register_pod_tools(
287
287
  logger.error(f"Error cleaning up pods: {e}")
288
288
  return {"success": False, "error": str(e)}
289
289
 
290
+ @server.tool(
291
+ annotations=ToolAnnotations(
292
+ title="Run Pod",
293
+ destructiveHint=True,
294
+ ),
295
+ )
296
+ def run_pod(
297
+ image: str,
298
+ name: Optional[str] = None,
299
+ namespace: str = "default",
300
+ command: Optional[List[str]] = None,
301
+ args: Optional[List[str]] = None,
302
+ env: Optional[Dict[str, str]] = None,
303
+ labels: Optional[Dict[str, str]] = None,
304
+ restart_policy: str = "Never",
305
+ context: str = ""
306
+ ) -> Dict[str, Any]:
307
+ """Run a container image as a pod (kubectl run equivalent).
308
+
309
+ Creates a pod with the specified container image. This is useful for
310
+ quick debugging, running one-off tasks, or testing container images.
311
+
312
+ Args:
313
+ image: Container image to run (e.g., 'nginx:latest', 'busybox')
314
+ name: Name for the pod (auto-generated if not specified)
315
+ namespace: Namespace to create the pod in
316
+ command: Override the container's entrypoint (list of strings)
317
+ args: Arguments to pass to the container command (list of strings)
318
+ env: Environment variables as key-value pairs
319
+ labels: Labels to apply to the pod
320
+ restart_policy: Pod restart policy (Never, OnFailure, Always)
321
+ context: Kubernetes context to use (uses current context if not specified)
322
+
323
+ Examples:
324
+ - Run nginx: run_pod(image="nginx:latest")
325
+ - Run busybox with command: run_pod(image="busybox", command=["sh", "-c"], args=["echo hello"])
326
+ - Run with env vars: run_pod(image="alpine", env={"MY_VAR": "value"})
327
+ """
328
+ if non_destructive:
329
+ return {"success": False, "error": "Blocked: non-destructive mode"}
330
+
331
+ try:
332
+ from kubernetes import client as k8s_client
333
+ import uuid
334
+
335
+ if not name:
336
+ short_id = str(uuid.uuid4())[:8]
337
+ image_base = image.split("/")[-1].split(":")[0].replace(".", "-")
338
+ name = f"{image_base}-{short_id}"
339
+
340
+ container = k8s_client.V1Container(name=name, image=image, command=command, args=args)
341
+ if env:
342
+ container.env = [k8s_client.V1EnvVar(name=k, value=v) for k, v in env.items()]
343
+
344
+ pod_labels = {"app": name, "run": name}
345
+ if labels:
346
+ pod_labels.update(labels)
347
+
348
+ valid_policies = ["Never", "OnFailure", "Always"]
349
+ if restart_policy not in valid_policies:
350
+ return {"success": False, "error": f"Invalid restart_policy '{restart_policy}'. Must be one of: {valid_policies}"}
351
+
352
+ pod = k8s_client.V1Pod(
353
+ api_version="v1",
354
+ kind="Pod",
355
+ metadata=k8s_client.V1ObjectMeta(name=name, namespace=namespace, labels=pod_labels),
356
+ spec=k8s_client.V1PodSpec(containers=[container], restart_policy=restart_policy)
357
+ )
358
+
359
+ v1 = get_k8s_client(context)
360
+ created = v1.create_namespaced_pod(namespace=namespace, body=pod)
361
+
362
+ return {
363
+ "success": True,
364
+ "context": context or "current",
365
+ "pod": {
366
+ "name": created.metadata.name,
367
+ "namespace": created.metadata.namespace,
368
+ "image": image,
369
+ "status": created.status.phase,
370
+ "uid": created.metadata.uid
371
+ },
372
+ "message": f"Pod '{name}' created successfully in namespace '{namespace}'",
373
+ "commands": {
374
+ "logs": f"kubectl logs {name} -n {namespace}",
375
+ "exec": f"kubectl exec -it {name} -n {namespace} -- /bin/sh",
376
+ "delete": f"kubectl delete pod {name} -n {namespace}"
377
+ }
378
+ }
379
+ except Exception as e:
380
+ logger.error(f"Error running pod: {e}")
381
+ return {"success": False, "error": str(e)}
382
+
290
383
  @server.tool(
291
384
  annotations=ToolAnnotations(
292
385
  title="Get Pod Conditions Detailed",