Skip to main content
Glama

Baton is a tiny, zero-dependency MCP server that gives your agents a shared, on-disk handoff baton. One .baton/ folder per project becomes the shared brain that survives the death of any single session.

Works with Claude Code, Codex, and any MCP-capable client.

The problem

Two agents on the same repo still can't hand off work:

  • Instructions are siloed. Claude Code auto-reads CLAUDE.md; Codex auto-reads AGENTS.md. Point both at one folder and one of them starts blind.

  • Session state is private and lossy. Claude stores transcripts in ~/.claude/…, Codex in ~/.codex/…. Neither reads the other's, and replaying a raw transcript is expensive and lossy. The intent — which step you're on, what you just learned, why you stopped — lives in the context window and dies when the session ends.

So "let Codex continue what Claude started" fails: the second agent sees the files, but not the plan.

Related MCP server: ContextPocket MCP Server

The fix

Baton writes the intent to disk in a format both sides read:

File

What it holds

HANDOFF.md

Human- and agent-readable "where we stopped / what's next."

.baton/baton.json

Structured live state — done, next, open questions, gotchas, key files.

.baton/ledger.jsonl

Append-only history. Crash-resilient: breadcrumbs survive even if an agent dies mid-task.

baton_init also bridges CLAUDE.md ⇄ AGENTS.md with a symlink, so both agents load the same instructions instead of one starting blind.

Install

No install step — it runs straight from GitHub via npx:

npx -y github:timurabi3/baton-mcp
# BATON_AGENT tags this agent in every handoff
claude mcp add baton -e BATON_AGENT=claude-code -- npx -y github:timurabi3/baton-mcp
[mcp_servers.baton]
command = "npx"
args = ["-y", "github:timurabi3/baton-mcp"]
env = { BATON_AGENT = "codex" }

Then add one line to your instructions (CLAUDE.md / AGENTS.md):

At session start, call baton_pick_up. Before you stop, call baton_pass.

The relay in practice

Claude Code ──(hits context limit)──▶ baton_pass { handoffNote, next, watchOut }
                                          │  writes .baton/ + HANDOFF.md
Codex       ──(fresh session)────────▶ baton_pick_up  ◀── reads it, continues

BATON_AGENT tags each pass, so the ledger reads like a relay log:

14:02 [claude-code] pass: moved 25 project folders; venvs for ayra-caller/cashclaw need rebuild
14:05 [codex]       pick_up
14:31 [codex]       pass: rebuilt venvs, wired both agents

Tools

Tool

When to call it

baton_status

Session start — is there a baton here?

baton_pick_up

Continue where the last agent left off. Returns the full handoff + recent ledger.

baton_pass

Stopping — record where you left off (merges state, rewrites HANDOFF.md).

baton_log

Drop a mid-task progress breadcrumb.

baton_history

Read the recent ledger.

baton_init

Create .baton/ and bridge CLAUDE.md ⇄ AGENTS.md.

Design notes

  • Zero dependencies. MCP stdio is newline-delimited JSON-RPC 2.0 — implemented directly, so npx works offline and the whole server is auditable in a single file.

  • stdout is protocol-only; all logs go to stderr.

  • Storage is per-project (.baton/ under the project root; override with BATON_PROJECT).

  • The full on-disk contract lives in PROTOCOL.md — an open, agent-neutral convention, not something private to one vendor.

Good to know

  • The baton is shared, plain-text state — read and edit .baton/ by hand any time; there's no database and no lock-in.

  • An unrelated package named baton-mcp exists on npm. Install from GitHub as shown above.

License

MIT © Timur Oral

Available Tools

6 tools
baton_historyB

Read the recent handoff/progress ledger for this project.

Inputs: limit, project.

baton_initA

Set up Baton for a project: create .baton/ and bridge CLAUDE.md <-> AGENTS.md (symlink) so both Claude Code and Codex read the same instructions.

Inputs: project.

baton_logA

Drop a lightweight progress breadcrumb into the ledger without a full baton pass. Use for mid-task milestones.

Inputs: note (required), project.

baton_passA

PASS THE BATON. Record where you're stopping so the next agent/session continues seamlessly. Merges with existing state — pass only the fields you want to update. Rewrites HANDOFF.md.

Inputs: handoffNote (required), done, goal, next, +6 more.

baton_pick_upA

PICK UP THE BATON. Returns the full current handoff state + recent history so you continue exactly where the previous agent (or session) stopped. Call this before doing any work if a baton might exist.

Inputs: project.

baton_statusA

Check if a baton (handoff state) exists for this project and get a one-glance summary. Call this at session start.

Inputs: project.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 6 tools

    Disambiguation4/5

    Most tools map to a distinct step in the handoff workflow with clear usage cues. status, history, and pick_up all expose handoff information at different levels of detail, so there is minor potential for confusion among the read-focused tools.

    Naming Consistency4/5

    All tools share the baton_ prefix and use snake_case, which makes the set feel coherent. The mix of verb-like names (pick_up, pass, log, init) and noun-like names (status, history) is a small inconsistency, but the pattern remains predictable.

    Tool Count5/5

    Six tools is well-scoped for a handoff utility: setup, status check, pick-up, history, progress logging, and passing cover the workflow without unnecessary bloat.

    Completeness4/5

    The core lifecycle is covered: initialize, check, pick up, log progress, and pass the baton. The main gap is the lack of a reset, clear, or discard operation for ending or resetting a handoff state.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers