kubectl-mcp-server 1.23.0__py3-none-any.whl → 1.23.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kubectl-mcp-server
3
- Version: 1.23.0
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
@@ -1,12 +1,12 @@
1
- kubectl_mcp_server-1.23.0.dist-info/licenses/LICENSE,sha256=nH9Z0W0WNH2oQ4cPrBAU8ldDcHfeI6NUbkSGiazYWgQ,1070
2
- kubectl_mcp_tool/__init__.py,sha256=VXHw0rDGepDETpgPBPmvZZLCso1PzghOb4xlwIexxhg,580
1
+ kubectl_mcp_server-1.23.1.dist-info/licenses/LICENSE,sha256=nH9Z0W0WNH2oQ4cPrBAU8ldDcHfeI6NUbkSGiazYWgQ,1070
2
+ kubectl_mcp_tool/__init__.py,sha256=kA6EKH7Jawvme4pUaNXL97G-_8OkUptR_PK5Htjy6W0,580
3
3
  kubectl_mcp_tool/__main__.py,sha256=CE6cTD6PA71Ap0i5_gE17Pb9FcedOJmtGRNzZ5-TFSc,1490
4
4
  kubectl_mcp_tool/crd_detector.py,sha256=xLZSyg3iIe_MjSMI3mESe2VK8wGJzu40rJ7F72fULBs,7139
5
5
  kubectl_mcp_tool/diagnostics.py,sha256=uwolSoHadRkB-J8PAsabbexfj6sTNCIIRRrABBRXoTU,11776
6
6
  kubectl_mcp_tool/k8s_config.py,sha256=XePmWOS_cfFFaxG5HjmqGQrtEr8MWOVrYAB4eAipaPg,18950
7
- kubectl_mcp_tool/mcp_server.py,sha256=bCNR_a_A9p3nctBmFCqnNghepEpYot_o1WcLIjYseYs,32691
8
- kubectl_mcp_tool/providers.py,sha256=oAPWBNYs0KzqWJjUXYTjH1n0xIrMChLrTb8_-VAaHyY,12837
9
- kubectl_mcp_tool/safety.py,sha256=7bx7tUH_hT4Y_eTnpH5knnHTsvnIZvI_0oo9MSBe_z8,5036
7
+ kubectl_mcp_tool/mcp_server.py,sha256=EdKWJWdgmQgbnKYyb_1W1F54aiKsDciViRWxVb4mKsY,32056
8
+ kubectl_mcp_tool/providers.py,sha256=gaU9mB0p6ygqbxC--e4E60TwfC8FzW_sVWvc8OWK07I,12805
9
+ kubectl_mcp_tool/safety.py,sha256=l9wb3hhdlr-Yifai_0fDutZvC4sW2HQtrgfuaJWjFjc,3472
10
10
  kubectl_mcp_tool/auth/__init__.py,sha256=ot8ivZZkDtV8Rg0y1UYruwobKCPyxX1svqh35wWxKvY,347
11
11
  kubectl_mcp_tool/auth/config.py,sha256=wi3wuJNMyDqMeluDHL0MaJyedIFv5CFVxiUaEVaTvzk,2267
12
12
  kubectl_mcp_tool/auth/scopes.py,sha256=KPmuGO0SrTkjzlElWFOV29ie9apTdMklOCkiA-965lI,6147
@@ -30,52 +30,53 @@ kubectl_mcp_tool/prompts/prompts.py,sha256=ogDhU6FVsYIlig_QuIw-TblxR9GQJ3Xp8mJHL
30
30
  kubectl_mcp_tool/resources/__init__.py,sha256=ERkn0ErlaGi9-dybv4wrAaT8WretvNp6K002h7Agjno,83
31
31
  kubectl_mcp_tool/resources/resources.py,sha256=kvK4OM3Ox5cFvWDqJBTXOfBgnRYdoqdvvjsdCg0PJfY,12713
32
32
  kubectl_mcp_tool/tools/__init__.py,sha256=ktvGmS_bUqYhv3J65dtQrrj2B8K7r9WTnS2_dbAGp_U,1955
33
+ kubectl_mcp_tool/tools/_cli_utils.py,sha256=lCJzvK1HOsdAfO_1yMhwjLfK8k78OCuQ3kDgngMYOw8,2204
33
34
  kubectl_mcp_tool/tools/backup.py,sha256=M1dvyc-fmCbGFp3o89pGNv8drtgPxds0sBk0OiN0TJA,29148
