kubectl-mcp-server 1.23.0__tar.gz → 1.23.1__tar.gz
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.23.0 → kubectl_mcp_server-1.23.1}/PKG-INFO +1 -1
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/PKG-INFO +1 -1
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/SOURCES.txt +1 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/__init__.py +1 -1
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/mcp_server.py +8 -24
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/providers.py +0 -1
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/safety.py +1 -53
- kubectl_mcp_server-1.23.1/kubectl_mcp_tool/tools/_cli_utils.py +75 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/cluster.py +2 -8
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/core.py +2 -8
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/cost.py +5 -12
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/deployments.py +2 -8
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/diagnostics.py +2 -9
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/helm.py +44 -44
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/kind.py +4 -8
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/networking.py +18 -14
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/operations.py +39 -13
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/pods.py +9 -12
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/vind.py +2 -7
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/setup.py +1 -1
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_browser.py +2 -3
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_ecosystem.py +2 -4
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_mcp_integration.py +4 -26
- kubectl_mcp_server-1.23.1/tests/test_safety.py +98 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_server.py +4 -5
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_tools.py +48 -40
- kubectl_mcp_server-1.23.0/tests/test_safety.py +0 -218
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/LICENSE +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/README.md +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/dependency_links.txt +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/entry_points.txt +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/requires.txt +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/top_level.txt +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/__main__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/auth/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/auth/config.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/auth/scopes.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/auth/verifier.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/cli/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/cli/__main__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/cli/cli.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/cli/errors.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/cli/output.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/config/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/config/loader.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/config/schema.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/crd_detector.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/diagnostics.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/k8s_config.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/observability/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/observability/metrics.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/observability/stats.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/observability/tracing.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/prompts/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/prompts/builtin.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/prompts/custom.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/prompts/prompts.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/resources/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/resources/resources.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/backup.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/browser.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/capi.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/certs.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/cilium.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/gitops.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/keda.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/kiali.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/kubevirt.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/policy.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/rollouts.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/security.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/storage.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/ui.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/utils.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/utils/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/utils/helpers.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/setup.cfg +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/__init__.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/conftest.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_auth.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_cli.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_config.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_kind.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_observability.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_prompts.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_resources.py +0 -0
- {kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/tests/test_vind.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kubectl-mcp-server
|
|
3
|
-
Version: 1.23.
|
|
3
|
+
Version: 1.23.1
|
|
4
4
|
Summary: A Model Context Protocol (MCP) server for Kubernetes with 270+ tools, 8 resources, and 8 prompts
|
|
5
5
|
Home-page: https://github.com/rohitg00/kubectl-mcp-server
|
|
6
6
|
Author: Rohit Ghumare
|
{kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kubectl-mcp-server
|
|
3
|
-
Version: 1.23.
|
|
3
|
+
Version: 1.23.1
|
|
4
4
|
Summary: A Model Context Protocol (MCP) server for Kubernetes with 270+ tools, 8 resources, and 8 prompts
|
|
5
5
|
Home-page: https://github.com/rohitg00/kubectl-mcp-server
|
|
6
6
|
Author: Rohit Ghumare
|
{kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_server.egg-info/SOURCES.txt
RENAMED
|
@@ -38,6 +38,7 @@ kubectl_mcp_tool/prompts/prompts.py
|
|
|
38
38
|
kubectl_mcp_tool/resources/__init__.py
|
|
39
39
|
kubectl_mcp_tool/resources/resources.py
|
|
40
40
|
kubectl_mcp_tool/tools/__init__.py
|
|
41
|
+
kubectl_mcp_tool/tools/_cli_utils.py
|
|
41
42
|
kubectl_mcp_tool/tools/backup.py
|
|
42
43
|
kubectl_mcp_tool/tools/browser.py
|
|
43
44
|
kubectl_mcp_tool/tools/capi.py
|
|
@@ -7,7 +7,7 @@ with Kubernetes clusters through natural language commands.
|
|
|
7
7
|
For more information, see: https://github.com/rohitg00/kubectl-mcp-server
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__version__ = "1.23.
|
|
10
|
+
__version__ = "1.23.1"
|
|
11
11
|
|
|
12
12
|
from .mcp_server import MCPServer
|
|
13
13
|
from .diagnostics import run_diagnostics, check_kubectl_installation, check_cluster_connection
|
|
@@ -23,15 +23,10 @@ import logging
|
|
|
23
23
|
import asyncio
|
|
24
24
|
import os
|
|
25
25
|
import platform
|
|
26
|
-
import signal
|
|
27
|
-
from pathlib import Path
|
|
28
26
|
from typing import List, Optional, Any, Dict
|
|
29
27
|
|
|
30
|
-
# Import k8s_config early to patch kubernetes config for in-cluster support
|
|
31
|
-
# This must be done before any tools are imported
|
|
32
28
|
import kubectl_mcp_tool.k8s_config # noqa: F401
|
|
33
29
|
|
|
34
|
-
# Import safety mode for operation control
|
|
35
30
|
from kubectl_mcp_tool.safety import (
|
|
36
31
|
SafetyMode,
|
|
37
32
|
set_safety_mode,
|
|
@@ -39,7 +34,6 @@ from kubectl_mcp_tool.safety import (
|
|
|
39
34
|
get_mode_info,
|
|
40
35
|
)
|
|
41
36
|
|
|
42
|
-
# Import observability for metrics and tracing
|
|
43
37
|
from kubectl_mcp_tool.observability import (
|
|
44
38
|
get_stats_collector,
|
|
45
39
|
get_metrics,
|
|
@@ -52,7 +46,6 @@ from kubectl_mcp_tool.observability import (
|
|
|
52
46
|
record_tool_error_metric,
|
|
53
47
|
)
|
|
54
48
|
|
|
55
|
-
# Import config loader
|
|
56
49
|
from kubectl_mcp_tool.config import (
|
|
57
50
|
load_config,
|
|
58
51
|
get_config,
|
|
@@ -60,12 +53,13 @@ from kubectl_mcp_tool.config import (
|
|
|
60
53
|
setup_sighup_handler,
|
|
61
54
|
)
|
|
62
55
|
|
|
63
|
-
# Import custom prompts
|
|
64
56
|
from kubectl_mcp_tool.prompts import (
|
|
65
57
|
load_prompts_from_config,
|
|
66
58
|
get_builtin_prompts,
|
|
67
59
|
)
|
|
68
60
|
|
|
61
|
+
from kubectl_mcp_tool import __version__
|
|
62
|
+
|
|
69
63
|
from kubectl_mcp_tool.tools import (
|
|
70
64
|
register_helm_tools,
|
|
71
65
|
register_pod_tools,
|
|
@@ -134,23 +128,13 @@ for handler in logging.root.handlers[:]:
|
|
|
134
128
|
if isinstance(handler, logging.StreamHandler) and handler.stream == sys.stdout:
|
|
135
129
|
logging.root.removeHandler(handler)
|
|
136
130
|
|
|
137
|
-
# FastMCP 3 from gofastmcp.com (standalone package)
|
|
138
|
-
# To revert to official SDK: from mcp.server.fastmcp import FastMCP
|
|
139
131
|
try:
|
|
140
132
|
from fastmcp import FastMCP
|
|
141
|
-
except ImportError:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
[sys.executable, "-m", "pip", "install", "fastmcp>=3.0.0b1"],
|
|
147
|
-
stdout=subprocess.DEVNULL,
|
|
148
|
-
stderr=subprocess.DEVNULL
|
|
149
|
-
)
|
|
150
|
-
from fastmcp import FastMCP
|
|
151
|
-
except Exception as e:
|
|
152
|
-
logger.error(f"Failed to install FastMCP: {e}")
|
|
153
|
-
raise
|
|
133
|
+
except ImportError as err:
|
|
134
|
+
raise ImportError(
|
|
135
|
+
"FastMCP is required but not installed. "
|
|
136
|
+
"Install with: pip install 'fastmcp>=3.0.0b1'"
|
|
137
|
+
) from err
|
|
154
138
|
|
|
155
139
|
|
|
156
140
|
class MCPServer:
|
|
@@ -622,7 +606,7 @@ class MCPServer:
|
|
|
622
606
|
},
|
|
623
607
|
"serverInfo": {
|
|
624
608
|
"name": self.name,
|
|
625
|
-
"version":
|
|
609
|
+
"version": __version__
|
|
626
610
|
}
|
|
627
611
|
}
|
|
628
612
|
elif method == "tools/list":
|
|
@@ -5,8 +5,7 @@ Provides read-only and disable-destructive modes to prevent accidental cluster m
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from enum import Enum
|
|
8
|
-
from
|
|
9
|
-
from typing import Any, Callable, Dict, Set
|
|
8
|
+
from typing import Any, Dict, Set
|
|
10
9
|
import logging
|
|
11
10
|
|
|
12
11
|
logger = logging.getLogger("mcp-server")
|
|
@@ -86,57 +85,6 @@ def set_safety_mode(mode: SafetyMode) -> None:
|
|
|
86
85
|
logger.info(f"Safety mode set to: {mode.value}")
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
def is_operation_allowed(operation_name: str) -> tuple[bool, str]:
|
|
90
|
-
"""
|
|
91
|
-
Check if an operation is allowed under the current safety mode.
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Tuple of (allowed: bool, reason: str)
|
|
95
|
-
"""
|
|
96
|
-
mode = get_safety_mode()
|
|
97
|
-
|
|
98
|
-
if mode == SafetyMode.NORMAL:
|
|
99
|
-
return True, ""
|
|
100
|
-
|
|
101
|
-
if mode == SafetyMode.READ_ONLY:
|
|
102
|
-
if operation_name in WRITE_OPERATIONS or operation_name in DESTRUCTIVE_OPERATIONS:
|
|
103
|
-
return False, f"Operation '{operation_name}' blocked: read-only mode is enabled"
|
|
104
|
-
|
|
105
|
-
if mode == SafetyMode.DISABLE_DESTRUCTIVE:
|
|
106
|
-
if operation_name in DESTRUCTIVE_OPERATIONS:
|
|
107
|
-
return False, f"Operation '{operation_name}' blocked: destructive operations are disabled"
|
|
108
|
-
|
|
109
|
-
return True, ""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def check_safety_mode(func: Callable) -> Callable:
|
|
113
|
-
"""
|
|
114
|
-
Decorator to check safety mode before executing a tool function.
|
|
115
|
-
|
|
116
|
-
Usage:
|
|
117
|
-
@check_safety_mode
|
|
118
|
-
def delete_pod(...):
|
|
119
|
-
...
|
|
120
|
-
"""
|
|
121
|
-
@wraps(func)
|
|
122
|
-
def wrapper(*args, **kwargs) -> Dict[str, Any]:
|
|
123
|
-
operation_name = func.__name__
|
|
124
|
-
allowed, reason = is_operation_allowed(operation_name)
|
|
125
|
-
|
|
126
|
-
if not allowed:
|
|
127
|
-
logger.warning(f"Blocked operation: {operation_name} (mode: {get_safety_mode().value})")
|
|
128
|
-
return {
|
|
129
|
-
"success": False,
|
|
130
|
-
"error": reason,
|
|
131
|
-
"blocked_by": get_safety_mode().value,
|
|
132
|
-
"operation": operation_name
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return func(*args, **kwargs)
|
|
136
|
-
|
|
137
|
-
return wrapper
|
|
138
|
-
|
|
139
|
-
|
|
140
88
|
def get_mode_info() -> Dict[str, Any]:
|
|
141
89
|
"""Get information about the current safety mode."""
|
|
142
90
|
mode = get_safety_mode()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@lru_cache(maxsize=16)
|
|
7
|
+
def cli_available(binary: str) -> bool:
|
|
8
|
+
"""Check if a CLI binary is available (cached)."""
|
|
9
|
+
try:
|
|
10
|
+
result = subprocess.run(
|
|
11
|
+
[binary, "version"],
|
|
12
|
+
capture_output=True,
|
|
13
|
+
timeout=10
|
|
14
|
+
)
|
|
15
|
+
return result.returncode == 0
|
|
16
|
+
except Exception:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@lru_cache(maxsize=16)
|
|
21
|
+
def get_cli_version(binary: str) -> Optional[str]:
|
|
22
|
+
"""Get CLI binary version string (cached)."""
|
|
23
|
+
try:
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
[binary, "version"],
|
|
26
|
+
capture_output=True,
|
|
27
|
+
text=True,
|
|
28
|
+
timeout=10
|
|
29
|
+
)
|
|
30
|
+
if result.returncode == 0:
|
|
31
|
+
return result.stdout.strip()
|
|
32
|
+
return None
|
|
33
|
+
except Exception:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_cli(
|
|
38
|
+
binary: str,
|
|
39
|
+
args: List[str],
|
|
40
|
+
timeout: int = 300,
|
|
41
|
+
capture_output: bool = True
|
|
42
|
+
) -> Dict[str, Any]:
|
|
43
|
+
"""Run a CLI command and return a standardized result dict.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
binary: CLI binary name (e.g., "kind", "vcluster", "helm")
|
|
47
|
+
args: Command arguments (without the binary prefix)
|
|
48
|
+
timeout: Command timeout in seconds
|
|
49
|
+
capture_output: Whether to capture stdout/stderr
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dict with success status and output/error
|
|
53
|
+
"""
|
|
54
|
+
cmd = [binary, *args]
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
cmd,
|
|
59
|
+
capture_output=capture_output,
|
|
60
|
+
text=True,
|
|
61
|
+
timeout=timeout
|
|
62
|
+
)
|
|
63
|
+
if result.returncode == 0:
|
|
64
|
+
output = result.stdout.strip() if capture_output else ""
|
|
65
|
+
return {"success": True, "output": output}
|
|
66
|
+
return {
|
|
67
|
+
"success": False,
|
|
68
|
+
"error": result.stderr.strip() if capture_output else f"Command failed with exit code {result.returncode}"
|
|
69
|
+
}
|
|
70
|
+
except subprocess.TimeoutExpired:
|
|
71
|
+
return {"success": False, "error": f"Command timed out after {timeout} seconds"}
|
|
72
|
+
except FileNotFoundError:
|
|
73
|
+
return {"success": False, "error": f"{binary} CLI not available"}
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return {"success": False, "error": str(e)}
|
|
@@ -23,18 +23,12 @@ from kubectl_mcp_tool.k8s_config import (
|
|
|
23
23
|
disable_kubeconfig_watch,
|
|
24
24
|
is_stateless_mode,
|
|
25
25
|
set_stateless_mode,
|
|
26
|
+
_get_kubectl_context_args,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
logger = logging.getLogger("mcp-server")
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
def _get_kubectl_context_args(context: str = "") -> List[str]:
|
|
32
|
-
"""Get kubectl context arguments."""
|
|
33
|
-
if context:
|
|
34
|
-
return ["--context", context]
|
|
35
|
-
return []
|
|
36
|
-
|
|
37
|
-
|
|
38
32
|
# DNS-1123 subdomain regex for node name validation
|
|
39
33
|
_DNS_1123_PATTERN = re.compile(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$')
|
|
40
34
|
|
|
@@ -60,7 +54,7 @@ def _validate_node_name(name: str) -> tuple:
|
|
|
60
54
|
return True, None
|
|
61
55
|
|
|
62
56
|
|
|
63
|
-
def register_cluster_tools(server, non_destructive: bool):
|
|
57
|
+
def register_cluster_tools(server: "FastMCP", non_destructive: bool):
|
|
64
58
|
"""Register cluster and context management tools."""
|
|
65
59
|
|
|
66
60
|
@server.tool(
|
|
@@ -7,19 +7,13 @@ from mcp.types import ToolAnnotations
|
|
|
7
7
|
from ..k8s_config import (
|
|
8
8
|
get_k8s_client,
|
|
9
9
|
get_apiextensions_client,
|
|
10
|
+
_get_kubectl_context_args,
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger("mcp-server")
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def
|
|
16
|
-
"""Get kubectl context arguments if context is specified."""
|
|
17
|
-
if context:
|
|
18
|
-
return ["--context", context]
|
|
19
|
-
return []
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def register_core_tools(server, non_destructive: bool):
|
|
16
|
+
def register_core_tools(server: "FastMCP", non_destructive: bool):
|
|
23
17
|
"""Register core Kubernetes resource tools."""
|
|
24
18
|
|
|
25
19
|
@server.tool(
|
|
@@ -6,18 +6,11 @@ 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
|
|
9
|
+
from ..k8s_config import get_k8s_client, get_apps_client, _get_kubectl_context_args
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger("mcp-server")
|
|
12
12
|
|
|
13
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
|
-
|
|
21
14
|
def _parse_cpu(cpu_str: str) -> int:
|
|
22
15
|
"""Parse CPU string to millicores."""
|
|
23
16
|
try:
|
|
@@ -28,7 +21,7 @@ def _parse_cpu(cpu_str: str) -> int:
|
|
|
28
21
|
return int(cpu_str[:-1]) // 1000000
|
|
29
22
|
else:
|
|
30
23
|
return int(float(cpu_str) * 1000)
|
|
31
|
-
except:
|
|
24
|
+
except (ValueError, TypeError):
|
|
32
25
|
return 0
|
|
33
26
|
|
|
34
27
|
|
|
@@ -50,7 +43,7 @@ def _parse_memory(mem_str: str) -> int:
|
|
|
50
43
|
return int(mem_str[:-1]) * 1000000000
|
|
51
44
|
else:
|
|
52
45
|
return int(mem_str)
|
|
53
|
-
except:
|
|
46
|
+
except (ValueError, TypeError):
|
|
54
47
|
return 0
|
|
55
48
|
|
|
56
49
|
|
|
@@ -61,11 +54,11 @@ def _calculate_available(hard: str, used: str) -> str:
|
|
|
61
54
|
used_num = int(re.sub(r'[^\d]', '', str(used)) or 0)
|
|
62
55
|
suffix = re.sub(r'[\d]', '', str(hard))
|
|
63
56
|
return f"{max(0, hard_num - used_num)}{suffix}"
|
|
64
|
-
except:
|
|
57
|
+
except (ValueError, TypeError):
|
|
65
58
|
return "N/A"
|
|
66
59
|
|
|
67
60
|
|
|
68
|
-
def register_cost_tools(server, non_destructive: bool):
|
|
61
|
+
def register_cost_tools(server: "FastMCP", non_destructive: bool):
|
|
69
62
|
"""Register cost and resource optimization tools."""
|
|
70
63
|
|
|
71
64
|
@server.tool(
|
{kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/deployments.py
RENAMED
|
@@ -10,19 +10,13 @@ from ..k8s_config import (
|
|
|
10
10
|
get_autoscaling_client,
|
|
11
11
|
get_policy_client,
|
|
12
12
|
_load_config_for_context,
|
|
13
|
+
_get_kubectl_context_args,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger("mcp-server")
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def
|
|
19
|
-
"""Get kubectl context arguments if context is specified."""
|
|
20
|
-
if context:
|
|
21
|
-
return ["--context", context]
|
|
22
|
-
return []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def register_deployment_tools(server, non_destructive: bool):
|
|
19
|
+
def register_deployment_tools(server: "FastMCP", non_destructive: bool):
|
|
26
20
|
"""Register deployment and workload management tools."""
|
|
27
21
|
|
|
28
22
|
@server.tool(
|
{kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/diagnostics.py
RENAMED
|
@@ -4,19 +4,12 @@ from typing import Any, Dict, List, Optional
|
|
|
4
4
|
|
|
5
5
|
from mcp.types import ToolAnnotations
|
|
6
6
|
|
|
7
|
-
from ..k8s_config import get_k8s_client, get_apps_client
|
|
7
|
+
from ..k8s_config import get_k8s_client, get_apps_client, _get_kubectl_context_args
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger("mcp-server")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
"""Get kubectl context arguments if context is specified."""
|
|
14
|
-
if context:
|
|
15
|
-
return ["--context", context]
|
|
16
|
-
return []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def register_diagnostics_tools(server, non_destructive: bool):
|
|
12
|
+
def register_diagnostics_tools(server: "FastMCP", non_destructive: bool):
|
|
20
13
|
"""Register diagnostic and troubleshooting tools.
|
|
21
14
|
|
|
22
15
|
Note: Pod-specific diagnostic tools (diagnose_pod_crash, detect_pending_pods,
|
|
@@ -8,6 +8,8 @@ from typing import Any, Callable, Dict, List, Optional
|
|
|
8
8
|
import yaml
|
|
9
9
|
from mcp.types import ToolAnnotations
|
|
10
10
|
|
|
11
|
+
from kubectl_mcp_tool.k8s_config import _get_kubectl_context_args
|
|
12
|
+
|
|
11
13
|
logger = logging.getLogger("mcp-server")
|
|
12
14
|
|
|
13
15
|
|
|
@@ -18,15 +20,43 @@ def _get_helm_context_args(context: str) -> List[str]:
|
|
|
18
20
|
return []
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
def
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
def _add_helm_repo(repo: str, chart: str) -> tuple:
|
|
24
|
+
"""Add a Helm repo and return the updated chart reference.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
repo: Repository in format 'repo_name=repo_url'
|
|
28
|
+
chart: Original chart reference
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple of (success: bool, chart_or_error: str)
|
|
32
|
+
"""
|
|
33
|
+
repo_parts = repo.split("=", 1)
|
|
34
|
+
if len(repo_parts) != 2:
|
|
35
|
+
return False, "Repository format should be 'repo_name=repo_url'"
|
|
36
|
+
|
|
37
|
+
repo_name, repo_url = (p.strip() for p in repo_parts)
|
|
38
|
+
if not repo_name or not repo_url:
|
|
39
|
+
return False, "Repository format should be 'repo_name=repo_url'"
|
|
40
|
+
try:
|
|
41
|
+
subprocess.check_output(
|
|
42
|
+
["helm", "repo", "add", repo_name, repo_url],
|
|
43
|
+
stderr=subprocess.PIPE, text=True
|
|
44
|
+
)
|
|
45
|
+
subprocess.check_output(
|
|
46
|
+
["helm", "repo", "update"],
|
|
47
|
+
stderr=subprocess.PIPE, text=True
|
|
48
|
+
)
|
|
49
|
+
except subprocess.CalledProcessError as e:
|
|
50
|
+
error_msg = e.stderr if hasattr(e, 'stderr') else str(e)
|
|
51
|
+
return False, f"Failed to add Helm repo: {error_msg}"
|
|
52
|
+
|
|
53
|
+
if '/' not in chart:
|
|
54
|
+
chart = f"{repo_name}/{chart}"
|
|
55
|
+
return True, chart
|
|
26
56
|
|
|
27
57
|
|
|
28
58
|
def register_helm_tools(
|
|
29
|
-
server,
|
|
59
|
+
server: "FastMCP",
|
|
30
60
|
non_destructive: bool,
|
|
31
61
|
check_helm_fn: Callable[[], bool]
|
|
32
62
|
):
|
|
@@ -69,25 +99,10 @@ def register_helm_tools(
|
|
|
69
99
|
|
|
70
100
|
try:
|
|
71
101
|
if repo:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
repo_name, repo_url = repo_parts
|
|
78
|
-
repo_add_cmd = ["helm", "repo", "add", repo_name, repo_url]
|
|
79
|
-
logger.debug(f"Running command: {' '.join(repo_add_cmd)}")
|
|
80
|
-
subprocess.check_output(repo_add_cmd, stderr=subprocess.PIPE, text=True)
|
|
81
|
-
|
|
82
|
-
repo_update_cmd = ["helm", "repo", "update"]
|
|
83
|
-
logger.debug(f"Running command: {' '.join(repo_update_cmd)}")
|
|
84
|
-
subprocess.check_output(repo_update_cmd, stderr=subprocess.PIPE, text=True)
|
|
85
|
-
|
|
86
|
-
if '/' not in chart:
|
|
87
|
-
chart = f"{repo_name}/{chart}"
|
|
88
|
-
except subprocess.CalledProcessError as e:
|
|
89
|
-
logger.error(f"Error adding Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}")
|
|
90
|
-
return {"success": False, "error": f"Failed to add Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}"}
|
|
102
|
+
success, result = _add_helm_repo(repo, chart)
|
|
103
|
+
if not success:
|
|
104
|
+
return {"success": False, "error": result}
|
|
105
|
+
chart = result
|
|
91
106
|
|
|
92
107
|
cmd = ["helm"] + _get_helm_context_args(context) + ["install", name, chart, "-n", namespace]
|
|
93
108
|
|
|
@@ -162,25 +177,10 @@ def register_helm_tools(
|
|
|
162
177
|
|
|
163
178
|
try:
|
|
164
179
|
if repo:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
repo_name, repo_url = repo_parts
|
|
171
|
-
repo_add_cmd = ["helm", "repo", "add", repo_name, repo_url]
|
|
172
|
-
logger.debug(f"Running command: {' '.join(repo_add_cmd)}")
|
|
173
|
-
subprocess.check_output(repo_add_cmd, stderr=subprocess.PIPE, text=True)
|
|
174
|
-
|
|
175
|
-
repo_update_cmd = ["helm", "repo", "update"]
|
|
176
|
-
logger.debug(f"Running command: {' '.join(repo_update_cmd)}")
|
|
177
|
-
subprocess.check_output(repo_update_cmd, stderr=subprocess.PIPE, text=True)
|
|
178
|
-
|
|
179
|
-
if '/' not in chart:
|
|
180
|
-
chart = f"{repo_name}/{chart}"
|
|
181
|
-
except subprocess.CalledProcessError as e:
|
|
182
|
-
logger.error(f"Error adding Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}")
|
|
183
|
-
return {"success": False, "error": f"Failed to add Helm repo: {e.stderr if hasattr(e, 'stderr') else str(e)}"}
|
|
180
|
+
success, result = _add_helm_repo(repo, chart)
|
|
181
|
+
if not success:
|
|
182
|
+
return {"success": False, "error": result}
|
|
183
|
+
chart = result
|
|
184
184
|
|
|
185
185
|
cmd = ["helm"] + _get_helm_context_args(context) + ["upgrade", name, chart, "-n", namespace]
|
|
186
186
|
|
|
@@ -7,17 +7,13 @@ It's a tool from Kubernetes SIG for local development and CI testing.
|
|
|
7
7
|
import subprocess
|
|
8
8
|
import json
|
|
9
9
|
import re
|
|
10
|
+
import shlex
|
|
10
11
|
import os
|
|
11
12
|
import tempfile
|
|
12
13
|
import yaml
|
|
13
14
|
from typing import Dict, Any, List, Optional
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
from fastmcp import FastMCP
|
|
17
|
-
from fastmcp.tools import ToolAnnotations
|
|
18
|
-
except ImportError:
|
|
19
|
-
from mcp.server.fastmcp import FastMCP
|
|
20
|
-
from mcp.types import ToolAnnotations
|
|
16
|
+
from mcp.types import ToolAnnotations
|
|
21
17
|
|
|
22
18
|
|
|
23
19
|
def _kind_available() -> bool:
|
|
@@ -911,7 +907,7 @@ def kind_node_exec(
|
|
|
911
907
|
}
|
|
912
908
|
|
|
913
909
|
result = _run_docker(
|
|
914
|
-
["exec", node] +
|
|
910
|
+
["exec", node] + shlex.split(command),
|
|
915
911
|
timeout=120
|
|
916
912
|
)
|
|
917
913
|
|
|
@@ -1350,7 +1346,7 @@ def kind_provider_info() -> Dict[str, Any]:
|
|
|
1350
1346
|
}
|
|
1351
1347
|
|
|
1352
1348
|
|
|
1353
|
-
def register_kind_tools(mcp: FastMCP, non_destructive: bool = False):
|
|
1349
|
+
def register_kind_tools(mcp: "FastMCP", non_destructive: bool = False):
|
|
1354
1350
|
"""Register kind (Kubernetes IN Docker) tools with the MCP server."""
|
|
1355
1351
|
|
|
1356
1352
|
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
{kubectl_mcp_server-1.23.0 → kubectl_mcp_server-1.23.1}/kubectl_mcp_tool/tools/networking.py
RENAMED
|
@@ -4,19 +4,12 @@ from typing import Any, Dict, List, Optional
|
|
|
4
4
|
|
|
5
5
|
from mcp.types import ToolAnnotations
|
|
6
6
|
|
|
7
|
-
from ..k8s_config import get_k8s_client, get_networking_client
|
|
7
|
+
from ..k8s_config import get_k8s_client, get_networking_client, _get_kubectl_context_args
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger("mcp-server")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
"""Get kubectl context arguments if context is specified."""
|
|
14
|
-
if context:
|
|
15
|
-
return ["--context", context]
|
|
16
|
-
return []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def register_networking_tools(server, non_destructive: bool):
|
|
12
|
+
def register_networking_tools(server: "FastMCP", non_destructive: bool):
|
|
20
13
|
"""Register networking-related tools."""
|
|
21
14
|
|
|
22
15
|
@server.tool(
|
|
@@ -271,7 +264,7 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
271
264
|
# Get endpoints
|
|
272
265
|
try:
|
|
273
266
|
endpoints = v1.read_namespaced_endpoints(service_name, namespace)
|
|
274
|
-
except:
|
|
267
|
+
except Exception:
|
|
275
268
|
endpoints = None
|
|
276
269
|
|
|
277
270
|
# Get pods matching selector
|
|
@@ -354,11 +347,22 @@ def register_networking_tools(server, non_destructive: bool):
|
|
|
354
347
|
namespace: Namespace of the pod
|
|
355
348
|
context: Kubernetes context to use (uses current context if not specified)
|
|
356
349
|
"""
|
|
350
|
+
if non_destructive:
|
|
351
|
+
return {
|
|
352
|
+
"success": False,
|
|
353
|
+
"error": "port_forward is not allowed in non-destructive mode"
|
|
354
|
+
}
|
|
357
355
|
try:
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
356
|
+
cmd = ["kubectl"] + _get_kubectl_context_args(context) + [
|
|
357
|
+
"port-forward", pod_name,
|
|
358
|
+
f"{int(local_port)}:{int(pod_port)}",
|
|
359
|
+
"-n", namespace
|
|
360
|
+
]
|
|
361
|
+
subprocess.Popen(
|
|
362
|
+
cmd,
|
|
363
|
+
stdout=subprocess.DEVNULL,
|
|
364
|
+
stderr=subprocess.DEVNULL,
|
|
365
|
+
)
|
|
362
366
|
return {
|
|
363
367
|
"success": True,
|
|
364
368
|
"context": context or "current",
|