MCP server β
Your MCP client needs to spawn Avenor runs and monitor them. avenor mcp is the canonical MCP server that gives your client these tools.
It's built into the Go binaryβno Node, Bun, npm, or separate package needed.
avenor mcpTransports β
stdio β
stdio is the default transport and is the best choice for most MCP clients.
avenor mcpExample MCP host configuration:
{
"mcpServers": {
"avenor": {
"command": "avenor",
"args": ["mcp"]
}
}
}If avenor is not on the host's PATH, use the absolute path:
{
"mcpServers": {
"avenor": {
"command": "/path/to/avenor",
"args": ["mcp"]
}
}
}HTTP β
HTTP transport uses MCP Streamable HTTP and is intended for local, long-lived clients.
MCP_AUTH_TOKEN="my-secret" avenor mcp --transport httpBy default it binds to 127.0.0.1:3748. HTTP requires a bearer token, supplied with either MCP_AUTH_TOKEN or --auth-token:
avenor mcp --transport http --addr 127.0.0.1:3748 --auth-token "my-secret"Example MCP host configuration:
{
"mcpServers": {
"avenor": {
"url": "http://127.0.0.1:3748",
"headers": {
"Authorization": "Bearer my-secret"
}
}
}
}The HTTP server rejects non-loopback hosts and non-loopback browser origins. Keep it on loopback unless you are deliberately putting another authenticated local proxy in front of it.
Typical workflow β
When you (an LLM) are using this MCP server to automate work, a run moves through a set of lifecycle states. Your supervision code must distinguish them because each requires a different action.
Run lifecycle states β
| Status | Means | Next action |
|---|---|---|
running | Agent is active; pending_permission: true may still interrupt it. | Answer permission if pending; otherwise keep waiting. |
done (has output) | Agent finished and produced a result. Final output and file changes exist. | Report the outcome. |
done (no output) | Agent finished without writing anything. It likely asked a clarifying question. | Inspect then call avenor_follow_up with your answer. |
failed | Agent hit an error mid-run. | Report the failure. |
timeout | Run exceeded its timeout. | Report the timeout. |
killed | Run was forcefully terminated. | Report the kill. |
waiting (pending_permission) | Agent hit a tool approval gate. It is blocked mid-task. | Answer via avenor_answer_permission, then resume waiting. |
Supervision approaches β
Option A: Blocking β let avenor_result handle the loop:
avenor_spawn(agent="jockey", repo_dir="/path/to/repo", prompt="fix the tests")
β { "run_id": "...", "label": "...", "supervisor_id": "..." }
avenor_result(run_id="...")
β { "status": "done", "ready": true, "output": "..." }
avenor_events(run_id="...")
β { "events": [ { "type": "...", ... }, ... ] }
avenor_shutdown()
β { "ok": true, "cleaned_up": [...] }- Call
avenor_spawnwith an agent name and repository path. - Call
avenor_resultto wait. It returns on terminal completion or a current pending permission. Answer the permission withavenor_answer_permission, then callavenor_resultagain. - Call
avenor_eventsonly when you need raw recent history. - Call
avenor_shutdownwhen the session is done.
Option B: Targeted waits β use avenor_status when you need lifecycle control without implementing a caller-side poll loop:
avenor_spawn(agent="jockey", repo_dir="/path/to/repo", prompt="fix the tests")
β { "run_id": "...", "label": "...", "supervisor_id": "..." }
avenor_status(run_id="...", wait_for="turn_complete", timeout="5m", view="lifecycle")
β { "status": "done", "phase": "done" }Each wait requires one run_id. The server polls internally at a fixed cadence. A caller does not provide a poll interval. A wait returns when its condition is met, when a current permission request appears, or when its timeout expires.
What not to watch for β
- File changes alone. An agent that ends its turn without writing anything has still ended its turn. Do not treat an empty working tree as "still running." Check
avenor_statusinstead. session.endalone. A permission-blocked run never reachessession.end. Checkpending_permissionseparately.
Follow up β
Call avenor_follow_up when a done run needs more direction. It spawns a new session continuing from the prior one. Treat the follow-up as a new run through the same lifecycle. The existing auto_approve policy is inherited by follow-ups, so approved runs remain unattended across continuation turns.
Progress notifications β
This issue uses long-poll responses instead of MCP progress notifications. The Go SDK exposes notification capability, but Claude Code host rendering and useful progress semantics are not established. Wait correctness is independent of notifications.
For background callers, a shell-side avenor await replaces log-tail monitors; avenor_result remains the in-protocol equivalent.
Supervisor lifecycle β
By default, avenor mcp starts a private child supervisor:
avenor stable --control-socket <socket> --idle-timeout 30mThe socket defaults to ~/.avenor/sockets/avenor-mcp-<pid>.sock. You can override it:
avenor mcp --control-socket ~/.avenor/sockets/avenor-mcp.sockTo connect to an existing supervisor instead of starting one:
avenor stable --control-socket /tmp/avenor-stable.sock
avenor mcp --supervisor-socket /tmp/avenor-stable.sock --no-autostart--supervisor-socket disables autostart. --no-autostart requires --supervisor-socket.
Flags β
| Flag | Default | Description |
|---|---|---|
--transport | stdio | stdio or http |
--addr | 127.0.0.1:3748 | HTTP bind address |
--auth-token | MCP_AUTH_TOKEN | Bearer token required for HTTP |
--control-socket | per-process socket | Socket path for the autostarted child supervisor |
--supervisor-socket | none | Existing supervisor socket to connect to |
--no-autostart | false | Require an existing supervisor |
--idle-timeout | 30m | Idle timeout for the autostarted child supervisor |
Tools β
All tools use the avenor_ prefix and are scoped to this MCP process's supervisor.
avenor_spawn β
Starts a new run.
Required:
repo_dirβ working directory for the run
Optional:
agentβ agent to run (e.g.,"jockey","butler")promptβ initial prompt textprompt_fileβ path to file containing the initial promptlabelβ human-friendly label (defaults torun_id)timeoutβ timeout as seconds or duration (e.g.,"90s","5m","1h")modelβ model to usebackendβ runtime backend (e.g.,"opencode-http","opencode-acp","codex-app-server","agy")roster_file/roster_entryβ optional roster selector pairserver_urlβ server URL for opencode-http backendsupervisor_idβ supervisor socket to use instead of the default
Returns:
{
"run_id": "...",
"label": "...",
"supervisor_id": "..."
}The returned run_id is generated and unique. Pass it to other tools to query or control this run.
avenor_status β
Queries the status of runs. Use this for lifecycle and permission checks, not final output retrieval.
Optional:
run_idβ specific run ID or label to query; omit to list all runsviewβlifecyclefor a compact response orfullfor compatibility (default:full)wait_forβ wait forterminal,phase_change,turn_complete, orpermissiontimeoutβ maximum wait such as30s,5m, or1h; only valid withwait_forsupervisor_idβ supervisor socket to query (default: the autostarted supervisor)
A wait requires one run_id. Without wait_for, avenor_status performs its existing one-shot query or list operation. The server polls internally at a fixed one-second cadence; callers do not provide a poll interval.
Wait conditions:
terminalβ returns on normalizeddone,failed,timeout, orkilledstatus.phase_changeβ returns when normalizedphasediffers from the first snapshot, or when a terminal status is reached (even if the phase string did not change).turn_completeβ returns on safely normalized completion of the current turn. Parked idle runs with a terminal phase count as complete. Active runs with a transient terminal phase do not.permissionβ returns when a current permission request is pending.
A pending permission interrupts every wait condition, including terminal. Inspect pending_permission, answer the request with avenor_answer_permission, and then issue another wait. The legacy waiting status remains supported.
Returns: One status object if run_id is given, or an array of status objects if omitted. A timed-out wait returns the latest status with timed_out: true; it does not cancel the underlying run. Lifecycle view retains timed_out but omits final_output and usage. Use avenor_result to harvest complete output.
avenor_result β
Waits for one run and returns its complete final output without transcript or raw event details.
Required:
run_idβ run ID or label to await
Optional:
waitβ wait for a terminal result (default:true)timeoutβ maximum time to wait, such as30sor5msupervisor_idβ supervisor socket to query
A terminal response has ready: true and includes the complete output when the backend exposed final assistant text. A blocked run returns its pending_permission immediately, even when its public status is still running. Answer the request before waiting again. If an older or unavailable control endpoint prevents lossless retrieval and a presentation fallback is returned, output_truncated: true and output_event_path make its possible truncation explicit; retry avenor_result or read the durable event path. If the result tool's own timeout expires, it returns the latest state with ready: false and timed_out: true; the underlying run keeps going.
avenor_answer_permission β
Responds to a pending permission request.
Required:
run_idβ run ID or labeloption_idβ which option to select
Optional:
request_idβ specific request ID to answer; if omitted, answers the current pending requestsupervisor_idβ supervisor socket to use
Returns:
{ "ok": true }avenor_events β
Reads historical events from a run's event log.
Required:
run_idβ run ID or label
Optional:
typesβ event types to filter by (e.g.,["phase"])limitβ maximum events to return (default:50)supervisor_idβ supervisor socket to use
Returns:
{ "events": [ ... ] }The event log skips malformed lines and returns the last N matching events.
avenor_follow_up β
Spawns a new run continuing a completed run's session.
This is not a live prompt into the old runtime; it spawns a new runtime that resumes from the prior session's state.
Required:
run_idβ completed run ID or labelmessageβ follow-up prompt text
Optional:
labelβ label for the new run (defaults to<prior-label>-followup)supervisor_idβ supervisor socket to use
Returns:
{
"run_id": "...",
"label": "..."
}avenor_shutdown β
Shuts down the supervisor and cleans up MCP-owned files.
Optional:
supervisor_idβ supervisor socket to shut down (default: the autostarted supervisor)forceβ request kill instead of graceful shutdown (default:false)
Returns:
{
"ok": true,
"cleaned_up": [
"path/to/sentinel/file",
"path/to/event/log"
]
}Workflow tools β
The avenor_workflow_* tools drive a durable workflow against a supervisor's control socket. They require the supervisor to have workflow support (a --workflow-root manager); otherwise the call fails with a method-not-found error.
| Tool | Purpose |
|---|---|
avenor_workflow_status | Lightweight status for a workflow instance. |
avenor_workflow_wait | Wait for a workflow to reach a terminal state or until timeout. |
avenor_workflow_inspect | Full instance detail for a workflow. |
avenor_workflow_events | Read log events from a workflow instance's event log. |
avenor_workflow_complete | Atomically complete a machine/external handoff activation. |
avenor_workflow_gate | Record a gate decision on a parked awaiting_gate activation. |
All take an optional supervisor_id (supervisor socket path). avenor_workflow_status, avenor_workflow_inspect, and avenor_workflow_events take workflow_id (and after_seq/limit for events). avenor_workflow_wait takes workflow_id and an optional timeout (e.g. 30s, 5m). avenor_workflow_complete takes workflow_id, node_id, activation_id, attempt_id, lease_id, owner_token, outcome, and optional outputs/artifacts. avenor_workflow_gate takes workflow_id, node_id, gate_id, activation_id, operation (satisfy, reject, waive, or external_result), and the operation's fields.
Registry scope: important limitation β
The Go MCP server keeps an in-memory run registry scoped to the MCP process. This registry maps run_id (generated by this MCP server) to stable runtime IDs, sentinel files, event logs, and other metadata.
This means:
avenor_eventsrequires a run spawned by this MCP process (must be in the registry).avenor_follow_uprequires the prior run to be in this MCP process's registry.- If you spawn a run with one MCP server and then try to query it with another MCP server, it will fail.
Durable cross-process recovery is intentionally out of scope. If you need to connect to an existing supervisor's runs from a different MCP process, use supervisor_id to point at that supervisor's control socket and query by stable runtime IDβbut you won't be able to read events or spawn follow-ups for those runs.