34
35
  kubectl_mcp_tool/tools/browser.py,sha256=OvXUHzEuciRQInnwLLC16iObOOdniu09b9RT81y7sdc,25338
35
36
  kubectl_mcp_tool/tools/capi.py,sha256=qcNStWl7KHqi4Jorf9xThfOjYRhp7d3ZhUGif_YWrV8,24293
36
37
  kubectl_mcp_tool/tools/certs.py,sha256=82FVevhLSaMTGDyu3rxJNhmiL_3KzBqFwys186b_2IM,25035
37
38
  kubectl_mcp_tool/tools/cilium.py,sha256=ijO-HWcS3R5FVzg1RrE-pKhrEL0SdG0CNyrQ6g0S8eM,18654
38
- kubectl_mcp_tool/tools/cluster.py,sha256=K8MGinOmzk0850dVB332693SadseAzgYf3jV-IKxmNM,54419
39
- kubectl_mcp_tool/tools/core.py,sha256=67Cx1z4DqV0Qd26yXkF1eBXbUIlBB3Qj2W8rOInvb54,17801
40
- kubectl_mcp_tool/tools/cost.py,sha256=fcFlIv-bsMdKESw-l-T6ECsUplDukNHaqM76JCYVHvQ,29629
41
- kubectl_mcp_tool/tools/deployments.py,sha256=e138KV30DQhLCnvXntrofx1XdLH0dC0_mlODbzkluKk,18329
42
- kubectl_mcp_tool/tools/diagnostics.py,sha256=LGpW-cx2ui40IIq4pZPT2O8nVzKCpOEVnnQ1OGUSVt0,7864
39
+ kubectl_mcp_tool/tools/cluster.py,sha256=wT_fePVC7QNgwO1IOT2v6uGbkzJQ3J9kG3ikCHIY0ik,54287
40
+ kubectl_mcp_tool/tools/core.py,sha256=Viyn4R2Av-jchcSZA6hIrV_YeOjj3Ysts4hftZvMXoc,17650
41
+ kubectl_mcp_tool/tools/cost.py,sha256=CTS6R0KPUq9_CWDxEU5by6l38aBD6zKU2_URXrduWp8,29546
42
+ kubectl_mcp_tool/tools/deployments.py,sha256=dCsIaGt0ssk60G60iInZz9ssGAeuiZL4LuBOSWD864o,18178
43
+ kubectl_mcp_tool/tools/diagnostics.py,sha256=i8d4ydbyIlE4YS3MegpDpDuRUiQb6q9XxLQK-dcmxFo,7709
43
44
  kubectl_mcp_tool/tools/gitops.py,sha256=aILtjkYI6QovPfsj5sdJDnkxvmzw5Ojzf8MDcpe2ns0,19168
44
- kubectl_mcp_tool/tools/helm.py,sha256=v_d1aH79VCKJmFi-kXG1LwzZRw6OFToyVC7D9tEp99A,61658
45
+ kubectl_mcp_tool/tools/helm.py,sha256=mS3RsUQpjQ_euRQA7Os0l3XS6EyFakyyg9qaLXLMIok,60732
45
46
  kubectl_mcp_tool/tools/keda.py,sha256=nxoGkLGWrBQmNpN_gFIk4AUxInwOak6HutgGz1nyWtc,14811
46
47
  kubectl_mcp_tool/tools/kiali.py,sha256=tPGH-wpXqhwmW7XfSQ5KldUD0ihe2yuy8NBvmD8Xn74,20531
47
- kubectl_mcp_tool/tools/kind.py,sha256=fWyWGbiI60t6vUPGeAqIXwtkAGjquwv8jliCZBu6JVA,52317
48
+ kubectl_mcp_tool/tools/kind.py,sha256=KGT7FS_SfPyS752Tc6Jb0NLQXN9Jz6tj7uAPHJ__tcI,52187
48
49
  kubectl_mcp_tool/tools/kubevirt.py,sha256=1M4xdihTQabH4O7NVoceQ3DQ11si_gbX1-bMDK5VOkw,25549
