harness-mcp
by giuseppecrj
README.md
# harness-mcp
An opinionated TypeScript boilerplate for building **MCP servers** with **harness engineering** in mind.
> Harness engineering is the discipline of designing the scaffolding around an LLM agent — tools, descriptions, errors, context — so the agent actually does the right thing. Most MCP boilerplates teach you the protocol. This one teaches you the protocol _and_ the practice.
## What's in the box
- **`defineTool()`** — one Zod schema feeds the MCP SDK, OpenAI's function-calling API, and the runtime handler. Validation and error wrapping are automatic.
- **Both transports** — stdio (`src/index.ts`) for Claude Code-style local clients, Streamable HTTP (`src/http.ts`) for remote/web clients. Both share one `createServer()`.
- **Structured `AgentError`s** — every error has a `code`, a `message`, and a `hint` written for the model: "call `items_list` first to find a valid id." Vague errors waste turns; this fixes that at the type level.
- **A real eval harness** — Vitest-based. Unit tests run free in CI; `tests/mcp/echo.test.ts` drives a real OpenAI model through the MCP server via an in-memory transport pair and asserts on the resulting tool-call trace.
- **A simple CRUD example** — `items_create / list / read / update / delete` plus an `echo` tool. Replace the in-memory store with your real backend; keep the shape.
## Quick start
```bash
bun install
bun test # unit tests, no API key needed
bun run start # stdio server on stdin/stdout
bun run start:http # HTTP server on http://localhost:3000/mcp
```
To run the model-in-the-loop evals:
```bash
cp .env.example .env
# add OPENAI_API_KEY
bun run test:mcp
```
## Wire into Claude Code
```json
{
"mcpServers": {
"harness-mcp": {
"command": "bun",
"args": ["run", "/absolute/path/to/harness/mcp/src/index.ts"]
}
}
}
```
## Layout
```
src/
index.ts stdio entry
http.ts streamable-http entry
core/
server.ts createServer() — shared by both transports
tool.ts defineTool() wrapper
errors.ts AgentError
store.ts replace with your backend
tools/
echo.ts smoke-test tool
items-*.ts CRUD example tools
index.ts registry
tests/
unit/ fast, no API key
tool.test.ts
store.test.ts
mcp/ protocol + model-in-the-loop
setup.ts in-memory client + runWithModel() helper
smoke.test.ts no model
echo.test.ts gpt-4o-mini, skipped without OPENAI_API_KEY
```
## The opinions
1. **Tool descriptions are prompt engineering.** Every description leads with `USE WHEN ...` and includes `DO NOT USE WHEN ...` for sibling tools the model could confuse this with. The smoke test enforces the convention.
2. **Errors teach.** Every `AgentError` carries a `hint` field. Read your error messages as if you were the agent — would you know what to do next? If not, rewrite.
3. **List endpoints paginate.** `items_list` returns `{ items, nextCursor }`. Default limit 20, hard cap 100. Don't dump unbounded data into the context.
4. **Destructive ops accept `dryRun`.** `items_delete` will tell you what _would_ happen if you weren't sure.
5. **One Zod, three consumers.** Don't maintain JSON Schema by hand alongside Zod — `defineTool` derives both.
6. **Evals are tests.** Tool-call traces are assertable. When a description regression breaks the model's behavior, your test catches it.
## License
MIT.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues