Harness MCP

Harness MCP

An MCP server that owns the harness: your project's constitution, structure, design rules, requirements and phased tasks. The agent reads it, writes code from it, and may only propose changes to it.

Category
访问服务器

README

Harness MCP

A durable project specification that a coding agent implements from — and cannot quietly rewrite.

An MCP server that owns the harness: your project's constitution, structure, design rules, requirements and phased tasks. The agent reads it, writes code from it, and may only propose changes to it. Nothing enters the harness until a human accepts a diff.

Zero native dependencies · one runtime package · 20 tools · MIT


The problem

You write a spec, the agent reads it, and forty turns later the code and the spec have quietly parted ways. Not because anyone lied — because every reasonable local decision ("this component needs a different radius", "let me put the API layer here") is invisible until it accumulates. Then the spec is a historical document and the only source of truth is whatever the code happens to say.

Regenerating the spec from the code does not fix this. It ratifies the drift: the spec becomes a mirror, and a mirror cannot disagree with you.

The principle

The harness is the source of truth, not a mirror of the code.

  • It is assembled once — from your description for a new project (harness_init), or reverse-engineered from the code for an existing one (harness_reverse).
  • After that it is edited, and every edit passes through human approval. An agent can only propose: the change lands in pending_changes with a diff, and is applied when a person accepts it.
  • The agent writes code from the harness. A design or structure decision goes into the harness — where it becomes a permanent, project-wide rule — never straight into the code.
  • Drift is therefore structurally impossible: the only route to a structural change is an approved harness change. harness_verify exists as a safety net, on demand, and never redraws the harness from code.
  new project                    existing project
  idea, in words                 code on disk
        │                              │
   harness_init                 harness_reverse
        └──────────────┬───────────────┘
                       ▼
        CONSTITUTION · STRUCTURE · DESIGN · SPEC · tasks/
                       │
        the agent implements FROM this
                       │
   "make the buttons green"  ──►  harness_chat
                       │              │
                       │       pending change + diff
                       │              │
                       │       human approves ──► permanent project-wide rule
                       │
        harness_verify  ──►  where the code and the harness disagree

Quick start

Requires Node 18+.

git clone https://github.com/mykolariabokon/harness-mcp.git
cd harness-mcp
npm install
npm run build

Register it with any MCP client:

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

Then, in your editor's chat:

  1. harness_configure with model: { mode: "native" } — creates /harness and tells the server to borrow your editor's own model (details below).
  2. harness_reverse for an existing codebase, or harness_init with a description for a new one.
  3. harness_render — look at what it understood, and correct it in words.

From then on, ask the agent to call harness_get_spec before it writes code.

The /harness folder

Created at the project root on first use:

harness/
├── harness.json      # state: entries, pending changes, design rules, approvals, checkpoints
├── config.json       # model + render settings
├── CONSTITUTION.md   # ┐
├── STRUCTURE.md      # │ committable markdown spec — a projection of the state,
├── DESIGN.md         # │ rewritten after every approved change
├── SPEC.md           # │
└── tasks/phase-N.md  # ┘

The markdown is meant to be committed and reviewed in a pull request — a structural change shows up as a spec diff next to the code diff. The state file, cache and pending changes are local working state, git-ignored by default.

The schema is versioned (schema_version + append-only migrations): a newer build opens an older /harness, and an older build refuses a newer one with a clear message instead of corrupting it.

Two modes, one logic

The harness needs a model of its own to turn "make the buttons green" into structured harness edits. It gets one of two ways, and everything downstream consumes the same structured result:

Mode When How
native your editor already runs an agent the harness returns a generation request (status: "needs_agent" with instructions and a JSON Schema); the editor's agent fulfils it with the model it is already running and calls harness_submit_generation. No API key.
universal a bare MCP client, or autonomous use the harness calls its own model from config.json (OpenRouter or Anthropic). One extra setup step, identical behaviour.

The same split applies to the visualization: one generator, two deliveries — the HTML is returned for a webview panel where the host has one, or served on 127.0.0.1 and opened in a browser where it does not.

The picture is an output. There is deliberately no direct manipulation: you criticise it in words through harness_chat, which turns the critique into proposed harness changes. You cannot drag a box into a different specification.

Design system

Without tokens the mockup renders as a grey skeleton — deliberately, so it communicates layout intent and nothing more. Feed it design tokens and the same layout tree is painted in your project's own visual language.

MCP servers do not call each other, so there are two ways in — both landing in the same normalized token set:

Path When How
host your editor already has a design-system MCP connected the agent passes the token payload to harness_set_design_tokens
direct nothing wires the two together set design_mcp.command in config.json; harness_sync_design_system connects to it as an MCP client itself

Design-system rules can come along too. They are proposals, not facts — they queue for approval like everything else. Rules that can be checked mechanically (hardcoded hex values, off-token shadows) arrive with a check attached, so harness_verify enforces them against the code.

Built against Design MCP's token shape (a Chakra-oriented design system); any source that can fill the normalized set works.

Tools

Tool Purpose
harness_hello Handshake — the editor announces agent_model / webview
harness_init Create /harness, assemble from a description (new project)
harness_reverse Assemble from existing code; code wins over stale docs, guesses are [assumption] + a question
harness_submit_generation Native-mode callback carrying the agent's structured result
harness_get_spec Read the harness — the agent implements from this
harness_chat "make the buttons green" → proposed harness changes
harness_propose_structure Generate or extend the project structure
harness_propose_change One precise proposal, no model involved
harness_add_design_rule A rule that applies globally (optionally with a machine check)
harness_set_design_tokens Design tokens handed in by the host
harness_sync_design_system The harness pulls tokens and rules itself
harness_list_pending Pending changes + the unapproved-count badge
harness_approve / harness_reject The human decision — the only thing that mutates the harness
summarize_session_to_harness Structured session summary → per-item proposals
harness_render The visualization (webview HTML or browser)
harness_verify On-demand code ↔ harness divergence report
harness_configure Read or update config.json
harness_checkpoint Create, list or restore rollback points

Session summary contract

summarize_session_to_harness demands structure, not prose:

{ "completed_tasks": [], "decisions": [], "open_questions": [], "touched_files": [] }

Each decision and each open question becomes its own pending item, so a human approves the session point by point instead of accepting a blob of text. That is the difference between "the agent wrote something down" and a specification.

Storage: a JSON file, not SQLite

This server is meant to ship inside an editor, so it must have zero native dependencies — a native module has to be rebuilt for every Electron ABI on every platform, and that debt never stops accruing. The data is dozens of records per project, so a document is the right size of tool.

What src/db/store.ts provides explicitly, since a file does not give it for free:

  • Atomic writes — temp file in the same directory, fsync, then rename. An interrupted write leaves the previous state standing. Approvals and checkpoints are not something a person should be able to lose to a crash.
  • Transactions — a mutation is applied to a copy, persisted, and only then adopted in memory. If the write fails, neither disk nor memory moved.
  • Concurrency — the in-memory document is authoritative for the process, and before every mutation the file's mtime/size are checked; if another process wrote in the meantime, the document is re-read and the mutation applied on fresh state. The residual race (two processes renaming within the same microseconds) is accepted rather than papered over with a lock file: contention here is human-paced, and a stale lock from a killed editor is the worse failure.
  • Loud refusal — an empty, truncated or non-JSON state file raises a specific error instead of quietly reading as "no harness yet".

Status and limitations

Early but real. Honest about where it stands:

  • Works today: the full loop — assemble, propose, approve/reject, render, verify, checkpoint/restore — under both model modes and both render modes, covered by 30 tests.
  • Not yet battle-tested. It has been exercised against real projects and a real design-system MCP, but it has not lived through months of daily use. Expect rough edges in the assembly prompts before you expect them in the storage.
  • The editor integration is not written yet. It speaks plain MCP, so any client can drive it, but the webview panel and the automatic harness_hello handshake for Peregrine are still to come. Until then a host announces itself by calling harness_hello, or you pin the mode in config.json.
  • Token mapping assumes a palette shape (neutral.0/50/200/500/800, brand.500). A design system with different scale names falls back to neutral defaults — it will not break, but it will not pick up your brand either.
  • harness_verify is structural, not semantic. It checks declared paths, unaccounted top-level areas, regex-checkable design rules and steps with no verification command. It does not read your code's meaning.

Development

npm run build     # tsc → build/
npm test          # vitest — lifecycle (assemble → propose → approve → verify → restore)
                  #          + store durability (torn write, corrupt file, migration)

"type": "module" — relative imports need the .js extension. tsc --noEmit does not catch a missing one; only running does.

The server must keep starting under an editor's runtime, which is Electron as Node — the environment a native module dies in:

ELECTRON_RUN_AS_NODE=1 "<path to>/Your Editor.exe" build/index.js

Issues and pull requests are welcome. If you change the storage layer, the durability tests in tests/store.test.ts are the contract — they exist because a JSON file has to earn the guarantees SQLite handed over for free.

Built with AI

This project was written by Claude (Opus 4.8) in Claude Code, working from specifications and review by @mykolariabokon — who set the direction, made the architectural calls, rejected what did not fit, and verified the result.

Saying so plainly matters more than the badge. What it means in practice:

  • The tests are real and they run. Every claim in this README about behaviour is backed by a test or by a command that was actually executed — including the Electron-runtime check, which exists precisely because "it should work" was not good enough.
  • A test caught a real bug during development (a migration that applied in memory but never persisted, because the migration function mutated its input). That is the point of writing them rather than asserting quality.
  • Read the code before you trust it. That advice holds for any dependency; it holds here too. It is a small codebase — about 3,800 lines of TypeScript plus 400 of tests — and the comments explain why, not what, so it is meant to be read.

There is a pleasing symmetry in a tool that exists to keep AI agents honest about specifications having been built by one, under review, from a specification.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选