49
- kubectl_mcp_tool/tools/networking.py,sha256=5I-2SHp2kK9OyGJJ9KUU0hetlYoREIfin1J6CJA6HVQ,14309
50
- kubectl_mcp_tool/tools/operations.py,sha256=c7j1quXwRDoB9pMkmnLZ5Mk4QoCAuEzWVXJl_hmg2tc,24862
51
- kubectl_mcp_tool/tools/pods.py,sha256=isl-uhuSGrzR78U8d2ePqt4D_4rO_qSTZo5-5N_TFYQ,31252
50
+ kubectl_mcp_tool/tools/networking.py,sha256=5Fj3ULC-psub65_gziWVbyfIPeKIsPeKqTMUbOziJ54,14482
51
+ kubectl_mcp_tool/tools/operations.py,sha256=UoSAAFwzVTBOPHk533mVtjf1VMjbgrCHizK26YHUTI8,26163
52
+ kubectl_mcp_tool/tools/pods.py,sha256=MT7tghx03ru7aQyt6bWcNJ4tC7Oq1TqoxNaYK85XxPg,31300
52
53
  kubectl_mcp_tool/tools/policy.py,sha256=Iz5YPpHUOLFySknFY-sGxw699s9HHtHp18vEzw72X-8,20475
53
54
  kubectl_mcp_tool/tools/rollouts.py,sha256=OtFAe2KIoN6fY4gv-f7WoiYAcQOGQaSJB-y6kI8UQvM,24568
54
55
  kubectl_mcp_tool/tools/security.py,sha256=d7WLj-JZwr-0opzkRNWyPdMykvBjdsDCBSZHovDIPLo,13971
55
56
  kubectl_mcp_tool/tools/storage.py,sha256=ILI3X5GYXASCETqg3A7X33B7PjwD5UsxL-iDG7yTi_8,5676
56
57
  kubectl_mcp_tool/tools/ui.py,sha256=HHi0KM2JTvZFpQjo5V6XkJf02hxFoasec7uyNY5wuuo,39702
57
58
  kubectl_mcp_tool/tools/utils.py,sha256=HvJoW6xa8duPf9omFdxWPnVGF5fSPnz6xfoCuwwCb_4,1416
58
- kubectl_mcp_tool/tools/vind.py,sha256=Sw_ubZRsY-hk7Rui8OlwhylUs2epmCa8j74Q2CdEJaA,21721
59
+ kubectl_mcp_tool/tools/vind.py,sha256=bAZM_SD-lKMjGafCeFEwXNXvZmqZ9Gyri2NqHg4t5sw,21573
59
60
  kubectl_mcp_tool/utils/__init__.py,sha256=CHBCpaXwt994DlqyRFkkRky2TK8OmmDl0Gyc28369gI,348
60
61
  kubectl_mcp_tool/utils/helpers.py,sha256=W--wiVSKKqmjpxxdLT0J6rmhOQcp1OFk9jLrtQUVpGw,2444
61
62
  tests/__init__.py,sha256=qZPXYXv3whkkWhi61Ngzj09GHnIFlVSZrajE0XRk55o,290
62
63
  tests/conftest.py,sha256=6054YlpuGleV3Wg8BnVj4lnKWhGk-Eqc9JYTXxOmsXs,10782
63
64
  tests/test_auth.py,sha256=PoESfWiN92wSGUdVwLL3Z1AP6C1zUsVmgTI7Q8ZdlxM,11074
64
- tests/test_browser.py,sha256=6rB6Atqq3b2-xWnKzFdCVFHbF2Q3nt2Dy8Rp4ZwIgNk,20621
65
+ tests/test_browser.py,sha256=g-Hv60wiwZ8kgNC0WJ7VJ1D3KFAnWTn3RijDhzW0vnQ,20539
65
66
  tests/test_cli.py,sha256=G-f9uByWooe-yUOH0ahBdoft4uz8TC0R0OzDs1Lom-4,10014
66
67
  tests/test_config.py,sha256=9_A6_ntjvX556GvFvHGHuel1ERGo-WrbnFJX8OniUvg,12568
67
- tests/test_ecosystem.py,sha256=SkxDWAASELIxzaYWu0d-Rw1HaKSSlyMMenwwF2HWvSA,13227
68
+ tests/test_ecosystem.py,sha256=gKq9xPcoX2R49oXfHUEbIFK64vXNniFZpmTzzk3iTrk,13113
68
69
  tests/test_kind.py,sha256=1vUNXz-cNcjQcaomytH3t_CG2wA3_H6uuqrl3SD-Rrs,45961
69
- tests/test_mcp_integration.py,sha256=9ePvu1MJ0mTnMRFHthDwl3D2jyVPgQ1beUbchpT8Y3M,8782
70
+ tests/test_mcp_integration.py,sha256=arREaEJ7VQ_4cQ_V8-fSuSO4LBBDVvzOPrEiIDiWC90,8063
70
71
  tests/test_observability.py,sha256=m0m-uIQrE9OJ3FFls_rVZJst3NeAFKRtNUZohFpIZE4,17829
