@mndpjm/harness-mcp
v0.6.1
Published
MCP server for MEA Harness v2 — bundles Python harness and exposes tools via Stdio
Readme
harness-mcp
MCP server for MEA Harness — enables AI agents to coordinate parallel work via Model Context Protocol.
What is this?
harness-mcp bundles a Python FastAPI service and exposes it as MCP tools. Multiple Kiro agent sessions can work in parallel from a shared pool of tasks — each agent claims the next available task without needing to know task IDs upfront.
Key features:
- Pool-based distribution — agents call
claim_task(pool_id), harness auto-selects the next eligible task - Synchronous Jev audit —
submit_taskblocks until Jev scores the work and returnsPASSorFAILwith feedback; no polling needed - Dependency graph — tasks with unmet deps are skipped until their prerequisites are done
- Atomic claim — per-task asyncio lock prevents double-claim under concurrent load
- Coordinator election —
claim_finalizeensures only one session proceeds to post-pool work (prove, review, capture) when multiple sessions work the same pool - Assignee tracking — each claimed task records which session is working on it
Requirements
- Node.js 18+
- Python 3.11+
Setup
Add to your mcp.json:
{
"mcpServers": {
"harness": {
"command": "npx",
"args": ["@mndpjm/harness-mcp"],
"env": {
"HARNESS_PORT": "18766",
"WORKSPACE_ROOT": "/path/to/your/workspace"
}
}
}
}On first run, harness-mcp will:
- Create a Python venv at
~/.harness-mcp/venv/and install dependencies (~30-60s first time) - Start the FastAPI server on
HARNESS_PORT(default18766) - Connect MCP Stdio transport
Subsequent runs reuse the cached venv and start in ~2-5s.
Agent Workflow
Single agent
1. create_session() → session_id
2. create_pool("my-plan") → pool_id
3. create_tasks(pool_id, []) → tasks with task_ids
Loop:
claim_task(pool_id, session_id) → {task, reason}
reason="all_done" → call claim_finalize (see below)
reason="waiting_for_deps" → wait and retry
reason="claimed" → implement task.module_name
submit_task(task.task_id, session_id, entry_file, code_diff)
→ verdict="PASS" → mark [x], loop back to claim
→ verdict="FAIL" → fix using feedback, retry_task(), loop back to claim
After all_done:
claim_finalize(pool_id, session_id) → {coordinator: true|false}
coordinator=true → this session proceeds to prove/review/capture
coordinator=false → this session exits (do NOT proceed to post-pool work)Multiple parallel agents (recommended pattern)
When several sessions work the same pool, claim_finalize prevents duplicate post-pool work:
Each session loops claim_task → implement → submit → claim_task
until reason="all_done"
Then ALL sessions concurrently call claim_finalize(pool_id, session_id):
First caller → coordinator=true → runs prove/review/capture
Other callers → coordinator=false → exit immediatelyImportant: Every session that receives
reason="all_done"MUST callclaim_finalize.
Sessions that receivecoordinator=falsemust stop — skipping this causes all sessions to run prove/capture simultaneously.
Available Tools
Pool & Session
| Tool | Description |
|------|-------------|
| create_session | Register agent session. Returns session_id. Call once per agent. |
| create_pool(name?) | Create a task pool. Returns pool_id. One pool per task plan. |
| get_pool(pool_id) | Pool status: pending/claimed/done counts, all_done flag, task list. |
Task Lifecycle
| Tool | Description |
|------|-------------|
| create_tasks(pool_id, tasks[]) | Batch-create tasks in a pool (atomic — all or none). Each task has module_name, quality_contract, optional dependencies. |
| claim_task(pool_id, session_id) | Claim the next eligible task from a pool. Returns {task, reason}. No task_id needed — harness auto-selects. |
| claim_finalize(pool_id, session_id) | Coordinator election. Call after reason="all_done". Returns {coordinator: bool}. First caller wins — coordinator=true proceeds to post-pool work; all others exit. |
| submit_task(task_id, session_id, entry_file, code_diff) | Submit completed work. Jev audits synchronously. Returns task with verdict: PASS\|FAIL and feedback. |
| retry_task(task_id, session_id) | Reset a rejected task back to pending for resubmission. Any valid session can retry (open retry). |
Monitoring
| Tool | Description |
|------|-------------|
| get_task(task_id) | Task details and current status. |
| get_state | All tasks and session count across the harness. |
| get_health | Server uptime and status. |
| get_failure_patterns | Aggregated failure patterns (available after ≥10 failures). |
ClaimResult
claim_task returns a ClaimResult object:
{
"task": { "task_id": "...", "module_name": "...", "assignee": "session-xyz", ... },
"reason": "claimed"
}When no task is available:
{ "task": null, "reason": "all_done" }
{ "task": null, "reason": "waiting_for_deps" }reason="all_done" means the pool is complete — call claim_finalize next.
FinalizeResult
claim_finalize returns:
{ "coordinator": true } // this session proceeds to prove/review/capture
{ "coordinator": false } // this session must exit — another session is coordinatorThe election is atomic — regardless of how many sessions call claim_finalize concurrently, exactly one receives coordinator=true.
submit_task Response
submit_task blocks until the Jev audit completes and returns the final verdict inline:
{
"task_id": "...",
"status": "done",
"verdict": { "verdict": "PASS", "score": 0.91, "confidence": 0.88 }
}On failure:
{
"status": "rejected",
"verdict": { "verdict": "FAIL", "next_action": "improve_quality", ... }
}HTTP 503 if Jev is unreachable — retry the submit.
Example: Four parallel agents
Agent A (coordinator) Agent B Agent C Agent D
─────────────────────────────────────────────────────────────────────────────
create_pool("feature") → pool_id
create_tasks(pool_id, 8 tasks)
create_session() ×4
─── All 4 agents loop claim → implement → submit ───────────────────────────
claim_task(...) claim_task(...) claim_task(...) claim_task(...)
→ task1 → task2 → task3 → task4
...implement... ...implement... ...implement... ...implement...
submit_task(task1, ...) submit_task(task2) submit_task(task3) submit_task(task4)
→ PASS → PASS → PASS → PASS
claim_task(...) claim_task(...) claim_task(...) claim_task(...)
→ task5 → task6 → task7 → task8
... ... ... ...
→ PASS → PASS → PASS → PASS
claim_task(...) claim_task(...) claim_task(...) claim_task(...)
→ all_done → all_done → all_done → all_done
─── All 4 call claim_finalize concurrently ─────────────────────────────────
claim_finalize(pool_id, A) claim_finalize(B) claim_finalize(C) claim_finalize(D)
→ coordinator=true → coordinator=false → coordinator=false → coordinator=false
EXIT EXIT EXIT
─── Only A continues ────────────────────────────────────────────────────────
prove()
review()
capture()Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| HARNESS_PORT | 18766 | Port for harness FastAPI server |
| WORKSPACE_ROOT | process.cwd() | Workspace root for artifact_path validation |
| HARNESS_STARTUP_TIMEOUT_MS | 15000 | Max ms to wait for harness to start |
| SCORING_BACKEND | none | Jev scoring: none (always-pass), jev, or laya |
| JEV_BASE_URL | https://api.typesafe.ai | Jev API endpoint |
| JEV_API_KEY | `` | Jev API key (required when SCORING_BACKEND=jev) |
What's new in v0.6.0
claim_finalize — coordinator election (Bug B fix)
When multiple sessions work the same pool, all of them previously proceeded to post-pool work (prove, review, capture) simultaneously after all_done. v0.6.0 adds claim_finalize to elect exactly one coordinator:
claim_finalize(pool_id, session_id) → { coordinator: bool }First session to call wins. All others receive coordinator=false and must exit.
Improved concurrent claim (Bug A fix)
Previously, when two sessions raced to claim eligible tasks, the losing session could receive waiting_for_deps even when other eligible tasks remained. v0.6.0 fixes this — the harness now tries all eligible tasks before returning waiting_for_deps.
Migrating from v0.5.x
Add claim_finalize call after reason="all_done":
if (result.reason === "all_done") {
- break; // exit and proceed to prove
+ const { coordinator } = await claim_finalize(pool_id, session_id);
+ if (!coordinator) return; // worker — exit completely
+ break; // coordinator — proceed to prove
}Migrating from v0.4.x
v0.5.0 replaced the task-id-based claim flow with pool-based distribution.
| v0.4.x | v0.5.0+ |
|--------|--------|
| create_task(module_name, ...) | create_tasks(pool_id, [{module_name, ...}]) |
| claim_task(task_id, session_id) | claim_task(pool_id, session_id) — no task_id |
| Poll get_task for verdict | submit_task returns verdict inline (synchronous) |
| complete_task(task_id) | Removed — PASS auto-completes |
License
MIT
