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,5 @@
1
+ from .resources import register_resources
2
+
3
+ __all__ = [
4
+ "register_resources",
5
+ ]
@@ -0,0 +1,305 @@
1
+ import json
2
+ import logging
3
+ import subprocess
4
+ from typing import Any
5
+
6
+ logger = logging.getLogger("mcp-server")
7
+
8
+
9
+ def register_resources(server):
10
+ """Register all MCP resources for Kubernetes data exposure.
11
+
12
+ Args:
13
+ server: FastMCP server instance
14
+ """
15
+
16
+ @server.resource("kubeconfig://contexts")
17
+ def get_kubeconfig_contexts() -> str:
18
+ """List all available kubectl contexts."""
19
+ try:
20
+ from kubernetes import config
21
+ contexts, active_context = config.list_kube_config_contexts()
22
+ result = {
23
+ "active_context": active_context.get("name") if active_context else None,
24
+ "contexts": [
25
+ {
26
+ "name": ctx.get("name"),
27
+ "cluster": ctx.get("context", {}).get("cluster"),
28
+ "user": ctx.get("context", {}).get("user"),
29
+ "namespace": ctx.get("context", {}).get("namespace", "default")
30
+ }
31
+ for ctx in contexts
32
+ ]
33
+ }
34
+ return json.dumps(result, indent=2)
35
+ except Exception as e:
36
+ return json.dumps({"error": str(e)})
37
+
38
+ @server.resource("kubeconfig://current-context")
39
+ def get_current_context() -> str:
40
+ """Get the current active kubectl context."""
41
+ try:
42
+ from kubernetes import config
43
+ _, active_context = config.list_kube_config_contexts()
44
+ if active_context:
45
+ result = {
46
+ "name": active_context.get("name"),
47
+ "cluster": active_context.get("context", {}).get("cluster"),
48
+ "user": active_context.get("context", {}).get("user"),
49
+ "namespace": active_context.get("context", {}).get("namespace", "default")
50
+ }
51
+ else:
52
+ result = {"error": "No active context found"}
53
+ return json.dumps(result, indent=2)
54
+ except Exception as e:
55
+ return json.dumps({"error": str(e)})
56
+
57
+ @server.resource("namespace://current")
58
+ def get_current_namespace() -> str:
59
+ """Get the current namespace from kubectl context."""
60
+ try:
61
+ from kubernetes import config
62
+ _, active_context = config.list_kube_config_contexts()
63
+ namespace = "default"
64
+ if active_context:
65
+ namespace = active_context.get("context", {}).get("namespace", "default")
66
+ return json.dumps({"namespace": namespace}, indent=2)
67
+ except Exception as e:
68
+ return json.dumps({"error": str(e)})
69
+
70
+ @server.resource("namespace://list")
71
+ def list_all_namespaces() -> str:
72
+ """List all namespaces in the cluster."""
73
+ try:
74
+ from kubernetes import client, config
75
+ config.load_kube_config()
76
+ v1 = client.CoreV1Api()
77
+ namespaces = v1.list_namespace()
78
+ result = {
79
+ "namespaces": [
80
+ {
81
+ "name": ns.metadata.name,
82
+ "status": ns.status.phase,
83
+ "created": ns.metadata.creation_timestamp.isoformat() if ns.metadata.creation_timestamp else None,
84
+ "labels": ns.metadata.labels or {}
85
+ }
86
+ for ns in namespaces.items
87
+ ]
88
+ }
89
+ return json.dumps(result, indent=2, default=str)
90
+ except Exception as e:
91
+ return json.dumps({"error": str(e)})
92
+
93
+ @server.resource("cluster://info")
94
+ def get_cluster_info() -> str:
95
+ """Get cluster information including version and nodes."""
96
+ try:
97
+ from kubernetes import client, config
98
+ config.load_kube_config()
99
+ v1 = client.CoreV1Api()
100
+ version_api = client.VersionApi()
101
+
102
+ version_info = version_api.get_code()
103
+ nodes = v1.list_node()
104
+
105
+ result = {
106
+ "version": {
107
+ "git_version": version_info.git_version,
108
+ "platform": version_info.platform,
109
+ "go_version": version_info.go_version
110
+ },
111
+ "nodes": {
112
+ "count": len(nodes.items),
113
+ "ready": sum(1 for n in nodes.items if any(
114
+ c.type == "Ready" and c.status == "True"
115
+ for c in n.status.conditions
116
+ ))
117
+ }
118
+ }
119
+ return json.dumps(result, indent=2)
120
+ except Exception as e:
121
+ return json.dumps({"error": str(e)})
122
+
123
+ @server.resource("cluster://nodes")
124
+ def get_cluster_nodes() -> str:
125
+ """Get detailed information about all cluster nodes."""
126
+ try:
127
+ from kubernetes import client, config
128
+ config.load_kube_config()
129
+ v1 = client.CoreV1Api()
130
+ nodes = v1.list_node()
131
+
132
+ result = {
133
+ "nodes": [
134
+ {
135
+ "name": node.metadata.name,
136
+ "status": next(
137
+ (c.status for c in node.status.conditions if c.type == "Ready"),
138
+ "Unknown"
139
+ ),
140
+ "roles": [
141
+ k.replace("node-role.kubernetes.io/", "")
142
+ for k in (node.metadata.labels or {}).keys()
143
+ if k.startswith("node-role.kubernetes.io/")
144
+ ] or ["worker"],
145
+ "kubernetes_version": node.status.node_info.kubelet_version,
146
+ "os": node.status.node_info.os_image,
147
+ "architecture": node.status.node_info.architecture,
148
+ "capacity": {
149
+ "cpu": node.status.capacity.get("cpu"),
150
+ "memory": node.status.capacity.get("memory"),
151
+ "pods": node.status.capacity.get("pods")
152
+ },
153
+ "allocatable": {
154
+ "cpu": node.status.allocatable.get("cpu"),
155
+ "memory": node.status.allocatable.get("memory"),
156
+ "pods": node.status.allocatable.get("pods")
157
+ }
158
+ }
159
+ for node in nodes.items
160
+ ]
161
+ }
162
+ return json.dumps(result, indent=2)
163
+ except Exception as e:
164
+ return json.dumps({"error": str(e)})
165
+
166
+ @server.resource("cluster://version")
167
+ def get_cluster_version() -> str:
168
+ """Get Kubernetes cluster version."""
169
+ try:
170
+ from kubernetes import client, config
171
+ config.load_kube_config()
172
+ version_api = client.VersionApi()
173
+ version_info = version_api.get_code()
174
+
175
+ result = {
176
+ "git_version": version_info.git_version,
177
+ "major": version_info.major,
178
+ "minor": version_info.minor,
179
+ "platform": version_info.platform,
180
+ "build_date": version_info.build_date,
181
+ "go_version": version_info.go_version,
182
+ "compiler": version_info.compiler
183
+ }
184
+ return json.dumps(result, indent=2)
185
+ except Exception as e:
186
+ return json.dumps({"error": str(e)})
187
+
188
+ @server.resource("cluster://api-resources")
189
+ def get_api_resources() -> str:
190
+ """Get available API resources in the cluster."""
191
+ try:
192
+ result = subprocess.run(
193
+ ["kubectl", "api-resources", "--output=wide"],
194
+ capture_output=True, text=True, timeout=30
195
+ )
196
+ if result.returncode == 0:
197
+ lines = result.stdout.strip().split("\n")
198
+ if len(lines) > 1:
199
+ resources = []
200
+ for line in lines[1:]:
201
+ parts = line.split()
202
+ if len(parts) >= 4:
203
+ resources.append({
204
+ "name": parts[0],
205
+ "shortnames": parts[1] if len(parts) > 4 else "",
206
+ "apigroup": parts[-4] if len(parts) > 4 else "",
207
+ "namespaced": parts[-3] if len(parts) > 4 else parts[-2],
208
+ "kind": parts[-2] if len(parts) > 4 else parts[-1]
209
+ })
210
+ return json.dumps({"resources": resources}, indent=2)
211
+ return json.dumps({"error": result.stderr or "Failed to get API resources"})
212
+ except Exception as e:
213
+ return json.dumps({"error": str(e)})
214
+
215
+ @server.resource("manifest://deployments/{namespace}/{name}")
216
+ def get_deployment_manifest(namespace: str, name: str) -> str:
217
+ """Get YAML manifest for a specific deployment."""
218
+ try:
219
+ from kubernetes import client, config
220
+ import yaml
221
+ config.load_kube_config()
222
+ apps_v1 = client.AppsV1Api()
223
+
224
+ deployment = apps_v1.read_namespaced_deployment(name, namespace)
225
+ manifest = client.ApiClient().sanitize_for_serialization(deployment)
226
+ return yaml.dump(manifest, default_flow_style=False)
227
+ except Exception as e:
228
+ return f"# Error: {str(e)}"
229
+
230
+ @server.resource("manifest://services/{namespace}/{name}")
231
+ def get_service_manifest(namespace: str, name: str) -> str:
232
+ """Get YAML manifest for a specific service."""
233
+ try:
234
+ from kubernetes import client, config
235
+ import yaml
236
+ config.load_kube_config()
237
+ v1 = client.CoreV1Api()
238
+
239
+ service = v1.read_namespaced_service(name, namespace)
240
+ manifest = client.ApiClient().sanitize_for_serialization(service)
241
+ return yaml.dump(manifest, default_flow_style=False)
242
+ except Exception as e:
243
+ return f"# Error: {str(e)}"
244
+
245
+ @server.resource("manifest://configmaps/{namespace}/{name}")
246
+ def get_configmap_manifest(namespace: str, name: str) -> str:
247
+ """Get YAML manifest for a specific ConfigMap."""
248
+ try:
249
+ from kubernetes import client, config
250
+ import yaml
251
+ config.load_kube_config()
252
+ v1 = client.CoreV1Api()
253
+
254
+ configmap = v1.read_namespaced_config_map(name, namespace)
255
+ manifest = client.ApiClient().sanitize_for_serialization(configmap)
256
+ return yaml.dump(manifest, default_flow_style=False)
257
+ except Exception as e:
258
+ return f"# Error: {str(e)}"
259
+
260
+ @server.resource("manifest://pods/{namespace}/{name}")
261
+ def get_pod_manifest(namespace: str, name: str) -> str:
262
+ """Get YAML manifest for a specific pod."""
263
+ try:
264
+ from kubernetes import client, config
265
+ import yaml
266
+ config.load_kube_config()
267
+ v1 = client.CoreV1Api()
268
+
269
+ pod = v1.read_namespaced_pod(name, namespace)
270
+ manifest = client.ApiClient().sanitize_for_serialization(pod)
271
+ return yaml.dump(manifest, default_flow_style=False)
272
+ except Exception as e:
273
+ return f"# Error: {str(e)}"
274
+
275
+ @server.resource("manifest://secrets/{namespace}/{name}")
276
+ def get_secret_manifest(namespace: str, name: str) -> str:
277
+ """Get YAML manifest for a specific secret (data masked)."""
278
+ try:
279
+ from kubernetes import client, config
280
+ import yaml
281
+ config.load_kube_config()
282
+ v1 = client.CoreV1Api()
283
+
284
+ secret = v1.read_namespaced_secret(name, namespace)
285
+ manifest = client.ApiClient().sanitize_for_serialization(secret)
286
+ if "data" in manifest and manifest["data"]:
287
+ manifest["data"] = {k: "[MASKED]" for k in manifest["data"].keys()}
288
+ return yaml.dump(manifest, default_flow_style=False)
289
+ except Exception as e:
290
+ return f"# Error: {str(e)}"
291
+
292
+ @server.resource("manifest://ingresses/{namespace}/{name}")
293
+ def get_ingress_manifest(namespace: str, name: str) -> str:
294
+ """Get YAML manifest for a specific ingress."""
295
+ try:
296
+ from kubernetes import client, config
297
+ import yaml
298
+ config.load_kube_config()
299
+ networking_v1 = client.NetworkingV1Api()
300
+
301
+ ingress = networking_v1.read_namespaced_ingress(name, namespace)
302
+ manifest = client.ApiClient().sanitize_for_serialization(ingress)
303
+ return yaml.dump(manifest, default_flow_style=False)
304
+ except Exception as e:
305
+ return f"# Error: {str(e)}"
@@ -0,0 +1,28 @@
1
+ from .helm import register_helm_tools
2
+ from .pods import register_pod_tools
3
+ from .core import register_core_tools
4
+ from .cluster import register_cluster_tools
5
+ from .deployments import register_deployment_tools
6
+ from .security import register_security_tools
7
+ from .networking import register_networking_tools
8
+ from .storage import register_storage_tools
9
+ from .operations import register_operations_tools
10
+ from .diagnostics import register_diagnostics_tools
11
+ from .cost import register_cost_tools
12
+ from .browser import register_browser_tools, is_browser_available
13
+
14
+ __all__ = [
15
+ "register_helm_tools",
16
+ "register_pod_tools",
17
+ "register_core_tools",
18
+ "register_cluster_tools",
19
+ "register_deployment_tools",
20
+ "register_security_tools",
21
+ "register_networking_tools",
22
+ "register_storage_tools",
23
+ "register_operations_tools",
24
+ "register_diagnostics_tools",
25
+ "register_cost_tools",
26
+ "register_browser_tools",
27
+ "is_browser_available",
28
+ ]