harness-dispatch
Delegate coding tasks to the agent CLIs you already pay for — a local MCP server routing each task to the best-fit harness (Claude Code, Codex, Cursor, Antigravity, or any endpoint), billing- and safety-aware.
Links
README
From the repo.
harness-dispatch
Route whole coding tasks — not API requests — to the agent CLIs you already pay for.
npm install -g harness-dispatch
harness-dispatch configure --yes
harness-dispatch doctor
Before running that: what it does on your machine.
Each harness keeps its own scaffolding, test loop, and codebase index. There is no proxy in between and nothing is re-implemented: Claude Code stays Claude Code. One orchestrating agent picks the right one per task and spends your flat-rate subscription quota before anything metered.
It is a local MCP server, so the harnesses on your machine — Claude Code, Codex, Cursor Agent, Antigravity CLI, plus any local or remote OpenAI-compatible endpoint — become tools any AI can call.
Six tools: dispatch starts routed work, job_status checks or lists it,
cancel_job stops one, retry_job runs a finished one again, workspace
inspects or keeps an isolated run's changes, and usage reads route and quota
state.
Documentation
| Configuration | Adding a harness, endpoint modes, what configure writes |
| MCP and HTTP surfaces | The six tools, the REST endpoints, chaining delegated work |
| Status and observability | The status model, quota, and what leaves your machine |
| CHANGELOG | What changed, and what each fix missed |
What it looks like
Your agent calls one tool:
{
"prompt": "Port the retry logic in src/net/ to the new backoff helper, then run the tests.",
"workingDir": "/path/to/project",
"hints": { "taskType": "execute" }
}
Quick tasks come straight back:
{
"mode": "single",
"completed": true,
"success": true,
"route": "codex_cli",
"model": "gpt-5.6-terra",
"output": "Ported 4 call sites to withBackoff(); 118 tests pass.",
"durationMs": 47210,
"routing": { "tier": 1, "taskType": "execute", "reason": "tier 1 best (3 available)" }
}
Slow ones hand back a jobId after 25 seconds instead, and keep running:
{ "mode": "single", "completed": false, "jobId": "job-1786977316001-b49d1232" }
Carry on working, then call job_status with that id for a live output tail or the
finished result. The run lives in a detached process, so nothing is lost to a client
timeout — or to the server itself restarting mid-run.
What it does on your machine
Stated plainly, up front, rather than left to be inferred:
- It spawns the CLIs above as subprocesses with your prompts.
- Those CLIs read and write files under the
workingDiryou pass (that's the point of the tool) and run shell commands, depending on the workspace and safety policy in effect. - At most 4 agent CLIs run at once; extra dispatches queue and start as slots
free. Tune with
max_concurrent_runs. serveadditionally binds a local HTTP port: loopback only by default, bearer-token gated. Read the HTTP surface docs before pointing--hostanywhere else.
None of this is unusual for a coding-agent tool. It's here in one place so you can decide before installing rather than after.
Install
Needs Node.js >=22.22.2 (so current LTS works) and at least one harness or endpoint.
git is optional but recommended: dispatch works without it, but the
workspace tool shells out to git to diff and apply an isolated run's changes,
and the git_worktree isolation policy needs it. doctor reports whether it
found one.
doctor checks your install, config, auth and routes without contacting any
provider. Add --live when you want it to prove a dispatch really works end to
end — that one sends a real request through an eligible route and spends
whatever quota that route bills against, so it is a deliberate step rather than
part of setup. configure is optional too: the tool auto-detects installed
harnesses and runs without a config.yaml at all. Write one when you want to
pin routes, add an endpoint, or change a default.
configure --yes detects installed harnesses, writes config.yaml into the
tool's own state directory (~/.harness-dispatch/, or HARNESS_DISPATCH_STATE_DIR)
— unless a config.yaml already exists in the current directory or
HARNESS_DISPATCH_CONFIG is set, in which case that file is the target.
Without --yes it previews and writes nothing.
Registering with your MCP clients. After writing, configure offers to
register this server with each client it finds (Claude Code, Cursor), showing
what it would write and what is already there before changing anything.
--no-clients skips the offer and prints a snippet to paste instead.
harness-dispatch connect does the same registration later on its own, and
connect --remove undoes it.
Re-running it. A file configure wrote and you have not edited is
regenerated, so installing a harness later is just configure --yes again. A
file you have changed is refused without --force — and because such a file
lists its own routes, even --force regenerates it from the file rather than
from a fresh detection. It says so when that happens; add detect: true to the
file to merge newly installed harnesses in.
What doctor checks. The whole chain: binary, config load, harness
detection, auth and billing classification, route readiness, whether
dist/job-runner.js is present (without it jobs run in-process and the
concurrency cap does not apply), and for a Codex route it asks codex login status whether the CLI is logged in. The other harnesses have no equivalent
this tool has verified, so their login state is not checked. --live goes
further and routes one tiny real prompt through an eligible route, so you see a
completion before wiring anything into your agent — that one spends quota, and
it never touches paid or unknown-billing routes unless you pass --allow-paid.
Your Claude Code / Codex / Cursor subscriptions run by default with no opt-in;
configure tells you if anything is blocked and why.
No global install needed either: npx harness-dispatch configure.
Plugin install (Claude Code / Claude Desktop / Codex)
The plugin/ directory packages the MCP server plus a delegation skill and
/route + /jobs commands for one-step installs — see
plugin/README.md
(absolute link on purpose: plugin/ is not shipped in the npm tarball, so a
relative link is dead on npmjs.com). Claude Code:
/plugin marketplace add <repo path or URL> then
/plugin install harness-dispatch@harness-dispatch. Codex:
node plugin/scripts/install-codex.mjs.
Where to put the instructions that tell an agent to delegate
In your user-level file — ~/.claude/CLAUDE.md, or the equivalent for your
client — not in a project file that gets committed.
Two reasons, and the second bites even among people who all run this tool:
- A teammate without it reads instructions for tools their agent does not have.
- Route ids do not travel.
codex_cli,local_inferenceand the rest are whatever your config declares. Someone else's install has different ones, so a committedservice:or model name is wrong for them rather than merely unused.
A project's checked-in CLAUDE.md is for the codebase: how it builds, how it is
tested, its conventions. Personal-but-project-specific notes go in
CLAUDE.local.md, which is gitignored by convention.
If a team does want a shared mention, keep it to one conditional sentence with no route ids, so it costs nothing to anyone who has not installed this. The CLAUDE.md in this repository is written that way on purpose.
Nothing here writes to a project file. configure and connect touch only
user-level client configs, and they show you the change before making it.
Billing, and what it can't promise
A configured harness runs automatically, with nothing to switch on. Routes that
have no billing backstop at all, meaning a raw metered API key or billing it can't
classify, stay blocked until you set allow_paid_usage: true on them.
The honest limit: if a harness's account already has paid or overage billing switched on at the provider (Cursor on-demand, Claude usage credits, Codex flexible credits), harness-dispatch will spend that too. It cannot see or change provider-side billing state. What it does is refuse routes where no provider-side ceiling exists at all.
Run status (or status --json) for any route's billing classification; the note:
lines spell out the reasoning per route.
Renamed from harness-router — upgrade notes
The npm package, CLI command, env var prefix (HARNESS_DISPATCH_*), and MCP resource
URIs (harness-dispatch://status) all changed together. From an older install:
npm uninstall -g harness-router && npm install -g harness-dispatch, then update any
mcpServers / claude_desktop_config.json entry to invoke harness-dispatch.
Two packages predate the rename and are unmaintained: harness-router (0.3.2) and
the separately-published harness-router-mcp (0.2.0). Both lack usage,
/v1/models, /v1/usage, Antigravity support, and every fix described here.
npx -y harness-dispatch, the plugin's fallback launch path, resolves to whatever
is currently on the npm registry, which can lag a local clone's dist/. Check with
npm ls -g harness-dispatch.
Safety profiles, and Cursor on Windows
A caller asks for one of three profiles, and each is a limit rather than a capability:
| Profile | Means |
|---|---|
read_only | Look, don't touch |
workspace_edit | Edit files in the workspace, no arbitrary shell |
full_auto | Edit files and run shell |
A route declares the floor it actually runs at (effective_safety), and is
skipped when that floor exceeds what was asked for. A route is never quietly
given more access than the caller requested.
cursor_cli is the interesting case, because its capability differs by mode:
read_onlyuses--mode plan, which is genuinely read-only (verified: asked to create one file and overwrite another, it did neither).full_autouses print mode, which edits and runs shell.workspace_editis skipped on Windows. Cursor's print mode grants write and shell together, and--sandbox enabled— the flag that would constrain shell while allowing edits — is macOS/Linux only. There is no edit-without-shell mode to route to, so claiming that level would mean handing shell access to a caller who explicitly asked not to have it.
Cursor still edits code on Windows. Ask for full_auto.
Overriding it
If you accept that Cursor's editing mode carries shell access and you want it
to serve workspace_edit anyway, declare the floor yourself in config.yaml —
your value replaces the shipped default:
clis:
- name: cursor_cli
harness: cursor
command: cursor-agent
effective_safety:
read_only: read_only
workspace_edit: workspace_edit # you are accepting shell access here
full_auto: full_auto
That is a deliberate local decision, not a bug workaround: the shipped default
is conservative because the tool cannot verify what a given cursor-agent
build will do. On macOS and Linux the better route is --sandbox enabled,
which constrains shell for real — untested here, so it is not shipped on by
default.
CLI
harness-dispatch configure # detect harnesses and prepare config
harness-dispatch doctor # validate install, auth, config and routes
harness-dispatch status # route readiness, quota, breaker state
harness-dispatch usage # per-route call counts and billing kind
harness-dispatch dispatch "..." # route one task and print the result
harness-dispatch serve # /mcp and /v1/* over local HTTP
Every command, its flags, and the hidden compatibility aliases are in MCP and HTTP surfaces.
Everything else
Route ids, protocol blocks and per-harness overrides live in Configuration. The tool and endpoint reference is in MCP and HTTP surfaces. Quota, breaker state and telemetry are in Status and observability.
Development
npm ci
npm run typecheck
npm test
npm run build
npm run smoke
npm audit --omit=dev
npm pack --dry-run
Live agent workflow smoke tests are opt-in because they call real harnesses and
can consume quota or product-plan usage. They create a disposable tiny Node
project under .harness-dispatch/smoke-workspaces, write the detailed task into a
workspace-local .harness-dispatch/agent-task.md, send the harness a short prompt
pointing at that brief, then verify node test.mjs passes.
$env:HARNESS_DISPATCH_LIVE_AGENT_SMOKE = '1'
npm run build
npm run smoke:agents -- --config config.yaml
To temporarily include routes that can incur paid usage:
$env:HARNESS_DISPATCH_LIVE_AGENT_SMOKE = '1'
npm run smoke:agents -- --config config.yaml --allow-paid
To include Cursor's full-auto print-mode route:
$env:HARNESS_DISPATCH_LIVE_AGENT_SMOKE = '1'
npm run smoke:agents -- --config config.yaml --allow-paid --safety full_auto
Release gates:
npm run check
npm run build
npm run test:coverage
npm run smoke
npm audit --omit=dev
npm pack --dry-run
Before publishing, also run smoke:agents with the installed harnesses you want
to claim as validated, and record which routes passed, failed, or were skipped.
Set HARNESS_DISPATCH_AGENT_SMOKE_ROOT only when you need the disposable
workspaces somewhere other than the repo-local shared smoke cache.
Collected info
- ★ 0 stars
- Language: TypeScript
- Source updated: 9/12/2026
Config for your environment
Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.
Tool
OS
Config file: ~/.cursor/mcp.json
{
"mcpServers": {
"mcp-server": {
"url": "{MCP_ENDPOINT_URL}"
}
}
}Paste into mcpServers in the config file. Restart Cursor after saving.
If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.