mcp-agent-relay
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-agent-relaydispatch a code review to codex"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-agent-relay
Durable MCP dispatch for explicit CLI executors.
mcp-agent-relay lets one MCP client hand a job to a locally installed CLI agent and retrieve a durable result. It keeps the queue, state, leases, cancellation, and review policy in one MCP-native relay while delegating each turn to a small, allowlisted executor adapter.
It is designed for a practical local workflow: Codex can request an independent read-only review from Codex, Claude Opus, or Claude Fable without turning the relay into a remote shell.
Status: research preview / single-machine relay. The durable queue and executor adapters are production-shaped; wake-up integration with Claude Code depends on Claude Code preview capabilities.
Why use it?
The hard part of agent-to-agent work is not starting a command. It is retaining a correct answer when processes restart, jobs collide, a worker loses its lease, or a caller gives up waiting.
The relay provides:
Durable dispatch. File-backed jobs survive server and worker restarts. A
request_idmakes dispatch idempotent.Explicit routing. The
tofield selects one allowlisted worker id; it cannot select a binary, arguments, Claude agent, or environment.Safe execution defaults. Jobs are read-only by default. Codex writes require an explicit worker opt-in; Claude writes are rejected in this release.
Correct worker coordination. Workers claim jobs using leases, fencing tokens, heartbeats, cancellation, timeout handling, and recovery states.
MCP-first integration. Any MCP client can
dispatch,poll,dispatch_wait, inspectaudit, read a boundedstreamor hash-verifiedchangelog, create/dispatch an approvedhandoff, or diagnose withtrace/health. A running Claude Code session can also be notified when a job changes state.Operational visibility. Every turn has a bounded deadline, redacted lifecycle events, worker heartbeat evidence, and a passive monitor for diagnosing Codex/Claude jobs without redispatching them.
Auditable executor profiles. Requested model policy, effective-model evidence, CLI version, timestamps, and exit status are recorded without accepting model selection from the payload.
Related MCP server: Parley
At a glance
Need | Use |
Quick read-only review with Codex |
|
Deep Claude review with the local |
|
Independent Claude review with the local |
|
Verified three-family review from Codex or Claude Code |
|
Two-lens cross-harness review with Sol/xhigh and Opus/max |
|
Wait for one result in the tool call |
|
Submit now and inspect later |
|
Inspect executor/model evidence after result retention |
|
Inspect one job's redacted lifecycle |
|
Inspect project provenance and handoff history |
|
Inspect relay and worker health |
|
Make a Codex change in an isolated worktree |
|
Architecture
MCP client durable relay worker
────────── ───────────── ──────
dispatch(to, task, request_id) ──▶ queue + request-id dedup
claim + lease + fencing ───────────▶ adapter.runTurn()
poll(job_id) ◀─── result + timing evidence ◀───────── Codex or Claude CLI
trace(job_id) ◀─── redacted process/state timeline
health() ◀─── server + worker heartbeat evidence
audit(job_id) ◀─── compact terminal ledgerLayer | Responsibility |
| Durable queue, locking, deduplication, leases, fencing, turn deadlines, expiry, cancellation, recovery, review state, trace events, and compact audit ledger |
| MCP stdio facade, inbox resources, synchronous wait, and optional Claude channel |
| Claim loop, heartbeat, timeout/deadline fencing, cancellation, process tracing, worktree orchestration, and durable completion |
| Worker autospawn/reuse, build identity, PID/heartbeat health, and safe replacement of stale daemons |
| Passive JSON/text monitor for jobs and Codex/Claude worker health |
| One-shot, read-only correlation of MCP registration, workspace store, workers, and queued jobs |
| Operator-triggered bounded retention for Context Store streams, summaries, handoffs, and locks |
| Cross-host contract check for CLIs, MCP, skills, profiles, monitor/debug, and the suite |
| Deterministic A → approved handoff → B contract test without starting a CLI |
| Real read-only MVP-0 smoke: workspace A → approved handoff → workspace B |
| Central allowlist from worker id to an adapter and its fixed configuration |
| One isolated CLI turn with final-result compatibility, normalized stream hooks, and process-group shutdown |
| Bounded, redacted per-attempt stream spool and passive tail reader; never queue state |
| Redaction and 8 KiB cap for executor errors crossing into durable state or MCP responses |
| Deterministic run summaries and redacted, hash-verified project changelog entries |
| Hash-verified, approval-gated context manifests between allowlisted workspaces/executors |
The executor boundary is deliberately narrow: the relay is executor-agnostic, not command-agnostic.
Host-aware /tri-r
The bundled tri-r skill always reviews with three distinct
model families: Opus, Fable, and GPT-5.6 Sol. The logical panel is the same in both
harnesses, while the native and relayed routes are inverted:
The complete cross-harness capability matrix is maintained in
docs/review-capability-matrix.md.
Orchestrator host | Opus lens | Fable lens | Sol lens |
Claude Code | Native | Native | AgentRelay |
Codex | AgentRelay | AgentRelay | Fresh native |
The Codex route never treats deep-reasoner.toml or fable-reasoner.toml as Claude
models. Agent names are prompts and routing labels, not model evidence. Every relayed
lens records requestedModelPolicy, effectiveModel, and modelEvidence when the
executor supplies them.
Install or refresh the same canonical skill in the personal discovery directories of both harnesses:
node bin/relay-install-tri-r.mjsThis writes ~/.agents/skills/tri-r/SKILL.md for Codex and
~/.claude/skills/tri-r/SKILL.md for Claude Code. The operation is idempotent. Use
--codex-only, --claude-only, or --home /alternate/home when needed. Restart open
Codex and Claude Code sessions after updating a skill because skill catalogs are loaded
when a session starts.
/tri-r is intentionally expensive and produces a consolidated, verified panel. For a
single-family review, ask the MCP client to use claude-opus, claude-fable, or
codex directly instead of running a partial tri-r.
A dispatch_wait timeout is not a failed review. It only ends the MCP caller's bounded
wait; the durable job continues. /tri-r records that lens as PENDING, preserves its
job_id, and does not consolidate the panel. After a terminal notification it performs
one targeted poll and resumes the same job without redispatching or changing the
request_id. Only terminal failed, cancelled, or expired states degrade a lens.
Host-aware /dual-r
dual-r is a smaller two-lens panel with inverted routing:
Orchestrator | Native lens | AgentRelay lens |
Codex |
|
|
Claude Code |
|
|
Install the same skill and the native profiles in both personal harnesses:
node bin/relay-install-dual-r.mjsThe installer is idempotent. It writes ~/.agents/skills/dual-r/SKILL.md,
~/.codex/agents/dual_sol_reviewer.toml, ~/.claude/skills/dual-r/SKILL.md, and
~/.claude/agents/dual-opus-reasoner.md. It does not edit
~/.codex/config.toml: register the Codex profile once if needed:
[agents.dual_sol_reviewer]
description = "Independent read-only Sol xhigh reviewer for dual-r"
config_file = "./agents/dual_sol_reviewer.toml"Add codex-sol-xhigh and claude-opus-max to RELAY_WORKER_AGENTS in the MCP
registration. Restart Codex and Claude Code sessions after installing skills; their
catalogs are loaded at session start. The durable wait rules are the same as tri-r:
dispatch once, optionally one bounded dispatch_wait, preserve PENDENTE on caller
timeout, and make one poll only after terminal notification.
The relay uses Claude's opus latest-model alias, not an invented numbered model id.
Use the returned effectiveModel/modelEvidence (or report the gap) before calling it
“Opus 5”. Both lenses are read-only; write: true is rejected before a process starts.
Available executors
Worker id / | Adapter | Fixed model policy | Turn ceiling | Policy kind | Write policy |
| Codex CLI |
| 10 min | Pinned profile | Denied unless that worker has |
| Codex CLI |
| 30 min | Pinned dual-r profile | Always rejected |
| Claude Code |
| 10 min | Latest Opus alias | Always rejected |
| Claude Code |
| 10 min | Latest Opus alias | Always rejected |
| Claude Code |
| 10 min | Pinned model id | Always rejected |
For the Claude routes, the local Claude Code installation must already be authenticated and must have the corresponding agent definitions:
~/.claude/agents/deep-reasoner
~/.claude/agents/dual-opus-reasoner
~/.claude/agents/fable-reasonerClaude runs in non-interactive stream-json mode. The initial structured event is the evidence source for the effective model, and the terminal structured event carries the final response and session id. The -- delimiter ensures a prompt cannot be parsed as a Claude CLI option.
Relay Claude turns are isolated from ambient Claude configuration: the adapter
loads only the local settings source (an empty --setting-sources would mean
the defaults), runs from a private temporary cwd and grants the target workspace
only through --add-dir, passes a fixed empty --mcp-config with
--strict-mcp-config, and supplies the allowlisted agent definition inline. It
disables Chrome, slash skills and session persistence,
and allows only the built-in Read, Grep and Glob tools. This prevents a review
from recursively starting AgentRelay, Backlog or browser MCP servers inherited from
the user's project. OAuth/keychain authentication remains available through the
isolated process's HOME; credential environment variables are deliberately not
inherited. The relay never installs credentials or accepts environment/configuration
from a payload.
claude-opus deliberately means the CLI's opus latest-model alias; it is not a claim that a specific numbered Opus release ran. claude-fable is currently pinned to the full claude-fable-5 model id. Payload fields such as command, args, agent, model, and environment settings cannot alter either profile.
Quick start: connect Codex
1. Clone the relay and verify Node
git clone https://github.com/brenoperucchi/mcp-agent-relay.git
cd mcp-agent-relay
node --version # Node 18.18 or newerNo package installation or build step is required.
2. Register the MCP server
Register a global Codex MCP server, or put the equivalent configuration in a trusted project if it should be project-scoped:
codex mcp add agentrelay \
--env RELAY_WORKER_AUTOSPAWN=1 \
--env RELAY_WORKER_AGENTS=codex,codex-sol-xhigh,claude-opus,claude-opus-max,claude-fable \
-- node /absolute/path/to/mcp-agent-relay/server.mjsIf a CLI is installed outside the inherited environment, provide a minimal explicit PATH for the relay process:
codex mcp add agentrelay \
--env PATH=/home/you/.local/bin:/usr/local/bin:/usr/bin:/bin \
--env RELAY_WORKER_AUTOSPAWN=1 \
--env RELAY_WORKER_AGENTS=codex,codex-sol-xhigh,claude-opus,claude-opus-max,claude-fable \
-- node /absolute/path/to/mcp-agent-relay/server.mjsConfirm the registration:
codex mcp listWhen the server receives a job, autospawn starts one short-lived worker per configured executor id as needed. You can instead run workers yourself; see Running workers.
3. Dispatch a read-only review
In a Codex session, call dispatch_wait with an explicit executor id:
{
"to": "claude-opus",
"task": {
"prompt": "Review the current diff for correctness, regressions, and missing tests. Report only actionable findings."
},
"request_id": "review-current-diff-opus-1",
"timeout_ms": 120000
}For the Fable-backed Claude agent:
{
"to": "claude-fable",
"task": {
"prompt": "Independently review the current diff. Focus on security and reliability risks."
},
"request_id": "review-current-diff-fable-1",
"timeout_ms": 120000
}For the Opus/max lens used by /dual-r from Codex:
{
"to": "claude-opus-max",
"task": {
"prompt": "Independently review the current diff. Read-only; report actionable findings with file/line evidence."
},
"request_id": "review-current-diff-opus-max-1",
"timeout_ms": 120000
}From Claude Code, the relayed Sol/xhigh lens uses the inverse route:
{
"to": "codex-sol-xhigh",
"task": {
"prompt": "Independently review the current diff. Read-only; focus on implementation edges and missing tests."
},
"request_id": "review-current-diff-sol-xhigh-1",
"timeout_ms": 120000
}If claude is missing, not on PATH, unauthenticated, or its named local agent is unavailable, the job reaches a clear failed state with the CLI error. The relay never installs Claude, changes global settings, or creates credentials.
MCP tools and job lifecycle
Submit now, retrieve later
dispatch creates (or deduplicates) a durable job and returns immediately:
// dispatch
{
"to": "codex",
"task": { "prompt": "Review the current diff for correctness." },
"request_id": "review-current-diff-codex-1"
}
// response
{ "job_id": "relay-…", "deduped": false, "state": "queued" }Call poll until it reaches a terminal state:
// poll
{ "job_id": "relay-…" }
// response
{
"found": true,
"state": "completed",
"result": { "output": "…" },
"attempts": 1,
"execution": {
"executorId": "claude-opus",
"requestedModelPolicy": "opus",
"modelPolicyKind": "latest_alias",
"effectiveModel": "claude-opus-…",
"modelEvidence": "claude-stream-json:init",
"cliVersion": "…",
"exitCode": 0
}
}The same request_id returns the same job rather than scheduling the work twice.
If a later request reuses that ID with a different target executor or payload, the
relay returns request_id_conflict instead of silently routing the new request to
the old job.
Submit and wait
dispatch_wait follows the same idempotent path but waits for a terminal result, up to timeout_ms:
{
"to": "codex",
"task": { "prompt": "Review the current diff for correctness." },
"request_id": "review-current-diff-codex-2",
"timeout_ms": 120000
}If the caller timeout expires first, the result says timed_out: true and reports the current queued or running state. The job continues server-side; retrieve it later with poll.
Audit executor and model evidence
poll and dispatch_wait expose execution while the job is retained. The audit tool keeps a compact terminal history independently of normal job/result retention:
// audit
{ "job_id": "relay-…" }
// response
{
"found": true,
"job_id": "relay-…",
"records": [
{
"state": "completed",
"to": "claude-opus",
"execution": {
"requestedModelPolicy": "opus",
"modelPolicyKind": "latest_alias",
"effectiveModel": "claude-opus-…",
"modelEvidence": "claude-stream-json:init"
},
"resultSha256": "…"
}
]
}The audit ledger stores no prompt, raw result, raw error, working-directory path, or request id. It stores hashes for correlation plus allowlisted execution metadata. It is bounded to the latest 5,000 terminal records per workspace. Existing version-1 stores migrate automatically with an empty audit history.
Executor failures are redacted and capped at 8 KiB before entering the durable job state; the full stderr remains available only through the separately redacted, quota-bound stream spool.
requestedModelPolicy says what the registry requested. effectiveModel is populated only when the CLI provides structured evidence; otherwise it remains null. In particular, passing -m gpt-5.6-sol proves the Codex policy requested by the relay, not a private backend rollout identifier.
Debugging and monitoring a live relay
The relay has two non-mutating diagnostics for investigating a request from either Codex or Claude Code:
// trace
{ "job_id": "relay-…", "limit": 200 }
// health
{ "agents": ["codex", "codex-sol-xhigh", "claude-opus", "claude-opus-max", "claude-fable"] }trace returns the bounded lifecycle (claimed, running, process spawn/close,
abort, and terminal transition) plus timing and worker metadata. health returns the
MCP server instance/build id, workspace store id, worker PIDs, heartbeat age, and build
mismatch status. Neither tool returns a prompt, result, claim token, or environment
variable.
Reading a workspace stream
stream reads only the bounded tail emitted by the executor adapter:
{ "job_id": "relay-…", "limit": 100 }Events carry sequence, timestamp, channel (stdout, stderr or
provider_event), adapter, attempt and redaction metadata. They are stored in
per-attempt *.stream.jsonl files outside relay-state.json. This is a
structured process stream, not a scrape of terminal pixels or another window's
screen buffer. PTY capture remains a later capability for explicitly interactive
adapters. A completed turn also gets a deterministic run_summary in the
separate Context Store: it contains hashes, counts, model evidence, truncation
and provenance, never the full prompt or response.
Changelog e proveniência
O Context Store também deriva uma entrada de changelog por execução terminal e por
handoff. A entrada contém apenas estado, timestamps, hashes, referências de artefatos
e a cadeia sourceJobId → handoffId → targetJobId; prompts, respostas, transcript e
variáveis de ambiente não entram no arquivo. A operação MCP é somente leitura:
{ "limit": 100 }O comando changelog devolve essas entradas com entryHash, e o monitor inclui o
mesmo resumo no snapshot JSON. Assim, a passagem A → B pode ser correlacionada sem
copiar o buffer de uma tela ou confiar em texto não verificado. Se a entrada for
adulterada, ela é omitida até ser regenerada a partir do run_summary/manifesto.
Handoff entre workspaces
Um handoff nunca copia uma tela inteira nem aceita um comando do texto de uma resposta. O workspace de origem cria um manifesto em rascunho, que só se torna aprovado após uma operação explícita, com resumo, referências de artefatos/faixas de eventos, IDs de origem/destino e hash de contexto:
{
"source_job_id": "relay-source…",
"target_workspace_id": "<store-id-do-destino>",
"target_agent_id": "claude-opus",
"summary": "Contexto sugerido para a próxima revisão.",
"created_by": "llm"
}Depois que um operador local registrar a aprovação humana, no workspace de destino:
node bin/relay-handoff-approve.mjs handoff-… --by "reviewer"{ "handoff_id": "handoff-…", "request_id": "handoff-destination-1" }Isso chama handoff_dispatch. O executor é lido do manifesto hash-verificado e
continua sujeito à allowlist, às políticas de escrita e ao lease normal. Um
handoff de job que não terminou em completed, com agente desconhecido,
manifesto adulterado ou caminho absoluto de artefato é recusado.
O worker reconstrói o contexto usando o run_summary hash-verificado e registra
handoff_id, job de origem e workspace de origem nos eventos e no resultado do
destino.
O endpoint MCP handoff_approve permanece fail-closed por compatibilidade, mas
nunca aceita uma identidade declarada pelo próprio modelo. target_workspace_id, artifactRefs e
eventRanges são referências opacas limitadas e não substituem um transporte ou
catálogo de artefatos entre hosts. Essa autoridade externa fica para a fase de
multi-host.
For a longer observation window, use the passive monitor in a separate terminal:
node bin/relay-monitor.mjs --workspace . \
--agents codex,codex-sol-xhigh,claude-opus,claude-opus-max,claude-fable --follow --interval 1000
# Machine-readable snapshots for a log collector:
node bin/relay-monitor.mjs --workspace . --follow --jsonThe monitor reports queued, running, completed, failed, needs_recovery, and
stale. It never dispatches, retries, cancels, or changes a payload. A stale job is a
diagnostic signal; the durable store's deadline fence is what transitions an expired
read-only turn to failed (or a write turn to needs_recovery).
The JSON snapshot also exposes bounded changelog metadata and handoff provenance;
the text mode appends a handoff=<id> marker for destination jobs.
Derived files are pruned opportunistically after terminal summaries and can also be collected explicitly without touching queue state:
node bin/relay-gc.mjs --workspace . --max-files 5000 --max-bytes 536870912Active jobs and draft/approved handoffs are retained. Workspace-local GC only considers manifests owned by that workspace; unknown/foreign manifests and all handoff locks are retained because an mtime is not proof that a lock is idle. Dispatched/old derived files are eligible once the count or byte budget is exceeded. This command is safe to run from a scheduled maintenance job on each host.
When a smoke test remains queued with attempts: 0, run the one-shot correlator before
dispatching again:
node bin/relay-debug.mjs \
--workspace ~/Devs/mcp-agent-relay \
--agents codex-sol-xhigh,claude-opus-max \
--job-id relay-…The command reports RELAY_WORKER_AGENTS/RELAY_WORKER_AUTOSPAWN from the shell and
the Codex registration (~/.codex/config.toml by default) as separate sources; it does
not pretend that a standalone shell has the exact environment of an already-running MCP
process. It reports worker heartbeat/PID state and scans the current RELAY_DATA_DIR
(or the default state root) for the job id. It prints only metadata—never task prompts,
results, or raw error text—and exits with status 2 when a P0/P1 operational finding is
present. Typical findings are:
mcp_config_unavailable: the default Codex registration is missing or unreadable and no explicit relay worker environment was supplied;worker_not_configured: the MCP server can enqueue thatto, but autospawn has no worker configured to claim it;queued_without_worker: the job is still queued with zero attempts and no live worker;configuration_divergence: the shell environment and persisted MCP registration disagree; inspect both before changing either one;store_mismatch: the job exists in another workspace store, so the monitor is watching the wrong--workspaceunder the same state root. If the server uses anotherRELAY_DATA_DIR, run the diagnostic with that same variable explicitly.
The diagnostic is intentionally passive. After correcting the registration, restart the
Codex/Claude session so its MCP environment is reloaded, then use relay-monitor with
the same workspace and RELAY_DATA_DIR to follow the new job.
Bounded logging with journald/systemd
relay-monitor is useful interactively, but it is not a daemon. For a bounded
observation window, use relay-observe. It samples the selected workspace and writes
redacted JSONL only when a job/worker is active or changes. The record includes job
state transitions as seen by the store, worker liveness, executor/model evidence,
elapsed/deadline fields, and a coarse error_code (timeout, cancelled,
lease_expired, write_policy, or executor_error). It never writes prompts,
results, raw errors, environment variables, or worker log paths. Historical terminal
records remain available through audit/trace and are not replayed on collector
startup.
One snapshot:
node bin/relay-observe.mjs \
--workspace "$(git rev-parse --show-toplevel)" \
--agents codex,codex-sol-xhigh,claude-opus,claude-opus-max,claude-fable \
--onceTo avoid choosing the wrong project store, use --all-stores. This scans every
workspace under the active RELAY_DATA_DIR (or ~/.mcp-agent-relay/state) and is the
recommended mode for the systemd service:
node bin/relay-observe.mjs --all-stores --duration-ms 7200000 --interval 1000Observe for two hours to stdout (which can be piped to rsyslog or jq):
node bin/relay-observe.mjs \
--workspace "$(git rev-parse --show-toplevel)" \
--duration-ms 7200000 \
--interval 1000 \
--output /tmp/mcp-agent-relay-observations.jsonlFor a user-level systemd collector, install the unit on each host where a Claude or Codex session dispatches jobs:
mkdir -p ~/.config/systemd/user ~/.config/mcp-agent-relay
cp systemd/mcp-agent-relay-observer.service ~/.config/systemd/user/
cp systemd/observer.env.example ~/.config/mcp-agent-relay/observer.env
# The example uses RELAY_OBSERVER_ALL_STORES=1, so no project path is required.
# Set it to 0 and edit RELAY_OBSERVER_WORKSPACE for one project only.
systemctl --user daemon-reload
systemctl --user enable --now mcp-agent-relay-observer.service
journalctl --user -u mcp-agent-relay-observer.service -f -o catThe unit is deliberately bounded (RuntimeMaxSec=2h) and sends JSONL to journald. Stop
it early with systemctl --user stop mcp-agent-relay-observer.service; it never
redispatches or mutates jobs. Repeat the same installation in SSHWSL, using the path
of the repository and project on that machine. If that WSL instance does not run
systemd, use the CLI directly with --output and keep the terminal/process alive for
the requested window.
Every claim now carries a server-side turn deadline in addition to its lease. A worker heartbeat can keep a healthy silent turn leased, but it cannot keep an expired executor running forever. An old daemon is identified by its relay build id. A new MCP server replaces an outdated daemon only while it has no active claim; if a turn is in flight, the update is deferred so a build change cannot abort→release→reclaim that job mid-review.
Job states
State | Meaning |
| Waiting for a worker |
| Claimed by one worker with an active lease |
| Durable final result available |
| The adapter or policy rejected the job |
| Cancellation was accepted |
| A write-capable run lost its lease; it is never silently re-executed |
| A human decision is required before or after execution |
Security model
The relay assumes task prompts are untrusted data. It does not treat them as a shell request.
The central registry owns the executable, fixed CLI arguments, Claude agent/model policy, and the Codex
gpt-5.6-sol/highplus dual-rgpt-5.6-sol/xhighprofiles.A Codex task may omit
modelandeffort; if it supplies either, it must exactly match that fixed profile. A downgrade or arbitrary model selection fails before the CLI starts.Claude tasks may omit
model; any conflicting payload model is rejected before the CLI starts. Agent, binary, arguments, and environment always come from the registry.The dual-r profiles also reject payload
effortoverrides;codex-sol-xhighandclaude-opus-maxare fixed allowlist entries, not arbitrary command runners.A job can choose only an exact, known
toid. Unknown ids fail safely.Claude adapters run with a reduced environment, an isolated
localsettings source, and an empty MCP configuration; they do not load user/project hooks, plugins, MCP aliases, or payload environment values. The registry still supplies the explicit agent/model.All Claude jobs are read-only in this version.
write: truefor either Claude worker is an explicit failure before a CLI process starts.Codex writes are deny-by-default and require both a
write: truejob and a worker launched with--allow-writes.Write jobs with an expired lease go to
needs_recoveryinstead of being replayed.Worktree execution is available for eligible Codex writes, so the main worktree stays untouched.
Wake-up notifications contain only a minimal job envelope, never untrusted prompt text or model output.
This protects the relay’s command-selection boundary. It does not make a prompt harmless to the model receiving it; write careful task prompts and inspect all results.
Running workers
Autospawn is convenient for local MCP use, but explicit workers work the same queue and are useful for long-lived or supervised setups.
# Claim at most one queued job, execute it, then exit.
node worker.mjs --agent codex --once
# Keep a read-only Claude worker running.
node worker.mjs --agent claude-opus --interval 1000
node worker.mjs --agent claude-opus-max --interval 1000
node worker.mjs --agent codex-sol-xhigh --interval 1000
node worker.mjs --agent claude-fable --interval 1000
# Permit Codex write jobs (still requires task.write: true).
node worker.mjs --agent codex --allow-writes --interval 1000
# Stop after five minutes with no jobs processed.
node worker.mjs --agent codex --idle-timeout 300000
# Fail a read-only turn that still runs after ten minutes. A timed-out write
# job is preserved in needs_recovery instead of being replayed.
node worker.mjs --agent claude-opus --timeout 600000Worker selection is always based on --agent and the registry. It is never taken from a job payload.
dispatch_wait.timeout_ms limits only the MCP caller's wait. The worker has a separate
per-turn budget. An explicit --timeout or RELAY_WORKER_TIMEOUT_MS override wins; otherwise
the allowlisted profile owns the budget (600000 ms for normal profiles and 1800000 ms for
codex-sol-xhigh). The selected value is persisted as a fenced deadline in the claim; a
read-only turn that exhausts it fails terminally rather than being re-run, while a write turn
enters needs_recovery. Increasing a job's ttl_ms or the MCP caller's timeout_ms does not
silently change the worker profile's execution ceiling.
Store location
By default, state is stored beneath:
~/.mcp-agent-relay/stateSet RELAY_DATA_DIR to choose another durable local location. Every process that participates in the same relay—the MCP server, workers, and optional hooks—must use the same store location.
The version-2 store embeds a bounded audit ledger alongside the active/retained job collection so terminal transition and audit evidence are committed by the same atomic store write.
Codex writes in isolated worktrees
Codex is the only executor that can write in this first release. To opt in, start the Codex worker with --allow-writes and request an isolated worktree:
{
"to": "codex",
"task": {
"prompt": "Implement TASK-192 and add focused tests.",
"write": true,
"worktree": true
},
"request_id": "implement-task-192-1"
}The relay creates a branch and git worktree based on the caller’s current HEAD. The result includes worktree.path, worktree.branch, and worktree.baseSha for manual review and merge. Nothing merges automatically.
If a write turn makes no change, its temporary worktree and branch are removed. If it fails after making changes, the worktree is preserved and its path is included in the error for manual recovery.
A worktree starts from the last commit, not from uncommitted edits in the caller’s main worktree.
Human review gate
Jobs can require a human decision rather than running or completing autonomously.
Add a non-empty
requireReviewreason to a task to put it inneeds_reviewbefore execution.An executor can self-flag an ambiguous or sensitive task with
RELAY_NEEDS_REVIEW: <reason>in the final response; its partial result is retained for inspection.Resolve gates only from the local review CLI, never through MCP tools:
node bin/relay-review.mjs list
node bin/relay-review.mjs approve <jobId> --by "reviewer" --note "approved after inspection"
node bin/relay-review.mjs reject <jobId> --by "reviewer" --note "not safe to run"Predeclared approval returns a job to queued so it can run. Approval of a self-flagged result accepts that captured result. Rejection marks the job failed.
The CLI gate prevents a normal MCP client from approving its own job through the tools it already holds. It is not a complete process-isolation boundary: a local process with shell access can invoke the review CLI. Stronger approval authority requires a separate credential or isolation boundary.
Claude Code integration
The relay can be used from Claude Code either as a plugin or as a plain MCP server.
Plugin install
claude plugin marketplace add <your-org>/mcp-agent-relay
claude plugin install mcp-agent-relayThe plugin declares agentrelay in .mcp.json, includes the host-aware
tri-r and dual-r skills, and includes relay slash commands. The supplied commands dispatch to
codex:
/mcp-agent-relay:review <path> [focus]requests a read-only Codex review./mcp-agent-relay:implement <task>requests an isolated Codex worktree run; it needs a Codex worker allowed to write.
Plain Claude MCP install
claude mcp add --scope user agentrelay \
node /absolute/path/to/mcp-agent-relay/server.mjs \
-e RELAY_AGENT=claude-main \
-e RELAY_WORKER_AUTOSPAWN=1 \
-e RELAY_WORKER_AGENTS=codex,codex-sol-xhigh,claude-opus,claude-opus-max,claude-fablePlugin and plain-server installations expose different MCP names:
Installation | Tool prefix | Channel source |
Plugin |
|
|
Plain |
|
|
Optional wake-up channel
Set a logical RELAY_AGENT identity on the Claude session and launch it with its corresponding development channel:
# Plugin installation
RELAY_AGENT=claude-main claude \
--dangerously-load-development-channels plugin:mcp-agent-relay@mcp-agent-relay
# Plain MCP installation
RELAY_AGENT=claude-main claude \
--dangerously-load-development-channels server:agentrelayThe channel sends only a small job_id and state notification. Claude then uses poll to obtain the normal structured result. When CLAUDE_CODE_SESSION_ID is available, notifications are narrowed to the specific session that dispatched the job.
Stop hook alternative
The channel is optional. A Stop hook checks the store as Claude is about to end a turn and gives it one more turn to poll a newly completed job:
# Add project settings. Use --global for ~/.claude/settings.json.
node bin/relay-install-hook.mjsThe helper is idempotent. Use --print to preview or --remove to undo it. The hook needs the same RELAY_AGENT and RELAY_DATA_DIR configuration as the MCP server. It fails open, so an internal hook error never blocks a Claude session from ending.
Requirements and limitations
Node.js 18.18 or newer. The runtime has no npm dependencies.
The
codexworker needs the Codex CLI available on itsPATH.Claude workers need an existing local, authenticated Claude Code CLI plus the allowlisted agent definitions. The relay does not provision either.
Claude jobs are read-only only. There is no Claude write mode in this release.
claude-opustracks the Claude CLIopusalias. Useexecution.effectiveModeloraudit, not the route name, when you need to know what the CLI reported for one completed run.claude-opus-maxuses that same latest alias with a fixed--effort max; it still does not prove a numbered Opus release withouteffectiveModelevidence.effectiveModelmay benullwhen an executor does not provide structured evidence. The relay never turns a requested alias into a fabricated effective version.Handoffs are explicit and hash-verified, but human identity/authentication and a global workspace/artifact registry are outside this local-file MVP.
The store is a local file-backed queue coordinated by an interprocess lock. It is designed for one machine, not a multi-host queue.
Stream fencing is amortized in bounded batches (up to 64 KiB or 16 events) under the queue lock; this preserves lease correctness while limiting the time a slow filesystem can delay another claim or heartbeat.
Handoff lock files are never unlinked by workspace-local GC. The lock protocol itself reclaims stale JSON locks only through an atomic rename after checking process identity; legacy or malformed locks fail closed and remain until an operator removes them because an old mtime is not proof that no process still holds them.
After updating relay source, restart the MCP server/session that owns the relay.
healthanddispatchdiagnostics now exposesource_build_id/build_mismatch; the server captures a startup fingerprint for worker fencing, so a mismatch is an explicit restart signal rather than a silent stale session.The development channel is a Claude Code preview feature and may require the explicit channel flag. The queue, polling, and Stop hook remain usable without it.
Development
node --testThe suite covers the store and MCP facade, worker lifecycle, review and worktree protections, Codex compatibility, executor-registry resolution, and mocked Claude CLI success, failure, cancellation, and payload-isolation behavior.
It also verifies the host-aware tri-r and dual-r routing contracts and the idempotent cross-harness skill installers.
For an end-to-end environment check without executing a real review turn, run:
node bin/relay-cross-host-test.mjsThe harness checks the five allowlisted executor profiles, codex/claude CLI
availability (--version only), MCP registrations, installed tri-r/dual-r skills
and native profiles, passive monitor/debug startup, and npm test locally and on
ryzen5wsl. Use --json for CI/log collection, --local-only to skip SSH, and
--skip-suite for a fast configuration check. A missing CLI or skill is reported as a
failure; the optional Codex native-agent registration is reported as a warning unless
--strict is used. The remote check is read-only and does not dispatch work.
For a minimal real smoke test, which does dispatch a fixed read-only prompt through the
actual MCP server and confirms the job in relay-monitor, run:
node bin/relay-real-smoke.mjs --jsonBy default it exercises codex-sol-xhigh, claude-opus, and claude-fable locally and
on ryzen5wsl. It accepts no prompt, command, model, agent definition, or environment
from the payload; the prompt is fixed to RELAY_SMOKE_OK with write:false. Use
--agents codex,claude-opus to reduce the matrix further or --local-only to test one
host. This test requires the real CLIs and local Claude authentication; it may consume a
small amount of model quota.
To validate the first Relay Studio business slice — transferring approved context from workspace A to workspace B — run:
node bin/relay-studio-mvp0-smoke.mjs \
--source-agent codex \
--target-agent claude-opus \
--timeout-ms 180000 \
--jsonThe harness creates two temporary workspaces, starts the real MCP server and
allowlisted workers, runs a fixed read-only turn in A, creates a draft handoff,
approves it through the local operator CLI, dispatches only the handoff_id to
B, and checks both relay-monitor and the bounded changelog. It never copies a
raw buffer or accepts a command/model/agent from the payload. This is a same-host
MVP-0 check; SSHWSL transport, external PTY capture and proactive workflow
transitions remain later slices. Real CLI availability and local authentication
are required.
For a fast deterministic self-test of the relay itself, without Claude/Codex installation, authentication or model quota, run:
npm run relay:studio:self-testThis uses injected turns but the real queue, leases, worker processing,
bounded stream spool, Context Store, approval-gated handoff, destination
provenance, changelog and passive monitor. It verifies that the destination
receives bounded approved context instead of the source's private result, that
write:true is denied for Claude before the executor runs, and that unknown
profiles are rejected. Use node bin/relay-studio-self-test.mjs --keep-state --json when the temporary state must be inspected after the run.
Relay Studio context map
For the complete architecture/document map used to discuss Relay Studio with
another LLM, start at
docs/relay-studio-chatgpt-context.md.
It points to the requirements, RFC, ADR, backlog, visual artifacts, implementation
boundaries and executable tests without duplicating those specifications.
License
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceRelay MCP server enabling synchronous multi-turn communication between Hermes agents, allowing one agent to delegate tasks and await replies without third-party dependencies.14MIT
- AlicenseNot gradedqualityAmaintenanceA transport-agnostic MCP seam for messages, context sharing, and task hand-off between humans, chat bots, and coding agents. Supports multiple backends like SQLite, Redis, Matrix, NATS, and XMPP to bridge Claude chat with Claude Code.40,616MIT
- AlicenseAqualityDmaintenanceMCP server for inter-agent communication. Gives multiple Claude Code sessions a shared message board, agent registry, and orchestration layer — backed by a cloud relay so agents can coordinate across machines, repos, and teams.840MIT
- AlicenseAqualityAmaintenanceEnables real-time cross-machine communication for Claude Code agents using a shared MCP relay server.1311MIT
Related MCP Connectors
Durable agent-to-agent handoffs and shared scratchpad for multi-agent workflows.
Agent-native collaboration network: orchestrate a team of long-running agents from any MCP client.
Coordinate multiple AI agents over MCP: atomic claims, leases, shared ledger, handoffs, tasks.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/brenoperucchi/mcp-agent-relay'
If you have feedback or need assistance with the MCP directory API, please join our Discord server