Skip to main content
Glama
README.md
# ops-toolbox

A small Model Context Protocol server that gives an assistant four things a
support engineer actually reaches for: tail a log, check whether an endpoint is
answering, search a runbook of past fixes, and write a new fix back into it.

No dependencies. It is plain Node, `node server.js` and it runs.

## What MCP is, briefly

Model Context Protocol is a standard way to hand an AI client a set of tools it
can call. The client starts your server as a subprocess and they speak JSON-RPC
2.0 over stdin and stdout, one message per line. The client asks `tools/list` to
find out what exists, then `tools/call` to run one and get the result back as
text.

That is the whole idea. There is no framework requirement, which is why this
server has no dependencies: the protocol is small enough to implement directly,
and doing it directly makes it obvious what is going on.

Two things matter when you write one:

- **stdout belongs to the protocol.** A stray `console.log` puts a non-JSON line
  in the stream and the client drops the connection. Every diagnostic in this
  server goes to stderr, which the client is free to capture or ignore.
- **A tool failing is not a protocol failure.** A missing file comes back as a
  normal result flagged `isError`, so the model can read the message and correct
  itself. Protocol errors are reserved for things like an unknown tool name or a
  missing required argument.

## The tools

| Tool | What it does |
| --- | --- |
| `tail_log` | Last N lines of a log file, with an optional case-insensitive filter. Confined to a configured log root. |
| `check_endpoint` | Requests a URL and reports status code and latency, so you can tell "running" from "actually answering". |
| `runbook_search` | Searches a local JSON runbook of symptoms and their fixes. |
| `runbook_add` | Records a new symptom and fix so the next person does not rediscover it. |

Two details worth pointing out because they are the kind of thing that bites you
in production:

- `tail_log` resolves the requested path and rejects anything that lands outside
  the configured root. A model will happily pass `../../../etc/shadow` if you let
  it.
- `tail_log` reads only the last 256 KB of a file rather than the whole thing. A
  tail of a rotated 4 GB log should not be an out-of-memory error.
- `runbook_add` writes to a temp file and renames it over the original. A rename
  on the same filesystem is atomic, so a crash halfway through cannot leave a
  truncated runbook behind.

## Requirements

Node 20 or newer. Nothing else.

## Install

```bash
git clone https://github.com/coreyhiggins/mcp-server-example.git
cd mcp-server-example
node selftest.js     # confirm it works before wiring it up
```

There is no `npm install` step because there is nothing to install.

## Configuration

Both paths come from the environment so the same server can point at sample data
during development and at real paths on a server.

| Variable | Default | Purpose |
| --- | --- | --- |
| `OPS_LOG_ROOT` | `./sample-logs` | Directory `tail_log` is allowed to read from. Set it to `/var/log` on a real host. |
| `OPS_RUNBOOK` | `./data/runbook.json` | JSON file the runbook tools read and write. |

## Wiring it into a client

### Claude Desktop

Edit the config file, then restart the app.

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "ops-toolbox": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-server-example/server.js"],
      "env": {
        "OPS_LOG_ROOT": "/var/log",
        "OPS_RUNBOOK": "/absolute/path/to/mcp-server-example/data/runbook.json"
      }
    }
  }
}
```

Use absolute paths. The client does not start the server in your shell's working
directory, so a relative path will not resolve the way you expect.

### Claude Code

```bash
claude mcp add ops-toolbox \
  --env OPS_LOG_ROOT=/var/log \
  --env OPS_RUNBOOK=/absolute/path/to/mcp-server-example/data/runbook.json \
  -- node /absolute/path/to/mcp-server-example/server.js
```

Then `claude mcp list` should show it connected.

## Driving it by hand

The server is just a program reading lines on stdin, so you can talk to it
without any client at all. This is the fastest way to debug one:

```bash
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25"}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"runbook_search","arguments":{"query":"nginx 502"}}}' \
  | node server.js
```

## Self-test

```bash
node selftest.js
```

20 assertions covering the tools, the JSON-RPC layer, and one real end-to-end
handshake against a spawned server over stdio. The HTTP checks hit a throwaway
server this script starts on `127.0.0.1`, so the test needs no network access.
Exits non-zero if anything fails.

## Layout

```
server.js       JSON-RPC plumbing, stdio loop, protocol version negotiation
tools.js        the four tools and their schemas
selftest.js     the one runnable check
data/           seed runbook
sample-logs/    a log file to point tail_log at while you try it
```

## How this is used in production

I run Ubuntu servers where the first ten minutes of any incident are the same
three questions: what does the log say, is the service actually answering, and
have we seen this before. Those questions are what these four tools are. Putting
them behind MCP means I can ask in plain language and get the answer without
switching to a terminal, and the runbook grows every time something breaks
instead of living in my head.

The security choices in here are not decoration. A tool that takes a file path
from a model is an untrusted input path, so it gets a root confinement check, a
read cap, and an atomic write. Same review I would give any script that runs on a
box with real users on it.

## License

MIT

TDQS

A3.9/5.0

Scored across 4 tools

Disambiguation5/5

Each tool targets a distinct operation: log tailing, endpoint checking, and runbook search/add. There is no overlap in purpose or resource type.

Naming Consistency4/5

Names are clear and readable, but they follow two patterns: verb_noun (tail_log, check_endpoint) and noun_verb (runbook_search, runbook_add). This minor inconsistency is not confusing but prevents a perfect score.

Tool Count5/5

With 4 tools, the set is well-scoped for an operations toolbox. Each tool serves a clear purpose without redundancy or bloat.

Completeness4/5

The set covers the primary operations for its domain: log inspection, endpoint validation, and runbook management (search/add). Minor gaps like runbook deletion exist, but the core workflow is complete.

Maintenance

ActivitySlowing
ResponsivenessNo issues