safe-runbook-mcp
# safe-runbook-mcp
A policy-gated MCP server for operational runbooks. It lets an AI inspect and run known commands without giving it an unrestricted shell.
## Why it exists
AI agents are useful for operations, but handing one a terminal is a large trust decision. This project keeps the useful part—repeatable diagnostics and maintenance—inside a small, reviewable boundary:
`AI client → inspect plan → policy checks → optional human approval → exact command`
## Safety model
- Runbooks are version-controlled JSON; the AI cannot invent a command.
- Execution is off by default.
- Executables must be explicitly allowlisted.
- Variables are regex-validated and become complete process arguments.
- Commands run with `shell: false` inside a realpath-confined workspace.
- Mutating and destructive plans require a short-lived HMAC approval token created outside MCP.
- The token is bound to the runbook and exact plan hash, so changed inputs invalidate it.
- Processes have time and output limits; declared secrets are redacted.
MCP tool annotations are also provided for clients, while server-side checks remain the authority.
## Stack
TypeScript 7, Node.js 22, MCP TypeScript SDK v2, Zod 4, Vitest 4, Biome 2, Docker, and GitHub Actions. The project is open source and has no paid API dependency.
## Quick start
```bash
npm install
npm run cli -- list
npm run cli -- plan docker-service-status --var service=api
npm test
```
Execution must be enabled explicitly:
```bash
RUNBOOK_EXECUTION_ENABLED=true npm run cli -- run disk-usage
```
For a mutating runbook, generate approval outside the MCP connection and use the same variables for approval and execution:
```bash
export RUNBOOK_EXECUTION_ENABLED=true
export RUNBOOK_APPROVAL_SECRET='replace-with-a-long-random-secret'
TOKEN=$(npm run --silent cli -- approve restart-compose-service --var service=api)
npm run cli -- run restart-compose-service --var service=api --approval "$TOKEN"
```
## Connect an MCP client
Build once, then add this stdio server to an MCP-compatible client. Replace the paths with absolute paths on your machine.
```json
{
"mcpServers": {
"safe-runbooks": {
"command": "node",
"args": ["/absolute/path/safe-runbook-mcp/dist/server.js"],
"env": {
"RUNBOOK_DIRECTORY": "/absolute/path/safe-runbook-mcp/runbooks",
"RUNBOOK_WORKSPACE": "/workspace/to/manage",
"RUNBOOK_EXECUTION_ENABLED": "false"
}
}
}
}
```
The server exposes:
- `list_runbooks` — discover available runbooks and risk levels.
- `inspect_runbook` — resolve variables and return the exact plan plus its hash.
- `execute_runbook` — execute the already-defined plan after policy checks.
- `runbook://catalog` — read-only catalog resource.
Logs go to stderr because stdout is reserved for MCP JSON-RPC traffic.
## Add a runbook
Create a JSON file in `runbooks/`:
```json
{
"id": "service-status",
"title": "Inspect a service",
"description": "Read one Compose service state.",
"risk": "diagnostic",
"variables": {
"service": {
"description": "Compose service name",
"pattern": "[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}",
"required": true
}
},
"steps": [
{
"id": "status",
"title": "Read status",
"executable": "docker",
"args": ["compose", "ps", "{{service}}"]
}
]
}
```
Choose `diagnostic`, `mutating`, or `destructive`. Variables must occupy a complete argument such as `"{{service}}"`; string interpolation is intentionally rejected.
## Docker
```bash
docker build -t safe-runbook-mcp .
docker run --rm -i \
-v "$PWD:/workspace:ro" \
-e RUNBOOK_WORKSPACE=/workspace \
-e RUNBOOK_DIRECTORY=/app/runbooks \
safe-runbook-mcp
```
Keep execution disabled for a read-only mount. If a runbook needs Docker, mount only the required socket or remote context after reviewing that trust boundary.
## Development
```bash
npm run check
npm run typecheck
npm test
npm run build
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for the branch workflow and [AGENTS.md](AGENTS.md) for repository rules used by coding agents.
## References
- [MCP TypeScript SDK packages](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/get-started/packages.md)
- [Build an MCP server](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md)
- [Serve over stdio](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/serving/stdio.md)
## License
MIT
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: inspect validates and previews, execute runs commands, and list enumerates available runbooks. There is no overlap or ambiguity between them.
All three tool names follow a consistent verb_noun pattern: inspect, execute, and list all pair with 'runbook'. The naming is uniform and predictable.
With only three tools, the server is lean but covers the essential operations for runbook management: discover, preview, and execute. It is slightly minimal but appropriate for a focused purpose.
The server provides the core lifecycle for runbooks: list, inspect, and execute. Missing are update/delete operations, but since runbooks are version-controlled externally, those may be intentionally out of scope. No critical gaps for safe execution.