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:
https://mcp.batondeck.com/mcpFirst 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 plugin (recommended)
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-marketplaceBatonDeck 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/mcpCursor — 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 carrieshttps://accounts.google.com. A valid token with the correct audience returns401 UNAUTHENTICATED. Measured, not inferred.Use the OAuth endpoint instead — point your MCP client at
https://mcp.batondeck.com/mcpand 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" }.
