Skip to main content
Glama
Madiocre

devenv-doctor-mcp

by Madiocre
README.md
# devenv-doctor-mcp

An MCP server that gives an LLM client (Claude, or any MCP-compatible host)
the ability to actually inspect your local dev environment — Docker
container health, pnpm workspace integrity, and stuck/hung processes —
instead of you copy-pasting terminal output back and forth.

Built for the **MCP to the Moon** hackathon.

## Why this exists

While building this, I hit a real pnpm bug: `pnpm self-update` corrupted my
global environment lockfile, and every single `pnpm` invocation started
crashing with `Cannot use 'in' operator to search for 'integrity' in
undefined`. Diagnosing it meant manually hunting through
`~/.local/share/pnpm`, checking process states, and re-running commands with
debug flags — exactly the kind of repetitive, mechanical diagnosis an LLM
with the right tools could do directly. This project is that toolset.

## What it does

Four tools, exposed over MCP:

### `ping`
Sanity check — confirms the server is alive and the connection works.

### `check_docker_status`
Given a container name (and optionally an expected host port), reports:
- whether the container exists at all
- its current state (`running`, `exited`, `created`, etc.)
- its healthcheck status, if the container defines one
- every port mapping it actually has configured
- if you passed an `expectedPort`: whether it's genuinely bound on the host
  right now — and if not, *why* (container isn't running vs. the mapping
  just isn't there)

### `check_pnpm_workspace`
Given a monorepo root path, validates:
- `pnpm-workspace.yaml` exists and parses (via a real YAML parser, not a
  naive line-splitter — handles comments, multi-key files, catalogs, etc.)
- every `package.json` found in the tree is covered by a workspace glob
  pattern (catches "added a new package folder, forgot to register it")
- every glob pattern in the workspace file actually matches something
  (catches typos and stale entries from deleted/renamed folders)
- the root `package.json` pins a `packageManager` version — this one is
  personal: the pnpm bug above happened because nothing pinned the version
  and it silently self-updated into a broken state
- whether `pnpm-lock.yaml` looks stale relative to the newest `package.json`
  in the workspace

### `diagnose_stuck_process`
Given a port number, finds whatever process is bound to it and reports its
PID, command name, and OS-level state — flagging `D`/`T`/`Z` states
(uninterruptible sleep / stopped / zombie) as potentially hung.

## Setup

```bash
npm install
npm run build
```

## Test without any client (MCP Inspector)

The Inspector is a local web UI that talks to your server directly — no
Claude Desktop or other host application required:

```bash
npx @modelcontextprotocol/inspector node build/index.js
```

Click **Connect**, then **List Tools**, then run any tool with real
arguments and see the raw request/response JSON.

Note: if you're on pnpm, `pnpm dlx @modelcontextprotocol/inspector` may fail
with `Cannot find package 'commander'` — that's a known phantom-dependency
bug in the Inspector package itself (works fine under `npx`, since npm's
flat `node_modules` papers over it; pnpm's strict isolation correctly
refuses to).

## Test via raw JSON-RPC (no UI)

```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"1.0.0"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"ping","arguments":{}}}' | node build/index.js
```

## Connect to an MCP host (e.g. Claude Desktop)

Add to your host's MCP config (for Claude Desktop:
`~/.config/Claude/claude_desktop_config.json` on Linux):

```json
{
  "mcpServers": {
    "devenv-doctor": {
      "command": "node",
      "args": ["/absolute/path/to/devenv-doctor-mcp/build/index.js"]
    }
  }
}
```

Fully restart the host app afterward.

## Deployment

This server supports two transports:

- **`stdio`** (default) — for local use, spawned as a subprocess by a host
  app (MCP Inspector, Claude Desktop, NitroStack Studio pointed at the
  project folder). This is what all the testing instructions above use.
- **Streamable HTTP** — for hosted/cloud deployment (e.g. NitroCloud),
  automatically enabled when a `PORT` environment variable is set:

```bash
PORT=3000 node build/index.js
# MCP endpoint: POST http://localhost:3000/mcp
```

Runs in stateless mode (no session tracking) since every tool call here is
independent — nothing needs to persist between requests.

## Known limitations


Being upfront about these rather than letting them surprise you:

- **Glob matching doesn't support negation patterns** (`!**/test/**`), which
  some `pnpm-workspace.yaml` files use to exclude folders. A workspace using
  negation may get spurious "dead pattern" warnings.
- **Lockfile staleness is a mtime heuristic**, and `git clone` resets file
  mtimes to roughly the same moment — so this check can false-positive
  immediately after a fresh clone, even on a perfectly healthy workspace.
- **`ps` flags used in `diagnose_stuck_process` are GNU/Linux-specific**
  (`--no-headers`); this tool hasn't been tested on macOS/BSD `ps`.
- **Stuck-process detection is a single snapshot**, not a trend — a process
  briefly in uninterruptible-sleep (`D`) state doing normal disk I/O can be
  flagged even though it's healthy. A more robust version would sample
  twice with a delay before flagging.

None of these are silent — each one is either surfaced as a message from
the tool itself, or documented here.

## Built with

- [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) `v1.29.0` (stable — the v2 rewrite wasn't released yet at time of building)
- TypeScript, Zod for input validation, `yaml` for real YAML parsing
- Node's `child_process.execFile` throughout (never `exec`) — every shell
  call uses array arguments, not interpolated strings, so user-supplied
  input (container names, paths) can't be used for shell injection

## License

MIT