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
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues