kubectl-mcp-server 1.13.0__py3-none-any.whl → 1.14.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.13.0.dist-info → kubectl_mcp_server-1.14.0.dist-info}/METADATA +1 -1
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.14.0.dist-info}/RECORD +14 -11
- 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/tools/browser.py +316 -28
- tests/test_browser.py +167 -5
- tests/test_cli.py +299 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.14.0.dist-info}/WHEEL +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.14.0.dist-info}/entry_points.txt +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.14.0.dist-info}/licenses/LICENSE +0 -0
- {kubectl_mcp_server-1.13.0.dist-info → kubectl_mcp_server-1.14.0.dist-info}/top_level.txt +0 -0
kubectl_mcp_tool/cli/cli.py
CHANGED
|
@@ -7,8 +7,38 @@ import logging
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import argparse
|
|
9
9
|
import traceback
|
|
10
|
+
import json
|
|
11
|
+
import shutil
|
|
12
|
+
import fnmatch
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
10
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
|
+
)
|
|
11
40
|
|
|
41
|
+
# Logging setup
|
|
12
42
|
log_file = os.environ.get("MCP_LOG_FILE")
|
|
13
43
|
log_level = logging.DEBUG if os.environ.get("MCP_DEBUG", "").lower() in ("1", "true") else logging.INFO
|
|
14
44
|
|
|
@@ -27,38 +57,393 @@ logger = logging.getLogger("kubectl-mcp-cli")
|
|
|
27
57
|
|
|
28
58
|
|
|
29
59
|
async def serve_stdio():
|
|
30
|
-
"""Serve the MCP server over stdio transport."""
|
|
31
60
|
server = MCPServer("kubernetes")
|
|
32
61
|
await server.serve_stdio()
|
|
33
62
|
|
|
34
63
|
|
|
35
64
|
async def serve_sse(host: str, port: int):
|
|
36
|
-
"""Serve the MCP server over SSE transport."""
|
|
37
65
|
server = MCPServer("kubernetes")
|
|
38
66
|
await server.serve_sse(host=host, port=port)
|
|
39
67
|
|
|
40
68
|
|
|
41
69
|
async def serve_http(host: str, port: int):
|
|
42
|
-
"""Serve the MCP server over HTTP transport."""
|
|
43
70
|
server = MCPServer("kubernetes")
|
|
44
71
|
await server.serve_http(host=host, port=port)
|
|
45
72
|
|
|
46
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
|
+
|
|
47
417
|
def main():
|
|
48
|
-
"""Main entry point for the CLI."""
|
|
49
418
|
parser = argparse.ArgumentParser(
|
|
50
|
-
|
|
419
|
+
prog="kubectl-mcp-server",
|
|
420
|
+
description="MCP server for Kubernetes with 127+ tools, 8 resources, and 8 prompts",
|
|
51
421
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
52
422
|
epilog="""
|
|
53
423
|
Examples:
|
|
54
|
-
kubectl-mcp serve # stdio
|
|
55
|
-
kubectl-mcp serve --transport
|
|
56
|
-
kubectl-mcp
|
|
57
|
-
kubectl-mcp
|
|
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
|
|
58
439
|
"""
|
|
59
440
|
)
|
|
441
|
+
|
|
442
|
+
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
|
443
|
+
|
|
60
444
|
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
61
445
|
|
|
446
|
+
# serve command (existing)
|
|
62
447
|
serve_parser = subparsers.add_parser("serve", help="Start the MCP server")
|
|
63
448
|
serve_parser.add_argument(
|
|
64
449
|
"--transport",
|
|
@@ -68,14 +453,55 @@ Examples:
|
|
|
68
453
|
)
|
|
69
454
|
serve_parser.add_argument("--host", type=str, default="0.0.0.0", help="Host for SSE/HTTP (default: 0.0.0.0)")
|
|
70
455
|
serve_parser.add_argument("--port", type=int, default=8000, help="Port for SSE/HTTP (default: 8000)")
|
|
71
|
-
serve_parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
|
72
456
|
|
|
457
|
+
# version command (existing)
|
|
73
458
|
subparsers.add_parser("version", help="Show version")
|
|
459
|
+
|
|
460
|
+
# diagnostics command (existing)
|
|
74
461
|
subparsers.add_parser("diagnostics", help="Run cluster diagnostics")
|
|
75
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
|
+
|
|
76
501
|
args = parser.parse_args()
|
|
77
502
|
|
|
78
|
-
|
|
503
|
+
# Enable debug logging
|
|
504
|
+
if args.debug:
|
|
79
505
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
80
506
|
os.environ["MCP_DEBUG"] = "1"
|
|
81
507
|
|
|
@@ -87,24 +513,57 @@ Examples:
|
|
|
87
513
|
asyncio.run(serve_sse(args.host, args.port))
|
|
88
514
|
elif args.transport in ("http", "streamable-http"):
|
|
89
515
|
asyncio.run(serve_http(args.host, args.port))
|
|
516
|
+
|
|
90
517
|
elif args.command == "version":
|
|
91
518
|
from .. import __version__
|
|
92
|
-
print(f"kubectl-mcp-
|
|
519
|
+
print(f"kubectl-mcp-server version {__version__}")
|
|
520
|
+
|
|
93
521
|
elif args.command == "diagnostics":
|
|
94
522
|
from ..diagnostics import run_diagnostics
|
|
95
|
-
import json
|
|
96
523
|
results = run_diagnostics()
|
|
97
524
|
print(json.dumps(results, indent=2))
|
|
98
|
-
|
|
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:
|
|
99
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
|
+
|
|
100
558
|
except KeyboardInterrupt:
|
|
101
559
|
pass
|
|
102
560
|
except Exception as e:
|
|
103
561
|
logger.error(f"Error: {e}")
|
|
104
|
-
if
|
|
562
|
+
if args.debug:
|
|
105
563
|
logger.error(traceback.format_exc())
|
|
106
|
-
return
|
|
107
|
-
|
|
564
|
+
return ErrorCode.SERVER_ERROR
|
|
565
|
+
|
|
566
|
+
return ErrorCode.SUCCESS
|
|
108
567
|
|
|
109
568
|
|
|
110
569
|
if __name__ == "__main__":
|