showrunner
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., "@showrunnerYou're a worker for the spireash show."
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.
showrunner
A tiny always-on server that coordinates multiple coding-agent sessions on one project (a "show"). Deploy it once. Then, from inside any project repo, tell any agent session, local or cloud, Claude Code or Cursor:
"You're a showrunner worker."
It works out which show from the repo it's sitting in (git remote name, or the directory name), registers, starts pulling tasks, and reports back. Tell a second session:
"You're the showrunner director."
It takes over planning: breaking work into tasks, answering blockers, reviewing completions. Kill that session anytime; state lives on the server, not in the session. Tell a new one "you're now the director" and it picks up exactly where the last one left off.
One SQLite file. One static dashboard. Eight MCP tools. No queue infra, no worktree manager, no per-agent push channel (agents poll; that's the only thing that works for cloud sessions anyway). See DESIGN.md for the full rationale.

A worker session, after the sentence:
> You're a showrunner worker.
⏺ showrunner:register({show: "mygame", kind: "claude-local"})
← member_id "eager-crane"
⏺ showrunner:await_work({member_id: "eager-crane"})
← task t-19ebd84582 "Invite links expire after 24h" (brief: apps/gateway/src/invites.ts)
⏺ …works on branch show/t-19ebd84582-invite-expiry, heartbeating update_task…
⏺ showrunner:update_task({status: "input-required", note: "renew flow or plain 410?"})
⏺ showrunner:send_message({to: "director", body: "Invite expiry UX: renew flow or 410?"})
⏺ showrunner:await_work(…) ← the director's answer arrives here; work resumesThe director session runs the same loop from the other side: it breaks work into tasks, reviews what comes back, and answers blockers. Kill it whenever; a new session with "you're now the director" picks up the same board.
(The screenshot is reproducible: scripts/seed-demo.mts fills a local server
with this demo show.)
60-second quickstart
fly launch --no-deploy # creates the app from fly.toml, skips first deploy
export SHOWRUNNER_TOKEN=$(openssl rand -hex 24) # keep this shell open, you'll need it below
fly secrets set SHOWRUNNER_TOKEN=$SHOWRUNNER_TOKEN
fly deployConnect a local Claude Code session (same shell, so $SHOWRUNNER_TOKEN is still set):
claude mcp add --transport http showrunner https://<your-app>.fly.dev/mcp \
--header "Authorization: Bearer $SHOWRUNNER_TOKEN"(Need the exact command for your deployed URL, or a Cursor config instead?
Clone this repo, npm install && npm run build, then run
node dist/cli/index.js snippets --url https://<your-app>.fly.dev to print
all of them -- see CLI below for why it's not npx showrunner.)
Now open a session in any project repo and say the sentence:
"You're a showrunner worker."
Watch it register and start polling at https://<your-app>.fly.dev.
Related MCP server: claude-sync
Connecting each client
Client | How | Notes |
Claude Code local |
| v2.1.2xx+; the 25s poll needs no timeout tuning |
Claude Code cloud | commit | no browser OAuth in cloud sessions, bearer token only |
Cursor local |
| 3.0+ required; allowlist/auto-run the showrunner tools or the poll loop stalls on approval prompts |
Cursor cloud | paste URL + a hardcoded token into the cursor.com/agents dashboard MCP config | best-effort: env interpolation and repo-committed config are both broken there as of 3.8 |
Committed .mcp.json (Claude Code, local and cloud):
{
"mcpServers": {
"showrunner": {
"type": "http",
"url": "https://<your-app>.fly.dev/mcp",
"headers": { "Authorization": "Bearer ${SHOWRUNNER_TOKEN}" }
}
}
}.cursor/mcp.json (Cursor local):
{
"mcpServers": {
"showrunner": {
"url": "https://<your-app>.fly.dev/mcp",
"headers": { "Authorization": "Bearer ${env:SHOWRUNNER_TOKEN}" }
}
}
}Working examples with a placeholder URL are in examples/. Or, from
a clone of this repo, run node dist/cli/index.js snippets --url <your-url>
(after npm install && npm run build) to get all of the above with your URL
already filled in.
Direction and takeover
There's one director per show, held as a lease, not tied to any session.
First session to say
claim_directionbecomes director.The human is the only one who can force a takeover: "you're now the director" calls
claim_direction(takeover: true), which always wins.No auto-promotion. If the director's session dies, the show runs headless: workers keep draining the queue, the callboard shows "no director", and nothing self-appoints. You decide who directs next.
Old director sessions find out they've been superseded the next time they call a director-only tool: the server returns a structured
supersedederror naming the new holder. That's the whole handoff protocol, no gossip between agents required.
The callboard
A static page at your server's root (https://<your-app>.fly.dev/), polling
every 2s. Enter the bearer token once (kept in localStorage). Shows:
Director card: who, epoch, lease freshness, last digest note.
Members: kind (claude-local, cursor-cloud, ...), role, current task, a staleness dot.
Task columns: queued / in-flight / needs-input / done+failed. Click a task to expand its journal.
Activity feed: last 50 notes and messages.
Red escalation banner: any
input-requiredtask, or any message addressed tohuman. This is the thing you actually watch for.Admin strip: post a message, create a task by hand, cancel a task, clear direction (same token, no separate login).
How it works
State is one SQLite file (
better-sqlite3, WAL) on a Fly volume. Nothing else is stateful.Every claim is a lease with a TTL: worker liveness, task ownership, direction. A background sweep (every
SWEEP_INTERVAL_S) expires stale leases and requeues their tasks; nothing is trusted to say goodbye.Director-only calls carry an
epochinteger; the server bumps it on every claim and rejects stale epochs. That's the entire fencing mechanism.Workers can't receive pushes (cloud sessions are outbound-only), so
await_workis a long-poll: it blocks up toPOLL_HOLD_SECONDS(default 25, chosen to clear Cursor/Fly/Claude Code's independent ~60s connection walls) and the worker re-polls immediately after.Polls return unread-only messages and pointer-style task briefs, not inlined specs, to keep the coordination overhead in tokens small.
Env knobs
Var | Default | Meaning |
| (required) | Bearer token gating |
|
| HTTP port. |
|
| Where the SQLite file lives. |
|
| Max long-poll hold for |
|
| Member liveness lease; renewed by any tool call. |
|
| Task ownership lease; renewed by |
|
| Director lease; renewed by any director tool call. |
|
| How often the reclaim sweep runs. |
CLI
This package isn't published to npm (the name showrunner is already taken
by an unrelated app, and this is a single-person tool, not a public package),
so it's node dist/cli/index.js <command> from a clone, not npx showrunner.
Build once (npm install && npm run build), then optionally alias it:
alias showrunner="node $(pwd)/dist/cli/index.js"Reading SHOWRUNNER_URL / SHOWRUNNER_TOKEN from the environment (or
--url / --token flags):
showrunner status [--show <name>]
showrunner task add --show <name> --title <t> --brief <b> [--priority <n>] [--assignee <id>] ...
showrunner message --show <name> --to <member-id|director|all|human> --body <text>
showrunner instructions
showrunner snippets [--url <url>]This is a human convenience over /api, not something agents call. Agents
talk MCP.
Verifying a deployment
scripts/live-verify.mts drives the full lifecycle against a real deployment
with the real MCP SDK client: register, claim direction, create task, worker
claims and completes, director reviews, mid-poll wake latency, takeover and
stale-epoch fencing, callboard, auth rejection.
SR_URL=https://<your-app>.fly.dev SR_TOKEN=<token> npx tsx scripts/live-verify.mtsIt creates a throwaway verify-* show; safe to run against a server in use.
FAQ
The director's session dies mid-run. What happens?
Nothing is lost. Tasks, journals, and messages are all on the server. Workers
keep pulling from the queue. The callboard marks the director card stale once
its 10-minute lease lapses (or shows "no director" if you use the callboard's
"clear direction" button first). Say "you're the director" to any session and
it calls claim_direction(takeover: true) and resumes with full board state
-- a normal claim_direction (no takeover) works too once the lease has
actually expired.
The server restarts (e.g. fly deploy)?
No tasks lost, state is in SQLite on the volume. Workers notice their next
poll fails, retry, and reconnect once the new machine is up. In-flight leases
just expire and requeue like any other stall.
A worker goes silent (crashed, context ran out, laptop closed)?
Its worker lease expires (WORKER_LEASE_S), the callboard marks it stale,
and any task it held requeues with attempt bumped once its task lease
(TASK_LEASE_S) also expires. Nobody has to notice and reassign by hand.
Token cost discipline?
await_work returns unread-only; get_board is a ~300-token summary unless
you ask for verbose. Keep task briefs as pointers into the repo ("see
docs/combat.md §3"), not inlined specs. Cheaper, and it also keeps secrets
out of the server.
Why is Cursor cloud "best-effort"?
Cursor's cloud dashboard doesn't read MCP config from the repo and doesn't
support ${env:...} interpolation there as of 3.8, so you paste a hardcoded
token into their UI. It works, but it's outside what the repo-committed
config controls, and it'll need re-pasting if Cursor changes that surface
again.
Security posture
One shared bearer token gates everything. Anyone with it can read and write
every show on the deployment. That's the intended tradeoff for one person
running several sessions. Rotate it with fly secrets set SHOWRUNNER_TOKEN=...
if it leaks. No per-member tokens or per-show ACLs in v1.
License
MIT, see 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.
Latest Blog Posts
- 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/khou/showrunner'
If you have feedback or need assistance with the MCP directory API, please join our Discord server