kubectl-mcp-server 1.14.0__py3-none-any.whl → 1.16.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.16.0.dist-info/METADATA +1047 -0
- kubectl_mcp_server-1.16.0.dist-info/RECORD +61 -0
- kubectl_mcp_tool/__init__.py +1 -1
- kubectl_mcp_tool/crd_detector.py +247 -0
- kubectl_mcp_tool/k8s_config.py +304 -63
- kubectl_mcp_tool/mcp_server.py +27 -0
- kubectl_mcp_tool/tools/__init__.py +20 -0
- kubectl_mcp_tool/tools/backup.py +881 -0
- kubectl_mcp_tool/tools/capi.py +727 -0
- kubectl_mcp_tool/tools/certs.py +709 -0
- kubectl_mcp_tool/tools/cilium.py +582 -0
- 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/gitops.py +552 -0
- kubectl_mcp_tool/tools/helm.py +133 -46
- kubectl_mcp_tool/tools/keda.py +464 -0
- kubectl_mcp_tool/tools/kiali.py +652 -0
- kubectl_mcp_tool/tools/kubevirt.py +803 -0
- 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/policy.py +554 -0
- kubectl_mcp_tool/tools/rollouts.py +790 -0
- kubectl_mcp_tool/tools/security.py +89 -36
- kubectl_mcp_tool/tools/storage.py +35 -16
- tests/test_browser.py +2 -2
- tests/test_ecosystem.py +331 -0
- tests/test_tools.py +73 -10
- kubectl_mcp_server-1.14.0.dist-info/METADATA +0 -780
- kubectl_mcp_server-1.14.0.dist-info/RECORD +0 -49
- {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/WHEEL +0 -0
- {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.14.0.dist-info → kubectl_mcp_server-1.16.0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import subprocess
|
|
3
|
-
from typing import Any, Dict, Optional
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
4
|
|
|
5
5
|
from mcp.types import ToolAnnotations
|
|
6
6
|
|
|
7
|
+
from ..k8s_config import get_k8s_client, get_networking_client
|
|
8
|
+
|
|
7
9
|
logger = logging.getLogger("mcp-server")
|
|
8
10
|
|
|
9
11
|
|
|
12
|
+
def _get_kubectl_context_args(context: str) -> List[str]:
|
|
13
|
+
"""Get kubectl context arguments if context is specified."""
|
|
14
|
+
if context:
|
|
15
|
+
return ["--context", context]
|
|
16
|
+
return []
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
def register_networking_tools(server, non_destructive: bool):
|
|
11
20
|
"""Register networking-related tools."""
|
|
12
21
|
|
|
@@ -16,12 +25,18 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
16
25
|
readOnlyHint=True,
|
|
17
26
|
),
|
|
18
27
|
)
|
|
19
|
-
def get_ingress(
|
|
20
|
-
|
|
28
|
+
def get_ingress(
|
|
29
|
+
namespace: Optional[str] = None,
|
|
30
|
+
context: str = ""
|
|
31
|
+
) -> Dict[str, Any]:
|
|
32
|
+
"""Get Ingress resources in a namespace or cluster-wide.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
namespace: Namespace to list ingresses from (all namespaces if not specified)
|
|
36
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
37
|
+
"""
|
|
21
38
|
try:
|
|
22
|
-
|
|
23
|
-
config.load_kube_config()
|
|
24
|
-
networking = client.NetworkingV1Api()
|
|
39
|
+
networking = get_networking_client(context)
|
|
25
40
|
|
|
26
41
|
if namespace:
|
|
27
42
|
ingresses = networking.list_namespaced_ingress(namespace)
|
|
@@ -30,6 +45,7 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
30
45
|
|
|
31
46
|
return {
|
|
32
47
|
"success": True,
|
|
48
|
+
"context": context or "current",
|
|
33
49
|
"ingresses": [
|
|
34
50
|
{
|
|
35
51
|
"name": ing.metadata.name,
|
|
@@ -70,12 +86,20 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
70
86
|
readOnlyHint=True,
|
|
71
87
|
),
|
|
72
88
|
)
|
|
73
|
-
def get_endpoints(
|
|
74
|
-
|
|
89
|
+
def get_endpoints(
|
|
90
|
+
namespace: Optional[str] = None,
|
|
91
|
+
service_name: Optional[str] = None,
|
|
92
|
+
context: str = ""
|
|
93
|
+
) -> Dict[str, Any]:
|
|
94
|
+
"""Get Endpoints for services.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
namespace: Namespace to list endpoints from (all namespaces if not specified)
|
|
98
|
+
service_name: Filter by specific service name
|
|
99
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
100
|
+
"""
|
|
75
101
|
try:
|
|
76
|
-
|
|
77
|
-
config.load_kube_config()
|
|
78
|
-
v1 = client.CoreV1Api()
|
|
102
|
+
v1 = get_k8s_client(context)
|
|
79
103
|
|
|
80
104
|
if namespace:
|
|
81
105
|
endpoints = v1.list_namespaced_endpoints(namespace)
|
|
@@ -107,7 +131,11 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
107
131
|
"addresses": addresses
|
|
108
132
|
})
|
|
109
133
|
|
|
110
|
-
return {
|
|
134
|
+
return {
|
|
135
|
+
"success": True,
|
|
136
|
+
"context": context or "current",
|
|
137
|
+
"endpoints": result
|
|
138
|
+
}
|
|
111
139
|
except Exception as e:
|
|
112
140
|
logger.error(f"Error getting Endpoints: {e}")
|
|
113
141
|
return {"success": False, "error": str(e)}
|
|
@@ -122,14 +150,24 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
122
150
|
source_pod: str,
|
|
123
151
|
target: str,
|
|
124
152
|
source_namespace: str = "default",
|
|
125
|
-
port: Optional[int] = None
|
|
153
|
+
port: Optional[int] = None,
|
|
154
|
+
context: str = ""
|
|
126
155
|
) -> Dict[str, Any]:
|
|
127
|
-
"""Diagnose network connectivity between pods or to external endpoints.
|
|
156
|
+
"""Diagnose network connectivity between pods or to external endpoints.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
source_pod: Name of the pod to test from
|
|
160
|
+
target: Target hostname or IP to test connectivity to
|
|
161
|
+
source_namespace: Namespace of the source pod
|
|
162
|
+
port: Optional port number to test
|
|
163
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
164
|
+
"""
|
|
128
165
|
try:
|
|
129
|
-
results = {"success": True, "tests": []}
|
|
166
|
+
results = {"success": True, "context": context or "current", "tests": []}
|
|
167
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
130
168
|
|
|
131
169
|
# Test DNS resolution
|
|
132
|
-
dns_cmd = ["kubectl"
|
|
170
|
+
dns_cmd = ["kubectl"] + ctx_args + ["exec", source_pod, "-n", source_namespace, "--", "nslookup", target]
|
|
133
171
|
dns_result = subprocess.run(dns_cmd, capture_output=True, text=True, timeout=30)
|
|
134
172
|
results["tests"].append({
|
|
135
173
|
"test": "DNS Resolution",
|
|
@@ -139,10 +177,10 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
139
177
|
|
|
140
178
|
# Test connectivity
|
|
141
179
|
if port:
|
|
142
|
-
conn_cmd = ["kubectl"
|
|
180
|
+
conn_cmd = ["kubectl"] + ctx_args + ["exec", source_pod, "-n", source_namespace, "--",
|
|
143
181
|
"nc", "-zv", "-w", "5", target, str(port)]
|
|
144
182
|
else:
|
|
145
|
-
conn_cmd = ["kubectl"
|
|
183
|
+
conn_cmd = ["kubectl"] + ctx_args + ["exec", source_pod, "-n", source_namespace, "--",
|
|
146
184
|
"ping", "-c", "3", target]
|
|
147
185
|
|
|
148
186
|
conn_result = subprocess.run(conn_cmd, capture_output=True, text=True, timeout=30)
|
|
@@ -166,24 +204,35 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
166
204
|
readOnlyHint=True,
|
|
167
205
|
),
|
|
168
206
|
)
|
|
169
|
-
def check_dns_resolution(
|
|
170
|
-
|
|
207
|
+
def check_dns_resolution(
|
|
208
|
+
hostname: str,
|
|
209
|
+
namespace: str = "default",
|
|
210
|
+
pod_name: Optional[str] = None,
|
|
211
|
+
context: str = ""
|
|
212
|
+
) -> Dict[str, Any]:
|
|
213
|
+
"""Check DNS resolution from within the cluster.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
hostname: Hostname to resolve
|
|
217
|
+
namespace: Namespace to find a pod in
|
|
218
|
+
pod_name: Specific pod to use for testing (optional)
|
|
219
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
220
|
+
"""
|
|
171
221
|
try:
|
|
172
222
|
if not pod_name:
|
|
173
223
|
# Find a running pod in the namespace
|
|
174
|
-
|
|
175
|
-
config.load_kube_config()
|
|
176
|
-
v1 = client.CoreV1Api()
|
|
224
|
+
v1 = get_k8s_client(context)
|
|
177
225
|
pods = v1.list_namespaced_pod(namespace, field_selector="status.phase=Running")
|
|
178
226
|
if not pods.items:
|
|
179
227
|
return {"success": False, "error": f"No running pods in namespace {namespace}"}
|
|
180
228
|
pod_name = pods.items[0].metadata.name
|
|
181
229
|
|
|
182
|
-
cmd = ["kubectl"
|
|
230
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["exec", pod_name, "-n", namespace, "--", "nslookup", hostname]
|
|
183
231
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
184
232
|
|
|
185
233
|
return {
|
|
186
234
|
"success": result.returncode == 0,
|
|
235
|
+
"context": context or "current",
|
|
187
236
|
"hostname": hostname,
|
|
188
237
|
"pod": pod_name,
|
|
189
238
|
"namespace": namespace,
|
|
@@ -201,12 +250,20 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
201
250
|
readOnlyHint=True,
|
|
202
251
|
),
|
|
203
252
|
)
|
|
204
|
-
def trace_service_chain(
|
|
205
|
-
|
|
253
|
+
def trace_service_chain(
|
|
254
|
+
service_name: str,
|
|
255
|
+
namespace: str = "default",
|
|
256
|
+
context: str = ""
|
|
257
|
+
) -> Dict[str, Any]:
|
|
258
|
+
"""Trace the connection chain from service to pods.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
service_name: Name of the service to trace
|
|
262
|
+
namespace: Namespace of the service
|
|
263
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
264
|
+
"""
|
|
206
265
|
try:
|
|
207
|
-
|
|
208
|
-
config.load_kube_config()
|
|
209
|
-
v1 = client.CoreV1Api()
|
|
266
|
+
v1 = get_k8s_client(context)
|
|
210
267
|
|
|
211
268
|
# Get service
|
|
212
269
|
service = v1.read_namespaced_service(service_name, namespace)
|
|
@@ -227,6 +284,7 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
227
284
|
|
|
228
285
|
result = {
|
|
229
286
|
"success": True,
|
|
287
|
+
"context": context or "current",
|
|
230
288
|
"service": {
|
|
231
289
|
"name": service.metadata.name,
|
|
232
290
|
"type": service.spec.type,
|
|
@@ -280,14 +338,30 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
280
338
|
destructiveHint=True,
|
|
281
339
|
),
|
|
282
340
|
)
|
|
283
|
-
def port_forward(
|
|
284
|
-
|
|
341
|
+
def port_forward(
|
|
342
|
+
pod_name: str,
|
|
343
|
+
local_port: int,
|
|
344
|
+
pod_port: int,
|
|
345
|
+
namespace: Optional[str] = "default",
|
|
346
|
+
context: str = ""
|
|
347
|
+
) -> Dict[str, Any]:
|
|
348
|
+
"""Start port forwarding to a pod (note: this starts a background process).
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
pod_name: Name of the pod to forward to
|
|
352
|
+
local_port: Local port to listen on
|
|
353
|
+
pod_port: Pod port to forward to
|
|
354
|
+
namespace: Namespace of the pod
|
|
355
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
356
|
+
"""
|
|
285
357
|
try:
|
|
286
358
|
import os
|
|
287
|
-
|
|
359
|
+
ctx_args = " ".join(_get_kubectl_context_args(context))
|
|
360
|
+
cmd = f"kubectl {ctx_args} port-forward {pod_name} {local_port}:{pod_port} -n {namespace} &"
|
|
288
361
|
os.system(cmd)
|
|
289
362
|
return {
|
|
290
363
|
"success": True,
|
|
364
|
+
"context": context or "current",
|
|
291
365
|
"message": f"Port forwarding started: localhost:{local_port} -> {pod_name}:{pod_port}",
|
|
292
366
|
"note": "Port forwarding is running in background. Use 'pkill -f port-forward' to stop."
|
|
293
367
|
}
|