kubectl-mcp-server 1.12.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.
Files changed (45) hide show
  1. kubectl_mcp_server-1.12.0.dist-info/METADATA +711 -0
  2. kubectl_mcp_server-1.12.0.dist-info/RECORD +45 -0
  3. kubectl_mcp_server-1.12.0.dist-info/WHEEL +5 -0
  4. kubectl_mcp_server-1.12.0.dist-info/entry_points.txt +3 -0
  5. kubectl_mcp_server-1.12.0.dist-info/licenses/LICENSE +21 -0
  6. kubectl_mcp_server-1.12.0.dist-info/top_level.txt +2 -0
  7. kubectl_mcp_tool/__init__.py +21 -0
  8. kubectl_mcp_tool/__main__.py +46 -0
  9. kubectl_mcp_tool/auth/__init__.py +13 -0
  10. kubectl_mcp_tool/auth/config.py +71 -0
  11. kubectl_mcp_tool/auth/scopes.py +148 -0
  12. kubectl_mcp_tool/auth/verifier.py +82 -0
  13. kubectl_mcp_tool/cli/__init__.py +9 -0
  14. kubectl_mcp_tool/cli/__main__.py +10 -0
  15. kubectl_mcp_tool/cli/cli.py +111 -0
  16. kubectl_mcp_tool/diagnostics.py +355 -0
  17. kubectl_mcp_tool/k8s_config.py +289 -0
  18. kubectl_mcp_tool/mcp_server.py +530 -0
  19. kubectl_mcp_tool/prompts/__init__.py +5 -0
  20. kubectl_mcp_tool/prompts/prompts.py +823 -0
  21. kubectl_mcp_tool/resources/__init__.py +5 -0
  22. kubectl_mcp_tool/resources/resources.py +305 -0
  23. kubectl_mcp_tool/tools/__init__.py +28 -0
  24. kubectl_mcp_tool/tools/browser.py +371 -0
  25. kubectl_mcp_tool/tools/cluster.py +315 -0
  26. kubectl_mcp_tool/tools/core.py +421 -0
  27. kubectl_mcp_tool/tools/cost.py +680 -0
  28. kubectl_mcp_tool/tools/deployments.py +381 -0
  29. kubectl_mcp_tool/tools/diagnostics.py +174 -0
  30. kubectl_mcp_tool/tools/helm.py +1561 -0
  31. kubectl_mcp_tool/tools/networking.py +296 -0
  32. kubectl_mcp_tool/tools/operations.py +501 -0
  33. kubectl_mcp_tool/tools/pods.py +582 -0
  34. kubectl_mcp_tool/tools/security.py +333 -0
  35. kubectl_mcp_tool/tools/storage.py +133 -0
  36. kubectl_mcp_tool/utils/__init__.py +17 -0
  37. kubectl_mcp_tool/utils/helpers.py +80 -0
  38. tests/__init__.py +9 -0
  39. tests/conftest.py +379 -0
  40. tests/test_auth.py +256 -0
  41. tests/test_browser.py +349 -0
  42. tests/test_prompts.py +536 -0
  43. tests/test_resources.py +343 -0
  44. tests/test_server.py +384 -0
  45. tests/test_tools.py +659 -0
