kubectl-mcp-server 1.13.0__py3-none-any.whl → 1.15.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.15.0.dist-info/METADATA +1026 -0
- kubectl_mcp_server-1.15.0.dist-info/RECORD +49 -0
- kubectl_mcp_tool/__init__.py +1 -1
- kubectl_mcp_tool/cli/__init__.py +53 -1
- kubectl_mcp_tool/cli/cli.py +476 -17
- kubectl_mcp_tool/cli/errors.py +262 -0
- kubectl_mcp_tool/cli/output.py +377 -0
- kubectl_mcp_tool/k8s_config.py +285 -63
- kubectl_mcp_tool/tools/browser.py +316 -28
- 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/helm.py +133 -46
- 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/security.py +89 -36
- kubectl_mcp_tool/tools/storage.py +35 -16
- tests/test_browser.py +167 -5
- tests/test_cli.py +299 -0
- tests/test_tools.py +10 -9
- kubectl_mcp_server-1.13.0.dist-info/METADATA +0 -780
- kubectl_mcp_server-1.13.0.dist-info/RECORD +0 -46
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/WHEEL +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.15.0.dist-info}/top_level.txt +0 -0
kubectl_mcp_tool/tools/core.py
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import subprocess
|
|
2
3
|
from typing import Any, Dict, List, Optional
|
|
3
4
|
|
|
4
5
|
from mcp.types import ToolAnnotations
|
|
5
6
|
|
|
7
|
+
from ..k8s_config import (
|
|
8
|
+
get_k8s_client,
|
|
9
|
+
get_apiextensions_client,
|
|
10
|
+
)
|
|
11
|
+
|
|
6
12
|
logger = logging.getLogger("mcp-server")
|
|
7
13
|
|
|
8
14
|
|
|
15
|
+
def _get_kubectl_context_args(context: str) -> List[str]:
|
|
16
|
+
"""Get kubectl context arguments if context is specified."""
|
|
17
|
+
if context:
|
|
18
|
+
return ["--context", context]
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
|
|
9
22
|
def register_core_tools(server, non_destructive: bool):
|
|
10
23
|
"""Register core Kubernetes resource tools."""
|
|
11
24
|
|
|
@@ -15,15 +28,18 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
15
28
|
readOnlyHint=True,
|
|
16
29
|
),
|
|
17
30
|
)
|
|
18
|
-
def get_namespaces() -> Dict[str, Any]:
|
|
19
|
-
"""Get all Kubernetes namespaces.
|
|
31
|
+
def get_namespaces(context: str = "") -> Dict[str, Any]:
|
|
32
|
+
"""Get all Kubernetes namespaces.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
36
|
+
"""
|
|
20
37
|
try:
|
|
21
|
-
|
|
22
|
-
config.load_kube_config()
|
|
23
|
-
v1 = client.CoreV1Api()
|
|
38
|
+
v1 = get_k8s_client(context)
|
|
24
39
|
namespaces = v1.list_namespace()
|
|
25
40
|
return {
|
|
26
41
|
"success": True,
|
|
42
|
+
"context": context or "current",
|
|
27
43
|
"namespaces": [ns.metadata.name for ns in namespaces.items]
|
|
28
44
|
}
|
|
29
45
|
except Exception as e:
|
|
@@ -36,18 +52,25 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
36
52
|
readOnlyHint=True,
|
|
37
53
|
),
|
|
38
54
|
)
|
|
39
|
-
def get_services(
|
|
40
|
-
|
|
55
|
+
def get_services(
|
|
56
|
+
namespace: Optional[str] = None,
|
|
57
|
+
context: str = ""
|
|
58
|
+
) -> Dict[str, Any]:
|
|
59
|
+
"""Get all services in the specified namespace.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
namespace: Namespace to list services from (all namespaces if not specified)
|
|
63
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
64
|
+
"""
|
|
41
65
|
try:
|
|
42
|
-
|
|
43
|
-
config.load_kube_config()
|
|
44
|
-
v1 = client.CoreV1Api()
|
|
66
|
+
v1 = get_k8s_client(context)
|
|
45
67
|
if namespace:
|
|
46
68
|
services = v1.list_namespaced_service(namespace)
|
|
47
69
|
else:
|
|
48
70
|
services = v1.list_service_for_all_namespaces()
|
|
49
71
|
return {
|
|
50
72
|
"success": True,
|
|
73
|
+
"context": context or "current",
|
|
51
74
|
"services": [
|
|
52
75
|
{
|
|
53
76
|
"name": svc.metadata.name,
|
|
@@ -67,15 +90,18 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
67
90
|
readOnlyHint=True,
|
|
68
91
|
),
|
|
69
92
|
)
|
|
70
|
-
def get_nodes() -> Dict[str, Any]:
|
|
71
|
-
"""Get all nodes in the cluster.
|
|
93
|
+
def get_nodes(context: str = "") -> Dict[str, Any]:
|
|
94
|
+
"""Get all nodes in the cluster.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
98
|
+
"""
|
|
72
99
|
try:
|
|
73
|
-
|
|
74
|
-
config.load_kube_config()
|
|
75
|
-
v1 = client.CoreV1Api()
|
|
100
|
+
v1 = get_k8s_client(context)
|
|
76
101
|
nodes = v1.list_node()
|
|
77
102
|
return {
|
|
78
103
|
"success": True,
|
|
104
|
+
"context": context or "current",
|
|
79
105
|
"nodes": [
|
|
80
106
|
{
|
|
81
107
|
"name": node.metadata.name,
|
|
@@ -104,18 +130,25 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
104
130
|
readOnlyHint=True,
|
|
105
131
|
),
|
|
106
132
|
)
|
|
107
|
-
def get_configmaps(
|
|
108
|
-
|
|
133
|
+
def get_configmaps(
|
|
134
|
+
namespace: Optional[str] = None,
|
|
135
|
+
context: str = ""
|
|
136
|
+
) -> Dict[str, Any]:
|
|
137
|
+
"""Get all ConfigMaps in the specified namespace.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
namespace: Namespace to list ConfigMaps from (all namespaces if not specified)
|
|
141
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
142
|
+
"""
|
|
109
143
|
try:
|
|
110
|
-
|
|
111
|
-
config.load_kube_config()
|
|
112
|
-
v1 = client.CoreV1Api()
|
|
144
|
+
v1 = get_k8s_client(context)
|
|
113
145
|
if namespace:
|
|
114
146
|
cms = v1.list_namespaced_config_map(namespace)
|
|
115
147
|
else:
|
|
116
148
|
cms = v1.list_config_map_for_all_namespaces()
|
|
117
149
|
return {
|
|
118
150
|
"success": True,
|
|
151
|
+
"context": context or "current",
|
|
119
152
|
"configmaps": [
|
|
120
153
|
{
|
|
121
154
|
"name": cm.metadata.name,
|
|
@@ -134,18 +167,25 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
134
167
|
readOnlyHint=True,
|
|
135
168
|
),
|
|
136
169
|
)
|
|
137
|
-
def get_secrets(
|
|
138
|
-
|
|
170
|
+
def get_secrets(
|
|
171
|
+
namespace: Optional[str] = None,
|
|
172
|
+
context: str = ""
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""Get all Secrets in the specified namespace.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
namespace: Namespace to list Secrets from (all namespaces if not specified)
|
|
178
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
179
|
+
"""
|
|
139
180
|
try:
|
|
140
|
-
|
|
141
|
-
config.load_kube_config()
|
|
142
|
-
v1 = client.CoreV1Api()
|
|
181
|
+
v1 = get_k8s_client(context)
|
|
143
182
|
if namespace:
|
|
144
183
|
secrets = v1.list_namespaced_secret(namespace)
|
|
145
184
|
else:
|
|
146
185
|
secrets = v1.list_secret_for_all_namespaces()
|
|
147
186
|
return {
|
|
148
187
|
"success": True,
|
|
188
|
+
"context": context or "current",
|
|
149
189
|
"secrets": [
|
|
150
190
|
{
|
|
151
191
|
"name": secret.metadata.name,
|
|
@@ -164,18 +204,25 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
164
204
|
readOnlyHint=True,
|
|
165
205
|
),
|
|
166
206
|
)
|
|
167
|
-
def get_events(
|
|
168
|
-
|
|
207
|
+
def get_events(
|
|
208
|
+
namespace: Optional[str] = None,
|
|
209
|
+
context: str = ""
|
|
210
|
+
) -> Dict[str, Any]:
|
|
211
|
+
"""Get Kubernetes events.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
namespace: Namespace to list events from (all namespaces if not specified)
|
|
215
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
216
|
+
"""
|
|
169
217
|
try:
|
|
170
|
-
|
|
171
|
-
config.load_kube_config()
|
|
172
|
-
v1 = client.CoreV1Api()
|
|
218
|
+
v1 = get_k8s_client(context)
|
|
173
219
|
if namespace:
|
|
174
220
|
events = v1.list_namespaced_event(namespace)
|
|
175
221
|
else:
|
|
176
222
|
events = v1.list_event_for_all_namespaces()
|
|
177
223
|
return {
|
|
178
224
|
"success": True,
|
|
225
|
+
"context": context or "current",
|
|
179
226
|
"events": [
|
|
180
227
|
{
|
|
181
228
|
"name": event.metadata.name,
|
|
@@ -197,11 +244,19 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
197
244
|
readOnlyHint=True,
|
|
198
245
|
),
|
|
199
246
|
)
|
|
200
|
-
def get_resource_usage(
|
|
201
|
-
|
|
247
|
+
def get_resource_usage(
|
|
248
|
+
namespace: Optional[str] = None,
|
|
249
|
+
context: str = ""
|
|
250
|
+
) -> Dict[str, Any]:
|
|
251
|
+
"""Get resource usage metrics for pods.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
namespace: Namespace to get metrics from (all namespaces if not specified)
|
|
255
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
256
|
+
"""
|
|
202
257
|
try:
|
|
203
|
-
import subprocess
|
|
204
258
|
cmd = ["kubectl", "top", "pods"]
|
|
259
|
+
cmd.extend(_get_kubectl_context_args(context))
|
|
205
260
|
if namespace:
|
|
206
261
|
cmd.extend(["-n", namespace])
|
|
207
262
|
else:
|
|
@@ -233,7 +288,11 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
233
288
|
"memory": parts[3]
|
|
234
289
|
})
|
|
235
290
|
|
|
236
|
-
return {
|
|
291
|
+
return {
|
|
292
|
+
"success": True,
|
|
293
|
+
"context": context or "current",
|
|
294
|
+
"pods": pods
|
|
295
|
+
}
|
|
237
296
|
except subprocess.TimeoutExpired:
|
|
238
297
|
return {"success": False, "error": "Metrics retrieval timed out"}
|
|
239
298
|
except Exception as e:
|
|
@@ -246,12 +305,18 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
246
305
|
readOnlyHint=True,
|
|
247
306
|
),
|
|
248
307
|
)
|
|
249
|
-
def get_service_accounts(
|
|
250
|
-
|
|
308
|
+
def get_service_accounts(
|
|
309
|
+
namespace: Optional[str] = None,
|
|
310
|
+
context: str = ""
|
|
311
|
+
) -> Dict[str, Any]:
|
|
312
|
+
"""Get service accounts in a namespace or cluster-wide.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
namespace: Namespace to list service accounts from (all namespaces if not specified)
|
|
316
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
317
|
+
"""
|
|
251
318
|
try:
|
|
252
|
-
|
|
253
|
-
config.load_kube_config()
|
|
254
|
-
v1 = client.CoreV1Api()
|
|
319
|
+
v1 = get_k8s_client(context)
|
|
255
320
|
|
|
256
321
|
if namespace:
|
|
257
322
|
sas = v1.list_namespaced_service_account(namespace)
|
|
@@ -260,6 +325,7 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
260
325
|
|
|
261
326
|
return {
|
|
262
327
|
"success": True,
|
|
328
|
+
"context": context or "current",
|
|
263
329
|
"serviceAccounts": [
|
|
264
330
|
{
|
|
265
331
|
"name": sa.metadata.name,
|
|
@@ -281,12 +347,18 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
281
347
|
readOnlyHint=True,
|
|
282
348
|
),
|
|
283
349
|
)
|
|
284
|
-
def get_crds(
|
|
285
|
-
|
|
350
|
+
def get_crds(
|
|
351
|
+
group: Optional[str] = None,
|
|
352
|
+
context: str = ""
|
|
353
|
+
) -> Dict[str, Any]:
|
|
354
|
+
"""Get Custom Resource Definitions in the cluster.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
group: Filter CRDs by API group
|
|
358
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
359
|
+
"""
|
|
286
360
|
try:
|
|
287
|
-
|
|
288
|
-
config.load_kube_config()
|
|
289
|
-
api = client.ApiextensionsV1Api()
|
|
361
|
+
api = get_apiextensions_client(context)
|
|
290
362
|
|
|
291
363
|
crds = api.list_custom_resource_definition()
|
|
292
364
|
|
|
@@ -307,7 +379,11 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
307
379
|
)
|
|
308
380
|
})
|
|
309
381
|
|
|
310
|
-
return {
|
|
382
|
+
return {
|
|
383
|
+
"success": True,
|
|
384
|
+
"context": context or "current",
|
|
385
|
+
"crds": result
|
|
386
|
+
}
|
|
311
387
|
except Exception as e:
|
|
312
388
|
logger.error(f"Error getting CRDs: {e}")
|
|
313
389
|
return {"success": False, "error": str(e)}
|
|
@@ -318,12 +394,18 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
318
394
|
readOnlyHint=True,
|
|
319
395
|
),
|
|
320
396
|
)
|
|
321
|
-
def get_resource_quotas(
|
|
322
|
-
|
|
397
|
+
def get_resource_quotas(
|
|
398
|
+
namespace: Optional[str] = None,
|
|
399
|
+
context: str = ""
|
|
400
|
+
) -> Dict[str, Any]:
|
|
401
|
+
"""Get resource quotas for namespaces.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
namespace: Namespace to list resource quotas from (all namespaces if not specified)
|
|
405
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
406
|
+
"""
|
|
323
407
|
try:
|
|
324
|
-
|
|
325
|
-
config.load_kube_config()
|
|
326
|
-
v1 = client.CoreV1Api()
|
|
408
|
+
v1 = get_k8s_client(context)
|
|
327
409
|
|
|
328
410
|
if namespace:
|
|
329
411
|
quotas = v1.list_namespaced_resource_quota(namespace)
|
|
@@ -332,6 +414,7 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
332
414
|
|
|
333
415
|
return {
|
|
334
416
|
"success": True,
|
|
417
|
+
"context": context or "current",
|
|
335
418
|
"quotas": [
|
|
336
419
|
{
|
|
337
420
|
"name": q.metadata.name,
|
|
@@ -352,12 +435,18 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
352
435
|
readOnlyHint=True,
|
|
353
436
|
),
|
|
354
437
|
)
|
|
355
|
-
def get_limit_ranges(
|
|
356
|
-
|
|
438
|
+
def get_limit_ranges(
|
|
439
|
+
namespace: Optional[str] = None,
|
|
440
|
+
context: str = ""
|
|
441
|
+
) -> Dict[str, Any]:
|
|
442
|
+
"""Get limit ranges for namespaces.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
namespace: Namespace to list limit ranges from (all namespaces if not specified)
|
|
446
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
447
|
+
"""
|
|
357
448
|
try:
|
|
358
|
-
|
|
359
|
-
config.load_kube_config()
|
|
360
|
-
v1 = client.CoreV1Api()
|
|
449
|
+
v1 = get_k8s_client(context)
|
|
361
450
|
|
|
362
451
|
if namespace:
|
|
363
452
|
limits = v1.list_namespaced_limit_range(namespace)
|
|
@@ -366,6 +455,7 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
366
455
|
|
|
367
456
|
return {
|
|
368
457
|
"success": True,
|
|
458
|
+
"context": context or "current",
|
|
369
459
|
"limitRanges": [
|
|
370
460
|
{
|
|
371
461
|
"name": lr.metadata.name,
|
|
@@ -394,17 +484,24 @@ def register_core_tools(server, non_destructive: bool):
|
|
|
394
484
|
readOnlyHint=True,
|
|
395
485
|
),
|
|
396
486
|
)
|
|
397
|
-
def get_priority_classes() -> Dict[str, Any]:
|
|
398
|
-
"""Get priority classes in the cluster.
|
|
487
|
+
def get_priority_classes(context: str = "") -> Dict[str, Any]:
|
|
488
|
+
"""Get priority classes in the cluster.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
context: Kubernetes context to use (uses current context if not specified)
|
|
492
|
+
"""
|
|
399
493
|
try:
|
|
400
|
-
from kubernetes import client
|
|
401
|
-
|
|
402
|
-
|
|
494
|
+
from kubernetes import client
|
|
495
|
+
from ..k8s_config import _load_config_for_context
|
|
496
|
+
|
|
497
|
+
api_client = _load_config_for_context(context)
|
|
498
|
+
api = client.SchedulingV1Api(api_client=api_client)
|
|
403
499
|
|
|
404
500
|
pcs = api.list_priority_class()
|
|
405
501
|
|
|
406
502
|
return {
|
|
407
503
|
"success": True,
|
|
504
|
+
"context": context or "current",
|
|
408
505
|
"priorityClasses": [
|
|
409
506
|
{
|
|
410
507
|
"name": pc.metadata.name,
|
kubectl_mcp_tool/tools/cost.py
CHANGED
|
@@ -2,13 +2,22 @@ import logging
|
|
|
2
2
|
import subprocess
|
|
3
3
|
import re
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import Any, Dict, Optional
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
6
|
|
|
7
7
|
from mcp.types import ToolAnnotations
|
|
8
8
|
|
|
9
|
+
from ..k8s_config import get_k8s_client, get_apps_client
|
|
10
|
+
|
|
9
11
|
logger = logging.getLogger("mcp-server")
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
def _get_kubectl_context_args(context: str) -> List[str]:
|
|
15
|
+
"""Get kubectl context arguments if context is specified."""
|
|
16
|
+
if context:
|
|
17
|
+
return ["--context", context]
|
|
18
|
+
return []
|
|
19
|
+
|
|
20
|
+
|
|
12
21
|
def _parse_cpu(cpu_str: str) -> int:
|
|
13
22
|
"""Parse CPU string to millicores."""
|
|
14
23
|
try:
|
|
@@ -67,13 +76,19 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
67
76
|
)
|
|
68
77
|
def get_resource_recommendations(
|
|
69
78
|
namespace: Optional[str] = None,
|
|
70
|
-
resource_type: str = "all"
|
|
79
|
+
resource_type: str = "all",
|
|
80
|
+
context: str = ""
|
|
71
81
|
) -> Dict[str, Any]:
|
|
72
|
-
"""Analyze resource usage and provide optimization recommendations for pods/deployments.
|
|
82
|
+
"""Analyze resource usage and provide optimization recommendations for pods/deployments.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
namespace: Target namespace (optional, all namespaces if not specified)
|
|
86
|
+
resource_type: Type of resource to analyze
|
|
87
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
88
|
+
"""
|
|
73
89
|
try:
|
|
74
|
-
from kubernetes import client
|
|
75
|
-
|
|
76
|
-
v1 = client.CoreV1Api()
|
|
90
|
+
from kubernetes import client
|
|
91
|
+
v1 = get_k8s_client(context)
|
|
77
92
|
|
|
78
93
|
recommendations = []
|
|
79
94
|
|
|
@@ -135,6 +150,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
135
150
|
|
|
136
151
|
return {
|
|
137
152
|
"success": True,
|
|
153
|
+
"context": context or "current",
|
|
138
154
|
"totalAnalyzed": len(pods),
|
|
139
155
|
"issuesFound": len(recommendations),
|
|
140
156
|
"recommendations": recommendations[:50]
|
|
@@ -152,11 +168,19 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
152
168
|
def get_idle_resources(
|
|
153
169
|
namespace: Optional[str] = None,
|
|
154
170
|
cpu_threshold: float = 10.0,
|
|
155
|
-
memory_threshold: float = 10.0
|
|
171
|
+
memory_threshold: float = 10.0,
|
|
172
|
+
context: str = ""
|
|
156
173
|
) -> Dict[str, Any]:
|
|
157
|
-
"""Find underutilized pods using less than threshold percentage of requested resources.
|
|
174
|
+
"""Find underutilized pods using less than threshold percentage of requested resources.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
namespace: Target namespace (optional, all namespaces if not specified)
|
|
178
|
+
cpu_threshold: CPU usage threshold percentage
|
|
179
|
+
memory_threshold: Memory usage threshold percentage
|
|
180
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
181
|
+
"""
|
|
158
182
|
try:
|
|
159
|
-
cmd = ["kubectl"
|
|
183
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["top", "pods", "--no-headers"]
|
|
160
184
|
if namespace:
|
|
161
185
|
cmd.extend(["-n", namespace])
|
|
162
186
|
else:
|
|
@@ -196,6 +220,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
196
220
|
|
|
197
221
|
return {
|
|
198
222
|
"success": True,
|
|
223
|
+
"context": context or "current",
|
|
199
224
|
"thresholds": {
|
|
200
225
|
"cpu": f"{cpu_threshold}%",
|
|
201
226
|
"memory": f"{memory_threshold}%"
|
|
@@ -215,12 +240,15 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
215
240
|
readOnlyHint=True,
|
|
216
241
|
),
|
|
217
242
|
)
|
|
218
|
-
def get_resource_quotas_usage(namespace: Optional[str] = None) -> Dict[str, Any]:
|
|
219
|
-
"""Show resource quota usage and availability across namespaces.
|
|
243
|
+
def get_resource_quotas_usage(namespace: Optional[str] = None, context: str = "") -> Dict[str, Any]:
|
|
244
|
+
"""Show resource quota usage and availability across namespaces.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
namespace: Target namespace (optional, all namespaces if not specified)
|
|
248
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
249
|
+
"""
|
|
220
250
|
try:
|
|
221
|
-
|
|
222
|
-
config.load_kube_config()
|
|
223
|
-
v1 = client.CoreV1Api()
|
|
251
|
+
v1 = get_k8s_client(context)
|
|
224
252
|
|
|
225
253
|
if namespace:
|
|
226
254
|
quotas = v1.list_namespaced_resource_quota(namespace).items
|
|
@@ -250,6 +278,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
250
278
|
|
|
251
279
|
return {
|
|
252
280
|
"success": True,
|
|
281
|
+
"context": context or "current",
|
|
253
282
|
"count": len(quota_usage),
|
|
254
283
|
"quotas": quota_usage
|
|
255
284
|
}
|
|
@@ -263,12 +292,15 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
263
292
|
readOnlyHint=True,
|
|
264
293
|
),
|
|
265
294
|
)
|
|
266
|
-
def get_cost_analysis(namespace: Optional[str] = None) -> Dict[str, Any]:
|
|
267
|
-
"""Analyze resource costs by namespace and workload based on resource requests.
|
|
295
|
+
def get_cost_analysis(namespace: Optional[str] = None, context: str = "") -> Dict[str, Any]:
|
|
296
|
+
"""Analyze resource costs by namespace and workload based on resource requests.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
namespace: Target namespace (optional, all namespaces if not specified)
|
|
300
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
301
|
+
"""
|
|
268
302
|
try:
|
|
269
|
-
|
|
270
|
-
config.load_kube_config()
|
|
271
|
-
v1 = client.CoreV1Api()
|
|
303
|
+
v1 = get_k8s_client(context)
|
|
272
304
|
|
|
273
305
|
if namespace:
|
|
274
306
|
pods = v1.list_namespaced_pod(namespace).items
|
|
@@ -325,6 +357,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
325
357
|
|
|
326
358
|
return {
|
|
327
359
|
"success": True,
|
|
360
|
+
"context": context or "current",
|
|
328
361
|
"note": "Cost estimates based on resource requests. Integrate with cloud billing for actual costs.",
|
|
329
362
|
"byNamespace": ns_summary,
|
|
330
363
|
"topWorkloads": sorted(workload_costs, key=lambda x: x["cpuMillicores"], reverse=True)[:20]
|
|
@@ -341,15 +374,20 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
341
374
|
)
|
|
342
375
|
def get_overprovisioned_resources(
|
|
343
376
|
namespace: Optional[str] = None,
|
|
344
|
-
threshold: float = 50.0
|
|
377
|
+
threshold: float = 50.0,
|
|
378
|
+
context: str = ""
|
|
345
379
|
) -> Dict[str, Any]:
|
|
346
|
-
"""Find pods using significantly less resources than requested (over-provisioned).
|
|
380
|
+
"""Find pods using significantly less resources than requested (over-provisioned).
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
namespace: Target namespace (optional, all namespaces if not specified)
|
|
384
|
+
threshold: Utilization threshold percentage below which resources are considered over-provisioned
|
|
385
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
386
|
+
"""
|
|
347
387
|
try:
|
|
348
|
-
|
|
349
|
-
config.load_kube_config()
|
|
350
|
-
v1 = client.CoreV1Api()
|
|
388
|
+
v1 = get_k8s_client(context)
|
|
351
389
|
|
|
352
|
-
cmd = ["kubectl"
|
|
390
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["top", "pods", "--no-headers"]
|
|
353
391
|
if namespace:
|
|
354
392
|
cmd.extend(["-n", namespace])
|
|
355
393
|
else:
|
|
@@ -418,6 +456,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
418
456
|
|
|
419
457
|
return {
|
|
420
458
|
"success": True,
|
|
459
|
+
"context": context or "current",
|
|
421
460
|
"threshold": f"{threshold}%",
|
|
422
461
|
"count": len(overprovisioned),
|
|
423
462
|
"overprovisioned": overprovisioned[:50]
|
|
@@ -436,14 +475,21 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
436
475
|
)
|
|
437
476
|
def get_resource_trends(
|
|
438
477
|
namespace: Optional[str] = None,
|
|
439
|
-
resource_type: str = "pods"
|
|
478
|
+
resource_type: str = "pods",
|
|
479
|
+
context: str = ""
|
|
440
480
|
) -> Dict[str, Any]:
|
|
441
|
-
"""Get current resource usage snapshot for trend analysis (requires metrics-server).
|
|
481
|
+
"""Get current resource usage snapshot for trend analysis (requires metrics-server).
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
namespace: Target namespace (optional, all namespaces if not specified)
|
|
485
|
+
resource_type: Type of resource to analyze (pods or nodes)
|
|
486
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
487
|
+
"""
|
|
442
488
|
try:
|
|
443
489
|
if resource_type == "nodes":
|
|
444
|
-
cmd = ["kubectl"
|
|
490
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["top", "nodes", "--no-headers"]
|
|
445
491
|
else:
|
|
446
|
-
cmd = ["kubectl"
|
|
492
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["top", "pods", "--no-headers"]
|
|
447
493
|
if namespace:
|
|
448
494
|
cmd.extend(["-n", namespace])
|
|
449
495
|
else:
|
|
@@ -501,6 +547,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
501
547
|
|
|
502
548
|
return {
|
|
503
549
|
"success": True,
|
|
550
|
+
"context": context or "current",
|
|
504
551
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
505
552
|
"resourceType": resource_type,
|
|
506
553
|
"summary": {
|
|
@@ -523,12 +570,14 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
523
570
|
readOnlyHint=True,
|
|
524
571
|
),
|
|
525
572
|
)
|
|
526
|
-
def get_namespace_cost_allocation() -> Dict[str, Any]:
|
|
527
|
-
"""Calculate resource allocation percentages across all namespaces.
|
|
573
|
+
def get_namespace_cost_allocation(context: str = "") -> Dict[str, Any]:
|
|
574
|
+
"""Calculate resource allocation percentages across all namespaces.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
578
|
+
"""
|
|
528
579
|
try:
|
|
529
|
-
|
|
530
|
-
config.load_kube_config()
|
|
531
|
-
v1 = client.CoreV1Api()
|
|
580
|
+
v1 = get_k8s_client(context)
|
|
532
581
|
|
|
533
582
|
pods = v1.list_pod_for_all_namespaces().items
|
|
534
583
|
|
|
@@ -573,6 +622,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
573
622
|
|
|
574
623
|
return {
|
|
575
624
|
"success": True,
|
|
625
|
+
"context": context or "current",
|
|
576
626
|
"clusterTotals": {
|
|
577
627
|
"totalCpuMillicores": total_cpu,
|
|
578
628
|
"totalMemoryMi": round(total_memory / (1024 * 1024), 2),
|
|
@@ -592,21 +642,26 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
592
642
|
)
|
|
593
643
|
def optimize_resource_requests(
|
|
594
644
|
namespace: str,
|
|
595
|
-
deployment_name: Optional[str] = None
|
|
645
|
+
deployment_name: Optional[str] = None,
|
|
646
|
+
context: str = ""
|
|
596
647
|
) -> Dict[str, Any]:
|
|
597
|
-
"""Suggest optimal resource requests based on current usage patterns.
|
|
648
|
+
"""Suggest optimal resource requests based on current usage patterns.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
namespace: Target namespace
|
|
652
|
+
deployment_name: Specific deployment to analyze (optional, all deployments if not specified)
|
|
653
|
+
context: Kubernetes context to use (optional, uses current context if not specified)
|
|
654
|
+
"""
|
|
598
655
|
try:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
apps = client.AppsV1Api()
|
|
602
|
-
v1 = client.CoreV1Api()
|
|
656
|
+
apps = get_apps_client(context)
|
|
657
|
+
v1 = get_k8s_client(context)
|
|
603
658
|
|
|
604
659
|
if deployment_name:
|
|
605
660
|
deployments = [apps.read_namespaced_deployment(deployment_name, namespace)]
|
|
606
661
|
else:
|
|
607
662
|
deployments = apps.list_namespaced_deployment(namespace).items
|
|
608
663
|
|
|
609
|
-
cmd = ["kubectl"
|
|
664
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + ["top", "pods", "-n", namespace, "--no-headers"]
|
|
610
665
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
611
666
|
|
|
612
667
|
usage_map = {}
|
|
@@ -669,6 +724,7 @@ def register_cost_tools(server, non_destructive: bool):
|
|
|
669
724
|
|
|
670
725
|
return {
|
|
671
726
|
"success": True,
|
|
727
|
+
"context": context or "current",
|
|
672
728
|
"namespace": namespace,
|
|
673
729
|
"note": "Suggestions based on current usage + 20% buffer. Monitor over time for accuracy.",
|
|
674
730
|
"suggestions": suggestions
|