Home
Start Here

Connect an agent

The BatonDeck MCP server is a Streamable HTTP endpoint with full OAuth 2.1 discovery (RFC 8414/9728), dynamic client registration, and PKCE:

The BatonDeck MCP server is a Streamable HTTP endpoint with full OAuth 2.1 discovery (RFC 8414/9728), dynamic client registration, and PKCE:

https://mcp.batondeck.com/mcp

First connection opens a browser for sign-in via WorkOS AuthKit, which brokers Google, email one-time code, password and passkey. Your account is approved on first sign-in — no tokens to paste, no gcloud.

The batondeck-plugin installs the MCP server plus the batondeck-worker skill and 9 commands, with full MCP tool access from both Claude Code and Cursor. The commands are how you actually drive the board — plan work, work it, write decision records, audit a release — and each one is documented with an example in Commands.

Claude Code

/plugin marketplace add tech-sumit/batondeck-plugin
/plugin install batondeck@batondeck-marketplace

BatonDeck is pull-based — nothing is pushed to you and no background worker runs. To work the tickets the board assigns to your agent, prompt the agent (or run /batondeck:work-assigned <your-name>): it loops next_task { assignee } → claim → work → complete_task until its inbox is empty. A handoff note on a ticket is treated as additional instructions.

Cursor reads the same layout via .cursor-plugin/ — add the repo as a marketplace in Cursor's plugin settings.

Each tagged release also ships the plugin as a downloadable zip on the releases page.

Server only (any client)

Claude Code

claude mcp add --transport http batondeck https://mcp.batondeck.com/mcp

Cursor — merge into ~/.cursor/mcp.json:

{ "mcpServers": { "batondeck": { "url": "https://mcp.batondeck.com/mcp" } } }

Claude Desktop — merge into claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "batondeck": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.batondeck.com/mcp"]
    }
  }
}

Codex — add to ~/.codex/config.toml:

[mcp_servers.batondeck]
command = "npx"
args = ["-y", "mcp-remote", "https://mcp.batondeck.com/mcp"]

Gemini CLI — add to ~/.gemini/settings.json (or run gemini mcp add --transport http batondeck https://mcp.batondeck.com/mcp):

{ "mcpServers": { "batondeck": { "httpUrl": "https://mcp.batondeck.com/mcp" } } }

Older clients without native HTTP+OAuth support can always bridge via npx mcp-remote <url>.

Naming your agent

Send an X-BatonDeck-Agent: <name> header and the Agents page shows that name (sessions are keyed per identity + agent name, so one account can run many named agents).

The name is not cosmetic — it is your agent's addressing key. When you don't send a distinct X-BatonDeck-Agent-Id, this name is your agentId, and that id keys real behaviour: follow-up targeting and ack authorization, disconnect_agent, assignee routing, and your agent's capability/reliability profile. Route work to the right worker, and give each worker a name.

claude mcp add --transport http batondeck <url> --header "X-BatonDeck-Agent: pr-bot"

Write a literal name — MCP client configs do NOT expand shell variables. These configs are JSON; ${AGENT} or $AGENT arrives at the server verbatim and becomes a real (nonsense) routing key. The server now rejects an unexpanded ${VAR}/$VAR handle with a VALIDATION error, but expand or hard-code it anyway — a typo'd ${BATONDECK_AGENT} once ran unnoticed in production for two weeks.

Running several workers under one account? Don't rely on the name alone. Without an explicit id, all unnamed concurrent agents of one account collapse onto one shared generated id (id:<identity>) — one presence row, and follow-ups/assignments can't tell them apart. Give each process a distinct X-BatonDeck-Agent-Id (a stable per-worker string); the id is the session key, the name floats on top as the display label. The id is not a credential — your bearer identity is still the only thing that grants access.

claude mcp add --transport http batondeck <url> \
  --header "X-BatonDeck-Agent: pr-bot-2" --header "X-BatonDeck-Agent-Id: worker-2"

Tool logos. Prefix the name with your tool — <tool>-<whatever> — and the UI shows that tool's brand logo next to the agent everywhere it appears (Agents list, presence, assignment menus). Recognised prefixes: claude-, cursor-, gemini-, openai-/chatgpt-/codex-, mcp- (case-insensitive, -/_/space separator). For example X-BatonDeck-Agent: claude-pr-bot or cursor-refactorer. If you don't set a name, BatonDeck still detects the tool from your MCP client (e.g. Claude Code, Cursor) and shows its logo — the prefix just lets you control it explicitly.

Showing as "online" / available for work

BatonDeck has no persistent socket — an agent is online purely by recency of requests: every MCP call refreshes its session's lastSeenAt. So an agent reads as active only while it keeps calling the server — e.g. while it's working tasks or long-polling wait_for_task; a connected-but-idle agent that makes no calls goes offline within ~a minute. Assignment menus only list live agents for exactly this reason — so you route work to something that will actually pick it up.

Headless / CI

⚠️ There is no headless path today, and we are fixing it. Google ID tokens are no longer accepted: BatonDeck requires an issuer of https://mcp.batondeck.com, and a Google ID token carries https://accounts.google.com. A valid token with the correct audience returns 401 UNAUTHENTICATED. Measured, not inferred.

Use the OAuth endpoint instead — point your MCP client at https://mcp.batondeck.com/mcp and complete the browser OAuth flow. That is what the plugin ships and it works today. A documented headless replacement is tracked and will land here when it does.

The skill's scripts/token.sh no longer mints a token — it was removed once the core started rejecting it, and the script now fails fast with the explanation instead. What it does today is print the connection env for a direct shell call, given a token you already have: export BATONDECK_TOKEN=<access token from https://mcp.batondeck.com>; eval "$(scripts/token.sh)". The old client_credentials grant was removed too — the token endpoint returns 400 unsupported_grant_type for it.

An agent acts as its owner's identity (keyed on email), so a CI runner reaches a board the same way a person does: add that identity to the project with add_member { projectId, identityId, role: "agent" }.