claude-plugin-kubernetes 0.1.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.
- claude_plugin_kubernetes-0.1.0/PKG-INFO +10 -0
- claude_plugin_kubernetes-0.1.0/README.md +84 -0
- claude_plugin_kubernetes-0.1.0/claude_plugin_kubernetes.egg-info/PKG-INFO +10 -0
- claude_plugin_kubernetes-0.1.0/claude_plugin_kubernetes.egg-info/SOURCES.txt +16 -0
- claude_plugin_kubernetes-0.1.0/claude_plugin_kubernetes.egg-info/dependency_links.txt +1 -0
- claude_plugin_kubernetes-0.1.0/claude_plugin_kubernetes.egg-info/entry_points.txt +2 -0
- claude_plugin_kubernetes-0.1.0/claude_plugin_kubernetes.egg-info/requires.txt +6 -0
- claude_plugin_kubernetes-0.1.0/claude_plugin_kubernetes.egg-info/top_level.txt +1 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/__init__.py +0 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/formatters.py +56 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/kubectl.py +127 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/server.py +93 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/tools/__init__.py +0 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/tools/awareness.py +286 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/tools/diagnostics.py +378 -0
- claude_plugin_kubernetes-0.1.0/k8s_mcp/tools/remediation.py +354 -0
- claude_plugin_kubernetes-0.1.0/pyproject.toml +31 -0
- claude_plugin_kubernetes-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-plugin-kubernetes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Claude MCP plugin for Kubernetes cluster awareness, diagnostics, and remediation
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: mcp>=1.0
|
|
7
|
+
Requires-Dist: pyyaml>=6.0
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Claude Plugin — Kubernetes
|
|
2
|
+
|
|
3
|
+
An MCP server that gives Claude deep Kubernetes cluster awareness, diagnostics, and remediation capabilities via `kubectl`.
|
|
4
|
+
|
|
5
|
+
## Tools (21)
|
|
6
|
+
|
|
7
|
+
### Awareness
|
|
8
|
+
| Tool | Description |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `k8s_cluster_info` | Current context, server version, API endpoint |
|
|
11
|
+
| `k8s_get_contexts` | List all kubeconfig contexts |
|
|
12
|
+
| `k8s_list_namespaces` | List namespaces with status |
|
|
13
|
+
| `k8s_list_nodes` | Nodes with roles, status, OS, IP |
|
|
14
|
+
| `k8s_list_pods` | Pods with status, restarts, node (filter by ns/label) |
|
|
15
|
+
| `k8s_list_deployments` | Deployments with replica counts |
|
|
16
|
+
| `k8s_list_services` | Services with type, IPs, ports |
|
|
17
|
+
| `k8s_list_events` | Cluster events (filterable by Warning type) |
|
|
18
|
+
|
|
19
|
+
### Diagnostics
|
|
20
|
+
| Tool | Description |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `k8s_describe` | `kubectl describe` any resource |
|
|
23
|
+
| `k8s_logs` | Pod logs (tail, container, previous container) |
|
|
24
|
+
| `k8s_top_pods` | Pod CPU/memory (requires metrics-server) |
|
|
25
|
+
| `k8s_top_nodes` | Node CPU/memory (requires metrics-server) |
|
|
26
|
+
| `k8s_find_issues` | Full cluster health scan — failing pods, node pressure, bad deployments, warning events |
|
|
27
|
+
| `k8s_get_yaml` | Export any resource as YAML |
|
|
28
|
+
|
|
29
|
+
### Remediation
|
|
30
|
+
| Tool | Risk | Description |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `k8s_restart_deployment` | Low | Rolling restart a deployment |
|
|
33
|
+
| `k8s_scale` | Medium | Scale deployment/statefulset replicas |
|
|
34
|
+
| `k8s_delete_pod` | Low-Med | Delete pod (triggers controller recreation) |
|
|
35
|
+
| `k8s_rollback_deployment` | Medium | Rollback to previous or specific revision |
|
|
36
|
+
| `k8s_apply_manifest` | Medium | Apply YAML/JSON manifest via stdin |
|
|
37
|
+
| `k8s_patch_resource` | Medium | JSON merge patch any resource |
|
|
38
|
+
| `k8s_node_operation` | High | Cordon / uncordon / drain a node |
|
|
39
|
+
|
|
40
|
+
## Setup
|
|
41
|
+
|
|
42
|
+
### Claude Desktop
|
|
43
|
+
|
|
44
|
+
Add the following to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"kubernetes": {
|
|
50
|
+
"command": "uvx",
|
|
51
|
+
"args": ["--from", "git+https://github.com/kanr/claude-plugin-kubernetes", "k8s-mcp"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Requires [`uv`](https://docs.astral.sh/uv/getting-started/installation/) to be installed. Once published to PyPI, the `git+https://...` can be replaced with simply `claude-plugin-kubernetes`.
|
|
58
|
+
|
|
59
|
+
### Claude Code (CLI)
|
|
60
|
+
|
|
61
|
+
The `.mcp.json` in this repo is already configured. Clone the repo and open it in Claude Code — the server registers automatically.
|
|
62
|
+
|
|
63
|
+
To verify, run `/mcp` inside Claude Code. You should see `kubernetes` listed with 21 tools.
|
|
64
|
+
|
|
65
|
+
### Local development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/kanr/claude-plugin-kubernetes
|
|
69
|
+
cd claude-plugin-kubernetes
|
|
70
|
+
python3 -m venv .venv && .venv/bin/pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Requirements
|
|
74
|
+
|
|
75
|
+
- `kubectl` installed and on `$PATH`
|
|
76
|
+
- A valid `~/.kube/config` (or `$KUBECONFIG` set)
|
|
77
|
+
- Python 3.11+
|
|
78
|
+
- `metrics-server` installed in the cluster for `k8s_top_pods` / `k8s_top_nodes`
|
|
79
|
+
|
|
80
|
+
## Security Notes
|
|
81
|
+
|
|
82
|
+
- The kubectl wrapper uses `asyncio.create_subprocess_exec` — no shell, no injection risk.
|
|
83
|
+
- All resource names are passed as discrete arguments, never interpolated into shell strings.
|
|
84
|
+
- Remediation tools carry risk labels. Claude will warn you before applying destructive changes.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-plugin-kubernetes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Claude MCP plugin for Kubernetes cluster awareness, diagnostics, and remediation
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: mcp>=1.0
|
|
7
|
+
Requires-Dist: pyyaml>=6.0
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
claude_plugin_kubernetes.egg-info/PKG-INFO
|
|
4
|
+
claude_plugin_kubernetes.egg-info/SOURCES.txt
|
|
5
|
+
claude_plugin_kubernetes.egg-info/dependency_links.txt
|
|
6
|
+
claude_plugin_kubernetes.egg-info/entry_points.txt
|
|
7
|
+
claude_plugin_kubernetes.egg-info/requires.txt
|
|
8
|
+
claude_plugin_kubernetes.egg-info/top_level.txt
|
|
9
|
+
k8s_mcp/__init__.py
|
|
10
|
+
k8s_mcp/formatters.py
|
|
11
|
+
k8s_mcp/kubectl.py
|
|
12
|
+
k8s_mcp/server.py
|
|
13
|
+
k8s_mcp/tools/__init__.py
|
|
14
|
+
k8s_mcp/tools/awareness.py
|
|
15
|
+
k8s_mcp/tools/diagnostics.py
|
|
16
|
+
k8s_mcp/tools/remediation.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
k8s_mcp
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Shared output formatting helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def section(title: str, body: str) -> str:
|
|
9
|
+
"""Format a titled section."""
|
|
10
|
+
bar = "─" * len(title)
|
|
11
|
+
return f"{title}\n{bar}\n{body}"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def bullet_list(items: list[str]) -> str:
|
|
15
|
+
return "\n".join(f" • {item}" for item in items)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def kv_table(pairs: list[tuple[str, Any]], indent: int = 0) -> str:
|
|
19
|
+
if not pairs:
|
|
20
|
+
return ""
|
|
21
|
+
max_key = max(len(str(k)) for k, _ in pairs)
|
|
22
|
+
pad = " " * indent
|
|
23
|
+
lines = [f"{pad}{str(k).ljust(max_key)} {v}" for k, v in pairs]
|
|
24
|
+
return "\n".join(lines)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def severity_icon(level: str) -> str:
|
|
28
|
+
return {"critical": "🔴", "warning": "🟡", "info": "🔵"}.get(level, "⚪")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def node_conditions_summary(conditions: list[dict]) -> str:
|
|
32
|
+
"""Summarise node conditions from the JSON .status.conditions list."""
|
|
33
|
+
issues = []
|
|
34
|
+
healthy = []
|
|
35
|
+
for c in conditions:
|
|
36
|
+
ctype = c.get("type", "")
|
|
37
|
+
status = c.get("status", "")
|
|
38
|
+
reason = c.get("reason", "")
|
|
39
|
+
msg = c.get("message", "")
|
|
40
|
+
|
|
41
|
+
# Ready=True is good; all others False/Unknown is good
|
|
42
|
+
if ctype == "Ready":
|
|
43
|
+
if status != "True":
|
|
44
|
+
issues.append(f"NotReady ({reason}): {msg}")
|
|
45
|
+
else:
|
|
46
|
+
healthy.append("Ready")
|
|
47
|
+
else:
|
|
48
|
+
if status == "True":
|
|
49
|
+
issues.append(f"{ctype} ({reason}): {msg}")
|
|
50
|
+
|
|
51
|
+
result_parts = []
|
|
52
|
+
if healthy:
|
|
53
|
+
result_parts.append(", ".join(healthy))
|
|
54
|
+
if issues:
|
|
55
|
+
result_parts.append("ISSUES: " + " | ".join(issues))
|
|
56
|
+
return " | ".join(result_parts) if result_parts else "Unknown"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async kubectl wrapper.
|
|
3
|
+
|
|
4
|
+
Uses asyncio.create_subprocess_exec — no shell involved, immune to injection.
|
|
5
|
+
All callers must pass resource names/values as explicit list elements, never
|
|
6
|
+
interpolated into a shell string.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import json
|
|
13
|
+
from typing import Sequence
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
KUBECTL_TIMEOUT = 60 # seconds
|
|
17
|
+
MAX_OUTPUT_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KubectlError(Exception):
|
|
21
|
+
"""Raised when kubectl exits with a non-zero status."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _build_args(
|
|
25
|
+
args: Sequence[str],
|
|
26
|
+
context: str | None = None,
|
|
27
|
+
namespace: str | None = None,
|
|
28
|
+
all_namespaces: bool = False,
|
|
29
|
+
) -> list[str]:
|
|
30
|
+
prefix: list[str] = []
|
|
31
|
+
suffix: list[str] = []
|
|
32
|
+
if context:
|
|
33
|
+
prefix += ["--context", context]
|
|
34
|
+
if all_namespaces:
|
|
35
|
+
suffix += ["--all-namespaces"]
|
|
36
|
+
elif namespace:
|
|
37
|
+
prefix += ["--namespace", namespace]
|
|
38
|
+
return prefix + list(args) + suffix
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def kubectl(
|
|
42
|
+
args: Sequence[str],
|
|
43
|
+
*,
|
|
44
|
+
context: str | None = None,
|
|
45
|
+
namespace: str | None = None,
|
|
46
|
+
all_namespaces: bool = False,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Run kubectl and return stdout as a string."""
|
|
49
|
+
full_args = _build_args(args, context=context, namespace=namespace, all_namespaces=all_namespaces)
|
|
50
|
+
|
|
51
|
+
proc = await asyncio.create_subprocess_exec(
|
|
52
|
+
"kubectl",
|
|
53
|
+
*full_args,
|
|
54
|
+
stdout=asyncio.subprocess.PIPE,
|
|
55
|
+
stderr=asyncio.subprocess.PIPE,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=KUBECTL_TIMEOUT)
|
|
60
|
+
except asyncio.TimeoutError:
|
|
61
|
+
proc.kill()
|
|
62
|
+
raise KubectlError(f"kubectl timed out after {KUBECTL_TIMEOUT}s: kubectl {' '.join(full_args)}")
|
|
63
|
+
|
|
64
|
+
if len(stdout) > MAX_OUTPUT_BYTES:
|
|
65
|
+
stdout = stdout[:MAX_OUTPUT_BYTES] + b"\n[... output truncated at 10 MB ...]"
|
|
66
|
+
|
|
67
|
+
if proc.returncode != 0:
|
|
68
|
+
err = stderr.decode(errors="replace").strip()
|
|
69
|
+
raise KubectlError(err or f"kubectl exited with code {proc.returncode}")
|
|
70
|
+
|
|
71
|
+
return stdout.decode(errors="replace").strip()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def kubectl_json(
|
|
75
|
+
args: Sequence[str],
|
|
76
|
+
*,
|
|
77
|
+
context: str | None = None,
|
|
78
|
+
namespace: str | None = None,
|
|
79
|
+
all_namespaces: bool = False,
|
|
80
|
+
) -> dict | list:
|
|
81
|
+
"""Run kubectl with -o json and parse the result."""
|
|
82
|
+
output = await kubectl(
|
|
83
|
+
list(args) + ["-o", "json"],
|
|
84
|
+
context=context,
|
|
85
|
+
namespace=namespace,
|
|
86
|
+
all_namespaces=all_namespaces,
|
|
87
|
+
)
|
|
88
|
+
return json.loads(output)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def kubectl_stdin(
|
|
92
|
+
args: Sequence[str],
|
|
93
|
+
stdin_data: str,
|
|
94
|
+
*,
|
|
95
|
+
context: str | None = None,
|
|
96
|
+
namespace: str | None = None,
|
|
97
|
+
) -> str:
|
|
98
|
+
"""Run kubectl with data piped to stdin (e.g. apply -f -)."""
|
|
99
|
+
full_args = _build_args(args, context=context, namespace=namespace)
|
|
100
|
+
|
|
101
|
+
proc = await asyncio.create_subprocess_exec(
|
|
102
|
+
"kubectl",
|
|
103
|
+
*full_args,
|
|
104
|
+
stdin=asyncio.subprocess.PIPE,
|
|
105
|
+
stdout=asyncio.subprocess.PIPE,
|
|
106
|
+
stderr=asyncio.subprocess.PIPE,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
stdout, stderr = await asyncio.wait_for(
|
|
111
|
+
proc.communicate(input=stdin_data.encode()),
|
|
112
|
+
timeout=KUBECTL_TIMEOUT,
|
|
113
|
+
)
|
|
114
|
+
except asyncio.TimeoutError:
|
|
115
|
+
proc.kill()
|
|
116
|
+
raise KubectlError(f"kubectl timed out after {KUBECTL_TIMEOUT}s")
|
|
117
|
+
|
|
118
|
+
if proc.returncode != 0:
|
|
119
|
+
err = stderr.decode(errors="replace").strip()
|
|
120
|
+
raise KubectlError(err or f"kubectl exited with code {proc.returncode}")
|
|
121
|
+
|
|
122
|
+
out = stdout.decode(errors="replace").strip()
|
|
123
|
+
err_out = stderr.decode(errors="replace").strip()
|
|
124
|
+
# kubectl apply prints useful info to stderr on success too
|
|
125
|
+
if err_out:
|
|
126
|
+
return f"{out}\n{err_out}".strip()
|
|
127
|
+
return out
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Claude MCP Plugin — Kubernetes
|
|
3
|
+
|
|
4
|
+
Exposes 21 tools across three categories:
|
|
5
|
+
• Awareness (8) — cluster state, contexts, nodes, pods, services, events
|
|
6
|
+
• Diagnostics (6) — describe, logs, metrics, health scan, YAML export
|
|
7
|
+
• Remediation (7) — restart, scale, delete, rollback, apply, patch, node ops
|
|
8
|
+
|
|
9
|
+
Run with:
|
|
10
|
+
python -m src.server
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from mcp.server import Server
|
|
19
|
+
from mcp.server.stdio import stdio_server
|
|
20
|
+
from mcp.types import (
|
|
21
|
+
CallToolResult,
|
|
22
|
+
ListToolsResult,
|
|
23
|
+
TextContent,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from k8s_mcp.tools.awareness import AWARENESS_HANDLERS, AWARENESS_TOOLS
|
|
27
|
+
from k8s_mcp.tools.diagnostics import DIAGNOSTIC_HANDLERS, DIAGNOSTIC_TOOLS
|
|
28
|
+
from k8s_mcp.tools.remediation import REMEDIATION_HANDLERS, REMEDIATION_TOOLS
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Server setup
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
server = Server("kubernetes")
|
|
35
|
+
|
|
36
|
+
ALL_TOOLS = AWARENESS_TOOLS + DIAGNOSTIC_TOOLS + REMEDIATION_TOOLS
|
|
37
|
+
|
|
38
|
+
ALL_HANDLERS: dict = {
|
|
39
|
+
**AWARENESS_HANDLERS,
|
|
40
|
+
**DIAGNOSTIC_HANDLERS,
|
|
41
|
+
**REMEDIATION_HANDLERS,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@server.list_tools()
|
|
46
|
+
async def list_tools() -> ListToolsResult:
|
|
47
|
+
return ListToolsResult(tools=ALL_TOOLS)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@server.call_tool()
|
|
51
|
+
async def call_tool(name: str, arguments: dict) -> CallToolResult:
|
|
52
|
+
args = arguments
|
|
53
|
+
|
|
54
|
+
handler = ALL_HANDLERS.get(name)
|
|
55
|
+
if handler is None:
|
|
56
|
+
return CallToolResult(
|
|
57
|
+
content=[TextContent(type="text", text=f"Unknown tool: {name}")],
|
|
58
|
+
isError=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
content = await handler(args)
|
|
63
|
+
return CallToolResult(content=content)
|
|
64
|
+
except Exception as exc: # noqa: BLE001
|
|
65
|
+
return CallToolResult(
|
|
66
|
+
content=[TextContent(type="text", text=f"Unexpected error: {exc}")],
|
|
67
|
+
isError=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Entry point
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
async def _run() -> None:
|
|
76
|
+
print(
|
|
77
|
+
f"kubernetes MCP server starting — {len(ALL_TOOLS)} tools registered",
|
|
78
|
+
file=sys.stderr,
|
|
79
|
+
)
|
|
80
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
81
|
+
await server.run(
|
|
82
|
+
read_stream,
|
|
83
|
+
write_stream,
|
|
84
|
+
server.create_initialization_options(),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> None:
|
|
89
|
+
asyncio.run(_run())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
main()
|
|
File without changes
|