@@ -0,0 +1,296 @@
1
+ import logging
2
+ import subprocess
3
+ from typing import Any, Dict, Optional
4
+
5
+ from mcp.types import ToolAnnotations
6
+
7
+ logger = logging.getLogger("mcp-server")
8
+
9
+
10
+ def register_networking_tools(server, non_destructive: bool):
11
+ """Register networking-related tools."""
12
+
13
+ @server.tool(
14
+ annotations=ToolAnnotations(
15
+ title="Get Ingress Resources",
16
+ readOnlyHint=True,
17
+ ),
18
+ )
19
+ def get_ingress(namespace: Optional[str] = None) -> Dict[str, Any]:
20
+ """Get Ingress resources in a namespace or cluster-wide."""
21
+ try:
22
+ from kubernetes import client, config
23
+ config.load_kube_config()
24
+ networking = client.NetworkingV1Api()
25
+
26
+ if namespace:
27
+ ingresses = networking.list_namespaced_ingress(namespace)
28
+ else:
29
+ ingresses = networking.list_ingress_for_all_namespaces()
30
+
31
+ return {
32
+ "success": True,
33
+ "ingresses": [
34
+ {
35
+ "name": ing.metadata.name,
36
+ "namespace": ing.metadata.namespace,
37
+ "ingressClassName": ing.spec.ingress_class_name,
38
+ "rules": [
39
+ {
40
+ "host": rule.host,
41
+ "paths": [
42
+ {
43
+ "path": path.path,
44
+ "pathType": path.path_type,
45
+ "backend": {
46
+ "serviceName": path.backend.service.name if path.backend.service else None,
47
+ "servicePort": path.backend.service.port.number if path.backend.service and path.backend.service.port else None
48
+ }
49
+ }
50
+ for path in (rule.http.paths if rule.http else [])
51
+ ]
52
+ }
53
+ for rule in (ing.spec.rules or [])
54
+ ],
55
+ "tls": [
56
+ {"hosts": tls.hosts, "secretName": tls.secret_name}
57
+ for tls in (ing.spec.tls or [])
58
+ ] if ing.spec.tls else None
59
+ }
60
+ for ing in ingresses.items
61
+ ]
62
+ }
63
+ except Exception as e:
64
+ logger.error(f"Error getting Ingresses: {e}")
65
+ return {"success": False, "error": str(e)}
66
+
67
+ @server.tool(
68
+ annotations=ToolAnnotations(
69
+ title="Get Endpoints",
70
+ readOnlyHint=True,
71
+ ),
72
+ )
73
+ def get_endpoints(namespace: Optional[str] = None, service_name: Optional[str] = None) -> Dict[str, Any]:
74
+ """Get Endpoints for services."""
75
+ try:
76
+ from kubernetes import client, config
77
+ config.load_kube_config()
78
+ v1 = client.CoreV1Api()
79
+
80
+ if namespace:
81
+ endpoints = v1.list_namespaced_endpoints(namespace)
82
+ else:
83
+ endpoints = v1.list_endpoints_for_all_namespaces()
84
+
85
+ result = []
86
+ for ep in endpoints.items:
87
+ if service_name and ep.metadata.name != service_name:
88
+ continue
89
+
90
+ addresses = []
91
+ for subset in (ep.subsets or []):
92
+ for addr in (subset.addresses or []):
93
+ for port in (subset.ports or []):
94
+ addresses.append({
95
+ "ip": addr.ip,
96
+ "port": port.port,
97
+ "protocol": port.protocol,
98
+ "targetRef": {
99
+ "kind": addr.target_ref.kind,
100
+ "name": addr.target_ref.name
101
+ } if addr.target_ref else None
102
+ })
103
+
104
+ result.append({
105
+ "name": ep.metadata.name,
106
+ "namespace": ep.metadata.namespace,
107
+ "addresses": addresses
108
+ })
109
+
110
+ return {"success": True, "endpoints": result}
111
+ except Exception as e:
112
+ logger.error(f"Error getting Endpoints: {e}")
113
+ return {"success": False, "error": str(e)}
114
+
115
+ @server.tool(
116
+ annotations=ToolAnnotations(
117
+ title="Diagnose Network Connectivity",
118
+ readOnlyHint=True,
119
+ ),
120
+ )
121
+ def diagnose_network_connectivity(
122
+ source_pod: str,
123
+ target: str,
124
+ source_namespace: str = "default",
125
+ port: Optional[int] = None
126
+ ) -> Dict[str, Any]:
127
+ """Diagnose network connectivity between pods or to external endpoints."""
128
+ try:
129
+ results = {"success": True, "tests": []}
130
+
131
+ # Test DNS resolution
132
+ dns_cmd = ["kubectl", "exec", source_pod, "-n", source_namespace, "--", "nslookup", target]
133
+ dns_result = subprocess.run(dns_cmd, capture_output=True, text=True, timeout=30)
134
+ results["tests"].append({
135
+ "test": "DNS Resolution",
136
+ "passed": dns_result.returncode == 0,
137
+ "output": dns_result.stdout if dns_result.returncode == 0 else dns_result.stderr
138
+ })
139
+
140
+ # Test connectivity
141
+ if port:
142
+ conn_cmd = ["kubectl", "exec", source_pod, "-n", source_namespace, "--",
143
+ "nc", "-zv", "-w", "5", target, str(port)]
144
+ else:
145
+ conn_cmd = ["kubectl", "exec", source_pod, "-n", source_namespace, "--",
146
+ "ping", "-c", "3", target]
147
+
148
+ conn_result = subprocess.run(conn_cmd, capture_output=True, text=True, timeout=30)
149
+ results["tests"].append({
150
+ "test": f"Connectivity to {target}" + (f":{port}" if port else ""),
151
+ "passed": conn_result.returncode == 0,
152
+ "output": conn_result.stdout if conn_result.returncode == 0 else conn_result.stderr
153
+ })
154
+
155
+ results["allPassed"] = all(t["passed"] for t in results["tests"])
156
+ return results
157
+ except subprocess.TimeoutExpired:
158
+ return {"success": False, "error": "Network test timed out"}
159
+ except Exception as e:
160
+ logger.error(f"Error diagnosing network: {e}")
161
+ return {"success": False, "error": str(e)}
162
+
163
+ @server.tool(
164
+ annotations=ToolAnnotations(
165
+ title="Check DNS Resolution",
166
+ readOnlyHint=True,
167
+ ),
168
+ )
169
+ def check_dns_resolution(hostname: str, namespace: str = "default", pod_name: Optional[str] = None) -> Dict[str, Any]:
170
+ """Check DNS resolution from within the cluster."""
171
+ try:
172
+ if not pod_name:
173
+ # Find a running pod in the namespace
174
+ from kubernetes import client, config
175
+ config.load_kube_config()
176
+ v1 = client.CoreV1Api()
177
+ pods = v1.list_namespaced_pod(namespace, field_selector="status.phase=Running")
178
+ if not pods.items:
179
+ return {"success": False, "error": f"No running pods in namespace {namespace}"}
180
+ pod_name = pods.items[0].metadata.name
181
+
182
+ cmd = ["kubectl", "exec", pod_name, "-n", namespace, "--", "nslookup", hostname]
183
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
184
+
185
+ return {
186
+ "success": result.returncode == 0,
187
+ "hostname": hostname,
188
+ "pod": pod_name,
189
+ "namespace": namespace,
190
+ "output": result.stdout if result.returncode == 0 else result.stderr
191
+ }
192
+ except subprocess.TimeoutExpired:
193
+ return {"success": False, "error": "DNS resolution timed out"}
194
+ except Exception as e:
195
+ logger.error(f"Error checking DNS: {e}")
196
+ return {"success": False, "error": str(e)}
197
+
198
+ @server.tool(
199
+ annotations=ToolAnnotations(
200
+ title="Trace Service to Pods",
201
+ readOnlyHint=True,
202
+ ),
203
+ )
204
+ def trace_service_chain(service_name: str, namespace: str = "default") -> Dict[str, Any]:
205
+ """Trace the connection chain from service to pods."""
206
+ try:
207
+ from kubernetes import client, config
208
+ config.load_kube_config()
209
+ v1 = client.CoreV1Api()
210
+
211
+ # Get service
212
+ service = v1.read_namespaced_service(service_name, namespace)
213
+
214
+ # Get endpoints
215
+ try:
216
+ endpoints = v1.read_namespaced_endpoints(service_name, namespace)
217
+ except:
218
+ endpoints = None
219
+
220
+ # Get pods matching selector
221
+ selector = service.spec.selector
222
+ if selector:
223
+ label_selector = ",".join([f"{k}={v}" for k, v in selector.items()])
224
+ pods = v1.list_namespaced_pod(namespace, label_selector=label_selector)
225
+ else:
226
+ pods = None
227
+
228
+ result = {
229
+ "success": True,
230
+ "service": {
231
+ "name": service.metadata.name,
232
+ "type": service.spec.type,
233
+ "clusterIP": service.spec.cluster_ip,
234
+ "ports": [
235
+ {
236
+ "name": p.name,
237
+ "port": p.port,
238
+ "targetPort": str(p.target_port),
239
+ "protocol": p.protocol
240
+ }
241
+ for p in (service.spec.ports or [])
242
+ ],
243
+ "selector": selector
244
+ },
245
+ "endpoints": [],
246
+ "pods": []
247
+ }
248
+
249
+ if endpoints:
250
+ for subset in (endpoints.subsets or []):
251
+ for addr in (subset.addresses or []):
252
+ for port in (subset.ports or []):
253
+ result["endpoints"].append({
254
+ "ip": addr.ip,
255
+ "port": port.port,
256
+ "podName": addr.target_ref.name if addr.target_ref else None
257
+ })
258
+
259
+ if pods:
260
+ result["pods"] = [
261
+ {
262
+ "name": p.metadata.name,
263
+ "status": p.status.phase,
264
+ "podIP": p.status.pod_ip,
265
+ "ready": all(
266
+ c.ready for c in (p.status.container_statuses or [])
267
+ )
268
+ }
269
+ for p in pods.items
270
+ ]
271
+
272
+ return result
273
+ except Exception as e:
274
+ logger.error(f"Error tracing service chain: {e}")
275
+ return {"success": False, "error": str(e)}
276
+
277
+ @server.tool(
278
+ annotations=ToolAnnotations(
279
+ title="Port Forward",
280
+ destructiveHint=True,
281
+ ),
282
+ )
283
+ def port_forward(pod_name: str, local_port: int, pod_port: int, namespace: Optional[str] = "default") -> Dict[str, Any]:
284
+ """Start port forwarding to a pod (note: this starts a background process)."""
285
+ try:
286
+ import os
287
+ cmd = f"kubectl port-forward {pod_name} {local_port}:{pod_port} -n {namespace} &"
288
+ os.system(cmd)
289
+ return {
290
+ "success": True,
291
+ "message": f"Port forwarding started: localhost:{local_port} -> {pod_name}:{pod_port}",
292
+ "note": "Port forwarding is running in background. Use 'pkill -f port-forward' to stop."
293
+ }
294
+ except Exception as e:
295
+ logger.error(f"Error setting up port forward: {e}")
296
+ return {"success": False, "error": str(e)}