71
72
  tests/test_prompts.py,sha256=VzZAXr5iFO-hG8kRwRnG4wom2LlqOmZ-SeW4d3HQ7hc,42888
72
73
  tests/test_resources.py,sha256=Z0Ex8WdRz-B3VZa1s0eAaDDGbhy7dRdqy1uFVOe2Qbo,12689
73
- tests/test_safety.py,sha256=5IOBQbcH2r8tANQD7ymsR6pBc6O50IspcFsuwrnwh1M,8292
74
- tests/test_server.py,sha256=lLvgbqutnivSgQMNrki0O48whBQt0UXjdwT047nf0nw,14415
75
- tests/test_tools.py,sha256=qZsyaXV-US1O05hNsIDNXjZmeZlg-kIPStM1r8hxySo,36711
74
+ tests/test_safety.py,sha256=SKmfHJpEoQmn6K70KIJS1T-Z9k0jmsdyPKaS1UYFImE,3638
75
+ tests/test_server.py,sha256=_rdUblJG0DmZbkEfBgU1H4vZHExAlTj-v1SIbekSVjk,14347
76
+ tests/test_tools.py,sha256=CrG74XmHCvf6BEc2VxaS8nE74aPjAWRkr-f2lqYFUDw,37271
76
77
  tests/test_vind.py,sha256=L3q6yOXlhbVXwjuH-5YRRnauEO8q4LBOUBB_RGqSTfk,19003
77
- kubectl_mcp_server-1.23.0.dist-info/METADATA,sha256=zSUdGN45F44cKAsowJTcBl8imcoTmHgFHfMY5iI3SC0,43396
78
- kubectl_mcp_server-1.23.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
79
- kubectl_mcp_server-1.23.0.dist-info/entry_points.txt,sha256=zeOxQGaNC4r58deEmqsLCU5hfMjF0VqFUt9P5wWsKEM,109
80
- kubectl_mcp_server-1.23.0.dist-info/top_level.txt,sha256=o5IpfOGG-lqU8rVWJeK9aYC0r4f6qEX09QiBhZlYbkQ,23
81
- kubectl_mcp_server-1.23.0.dist-info/RECORD,,
78
+ kubectl_mcp_server-1.23.1.dist-info/METADATA,sha256=7Wf_HEIiKcqd6QpekGzexH5jQqD-CqJHMd3wku7J2bg,43396
79
+ kubectl_mcp_server-1.23.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
80
+ kubectl_mcp_server-1.23.1.dist-info/entry_points.txt,sha256=zeOxQGaNC4r58deEmqsLCU5hfMjF0VqFUt9P5wWsKEM,109
81
+ kubectl_mcp_server-1.23.1.dist-info/top_level.txt,sha256=o5IpfOGG-lqU8rVWJeK9aYC0r4f6qEX09QiBhZlYbkQ,23
82
+ kubectl_mcp_server-1.23.1.dist-info/RECORD,,
@@ -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.0"
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
- logger.error("FastMCP not found. Installing...")
143
- import subprocess
144
- try:
145
- subprocess.check_call(
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": "1.2.0"
609
+ "version": __version__
626
610
  }
627
611
  }
628
612
  elif method == "tools/list":
@@ -3,7 +3,6 @@ import logging
3
3
  from enum import Enum
4
4
  from typing import Any, Dict, List, Optional, Tuple
5
5
  from dataclasses import dataclass, field
6
- from functools import lru_cache
7
6
 
8
7
  logger = logging.getLogger("mcp-server")
9
8
 
@@ -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 functools import wraps
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 _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
-
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(
@@ -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 _get_kubectl_context_args(context: str) -> List[str]:
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(
@@ -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 _get_kubectl_context_args(context: str) -> List[str]:
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 _get_kubectl_context_args(context: str) -> List[str]:
22
- """Get kubectl context arguments if context is specified."""
23
- if context:
24
- return ["--context", context]
25
- return []
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
- try:
73
- repo_parts = repo.split('=')
74
- if len(repo_parts) != 2:
75
- return {"success": False, "error": "Repository format should be 'repo_name=repo_url'"}
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
- try:
166
- repo_parts = repo.split('=')
167
- if len(repo_parts) != 2:
168
- return {"success": False, "error": "Repository format should be 'repo_name=repo_url'"}
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
- try:
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] + command.split(),
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))