Skip to main content
Glama
README.md
# mcp-dev-tools

An MCP (Model Context Protocol) server I built to expose everyday dev commands — git status, recent commits, running tests, running the linter — as tools an AI coding assistant (Claude Code, Claude Desktop, or any other MCP-compatible client) can call directly against a real repository.

## Why I built this

I wanted to actually understand how MCP servers work end to end — the handshake, the transport, tool schemas — rather than just wiring one up from a template. So this was built from an empty folder, one piece at a time, verifying each step before adding the next.

## How I built it

1. **Scaffolded the project** — `npm init`, TypeScript config for Node ESM (`NodeNext` module/resolution), installed `@modelcontextprotocol/sdk` and `zod`.
2. **Wrote the smallest possible server** — an `McpServer` instance, a single dummy `ping` tool, connected over a stdio transport.
3. **Verified it with the MCP Inspector** before writing anything real — spawned the server as a subprocess, confirmed the `initialize` handshake completed and `tools/list`/`tools/call` worked, using the raw protocol traffic rather than assuming it worked.
4. **Added the real tools one at a time** (`git_status`, `recent_commits`, `run_tests`, `run_linter`), re-testing after each addition rather than batching changes.
5. **Made the target repo configurable** — moved from hardcoding `process.cwd()` to reading a CLI argument, so the server can run against any project, not just itself.
6. **Hit and fixed a real platform bug** along the way — see below.

## A real bug this surfaced

`run_tests` and `run_linter` initially failed on Windows with a cryptic `spawn EINVAL`. Cause: Node deliberately blocks `execFile`/`spawn` from directly invoking `.cmd`/`.bat` files (like `npm.cmd`) without `shell: true` — a security fix (CVE-2024-27980) to stop unsafe argument injection into batch files. The `git` calls were unaffected since `git.exe` is a real executable, not a shell shim. Fixed by scoping `shell: true` to just the two `npm` calls, where the arguments are fixed literals rather than dynamic input, so the shell-parsing risk the restriction exists to prevent doesn't actually apply here.

## Tools

| Tool | Description | Arguments |
|---|---|---|
| `ping` | Echoes a message back; used to verify the server is wired up | `message: string` |
| `git_status` | Working tree status (staged / unstaged / untracked) of the target repo | — |
| `recent_commits` | Recent commit history on the current branch | `count?: number` (default 10, max 50) |
| `run_tests` | Runs `npm test` in the target repo and returns output, including failures | — |
| `run_linter` | Runs `npm run lint` in the target repo and returns output, including lint errors | — |

## How it works

- **Transport:** stdio — the client spawns this process and communicates over stdin/stdout using JSON-RPC 2.0. stdout is reserved exclusively for protocol traffic; all logging goes to stderr (`console.error`), otherwise it would corrupt the stream.
- **Tool execution:** each tool shells out to a real command (`git`, `npm`) via Node's `child_process.execFile`, using an argument array rather than a single shell string — this avoids shell injection entirely for the git-based tools.
- **Error handling:** a failing command (e.g. failing tests, a missing lint script) is not treated as a protocol error. It's caught and returned as a normal tool result with `isError: true` and the real command output attached, so the calling model can see *what* went wrong rather than getting an opaque failure.
- **Target repo:** a single `REPO_DIR` constant, resolved once at startup from a CLI argument (falling back to the current working directory), is threaded through every tool — so the server can be pointed at any project without code changes.

## How to use it yourself

**Prerequisites:** Node.js 20+ and npm.

```bash
git clone <this-repo-url>
cd mcp-dev-tools
npm install
```

Run directly against the source with `tsx` (no build step needed):

```bash
npm run dev -- C:\path\to\some\other\repo
```

Or compile and run the built output:

```bash
npm run build
npm start -- C:\path\to\some\other\repo
```

If no path is given, it defaults to the directory the server is launched from.

### Wiring it into an MCP client (Claude Code / Claude Desktop)

Add an entry to the client's MCP server config, pointing `args` at the built server and the repo you want it to operate on:

```json
{
  "mcpServers": {
    "dev-tools": {
      "command": "node",
      "args": [
        "C:\\path\\to\\mcp-dev-tools\\build\\index.js",
        "C:\\path\\to\\the\\project\\you\\want\\to\\work\\on"
      ]
    }
  }
}
```

### Debugging with the MCP Inspector

```bash
npx @modelcontextprotocol/inspector npx tsx src/index.ts C:\path\to\some\other\repo
```

Opens a browser UI showing the live tool list and lets you call each tool manually while watching the raw JSON-RPC exchange.

## Tech stack

- TypeScript, compiled with `tsc` to ES2022/NodeNext modules
- [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) — official MCP server implementation
- [`zod`](https://www.npmjs.com/package/zod) — runtime validation for tool input schemas
- `tsx` for zero-build local development

## Project structure

```
mcp-dev-tools/
├── src/
│   └── index.ts      # server setup + all tool definitions
├── tsconfig.json
├── package.json
└── README.md
```

## License

ISC