Skip to main content

Tutorial: Drive harnessd from Claude Desktop

The harness-mcp proxy is a small binary that lets Claude Desktop talk to a running harnessd instance. Once registered, Claude Desktop can start agent runs, check their status, wait for completion, continue conversations, and list recent runs — all without leaving the chat interface.

This tutorial walks you through the full setup: start harnessd, build the proxy, register it with Claude Desktop, and exercise all five tools.

Three separate MCP surfaces exist: the HTTP endpoint at /mcp (always available when harnessd runs in HTTP mode), the stdio server launched with harnessd --mcp (exposes the full harness tool catalog), and the harness-mcp proxy (five curated run-management tools over stdio). This tutorial uses the proxy path. See Exposing harnessd as an MCP Server for a comparison of all three.


What you will build

Claude Desktop
│ (stdio JSON-RPC 2.0)

harness-mcp ←─ bin you build in this tutorial
│ (HTTP REST)

harnessd ←─ daemon already listening on :8080

harness-mcp reads JSON-RPC 2.0 from stdin, translates each tool call into a REST request against harnessd, and writes the JSON-RPC response back to stdout. Claude Desktop launches the proxy as a subprocess; the proxy connects to harnessd at the URL given by the HARNESS_ADDR environment variable.


Prerequisites

  • Go 1.25 or later installed.
  • The go-code repository cloned locally.
  • Claude Desktop installed and running.
  • OPENAI_API_KEY set, or willingness to run in key-free fake mode for a dry run.

Step 1 — Run harnessd

Before the proxy can do anything, harnessd must be running and reachable.

# From the repo root
go build -o bin/harnessd ./cmd/harnessd
OPENAI_API_KEY=sk-... ./bin/harnessd

harnessd logs harness server listening on :8080 when it is ready.

Confirm harnessd is healthy:

curl http://localhost:8080/healthz
# {"status":"ok"}

The /healthz endpoint requires no authentication and always returns {"status":"ok"} when the server is up.


Step 2 — Build and register the proxy

  1. Build harness-mcp

    go build -o bin/harness-mcp ./cmd/harness-mcp

    This compiles cmd/harness-mcp/main.go into bin/harness-mcp. Note the absolute path to the binary — you will need it in the next step.

    # Print the absolute path
    echo "$(pwd)/bin/harness-mcp"
  2. Edit claude_desktop_config.json

    Open ~/Library/Application Support/Claude/claude_desktop_config.json in a text editor and add an entry under mcpServers:

    {
    "mcpServers": {
    "harness": {
    "command": "/ABSOLUTE/PATH/TO/bin/harness-mcp",
    "env": {
    "HARNESS_ADDR": "http://localhost:8080"
    }
    }
    }
    }

    Replace /ABSOLUTE/PATH/TO/bin/harness-mcp with the path you printed above.

    HARNESS_ADDR defaults to http://localhost:8080 if you omit it, but making it explicit avoids surprises when you later run harnessd on a different port.

  3. Restart Claude Desktop

    Quit and reopen Claude Desktop. On restart it reads claude_desktop_config.json and launches the proxy subprocess. The "harness" server should appear in the tool list (the hammer icon or tool picker in the chat UI).

    If it does not appear, check Claude Desktop's MCP log (usually accessible from the Developer menu) for errors from the harness-mcp process.


Step 3 — Use it

With harnessd running and the proxy registered, Claude Desktop has access to five tools.

The 5 harness-mcp tools

ToolRequired argsOptional argsWhat it does
start_runpromptmodel, conversation_id, max_steps, max_cost_usdSubmits a new agent run; returns run_id immediately
get_run_statusrun_idReturns current status and any error; messages and cost_usd are always empty in this version — use wait_for_run or the REST API to get output
wait_for_runrun_idtimeout_seconds (default 300)Polls every 2 seconds until the run reaches a terminal state; returns status and any error; messages and cost_usd in the result are always empty in this version
continue_runrun_id, promptLooks up the previous run's conversation_id and starts a new run in that conversation
list_runsconversation_id, limit (default 20)Lists recent runs, optionally filtered by conversation. Requires HARNESS_RUN_DB to be set — returns an error if run persistence is not configured.

Example conversation in Claude Desktop

Try asking Claude something like:

"Start a harness run with the prompt 'What is 2 + 2?' and wait for it to finish."

Claude will call start_run to get a run_id, then call wait_for_run to block until the run completes or times out (default 300 seconds), and finally surface the output to you.

For a multi-turn session:

"Continue that run with the follow-up: 'What is 3 + 3?'"

Claude will call continue_run using the run_id from the previous turn. Internally the proxy fetches the conversation_id from GET /v1/runs/{runID} and submits a new run tied to that conversation, preserving context across turns.

Understanding wait_for_run

wait_for_run is a blocking poll — it keeps calling GET /v1/runs/{runID} every 2 seconds and returns as soon as the run status becomes one of:

  • completed — run finished successfully
  • failed — run encountered an error
  • waiting_for_user — run is paused and needs user input

If the timeout expires (default 300 seconds) before a terminal state is reached, the tool returns an error. You can pass timeout_seconds to extend or shorten this window.


Step 4 — Alternative: harnessd --mcp (stdio mode)

If you want Claude Desktop to have access to the full harness tool catalog — not just the five run-management tools — you can point Claude Desktop directly at harnessd --mcp without the proxy:

{
"mcpServers": {
"harness-full": {
"command": "/ABSOLUTE/PATH/TO/bin/harnessd",
"args": ["--mcp", "--mcp-workspace", "/path/to/your/workspace"],
"env": {
"OPENAI_API_KEY": "sk-..."
}
}
}
}

In this mode Claude Desktop launches harnessd as a subprocess that speaks JSON-RPC over stdin/stdout. harnessd does not listen on any TCP port — all communication is via stdio. The workspace root is resolved from --mcp-workspace, then HARNESS_WORKSPACE, then ..

When to prefer the proxy over --mcp:

  • You are already running harnessd as a persistent daemon and want to keep it running across Claude Desktop restarts.
  • Multiple clients or processes share one harnessd instance.
  • You want a minimal, focused interface (5 tools) rather than the full catalog.

When to prefer --mcp directly:

  • You want the full harness tool catalog available in Claude Desktop.
  • You are fine with Claude Desktop owning the harnessd process lifetime.
  • You are in a single-client setup.

Troubleshooting

The "harness" server does not appear in Claude Desktop.

Check the absolute path in claude_desktop_config.json. The command field must be an absolute path to the binary — relative paths are not resolved. Re-run echo "$(pwd)/bin/harness-mcp" from the repo root and paste the result.

harnessd exits immediately when HARNESS_PROVIDER=fake.

The fake provider requires HARNESS_FAKE_TURNS to be set to a valid turns file. If it is unset, harnessd fails at startup with fatal: create provider: fake provider: read fake turns file "" and never serves any requests. The fix is to create a turns file and set HARNESS_FAKE_TURNS to its path before starting harnessd — see the Key-free tab in Step 1 for a minimal example.

start_run returns an error about authentication.

harnessd implicitly disables auth when no run store (HARNESS_RUN_DB) is configured, so in the default no-persistence setup the proxy works without credentials. If you have HARNESS_RUN_DB set (which enables auth), use HARNESS_AUTH_DISABLED=true for local development. Direct Bearer-token configuration for harness-mcp is not supported today.

start_run via the proxy returns "API key env OPENAI_API_KEY is not set".

This happens when running harnessd with HARNESS_PROVIDER=fake but submitting a run via the proxy without allow_fallback: true. The harness-mcp proxy does not set allow_fallback automatically, so harnessd's model router tries to reach the real OpenAI provider. The fake-provider mode is intended for smoke-testing harnessd directly (via curl with "allow_fallback": true) — not for verifying the Claude Desktop integration path. For the proxy to work end-to-end you need a real API key.


Next steps