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,421 @@
1
+ import logging
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from mcp.types import ToolAnnotations
5
+
6
+ logger = logging.getLogger("mcp-server")
7
+
8
+
9
+ def register_core_tools(server, non_destructive: bool):
10
+ """Register core Kubernetes resource tools."""
11
+
12
+ @server.tool(
13
+ annotations=ToolAnnotations(
14
+ title="Get Namespaces",
15
+ readOnlyHint=True,
16
+ ),
17
+ )
18
+ def get_namespaces() -> Dict[str, Any]:
19
+ """Get all Kubernetes namespaces."""
20
+ try:
21
+ from kubernetes import client, config
22
+ config.load_kube_config()
23
+ v1 = client.CoreV1Api()
24
+ namespaces = v1.list_namespace()
25
+ return {
26
+ "success": True,
27
+ "namespaces": [ns.metadata.name for ns in namespaces.items]
28
+ }
29
+ except Exception as e:
30
+ logger.error(f"Error getting namespaces: {e}")
31
+ return {"success": False, "error": str(e)}
32
+
33
+ @server.tool(
34
+ annotations=ToolAnnotations(
35
+ title="Get Services",
36
+ readOnlyHint=True,
37
+ ),
38
+ )
39
+ def get_services(namespace: Optional[str] = None) -> Dict[str, Any]:
40
+ """Get all services in the specified namespace."""
41
+ try:
42
+ from kubernetes import client, config
43
+ config.load_kube_config()
44
+ v1 = client.CoreV1Api()
45
+ if namespace:
46
+ services = v1.list_namespaced_service(namespace)
47
+ else:
48
+ services = v1.list_service_for_all_namespaces()
49
+ return {
50
+ "success": True,
51
+ "services": [
52
+ {
53
+ "name": svc.metadata.name,
54
+ "namespace": svc.metadata.namespace,
55
+ "type": svc.spec.type,
56
+ "cluster_ip": svc.spec.cluster_ip
57
+ } for svc in services.items
58
+ ]
59
+ }
60
+ except Exception as e:
61
+ logger.error(f"Error getting services: {e}")
62
+ return {"success": False, "error": str(e)}
63
+
64
+ @server.tool(
65
+ annotations=ToolAnnotations(
66
+ title="Get Nodes",
67
+ readOnlyHint=True,
68
+ ),
69
+ )
70
+ def get_nodes() -> Dict[str, Any]:
71
+ """Get all nodes in the cluster."""
72
+ try:
73
+ from kubernetes import client, config
74
+ config.load_kube_config()
75
+ v1 = client.CoreV1Api()
76
+ nodes = v1.list_node()
77
+ return {
78
+ "success": True,
79
+ "nodes": [
80
+ {
81
+ "name": node.metadata.name,
82
+ "status": (
83
+ "Ready"
84
+ if any(
85
+ cond.type == "Ready" and cond.status == "True"
86
+ for cond in node.status.conditions
87
+ )
88
+ else "NotReady"
89
+ ),
90
+ "addresses": [
91
+ addr.address for addr in node.status.addresses
92
+ ],
93
+ }
94
+ for node in nodes.items
95
+ ],
96
+ }
97
+ except Exception as e:
98
+ logger.error(f"Error getting nodes: {e}")
99
+ return {"success": False, "error": str(e)}
100
+
101
+ @server.tool(
102
+ annotations=ToolAnnotations(
103
+ title="Get ConfigMaps",
104
+ readOnlyHint=True,
105
+ ),
106
+ )
107
+ def get_configmaps(namespace: Optional[str] = None) -> Dict[str, Any]:
108
+ """Get all ConfigMaps in the specified namespace."""
109
+ try:
110
+ from kubernetes import client, config
111
+ config.load_kube_config()
112
+ v1 = client.CoreV1Api()
113
+ if namespace:
114
+ cms = v1.list_namespaced_config_map(namespace)
115
+ else:
116
+ cms = v1.list_config_map_for_all_namespaces()
117
+ return {
118
+ "success": True,
119
+ "configmaps": [
120
+ {
121
+ "name": cm.metadata.name,
122
+ "namespace": cm.metadata.namespace,
123
+ "data": cm.data
124
+ } for cm in cms.items
125
+ ]
126
+ }
127
+ except Exception as e:
128
+ logger.error(f"Error getting ConfigMaps: {e}")
129
+ return {"success": False, "error": str(e)}
130
+
131
+ @server.tool(
132
+ annotations=ToolAnnotations(
133
+ title="Get Secrets",
134
+ readOnlyHint=True,
135
+ ),
136
+ )
137
+ def get_secrets(namespace: Optional[str] = None) -> Dict[str, Any]:
138
+ """Get all Secrets in the specified namespace."""
139
+ try:
140
+ from kubernetes import client, config
141
+ config.load_kube_config()
142
+ v1 = client.CoreV1Api()
143
+ if namespace:
144
+ secrets = v1.list_namespaced_secret(namespace)
145
+ else:
146
+ secrets = v1.list_secret_for_all_namespaces()
147
+ return {
148
+ "success": True,
149
+ "secrets": [
150
+ {
151
+ "name": secret.metadata.name,
152
+ "namespace": secret.metadata.namespace,
153
+ "type": secret.type
154
+ } for secret in secrets.items
155
+ ]
156
+ }
157
+ except Exception as e:
158
+ logger.error(f"Error getting Secrets: {e}")
159
+ return {"success": False, "error": str(e)}
160
+
161
+ @server.tool(
162
+ annotations=ToolAnnotations(
163
+ title="Get Events",
164
+ readOnlyHint=True,
165
+ ),
166
+ )
167
+ def get_events(namespace: Optional[str] = None) -> Dict[str, Any]:
168
+ """Get Kubernetes events."""
169
+ try:
170
+ from kubernetes import client, config
171
+ config.load_kube_config()
172
+ v1 = client.CoreV1Api()
173
+ if namespace:
174
+ events = v1.list_namespaced_event(namespace)
175
+ else:
176
+ events = v1.list_event_for_all_namespaces()
177
+ return {
178
+ "success": True,
179
+ "events": [
180
+ {
181
+ "name": event.metadata.name,
182
+ "namespace": event.metadata.namespace,
183
+ "type": event.type,
184
+ "reason": event.reason,
185
+ "message": event.message,
186
+ "timestamp": event.last_timestamp.isoformat() if event.last_timestamp else None
187
+ } for event in events.items
188
+ ]
189
+ }
190
+ except Exception as e:
191
+ logger.error(f"Error getting events: {e}")
192
+ return {"success": False, "error": str(e)}
193
+
194
+ @server.tool(
195
+ annotations=ToolAnnotations(
196
+ title="Get Resource Usage",
197
+ readOnlyHint=True,
198
+ ),
199
+ )
200
+ def get_resource_usage(namespace: Optional[str] = None) -> Dict[str, Any]:
201
+ """Get resource usage metrics for pods."""
202
+ try:
203
+ import subprocess
204
+ cmd = ["kubectl", "top", "pods"]
205
+ if namespace:
206
+ cmd.extend(["-n", namespace])
207
+ else:
208
+ cmd.append("--all-namespaces")
209
+ cmd.append("--no-headers")
210
+
211
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
212
+ if result.returncode != 0:
213
+ return {"success": False, "error": result.stderr or "Failed to get metrics"}
214
+
215
+ pods = []
216
+ for line in result.stdout.strip().split("\n"):
217
+ if not line.strip():
218
+ continue
219
+ parts = line.split()
220
+ if namespace:
221
+ if len(parts) >= 3:
222
+ pods.append({
223
+ "name": parts[0],
224
+ "cpu": parts[1],
225
+ "memory": parts[2]
226
+ })
227
+ else:
228
+ if len(parts) >= 4:
229
+ pods.append({
230
+ "namespace": parts[0],
231
+ "name": parts[1],
232
+ "cpu": parts[2],
233
+ "memory": parts[3]
234
+ })
235
+
236
+ return {"success": True, "pods": pods}
237
+ except subprocess.TimeoutExpired:
238
+ return {"success": False, "error": "Metrics retrieval timed out"}
239
+ except Exception as e:
240
+ logger.error(f"Error getting resource usage: {e}")
241
+ return {"success": False, "error": str(e)}
242
+
243
+ @server.tool(
244
+ annotations=ToolAnnotations(
245
+ title="Get Service Accounts",
246
+ readOnlyHint=True,
247
+ ),
248
+ )
249
+ def get_service_accounts(namespace: Optional[str] = None) -> Dict[str, Any]:
250
+ """Get service accounts in a namespace or cluster-wide."""
251
+ try:
252
+ from kubernetes import client, config
253
+ config.load_kube_config()
254
+ v1 = client.CoreV1Api()
255
+
256
+ if namespace:
257
+ sas = v1.list_namespaced_service_account(namespace)
258
+ else:
259
+ sas = v1.list_service_account_for_all_namespaces()
260
+
261
+ return {
262
+ "success": True,
263
+ "serviceAccounts": [
264
+ {
265
+ "name": sa.metadata.name,
266
+ "namespace": sa.metadata.namespace,
267
+ "secrets": [s.name for s in (sa.secrets or [])],
268
+ "imagePullSecrets": [s.name for s in (sa.image_pull_secrets or [])],
269
+ "automountToken": sa.automount_service_account_token
270
+ }
271
+ for sa in sas.items
272
+ ]
273
+ }
274
+ except Exception as e:
275
+ logger.error(f"Error getting service accounts: {e}")
276
+ return {"success": False, "error": str(e)}
277
+
278
+ @server.tool(
279
+ annotations=ToolAnnotations(
280
+ title="Get Custom Resource Definitions",
281
+ readOnlyHint=True,
282
+ ),
283
+ )
284
+ def get_crds(group: Optional[str] = None) -> Dict[str, Any]:
285
+ """Get Custom Resource Definitions in the cluster."""
286
+ try:
287
+ from kubernetes import client, config
288
+ config.load_kube_config()
289
+ api = client.ApiextensionsV1Api()
290
+
291
+ crds = api.list_custom_resource_definition()
292
+
293
+ result = []
294
+ for crd in crds.items:
295
+ if group and crd.spec.group != group:
296
+ continue
297
+ result.append({
298
+ "name": crd.metadata.name,
299
+ "group": crd.spec.group,
300
+ "version": crd.spec.versions[0].name if crd.spec.versions else None,
301
+ "scope": crd.spec.scope,
302
+ "kind": crd.spec.names.kind,
303
+ "plural": crd.spec.names.plural,
304
+ "established": any(
305
+ c.type == "Established" and c.status == "True"
306
+ for c in (crd.status.conditions or [])
307
+ )
308
+ })
309
+
310
+ return {"success": True, "crds": result}
311
+ except Exception as e:
312
+ logger.error(f"Error getting CRDs: {e}")
313
+ return {"success": False, "error": str(e)}
314
+
315
+ @server.tool(
316
+ annotations=ToolAnnotations(
317
+ title="Get Resource Quotas",
318
+ readOnlyHint=True,
319
+ ),
320
+ )
321
+ def get_resource_quotas(namespace: Optional[str] = None) -> Dict[str, Any]:
322
+ """Get resource quotas for namespaces."""
323
+ try:
324
+ from kubernetes import client, config
325
+ config.load_kube_config()
326
+ v1 = client.CoreV1Api()
327
+
328
+ if namespace:
329
+ quotas = v1.list_namespaced_resource_quota(namespace)
330
+ else:
331
+ quotas = v1.list_resource_quota_for_all_namespaces()
332
+
333
+ return {
334
+ "success": True,
335
+ "quotas": [
336
+ {
337
+ "name": q.metadata.name,
338
+ "namespace": q.metadata.namespace,
339
+ "hard": q.status.hard,
340
+ "used": q.status.used
341
+ }
342
+ for q in quotas.items
343
+ ]
344
+ }
345
+ except Exception as e:
346
+ logger.error(f"Error getting resource quotas: {e}")
347
+ return {"success": False, "error": str(e)}
348
+
349
+ @server.tool(
350
+ annotations=ToolAnnotations(
351
+ title="Get Limit Ranges",
352
+ readOnlyHint=True,
353
+ ),
354
+ )
355
+ def get_limit_ranges(namespace: Optional[str] = None) -> Dict[str, Any]:
356
+ """Get limit ranges for namespaces."""
357
+ try:
358
+ from kubernetes import client, config
359
+ config.load_kube_config()
360
+ v1 = client.CoreV1Api()
361
+
362
+ if namespace:
363
+ limits = v1.list_namespaced_limit_range(namespace)
364
+ else:
365
+ limits = v1.list_limit_range_for_all_namespaces()
366
+
367
+ return {
368
+ "success": True,
369
+ "limitRanges": [
370
+ {
371
+ "name": lr.metadata.name,
372
+ "namespace": lr.metadata.namespace,
373
+ "limits": [
374
+ {
375
+ "type": item.type,
376
+ "default": item.default,
377
+ "defaultRequest": item.default_request,
378
+ "max": item.max,
379
+ "min": item.min
380
+ }
381
+ for item in (lr.spec.limits or [])
382
+ ]
383
+ }
384
+ for lr in limits.items
385
+ ]
386
+ }
387
+ except Exception as e:
388
+ logger.error(f"Error getting limit ranges: {e}")
389
+ return {"success": False, "error": str(e)}
390
+
391
+ @server.tool(
392
+ annotations=ToolAnnotations(
393
+ title="Get Priority Classes",
394
+ readOnlyHint=True,
395
+ ),
396
+ )
397
+ def get_priority_classes() -> Dict[str, Any]:
398
+ """Get priority classes in the cluster."""
399
+ try:
400
+ from kubernetes import client, config
401
+ config.load_kube_config()
402
+ api = client.SchedulingV1Api()
403
+
404
+ pcs = api.list_priority_class()
405
+
406
+ return {
407
+ "success": True,
408
+ "priorityClasses": [
409
+ {
410
+ "name": pc.metadata.name,
411
+ "value": pc.value,
412
+ "globalDefault": pc.global_default,
413
+ "description": pc.description,
414
+ "preemptionPolicy": pc.preemption_policy
415
+ }
416
+ for pc in pcs.items
417
+ ]
418
+ }
419
+ except Exception as e:
420
+ logger.error(f"Error getting priority classes: {e}")
421
+ return {"success": False, "error": str(e)}