ocp-triage-mcp
by aasthapit
README.md
# ocp-triage-mcp
An MCP server that triages OpenShift alerts by orchestrating an **upstream OCP
MCP server** (the one exposing `oc get nodes`, `get namespaces`, `describe
pods`, etc.). This server is both an MCP *server* (to whoever is triaging) and
an MCP *client* (of the OCP MCP) — the consuming team never touches the
upstream server directly.
```
LLM / agent ──MCP──▶ ocp-triage-mcp ──MCP (Streamable HTTP)──▶ OCP MCP ──▶ cluster
│
└── runbooks/*.yaml (one file per alert code)
```
Each alert code maps to a **runbook**: a YAML-defined sequence of upstream tool
calls. Triage is deterministic — no LLM inside this server — so evidence
collection is repeatable, auditable, and cheap. The LLM sitting above it
interprets the evidence bundle.
## Tools exposed
| Tool | Purpose |
|---|---|
| `list_runbooks` | Alert codes supported, required/optional inputs, steps |
| `triage_alert(alert_code, params)` | Run the full runbook, return the evidence bundle |
| `run_step(alert_code, step_id, params)` | Re-run one step of a runbook |
| `validate_runbooks` | Check all runbooks against the live upstream tool list |
The evidence bundle reports per-step status (`ok` / `error` / `skipped` /
`aborted`) so partial failures are visible, never silent.
### Passthrough discovery tools
Callers usually need to *find* the runbook inputs first — which clusters,
namespaces, and pods exist. Set `TRIAGE_PASSTHROUGH_TOOLS` to a
comma-separated allowlist of upstream tool names (fnmatch patterns allowed):
```
TRIAGE_PASSTHROUGH_TOOLS=get_clusters,get_namespaces,get_pods,list_*
```
Matching upstream tools are re-exposed on this server verbatim — same name,
same input schema, same description — and calls are forwarded to the OCP MCP.
Nothing is passed through by default; the surface stays curated. The tool list
is fetched from the upstream lazily and cached; `validate_runbooks` refreshes
it and reports which names currently match.
## Setup
> Full guide — install, verification, hosting for another team, container
> deployment, troubleshooting: **[docs/setup.md](docs/setup.md)**
Quick start:
```bash
pip install -e .
```
Configuration is via environment variables:
| Variable | Meaning | Default |
|---|---|---|
| `OCP_MCP_URL` | Upstream OCP MCP Streamable HTTP endpoint, e.g. `https://host/mcp` | *(required)* |
| `OCP_MCP_HEADERS` | Extra upstream headers, `;;`-separated: `Authorization: Bearer x;;X-Y: z` | none |
| `TRIAGE_PASSTHROUGH_TOOLS` | Upstream tools to re-expose here (comma-separated, fnmatch patterns) | none |
| `TRIAGE_RUNBOOKS_DIR` | Directory of runbook YAMLs | `./runbooks` |
| `TRIAGE_MCP_TRANSPORT` | This server's transport: `stdio`, `streamable-http`, `sse` | `stdio` |
| `TRIAGE_HTTP_HOST` / `TRIAGE_HTTP_PORT` | Listen address for the HTTP transports | `127.0.0.1` / `8000` |
Variables can also live in a `.env` file next to the server (copy
[.env.example](.env.example)); real environment variables override it.
Run it:
```bash
ocp-triage-mcp
```
Registering in Claude Code (stdio):
```json
{
"mcpServers": {
"ocp-triage": {
"command": "ocp-triage-mcp",
"env": {
"OCP_MCP_URL": "https://ocp-mcp.example.com/mcp",
"OCP_MCP_HEADERS": "Authorization: Bearer <token>",
"TRIAGE_RUNBOOKS_DIR": "C:/GIT/mcp-runbook/runbooks"
}
}
}
}
```
To serve it to another team over HTTP instead, set
`TRIAGE_MCP_TRANSPORT=streamable-http` and deploy it like any web service.
## Writing runbooks
One YAML file per alert code in `runbooks/`:
```yaml
alert: KubePodCrashLooping # the alert code callers pass to triage_alert
description: What this runbook collects and why.
inputs:
required: [namespace, pod] # must be present in params
optional: [cluster]
steps:
- id: describe_pod # unique id; defaults to the tool name
tool: describe_pod # tool name ON THE UPSTREAM OCP MCP
args:
namespace: "{{namespace}}" # template from params...
pod: "{{pod}}"
- id: node_status
tool: describe_node
when: "{{describe_pod.spec.nodeName}}" # skip unless resolvable & truthy
continue_on_error: true # don't abort the runbook on failure
args:
node: "{{describe_pod.spec.nodeName}}" # ...or from earlier step results
```
Templating rules:
- `{{name}}` resolves from `params` first, then from earlier step results by
step id.
- Dotted paths (`{{describe_pod.spec.nodeName}}`) walk into a step's result —
this requires the upstream tool to return JSON (structured content or a JSON
text block). Plain-text output is kept verbatim and can't be path-referenced.
- A string that is exactly one template keeps the referenced value's type
(numbers, booleans, objects); mixed strings are substituted as text.
- Steps run sequentially. A step failure aborts the rest of the runbook unless
the failing step has `continue_on_error: true`.
**The example runbooks use placeholder tool names.** After pointing
`OCP_MCP_URL` at your real server, call `validate_runbooks` — it lists the
upstream's actual tools and flags every runbook step that references a tool
the upstream doesn't expose.
## Design notes
- **Fresh upstream connection per call.** Each `triage_alert` opens its own
Streamable HTTP session to the upstream and closes it when done. Remote
sessions get dropped by idle timeouts/proxies; reconnecting per run makes
every triage self-contained at negligible handshake cost.
- **Runbooks are re-read from disk on every call**, so editing a YAML takes
effect without restarting the server. If load cost ever matters, add mtime
caching in `server._load`.
- **No LLM inside.** If a runbook someday needs in-flight reasoning, first try
extending `when:` conditions; embedding an agent is the last resort.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues