mcp-dev-tools
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
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues