kubectl-mcp-server 1.13.0__tar.gz → 1.14.0__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.
Files changed (56) hide show
  1. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/PKG-INFO +1 -1
  2. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_server.egg-info/PKG-INFO +1 -1
  3. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_server.egg-info/SOURCES.txt +3 -0
  4. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/__init__.py +1 -1
  5. kubectl_mcp_server-1.14.0/kubectl_mcp_tool/cli/__init__.py +61 -0
  6. kubectl_mcp_server-1.14.0/kubectl_mcp_tool/cli/cli.py +570 -0
  7. kubectl_mcp_server-1.14.0/kubectl_mcp_tool/cli/errors.py +262 -0
  8. kubectl_mcp_server-1.14.0/kubectl_mcp_tool/cli/output.py +377 -0
  9. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/browser.py +316 -28
  10. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/setup.py +1 -1
  11. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/test_browser.py +167 -5
  12. kubectl_mcp_server-1.14.0/tests/test_cli.py +299 -0
  13. kubectl_mcp_server-1.13.0/kubectl_mcp_tool/cli/__init__.py +0 -9
  14. kubectl_mcp_server-1.13.0/kubectl_mcp_tool/cli/cli.py +0 -111
  15. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/LICENSE +0 -0
  16. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/README.md +0 -0
  17. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_server.egg-info/dependency_links.txt +0 -0
  18. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_server.egg-info/entry_points.txt +0 -0
  19. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_server.egg-info/requires.txt +0 -0
  20. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_server.egg-info/top_level.txt +0 -0
  21. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/__main__.py +0 -0
  22. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/auth/__init__.py +0 -0
  23. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/auth/config.py +0 -0
  24. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/auth/scopes.py +0 -0
  25. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/auth/verifier.py +0 -0
  26. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/cli/__main__.py +0 -0
  27. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/diagnostics.py +0 -0
  28. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/k8s_config.py +0 -0
  29. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/mcp_server.py +0 -0
  30. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/prompts/__init__.py +0 -0
  31. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/prompts/prompts.py +0 -0
  32. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/resources/__init__.py +0 -0
  33. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/resources/resources.py +0 -0
  34. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/__init__.py +0 -0
  35. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/cluster.py +0 -0
  36. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/core.py +0 -0
  37. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/cost.py +0 -0
  38. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/deployments.py +0 -0
  39. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/diagnostics.py +0 -0
  40. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/helm.py +0 -0
  41. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/networking.py +0 -0
  42. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/operations.py +0 -0
  43. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/pods.py +0 -0
  44. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/security.py +0 -0
  45. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/storage.py +0 -0
  46. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/tools/ui.py +0 -0
  47. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/utils/__init__.py +0 -0
  48. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/kubectl_mcp_tool/utils/helpers.py +0 -0
  49. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/setup.cfg +0 -0
  50. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/__init__.py +0 -0
  51. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/conftest.py +0 -0
  52. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/test_auth.py +0 -0
  53. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/test_prompts.py +0 -0
  54. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/test_resources.py +0 -0
  55. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/test_server.py +0 -0
  56. {kubectl_mcp_server-1.13.0 → kubectl_mcp_server-1.14.0}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kubectl-mcp-server
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: A Model Context Protocol (MCP) server for Kubernetes with 127+ tools, 8 resources, and 8 prompts
5
5
  Home-page: https://github.com/rohitg00/kubectl-mcp-server
6
6
  Author: Rohit Ghumare
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kubectl-mcp-server
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: A Model Context Protocol (MCP) server for Kubernetes with 127+ tools, 8 resources, and 8 prompts
5
5
  Home-page: https://github.com/rohitg00/kubectl-mcp-server
6
6
  Author: Rohit Ghumare
@@ -19,6 +19,8 @@ kubectl_mcp_tool/auth/verifier.py
19
19
  kubectl_mcp_tool/cli/__init__.py
20
20
  kubectl_mcp_tool/cli/__main__.py
21
21
  kubectl_mcp_tool/cli/cli.py
22
+ kubectl_mcp_tool/cli/errors.py
23
+ kubectl_mcp_tool/cli/output.py
22
24
  kubectl_mcp_tool/prompts/__init__.py
23
25
  kubectl_mcp_tool/prompts/prompts.py
24
26
  kubectl_mcp_tool/resources/__init__.py
@@ -43,6 +45,7 @@ tests/__init__.py
43
45
  tests/conftest.py
44
46
  tests/test_auth.py
45
47
  tests/test_browser.py
48
+ tests/test_cli.py
46
49
  tests/test_prompts.py
47
50
  tests/test_resources.py
48
51
  tests/test_server.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.13.0"
10
+ __version__ = "1.14.0"
11
11
 
12
12
  from .mcp_server import MCPServer
13
13
  from .diagnostics import run_diagnostics, check_kubectl_installation, check_cluster_connection
@@ -0,0 +1,61 @@
1
+ """
2
+ CLI package for kubectl-mcp-tool.
3
+
4
+ Provides command-line interface for managing the MCP server.
5
+ """
6
+
7
+ from .cli import main
8
+ from .errors import (
9
+ CliError,
10
+ ErrorCode,
11
+ format_cli_error,
12
+ tool_not_found_error,
13
+ tool_execution_error,
14
+ k8s_connection_error,
15
+ k8s_context_error,
16
+ browser_not_found_error,
17
+ browser_connection_error,
18
+ invalid_json_error,
19
+ missing_argument_error,
20
+ unknown_subcommand_error,
21
+ )
22
+ from .output import (
23
+ format_tools_list,
24
+ format_tool_schema,
25
+ format_tools_search,
26
+ format_resources_list,
27
+ format_prompts_list,
28
+ format_call_result,
29
+ format_server_info,
30
+ format_context_info,
31
+ format_doctor_results,
32
+ should_colorize,
33
+ )
34
+
35
+ __all__ = [
36
+ "main",
37
+ # Errors
38
+ "CliError",
39
+ "ErrorCode",
40
+ "format_cli_error",
41
+ "tool_not_found_error",
42
+ "tool_execution_error",
43
+ "k8s_connection_error",
44
+ "k8s_context_error",
45
+ "browser_not_found_error",
46
+ "browser_connection_error",
47
+ "invalid_json_error",
48
+ "missing_argument_error",
49
+ "unknown_subcommand_error",
50
+ # Output
51
+ "format_tools_list",
52
+ "format_tool_schema",
53
+ "format_tools_search",
54
+ "format_resources_list",
55
+ "format_prompts_list",
56
+ "format_call_result",
57
+ "format_server_info",
58
+ "format_context_info",
59
+ "format_doctor_results",
60
+ "should_colorize",
61
+ ]
@@ -0,0 +1,570 @@
1
+ #!/usr/bin/env python3
2
+ """CLI module for kubectl-mcp-tool."""
3
+
4
+ import sys
5
+ import os
6
+ import logging
7
+ import asyncio
8
+ import argparse
9
+ import traceback
10
+ import json
11
+ import shutil
12
+ import fnmatch
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ from ..mcp_server import MCPServer
16
+ from .errors import (
17
+ ErrorCode,
18
+ format_cli_error,
19
+ tool_not_found_error,
20
+ tool_execution_error,
21
+ invalid_json_error,
22
+ missing_argument_error,
23
+ unknown_subcommand_error,
24
+ k8s_context_error,
25
+ dependency_missing_error,
26
+ )
27
+ from .output import (
28
+ format_tools_list,
29
+ format_tool_schema,
30
+ format_tools_search,
31
+ format_resources_list,
32
+ format_prompts_list,
33
+ format_call_result,
34
+ format_server_info,
35
+ format_context_info,
36
+ format_doctor_results,
37
+ format_error,
38
+ format_success,
39
+ )
40
+
41
+ # Logging setup
42
+ log_file = os.environ.get("MCP_LOG_FILE")
43
+ log_level = logging.DEBUG if os.environ.get("MCP_DEBUG", "").lower() in ("1", "true") else logging.INFO
44
+
45
+ handlers = []
46
+ if log_file:
47
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
48
+ handlers.append(logging.FileHandler(log_file))
49
+ handlers.append(logging.StreamHandler(sys.stderr))
50
+
51
+ logging.basicConfig(
52
+ level=log_level,
53
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
54
+ handlers=handlers
55
+ )
56
+ logger = logging.getLogger("kubectl-mcp-cli")
57
+
58
+
59
+ async def serve_stdio():
60
+ server = MCPServer("kubernetes")
61
+ await server.serve_stdio()
62
+
63
+
64
+ async def serve_sse(host: str, port: int):
65
+ server = MCPServer("kubernetes")
66
+ await server.serve_sse(host=host, port=port)
67
+
68
+
69
+ async def serve_http(host: str, port: int):
70
+ server = MCPServer("kubernetes")
71
+ await server.serve_http(host=host, port=port)
72
+
73
+
74
+ def get_all_tools() -> List[Dict[str, Any]]:
75
+ server = MCPServer("kubernetes")
76
+
77
+ async def _get():
78
+ tools = await server.server.list_tools()
79
+ return [
80
+ {
81
+ "name": t.name,
82
+ "description": t.description or "",
83
+ "inputSchema": t.inputSchema if hasattr(t, 'inputSchema') else {},
84
+ "category": _get_tool_category(t.name),
85
+ }
86
+ for t in tools
87
+ ]
88
+
89
+ return asyncio.run(_get())
90
+
91
+
92
+ def _get_tool_category(tool_name: str) -> str:
93
+ """Determine tool category from name."""
94
+ categories = {
95
+ "pod": "pods",
96
+ "deployment": "deployments",
97
+ "statefulset": "deployments",
98
+ "daemonset": "deployments",
99
+ "replicaset": "deployments",
100
+ "namespace": "core",
101
+ "configmap": "core",
102
+ "secret": "core",
103
+ "service": "networking",
104
+ "ingress": "networking",
105
+ "network": "networking",
106
+ "pvc": "storage",
107
+ "pv": "storage",
108
+ "storage": "storage",
109
+ "rbac": "security",
110
+ "role": "security",
111
+ "serviceaccount": "security",
112
+ "helm": "helm",
113
+ "apply": "operations",
114
+ "patch": "operations",
115
+ "delete": "operations",
116
+ "scale": "operations",
117
+ "rollout": "operations",
118
+ "context": "cluster",
119
+ "cluster": "cluster",
120
+ "node": "cluster",
121
+ "metric": "diagnostics",
122
+ "compare": "diagnostics",
123
+ "event": "diagnostics",
124
+ "cost": "cost",
125
+ "browser": "browser",
126
+ "screenshot": "browser",
127
+ "ui": "ui",
128
+ "dashboard": "ui",
129
+ }
130
+
131
+ name_lower = tool_name.lower()
132
+ for keyword, category in categories.items():
133
+ if keyword in name_lower:
134
+ return category
135
+
136
+ return "other"
137
+
138
+
139
+ def cmd_tools(args):
140
+ tools = get_all_tools()
141
+
142
+ if args.name:
143
+ # Show specific tool schema
144
+ tool = next((t for t in tools if t["name"] == args.name), None)
145
+ if not tool:
146
+ available = [t["name"] for t in tools]
147
+ print(format_cli_error(tool_not_found_error(args.name, available)), file=sys.stderr)
148
+ return ErrorCode.CLIENT_ERROR
149
+
150
+ print(format_tool_schema(tool, as_json=args.json))
151
+ else:
152
+ # List all tools
153
+ print(format_tools_list(tools, with_descriptions=args.with_descriptions, as_json=args.json))
154
+
155
+ return ErrorCode.SUCCESS
156
+
157
+
158
+ def get_all_resources() -> List[Dict[str, Any]]:
159
+ server = MCPServer("kubernetes")
160
+
161
+ async def _get():
162
+ resources = await server.server.list_resources()
163
+ return [
164
+ {
165
+ "uri": r.uri,
166
+ "name": r.name,
167
+ "description": r.description or "",
168
+ "mimeType": getattr(r, 'mimeType', None),
169
+ }
170
+ for r in resources
171
+ ]
172
+
173
+ return asyncio.run(_get())
174
+
175
+
176
+ def cmd_resources(args):
177
+ resources = get_all_resources()
178
+ print(format_resources_list(resources, as_json=args.json))
179
+ return ErrorCode.SUCCESS
180
+
181
+
182
+ def get_all_prompts() -> List[Dict[str, Any]]:
183
+ server = MCPServer("kubernetes")
184
+
185
+ async def _get():
186
+ prompts = await server.server.list_prompts()
187
+ return [
188
+ {
189
+ "name": p.name,
190
+ "description": p.description or "",
191
+ "arguments": [
192
+ {"name": a.name, "description": a.description, "required": a.required}
193
+ for a in (p.arguments or [])
194
+ ],
195
+ }
196
+ for p in prompts
197
+ ]
198
+
199
+ return asyncio.run(_get())
200
+
201
+
202
+ def cmd_prompts(args):
203
+ prompts = get_all_prompts()
204
+ print(format_prompts_list(prompts, as_json=args.json))
205
+ return ErrorCode.SUCCESS
206
+
207
+
208
+ def cmd_call(args):
209
+ tools = get_all_tools()
210
+ tool = next((t for t in tools if t["name"] == args.tool), None)
211
+
212
+ if not tool:
213
+ available = [t["name"] for t in tools]
214
+ print(format_cli_error(tool_not_found_error(args.tool, available)), file=sys.stderr)
215
+ return ErrorCode.CLIENT_ERROR
216
+
217
+ # Parse JSON arguments
218
+ json_args = args.args
219
+
220
+ # Read from stdin if no args provided and stdin is not a tty
221
+ if not json_args and not sys.stdin.isatty():
222
+ json_args = sys.stdin.read().strip()
223
+
224
+ # Default to empty object
225
+ if not json_args:
226
+ json_args = "{}"
227
+
228
+ try:
229
+ tool_args = json.loads(json_args)
230
+ except json.JSONDecodeError as e:
231
+ print(format_cli_error(invalid_json_error(json_args, str(e))), file=sys.stderr)
232
+ return ErrorCode.CLIENT_ERROR
233
+
234
+ # Execute the tool
235
+ server = MCPServer("kubernetes")
236
+
237
+ async def _call():
238
+ result = await server.server.call_tool(args.tool, tool_args)
239
+ return result
240
+
241
+ try:
242
+ result = asyncio.run(_call())
243
+ print(format_call_result(result, as_json=args.json))
244
+ return ErrorCode.SUCCESS
245
+ except Exception as e:
246
+ print(format_cli_error(tool_execution_error(args.tool, str(e))), file=sys.stderr)
247
+ return ErrorCode.SERVER_ERROR
248
+
249
+
250
+ def cmd_grep(args):
251
+ tools = get_all_tools()
252
+ pattern = args.pattern
253
+
254
+ # Support glob patterns
255
+ if not pattern.startswith("*") and not pattern.endswith("*"):
256
+ # Make it a contains search by default
257
+ pattern = f"*{pattern}*"
258
+
259
+ matches = [
260
+ t for t in tools
261
+ if fnmatch.fnmatch(t["name"].lower(), pattern.lower())
262
+ or fnmatch.fnmatch((t.get("description") or "").lower(), pattern.lower())
263
+ ]
264
+
265
+ print(format_tools_search(matches, args.pattern, with_descriptions=args.with_descriptions))
266
+ return ErrorCode.SUCCESS
267
+
268
+
269
+ def cmd_info(args):
270
+ from .. import __version__
271
+
272
+ tools = get_all_tools()
273
+ resources = get_all_resources()
274
+ prompts = get_all_prompts()
275
+
276
+ # Get current k8s context
277
+ context = None
278
+ try:
279
+ from kubernetes import config
280
+ _, active_context = config.list_kube_config_contexts()
281
+ context = active_context.get("name") if active_context else None
282
+ except Exception:
283
+ pass
284
+
285
+ print(format_server_info(
286
+ version=__version__,
287
+ tool_count=len(tools),
288
+ resource_count=len(resources),
289
+ prompt_count=len(prompts),
290
+ context=context,
291
+ as_json=getattr(args, 'json', False)
292
+ ))
293
+ return ErrorCode.SUCCESS
294
+
295
+
296
+ def cmd_context(args):
297
+ try:
298
+ from kubernetes import config
299
+ import subprocess
300
+
301
+ contexts, active_context = config.list_kube_config_contexts()
302
+ current = active_context.get("name") if active_context else None
303
+ available = [c.get("name") for c in contexts] if contexts else []
304
+
305
+ if args.name:
306
+ # Switch context
307
+ if args.name not in available:
308
+ print(format_cli_error(k8s_context_error(args.name, available)), file=sys.stderr)
309
+ return ErrorCode.K8S_ERROR
310
+
311
+ result = subprocess.run(
312
+ ["kubectl", "config", "use-context", args.name],
313
+ capture_output=True,
314
+ text=True
315
+ )
316
+ if result.returncode == 0:
317
+ print(format_success(f"Switched to context: {args.name}"))
318
+ return ErrorCode.SUCCESS
319
+ else:
320
+ print(format_error(result.stderr.strip()), file=sys.stderr)
321
+ return ErrorCode.K8S_ERROR
322
+ else:
323
+ # Show current context
324
+ print(format_context_info(current or "(none)", available, as_json=getattr(args, 'json', False)))
325
+ return ErrorCode.SUCCESS
326
+
327
+ except Exception as e:
328
+ print(format_error(f"Failed to get contexts: {e}"), file=sys.stderr)
329
+ return ErrorCode.K8S_ERROR
330
+
331
+
332
+ def cmd_doctor(args):
333
+ checks = []
334
+
335
+ # Check kubectl
336
+ kubectl_path = shutil.which("kubectl")
337
+ if kubectl_path:
338
+ try:
339
+ import subprocess
340
+ result = subprocess.run(["kubectl", "version", "--client", "-o", "json"],
341
+ capture_output=True, text=True, timeout=5)
342
+ if result.returncode == 0:
343
+ version_info = json.loads(result.stdout)
344
+ version = version_info.get("clientVersion", {}).get("gitVersion", "unknown")
345
+ checks.append({"name": "kubectl", "status": "ok", "version": version, "details": kubectl_path})
346
+ else:
347
+ checks.append({"name": "kubectl", "status": "warning", "details": "kubectl found but version check failed"})
348
+ except Exception as e:
349
+ checks.append({"name": "kubectl", "status": "warning", "details": str(e)})
350
+ else:
351
+ checks.append({"name": "kubectl", "status": "error", "details": "kubectl not found in PATH"})
352
+
353
+ # Check helm
354
+ helm_path = shutil.which("helm")
355
+ if helm_path:
356
+ try:
357
+ import subprocess
358
+ result = subprocess.run(["helm", "version", "--short"], capture_output=True, text=True, timeout=5)
359
+ if result.returncode == 0:
360
+ version = result.stdout.strip()
361
+ checks.append({"name": "helm", "status": "ok", "version": version, "details": helm_path})
362
+ else:
363
+ checks.append({"name": "helm", "status": "warning", "details": "helm found but version check failed"})
364
+ except Exception as e:
365
+ checks.append({"name": "helm", "status": "warning", "details": str(e)})
366
+ else:
367
+ checks.append({"name": "helm", "status": "warning", "details": "helm not found (optional)"})
368
+
369
+ # Check Kubernetes connection
370
+ try:
371
+ from kubernetes import client, config
372
+ config.load_kube_config()
373
+ v1 = client.CoreV1Api()
374
+ v1.list_namespace(limit=1)
375
+ _, active_context = config.list_kube_config_contexts()
376
+ context_name = active_context.get("name") if active_context else "unknown"
377
+ checks.append({"name": "kubernetes", "status": "ok", "version": context_name, "details": "Connected"})
378
+ except Exception as e:
379
+ checks.append({"name": "kubernetes", "status": "error", "details": str(e)})
380
+
381
+ # Check agent-browser (optional)
382
+ browser_path = shutil.which("agent-browser")
383
+ if browser_path:
384
+ try:
385
+ import subprocess
386
+ result = subprocess.run(["agent-browser", "--version"], capture_output=True, text=True, timeout=5)
387
+ if result.returncode == 0:
388
+ version = result.stdout.strip()
389
+ checks.append({"name": "agent-browser", "status": "ok", "version": version, "details": browser_path})
390
+ else:
391
+ checks.append({"name": "agent-browser", "status": "ok", "details": browser_path})
392
+ except Exception:
393
+ checks.append({"name": "agent-browser", "status": "ok", "details": browser_path})
394
+ else:
395
+ enabled = os.environ.get("MCP_BROWSER_ENABLED", "").lower() in ("1", "true")
396
+ if enabled:
397
+ checks.append({"name": "agent-browser", "status": "warning",
398
+ "details": "MCP_BROWSER_ENABLED=true but agent-browser not found"})
399
+ else:
400
+ checks.append({"name": "agent-browser", "status": "ok",
401
+ "details": "Not installed (optional, set MCP_BROWSER_ENABLED=true to use)"})
402
+
403
+ # Check Python dependencies
404
+ try:
405
+ import fastmcp
406
+ checks.append({"name": "fastmcp", "status": "ok", "version": getattr(fastmcp, '__version__', 'installed')})
407
+ except ImportError:
408
+ checks.append({"name": "fastmcp", "status": "error", "details": "fastmcp not installed"})
409
+
410
+ print(format_doctor_results(checks, as_json=getattr(args, 'json', False)))
411
+
412
+ # Return error code if any critical checks failed
413
+ has_errors = any(c["status"] == "error" for c in checks)
414
+ return ErrorCode.CLIENT_ERROR if has_errors else ErrorCode.SUCCESS
415
+
416
+
417
+ def main():
418
+ parser = argparse.ArgumentParser(
419
+ prog="kubectl-mcp-server",
420
+ description="MCP server for Kubernetes with 127+ tools, 8 resources, and 8 prompts",
421
+ formatter_class=argparse.RawDescriptionHelpFormatter,
422
+ epilog="""
423
+ Examples:
424
+ kubectl-mcp-server serve # Start stdio server (Claude/Cursor)
425
+ kubectl-mcp-server serve --transport http # Start HTTP server
426
+ kubectl-mcp-server tools # List all tools
427
+ kubectl-mcp-server tools -d # List tools with descriptions
428
+ kubectl-mcp-server tools get_pods # Show tool schema
429
+ kubectl-mcp-server grep "*pod*" # Search for pod-related tools
430
+ kubectl-mcp-server call get_pods '{"namespace": "default"}' # Call a tool
431
+ echo '{"namespace": "kube-system"}' | kubectl-mcp-server call get_pods
432
+ kubectl-mcp-server context # Show k8s context
433
+ kubectl-mcp-server doctor # Check dependencies
434
+
435
+ Environment Variables:
436
+ MCP_DEBUG=true Enable debug logging
437
+ MCP_BROWSER_ENABLED=true Enable browser automation tools
438
+ NO_COLOR=1 Disable colored output
439
+ """
440
+ )
441
+
442
+ parser.add_argument("--debug", action="store_true", help="Enable debug logging")
443
+
444
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
445
+
446
+ # serve command (existing)
447
+ serve_parser = subparsers.add_parser("serve", help="Start the MCP server")
448
+ serve_parser.add_argument(
449
+ "--transport",
450
+ choices=["stdio", "sse", "http", "streamable-http"],
451
+ default="stdio",
452
+ help="Transport to use (default: stdio)"
453
+ )
454
+ serve_parser.add_argument("--host", type=str, default="0.0.0.0", help="Host for SSE/HTTP (default: 0.0.0.0)")
455
+ serve_parser.add_argument("--port", type=int, default=8000, help="Port for SSE/HTTP (default: 8000)")
456
+
457
+ # version command (existing)
458
+ subparsers.add_parser("version", help="Show version")
459
+
460
+ # diagnostics command (existing)
461
+ subparsers.add_parser("diagnostics", help="Run cluster diagnostics")
462
+
463
+ # tools command (new)
464
+ tools_parser = subparsers.add_parser("tools", help="List or inspect tools")
465
+ tools_parser.add_argument("name", nargs="?", help="Tool name to inspect")
466
+ tools_parser.add_argument("-d", "--with-descriptions", action="store_true", help="Include descriptions")
467
+ tools_parser.add_argument("--json", action="store_true", help="Output as JSON")
468
+
469
+ # resources command (new)
470
+ resources_parser = subparsers.add_parser("resources", help="List available resources")
471
+ resources_parser.add_argument("--json", action="store_true", help="Output as JSON")
472
+
473
+ # prompts command (new)
474
+ prompts_parser = subparsers.add_parser("prompts", help="List available prompts")
475
+ prompts_parser.add_argument("--json", action="store_true", help="Output as JSON")
476
+
477
+ # call command (new)
478
+ call_parser = subparsers.add_parser("call", help="Call a tool directly")
479
+ call_parser.add_argument("tool", help="Tool name to call")
480
+ call_parser.add_argument("args", nargs="?", help="JSON arguments (reads stdin if omitted)")
481
+ call_parser.add_argument("--json", action="store_true", help="Force JSON output")
482
+
483
+ # grep command (new)
484
+ grep_parser = subparsers.add_parser("grep", help="Search tools by pattern")
485
+ grep_parser.add_argument("pattern", help="Glob pattern to search (e.g., '*pod*')")
486
+ grep_parser.add_argument("-d", "--with-descriptions", action="store_true", help="Include descriptions")
487
+
488
+ # info command (new)
489
+ info_parser = subparsers.add_parser("info", help="Show server information")
490
+ info_parser.add_argument("--json", action="store_true", help="Output as JSON")
491
+
492
+ # context command (new)
493
+ context_parser = subparsers.add_parser("context", help="Show/switch Kubernetes context")
494
+ context_parser.add_argument("name", nargs="?", help="Context to switch to")
495
+ context_parser.add_argument("--json", action="store_true", help="Output as JSON")
496
+
497
+ # doctor command (new)
498
+ doctor_parser = subparsers.add_parser("doctor", help="Check dependencies and configuration")
499
+ doctor_parser.add_argument("--json", action="store_true", help="Output as JSON")
500
+
501
+ args = parser.parse_args()
502
+
503
+ # Enable debug logging
504
+ if args.debug:
505
+ logging.getLogger().setLevel(logging.DEBUG)
506
+ os.environ["MCP_DEBUG"] = "1"
507
+
508
+ try:
509
+ if args.command == "serve":
510
+ if args.transport == "stdio":
511
+ asyncio.run(serve_stdio())
512
+ elif args.transport == "sse":
513
+ asyncio.run(serve_sse(args.host, args.port))
514
+ elif args.transport in ("http", "streamable-http"):
515
+ asyncio.run(serve_http(args.host, args.port))
516
+
517
+ elif args.command == "version":
518
+ from .. import __version__
519
+ print(f"kubectl-mcp-server version {__version__}")
520
+
521
+ elif args.command == "diagnostics":
522
+ from ..diagnostics import run_diagnostics
523
+ results = run_diagnostics()
524
+ print(json.dumps(results, indent=2))
525
+
526
+ elif args.command == "tools":
527
+ return cmd_tools(args)
528
+
529
+ elif args.command == "resources":
530
+ return cmd_resources(args)
531
+
532
+ elif args.command == "prompts":
533
+ return cmd_prompts(args)
534
+
535
+ elif args.command == "call":
536
+ return cmd_call(args)
537
+
538
+ elif args.command == "grep":
539
+ return cmd_grep(args)
540
+
541
+ elif args.command == "info":
542
+ return cmd_info(args)
543
+
544
+ elif args.command == "context":
545
+ return cmd_context(args)
546
+
547
+ elif args.command == "doctor":
548
+ return cmd_doctor(args)
549
+
550
+ elif args.command is None:
551
+ parser.print_help()
552
+
553
+ else:
554
+ # Unknown subcommand
555
+ print(format_cli_error(unknown_subcommand_error(args.command)), file=sys.stderr)
556
+ return ErrorCode.CLIENT_ERROR
557
+
558
+ except KeyboardInterrupt:
559
+ pass
560
+ except Exception as e:
561
+ logger.error(f"Error: {e}")
562
+ if args.debug:
563
+ logger.error(traceback.format_exc())
564
+ return ErrorCode.SERVER_ERROR
565
+
566
+ return ErrorCode.SUCCESS
567
+
568
+
569
+ if __name__ == "__main__":
570
+ sys.exit(main())