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,33 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cluster and context management tools for kubectl-mcp-server.
|
|
3
|
+
|
|
4
|
+
All tools support multi-cluster operations via the optional 'context' parameter.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
1
8
|
import logging
|
|
9
|
+
import os
|
|
2
10
|
import subprocess
|
|
3
|
-
from typing import Any, Dict, Optional
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
4
12
|
|
|
5
13
|
from mcp.types import ToolAnnotations
|
|
6
14
|
|
|
15
|
+
from kubectl_mcp_tool.k8s_config import (
|
|
16
|
+
get_k8s_client,
|
|
17
|
+
get_version_client,
|
|
18
|
+
get_admissionregistration_client,
|
|
19
|
+
list_contexts,
|
|
20
|
+
get_active_context,
|
|
21
|
+
)
|
|
22
|
+
|
|
7
23
|
logger = logging.getLogger("mcp-server")
|
|
8
24
|
|
|
9
25
|
|
|
26
|
+
def _get_kubectl_context_args(context: str = "") -> List[str]:
|
|
27
|
+
"""Get kubectl context arguments."""
|
|
28
|
+
if context:
|
|
29
|
+
return ["--context", context]
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
|
|
10
33
|
def register_cluster_tools(server, non_destructive: bool):
|
|
11
34
|
"""Register cluster and context management tools."""
|
|
12
35
|
|
|
36
|
+
# ========== Config Toolset ==========
|
|
37
|
+
|
|
13
38
|
@server.tool(
|
|
14
39
|
annotations=ToolAnnotations(
|
|
15
|
-
title="
|
|
16
|
-
|
|
40
|
+
title="List Contexts",
|
|
41
|
+
readOnlyHint=True,
|
|
17
42
|
),
|
|
18
43
|
)
|
|
19
|
-
def
|
|
20
|
-
"""
|
|
44
|
+
def list_contexts_tool() -> Dict[str, Any]:
|
|
45
|
+
"""List all available kubectl contexts with detailed info.
|
|
46
|
+
|
|
47
|
+
Returns all contexts from kubeconfig with cluster, user, namespace info.
|
|
48
|
+
"""
|
|
21
49
|
try:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
contexts = list_contexts()
|
|
51
|
+
active = get_active_context()
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"success": True,
|
|
55
|
+
"contexts": contexts,
|
|
56
|
+
"active_context": active,
|
|
57
|
+
"total": len(contexts)
|
|
58
|
+
}
|
|
29
59
|
except Exception as e:
|
|
30
|
-
logger.error(f"Error
|
|
60
|
+
logger.error(f"Error listing contexts: {e}")
|
|
31
61
|
return {"success": False, "error": str(e)}
|
|
32
62
|
|
|
33
63
|
@server.tool(
|
|
@@ -39,73 +69,111 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
39
69
|
def get_current_context() -> Dict[str, Any]:
|
|
40
70
|
"""Get the current kubectl context."""
|
|
41
71
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if result.returncode == 0:
|
|
47
|
-
return {"success": True, "context": result.stdout.strip()}
|
|
48
|
-
return {"success": False, "error": result.stderr}
|
|
72
|
+
active = get_active_context()
|
|
73
|
+
if active:
|
|
74
|
+
return {"success": True, "context": active}
|
|
75
|
+
return {"success": False, "error": "No active context found"}
|
|
49
76
|
except Exception as e:
|
|
50
77
|
logger.error(f"Error getting current context: {e}")
|
|
51
78
|
return {"success": False, "error": str(e)}
|
|
52
79
|
|
|
53
80
|
@server.tool(
|
|
54
81
|
annotations=ToolAnnotations(
|
|
55
|
-
title="
|
|
82
|
+
title="Get Context Details",
|
|
56
83
|
readOnlyHint=True,
|
|
57
84
|
),
|
|
58
85
|
)
|
|
59
|
-
def
|
|
60
|
-
"""
|
|
86
|
+
def get_context_details(context_name: str) -> Dict[str, Any]:
|
|
87
|
+
"""Get details about a specific context.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
context_name: Name of the context to get details for
|
|
91
|
+
"""
|
|
61
92
|
try:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
"cluster": ctx.get("context", {}).get("cluster"),
|
|
70
|
-
"user": ctx.get("context", {}).get("user"),
|
|
71
|
-
"namespace": ctx.get("context", {}).get("namespace", "default"),
|
|
72
|
-
"active": ctx.get("name") == (active_context.get("name") if active_context else None)
|
|
93
|
+
contexts = list_contexts()
|
|
94
|
+
|
|
95
|
+
for ctx in contexts:
|
|
96
|
+
if ctx.get("name") == context_name:
|
|
97
|
+
return {
|
|
98
|
+
"success": True,
|
|
99
|
+
"context": ctx
|
|
73
100
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"active_context": active_context.get("name") if active_context else None
|
|
77
|
-
}
|
|
101
|
+
|
|
102
|
+
return {"success": False, "error": f"Context '{context_name}' not found"}
|
|
78
103
|
except Exception as e:
|
|
79
|
-
logger.error(f"Error
|
|
104
|
+
logger.error(f"Error getting context details: {e}")
|
|
80
105
|
return {"success": False, "error": str(e)}
|
|
81
106
|
|
|
82
107
|
@server.tool(
|
|
83
108
|
annotations=ToolAnnotations(
|
|
84
|
-
title="
|
|
109
|
+
title="View Kubeconfig",
|
|
85
110
|
readOnlyHint=True,
|
|
86
111
|
),
|
|
87
112
|
)
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
113
|
+
def kubeconfig_view(minify: bool = True) -> Dict[str, Any]:
|
|
114
|
+
"""View kubeconfig file contents (sanitized - no secrets).
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
minify: If True, show only current context info. If False, show all.
|
|
118
|
+
"""
|
|
90
119
|
try:
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
cmd = ["kubectl", "config", "view"]
|
|
121
|
+
if minify:
|
|
122
|
+
cmd.append("--minify")
|
|
123
|
+
cmd.extend(["--raw=false", "-o", "json"]) # raw=false strips sensitive data
|
|
124
|
+
|
|
125
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
126
|
+
|
|
127
|
+
if result.returncode == 0:
|
|
128
|
+
try:
|
|
129
|
+
config_data = json.loads(result.stdout)
|
|
130
|
+
# Additional sanitization
|
|
131
|
+
for user in config_data.get("users", []):
|
|
132
|
+
if "user" in user:
|
|
133
|
+
user_data = user["user"]
|
|
134
|
+
for sensitive in ["client-certificate-data", "client-key-data", "token"]:
|
|
135
|
+
if sensitive in user_data:
|
|
136
|
+
user_data[sensitive] = "[REDACTED]"
|
|
93
137
|
|
|
94
|
-
for ctx in contexts:
|
|
95
|
-
if ctx.get("name") == context_name:
|
|
96
138
|
return {
|
|
97
139
|
"success": True,
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
"cluster": ctx.get("context", {}).get("cluster"),
|
|
101
|
-
"user": ctx.get("context", {}).get("user"),
|
|
102
|
-
"namespace": ctx.get("context", {}).get("namespace", "default")
|
|
103
|
-
}
|
|
140
|
+
"minified": minify,
|
|
141
|
+
"kubeconfig": config_data
|
|
104
142
|
}
|
|
143
|
+
except json.JSONDecodeError:
|
|
144
|
+
return {"success": True, "kubeconfig": result.stdout}
|
|
105
145
|
|
|
106
|
-
return {"success": False, "error":
|
|
146
|
+
return {"success": False, "error": result.stderr}
|
|
107
147
|
except Exception as e:
|
|
108
|
-
logger.error(f"Error
|
|
148
|
+
logger.error(f"Error viewing kubeconfig: {e}")
|
|
149
|
+
return {"success": False, "error": str(e)}
|
|
150
|
+
|
|
151
|
+
@server.tool(
|
|
152
|
+
annotations=ToolAnnotations(
|
|
153
|
+
title="Switch Context",
|
|
154
|
+
destructiveHint=True,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
def switch_context(context_name: str) -> Dict[str, Any]:
|
|
158
|
+
"""Switch to a different kubectl context (changes default context).
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
context_name: Name of the context to switch to
|
|
162
|
+
|
|
163
|
+
Note: This changes the default context in kubeconfig. For multi-cluster
|
|
164
|
+
operations without changing default, use the 'context' parameter on
|
|
165
|
+
individual tools instead.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
result = subprocess.run(
|
|
169
|
+
["kubectl", "config", "use-context", context_name],
|
|
170
|
+
capture_output=True, text=True, timeout=10
|
|
171
|
+
)
|
|
172
|
+
if result.returncode == 0:
|
|
173
|
+
return {"success": True, "message": f"Switched to context: {context_name}"}
|
|
174
|
+
return {"success": False, "error": result.stderr}
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Error switching context: {e}")
|
|
109
177
|
return {"success": False, "error": str(e)}
|
|
110
178
|
|
|
111
179
|
@server.tool(
|
|
@@ -114,8 +182,16 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
114
182
|
destructiveHint=True,
|
|
115
183
|
),
|
|
116
184
|
)
|
|
117
|
-
def set_namespace_for_context(
|
|
118
|
-
|
|
185
|
+
def set_namespace_for_context(
|
|
186
|
+
namespace: str,
|
|
187
|
+
context_name: Optional[str] = None
|
|
188
|
+
) -> Dict[str, Any]:
|
|
189
|
+
"""Set the default namespace for a context.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
namespace: Namespace to set as default
|
|
193
|
+
context_name: Context to modify (uses current context if not specified)
|
|
194
|
+
"""
|
|
119
195
|
try:
|
|
120
196
|
cmd = ["kubectl", "config", "set-context"]
|
|
121
197
|
if context_name:
|
|
@@ -132,45 +208,98 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
132
208
|
logger.error(f"Error setting namespace: {e}")
|
|
133
209
|
return {"success": False, "error": str(e)}
|
|
134
210
|
|
|
211
|
+
# ========== Cluster Info Tools ==========
|
|
212
|
+
|
|
135
213
|
@server.tool(
|
|
136
214
|
annotations=ToolAnnotations(
|
|
137
|
-
title="
|
|
215
|
+
title="Get Cluster Info",
|
|
138
216
|
readOnlyHint=True,
|
|
139
217
|
),
|
|
140
218
|
)
|
|
141
|
-
def
|
|
142
|
-
"""
|
|
219
|
+
def get_cluster_info(context: str = "") -> Dict[str, Any]:
|
|
220
|
+
"""Get cluster information.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
224
|
+
"""
|
|
143
225
|
try:
|
|
226
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
144
227
|
result = subprocess.run(
|
|
145
|
-
["kubectl"
|
|
146
|
-
capture_output=True, text=True, timeout=
|
|
228
|
+
["kubectl"] + ctx_args + ["cluster-info"],
|
|
229
|
+
capture_output=True, text=True, timeout=30
|
|
147
230
|
)
|
|
148
231
|
if result.returncode == 0:
|
|
149
|
-
|
|
150
|
-
|
|
232
|
+
return {
|
|
233
|
+
"success": True,
|
|
234
|
+
"context": context or "current",
|
|
235
|
+
"info": result.stdout
|
|
236
|
+
}
|
|
151
237
|
return {"success": False, "error": result.stderr}
|
|
152
238
|
except Exception as e:
|
|
153
|
-
logger.error(f"Error
|
|
239
|
+
logger.error(f"Error getting cluster info: {e}")
|
|
154
240
|
return {"success": False, "error": str(e)}
|
|
155
241
|
|
|
156
242
|
@server.tool(
|
|
157
243
|
annotations=ToolAnnotations(
|
|
158
|
-
title="Get Cluster Info",
|
|
244
|
+
title="Get Cluster Version Info",
|
|
159
245
|
readOnlyHint=True,
|
|
160
246
|
),
|
|
161
247
|
)
|
|
162
|
-
def
|
|
163
|
-
"""Get cluster information.
|
|
248
|
+
def get_cluster_version(context: str = "") -> Dict[str, Any]:
|
|
249
|
+
"""Get Kubernetes cluster version information.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
253
|
+
"""
|
|
164
254
|
try:
|
|
255
|
+
version_api = get_version_client(context)
|
|
256
|
+
version_info = version_api.get_code()
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"success": True,
|
|
260
|
+
"context": context or "current",
|
|
261
|
+
"version": {
|
|
262
|
+
"gitVersion": version_info.git_version,
|
|
263
|
+
"major": version_info.major,
|
|
264
|
+
"minor": version_info.minor,
|
|
265
|
+
"platform": version_info.platform,
|
|
266
|
+
"buildDate": version_info.build_date,
|
|
267
|
+
"goVersion": version_info.go_version,
|
|
268
|
+
"compiler": version_info.compiler
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.error(f"Error getting cluster version: {e}")
|
|
273
|
+
return {"success": False, "error": str(e)}
|
|
274
|
+
|
|
275
|
+
@server.tool(
|
|
276
|
+
annotations=ToolAnnotations(
|
|
277
|
+
title="Health Check",
|
|
278
|
+
readOnlyHint=True,
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
def health_check(context: str = "") -> Dict[str, Any]:
|
|
282
|
+
"""Perform a cluster health check.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
165
289
|
result = subprocess.run(
|
|
166
|
-
["kubectl", "
|
|
290
|
+
["kubectl"] + ctx_args + ["get", "componentstatuses", "-o", "json"],
|
|
167
291
|
capture_output=True, text=True, timeout=30
|
|
168
292
|
)
|
|
169
293
|
if result.returncode == 0:
|
|
170
|
-
|
|
294
|
+
data = json.loads(result.stdout)
|
|
295
|
+
return {
|
|
296
|
+
"success": True,
|
|
297
|
+
"context": context or "current",
|
|
298
|
+
"components": data.get("items", [])
|
|
299
|
+
}
|
|
171
300
|
return {"success": False, "error": result.stderr}
|
|
172
301
|
except Exception as e:
|
|
173
|
-
logger.error(f"Error
|
|
302
|
+
logger.error(f"Error performing health check: {e}")
|
|
174
303
|
return {"success": False, "error": str(e)}
|
|
175
304
|
|
|
176
305
|
@server.tool(
|
|
@@ -179,14 +308,27 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
179
308
|
readOnlyHint=True,
|
|
180
309
|
),
|
|
181
310
|
)
|
|
182
|
-
def kubectl_explain(
|
|
183
|
-
|
|
311
|
+
def kubectl_explain(
|
|
312
|
+
resource: str,
|
|
313
|
+
context: str = ""
|
|
314
|
+
) -> Dict[str, Any]:
|
|
315
|
+
"""Explain a Kubernetes resource.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
resource: Resource type to explain (e.g., pods, deployments.spec)
|
|
319
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
320
|
+
"""
|
|
184
321
|
try:
|
|
322
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
185
323
|
result = subprocess.run(
|
|
186
|
-
["kubectl"
|
|
324
|
+
["kubectl"] + ctx_args + ["explain", resource],
|
|
187
325
|
capture_output=True, text=True, timeout=30
|
|
188
326
|
)
|
|
189
|
-
return {
|
|
327
|
+
return {
|
|
328
|
+
"success": result.returncode == 0,
|
|
329
|
+
"context": context or "current",
|
|
330
|
+
"output": result.stdout or result.stderr
|
|
331
|
+
}
|
|
190
332
|
except Exception as e:
|
|
191
333
|
logger.error(f"Error explaining resource: {e}")
|
|
192
334
|
return {"success": False, "error": str(e)}
|
|
@@ -197,68 +339,56 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
197
339
|
readOnlyHint=True,
|
|
198
340
|
),
|
|
199
341
|
)
|
|
200
|
-
def get_api_resources() -> Dict[str, Any]:
|
|
201
|
-
"""Get available API resources.
|
|
342
|
+
def get_api_resources(context: str = "") -> Dict[str, Any]:
|
|
343
|
+
"""Get available API resources.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
347
|
+
"""
|
|
202
348
|
try:
|
|
349
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
203
350
|
result = subprocess.run(
|
|
204
|
-
["kubectl"
|
|
351
|
+
["kubectl"] + ctx_args + ["api-resources"],
|
|
205
352
|
capture_output=True, text=True, timeout=30
|
|
206
353
|
)
|
|
207
|
-
return {
|
|
354
|
+
return {
|
|
355
|
+
"success": result.returncode == 0,
|
|
356
|
+
"context": context or "current",
|
|
357
|
+
"output": result.stdout or result.stderr
|
|
358
|
+
}
|
|
208
359
|
except Exception as e:
|
|
209
360
|
logger.error(f"Error getting API resources: {e}")
|
|
210
361
|
return {"success": False, "error": str(e)}
|
|
211
362
|
|
|
212
363
|
@server.tool(
|
|
213
364
|
annotations=ToolAnnotations(
|
|
214
|
-
title="
|
|
365
|
+
title="Get API Versions",
|
|
215
366
|
readOnlyHint=True,
|
|
216
367
|
),
|
|
217
368
|
)
|
|
218
|
-
def
|
|
219
|
-
"""
|
|
369
|
+
def get_api_versions(context: str = "") -> Dict[str, Any]:
|
|
370
|
+
"""Get available API versions.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
374
|
+
"""
|
|
220
375
|
try:
|
|
376
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
221
377
|
result = subprocess.run(
|
|
222
|
-
["kubectl"
|
|
378
|
+
["kubectl"] + ctx_args + ["api-versions"],
|
|
223
379
|
capture_output=True, text=True, timeout=30
|
|
224
380
|
)
|
|
225
381
|
if result.returncode == 0:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return {"success": False, "error": str(e)}
|
|
233
|
-
|
|
234
|
-
@server.tool(
|
|
235
|
-
annotations=ToolAnnotations(
|
|
236
|
-
title="Get Cluster Version Info",
|
|
237
|
-
readOnlyHint=True,
|
|
238
|
-
),
|
|
239
|
-
)
|
|
240
|
-
def get_cluster_version() -> Dict[str, Any]:
|
|
241
|
-
"""Get Kubernetes cluster version information."""
|
|
242
|
-
try:
|
|
243
|
-
from kubernetes import client, config
|
|
244
|
-
config.load_kube_config()
|
|
245
|
-
version_api = client.VersionApi()
|
|
246
|
-
version_info = version_api.get_code()
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
"success": True,
|
|
250
|
-
"version": {
|
|
251
|
-
"gitVersion": version_info.git_version,
|
|
252
|
-
"major": version_info.major,
|
|
253
|
-
"minor": version_info.minor,
|
|
254
|
-
"platform": version_info.platform,
|
|
255
|
-
"buildDate": version_info.build_date,
|
|
256
|
-
"goVersion": version_info.go_version,
|
|
257
|
-
"compiler": version_info.compiler
|
|
382
|
+
versions = [v.strip() for v in result.stdout.strip().split("\n") if v.strip()]
|
|
383
|
+
return {
|
|
384
|
+
"success": True,
|
|
385
|
+
"context": context or "current",
|
|
386
|
+
"versions": versions,
|
|
387
|
+
"total": len(versions)
|
|
258
388
|
}
|
|
259
|
-
}
|
|
389
|
+
return {"success": False, "error": result.stderr}
|
|
260
390
|
except Exception as e:
|
|
261
|
-
logger.error(f"Error getting
|
|
391
|
+
logger.error(f"Error getting API versions: {e}")
|
|
262
392
|
return {"success": False, "error": str(e)}
|
|
263
393
|
|
|
264
394
|
@server.tool(
|
|
@@ -267,18 +397,21 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
267
397
|
readOnlyHint=True,
|
|
268
398
|
),
|
|
269
399
|
)
|
|
270
|
-
def get_admission_webhooks() -> Dict[str, Any]:
|
|
271
|
-
"""Get admission webhooks configured in the cluster.
|
|
400
|
+
def get_admission_webhooks(context: str = "") -> Dict[str, Any]:
|
|
401
|
+
"""Get admission webhooks configured in the cluster.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
405
|
+
"""
|
|
272
406
|
try:
|
|
273
|
-
|
|
274
|
-
config.load_kube_config()
|
|
275
|
-
api = client.AdmissionregistrationV1Api()
|
|
407
|
+
api = get_admissionregistration_client(context)
|
|
276
408
|
|
|
277
409
|
validating = api.list_validating_webhook_configuration()
|
|
278
410
|
mutating = api.list_mutating_webhook_configuration()
|
|
279
411
|
|
|
280
412
|
return {
|
|
281
413
|
"success": True,
|
|
414
|
+
"context": context or "current",
|
|
282
415
|
"validatingWebhooks": [
|
|
283
416
|
{
|
|
284
417
|
"name": w.metadata.name,
|
|
@@ -313,3 +446,144 @@ def register_cluster_tools(server, non_destructive: bool):
|
|
|
313
446
|
except Exception as e:
|
|
314
447
|
logger.error(f"Error getting admission webhooks: {e}")
|
|
315
448
|
return {"success": False, "error": str(e)}
|
|
449
|
+
|
|
450
|
+
@server.tool(
|
|
451
|
+
annotations=ToolAnnotations(
|
|
452
|
+
title="Check CRD Exists",
|
|
453
|
+
readOnlyHint=True,
|
|
454
|
+
),
|
|
455
|
+
)
|
|
456
|
+
def check_crd_exists(
|
|
457
|
+
crd_name: str,
|
|
458
|
+
context: str = ""
|
|
459
|
+
) -> Dict[str, Any]:
|
|
460
|
+
"""Check if a Custom Resource Definition exists in the cluster.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
crd_name: Name of the CRD to check (e.g., certificates.cert-manager.io)
|
|
464
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
465
|
+
"""
|
|
466
|
+
try:
|
|
467
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
468
|
+
result = subprocess.run(
|
|
469
|
+
["kubectl"] + ctx_args + ["get", "crd", crd_name, "-o", "name"],
|
|
470
|
+
capture_output=True, text=True, timeout=10
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
exists = result.returncode == 0
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
"success": True,
|
|
477
|
+
"context": context or "current",
|
|
478
|
+
"crd": crd_name,
|
|
479
|
+
"exists": exists
|
|
480
|
+
}
|
|
481
|
+
except Exception as e:
|
|
482
|
+
logger.error(f"Error checking CRD: {e}")
|
|
483
|
+
return {"success": False, "error": str(e)}
|
|
484
|
+
|
|
485
|
+
@server.tool(
|
|
486
|
+
annotations=ToolAnnotations(
|
|
487
|
+
title="List CRDs",
|
|
488
|
+
readOnlyHint=True,
|
|
489
|
+
),
|
|
490
|
+
)
|
|
491
|
+
def list_crds(context: str = "") -> Dict[str, Any]:
|
|
492
|
+
"""List all Custom Resource Definitions in the cluster.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
496
|
+
"""
|
|
497
|
+
try:
|
|
498
|
+
ctx_args = _get_kubectl_context_args(context)
|
|
499
|
+
result = subprocess.run(
|
|
500
|
+
["kubectl"] + ctx_args + ["get", "crd", "-o", "json"],
|
|
501
|
+
capture_output=True, text=True, timeout=30
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
if result.returncode == 0:
|
|
505
|
+
data = json.loads(result.stdout)
|
|
506
|
+
crds = []
|
|
507
|
+
for item in data.get("items", []):
|
|
508
|
+
crds.append({
|
|
509
|
+
"name": item.get("metadata", {}).get("name"),
|
|
510
|
+
"group": item.get("spec", {}).get("group"),
|
|
511
|
+
"scope": item.get("spec", {}).get("scope"),
|
|
512
|
+
"versions": [
|
|
513
|
+
v.get("name") for v in item.get("spec", {}).get("versions", [])
|
|
514
|
+
]
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
"success": True,
|
|
519
|
+
"context": context or "current",
|
|
520
|
+
"crds": crds,
|
|
521
|
+
"total": len(crds)
|
|
522
|
+
}
|
|
523
|
+
return {"success": False, "error": result.stderr}
|
|
524
|
+
except Exception as e:
|
|
525
|
+
logger.error(f"Error listing CRDs: {e}")
|
|
526
|
+
return {"success": False, "error": str(e)}
|
|
527
|
+
|
|
528
|
+
@server.tool(
|
|
529
|
+
annotations=ToolAnnotations(
|
|
530
|
+
title="Get Nodes Summary",
|
|
531
|
+
readOnlyHint=True,
|
|
532
|
+
),
|
|
533
|
+
)
|
|
534
|
+
def get_nodes_summary(context: str = "") -> Dict[str, Any]:
|
|
535
|
+
"""Get summary of all nodes in the cluster.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
539
|
+
"""
|
|
540
|
+
try:
|
|
541
|
+
v1 = get_k8s_client(context)
|
|
542
|
+
nodes = v1.list_node()
|
|
543
|
+
|
|
544
|
+
summary = {
|
|
545
|
+
"total": len(nodes.items),
|
|
546
|
+
"ready": 0,
|
|
547
|
+
"notReady": 0,
|
|
548
|
+
"nodes": []
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
for node in nodes.items:
|
|
552
|
+
node_info = {
|
|
553
|
+
"name": node.metadata.name,
|
|
554
|
+
"status": "Unknown",
|
|
555
|
+
"roles": [],
|
|
556
|
+
"kubeletVersion": node.status.node_info.kubelet_version if node.status.node_info else None,
|
|
557
|
+
"os": node.status.node_info.os_image if node.status.node_info else None,
|
|
558
|
+
"capacity": {
|
|
559
|
+
"cpu": node.status.capacity.get("cpu") if node.status.capacity else None,
|
|
560
|
+
"memory": node.status.capacity.get("memory") if node.status.capacity else None,
|
|
561
|
+
"pods": node.status.capacity.get("pods") if node.status.capacity else None
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
# Get node status
|
|
566
|
+
for condition in (node.status.conditions or []):
|
|
567
|
+
if condition.type == "Ready":
|
|
568
|
+
node_info["status"] = "Ready" if condition.status == "True" else "NotReady"
|
|
569
|
+
if condition.status == "True":
|
|
570
|
+
summary["ready"] += 1
|
|
571
|
+
else:
|
|
572
|
+
summary["notReady"] += 1
|
|
573
|
+
|
|
574
|
+
# Get node roles
|
|
575
|
+
for label, value in (node.metadata.labels or {}).items():
|
|
576
|
+
if label.startswith("node-role.kubernetes.io/"):
|
|
577
|
+
role = label.split("/")[1]
|
|
578
|
+
node_info["roles"].append(role)
|
|
579
|
+
|
|
580
|
+
summary["nodes"].append(node_info)
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
"success": True,
|
|
584
|
+
"context": context or "current",
|
|
585
|
+
"summary": summary
|
|
586
|
+
}
|
|
587
|
+
except Exception as e:
|
|
588
|
+
logger.error(f"Error getting nodes summary: {e}")
|
|
589
|
+
return {"success": False, "error": str(e)}
|