parliament-mcp
v0.2.0-rc.1
Published
Multi-agent plan-debate room served over MCP — chat agents from any vendor (Claude Code, Codex, Cursor, ...) join a shared room, debate a plan through distinct lenses, and a Speaker moderates and produces a verdict.
Maintainers
Readme
parliament-mcp
A multi-agent plan-debate room served over MCP. Chat agents from any vendor — Claude Code, Codex, Cursor, anything that speaks MCP — join a shared room from their own chat windows, debate a plan through distinct adversarial lenses, and a Speaker moderates and produces a verdict with a consolidated review.
Because each panelist is just a chat window, model and effort are per-window choices — all on your existing subscriptions, no API keys.
Scope (v0.2): local, single machine, single user. All windows in one debate must run the same parliament-mcp version.
Install
npm install && npm run build # (from source; npm package pending)
# 1) Register the MCP server in each client
claude mcp add --scope user parliament -- node "$PWD/dist/index.js"
codex mcp add parliament -- node "$PWD/dist/index.js"
# 2) Install the slash commands (/start-debate, /enter-debate, /exit-debate, /stop-debate)
node dist/index.js install-commandsConnectivity check — in any chat window, ask the agent to call the parliament list_debates tool. On a fresh install the expected response is an empty list []: that means working, no debates yet. An error means the MCP registration is wrong.
Avoid approval prompts (recommended — a debate stalls behind any prompt you're not watching):
- Claude Code — add to
~/.claude/settings.json:{ "permissions": { "allow": ["mcp__parliament"] } } - Codex — approvals follow your
approval_policyin~/.codex/config.toml;never(or approving the server once) covers it.
Pin the version once published ([email protected], not @latest) — mixed versions across windows are detected and refused, but pinning avoids hitting that mid-debate.
After upgrading parliament-mcp, reconnect every open session. MCP servers load their code once, at session start: an already-open chat window keeps running the OLD version until its server reconnects (/mcp → parliament → reconnect in Claude Code; restart the session in Codex). Rooms record which version created them (schemaVersion is the compatibility counter — bumped for format or coordination-semantics changes) and every join stamps the joining server's version; skew produces a loud warning naming the seat.
The happy path
Speaker (the chat that has the plan): create_debate → wait_for_event loop → produce_verdict.
Panelist (each other window): join_debate → post_opening → wait_for_mic loop → post_turn.
That's the whole conceptual model. In practice you type:
/start-debatein the chat where you planned something — optionally/start-debate panel: opus, codex:xhighto auto-launch the panelist terminals (macOS only; otherwise it prints join commands to paste). The agent digests the plan, writes the briefing, creates the room, and hands out per-seat join commands.- Each panelist window:
/enter-debate <debate-id> <seat-id> <seat-token>— keep these commands; they are also the recovery path. - Blind openings are filed (sealed until all are in), the floor opens, seats debate by point id, pass when done. Talk to the Speaker anytime — your scope changes broadcast as amendments.
- The Speaker rules on points, then delivers the verdict. Everything lands in
~/.parliament/debates/<id>/:briefing.md, per-turn transcripts,amendments.log,verdict.json,consolidated-review.md.
Recovery shelf (for humans)
Chat windows die: turn limits, closed terminals, context compaction. The debate survives all of it — state lives on disk, not in any window.
| Symptom | Fix |
| --- | --- |
| A panelist window died / is confused / lost context | Re-paste its original join command (any window). Re-join is idempotent and returns full state. Claude windows launched by /start-debate panel: also get a claude --resume <uuid> command that reopens the original session with context intact. |
| Mic stuck on a dead seat | The Speaker's poll surfaces a stalled warning naming the seat. Speaker: grant_mic to another seat, or drop_seat the dead one (its points survive; it can rejoin later). |
| A seat never joined and assembly is stuck | Speaker: open_floor (starts with the ≥2 seats that filed; absentees are dropped, can rejoin). |
| Want a quick look without blocking | get_turn_board / get_ledger (non-blocking reads). |
| A panelist window is filling up / degrading | There is no reliable way for an agent (or this server) to detect context exhaustion — don't wait for one. The seat can post a wrap-up turn (its points stay open), leave, and a fresh window re-pastes the same join command: the debate loses nothing but the agent's unexpressed private reasoning. |
| Everything is on fire | /stop-debate — halts immediately, state preserved on disk. |
Design invariant (tested): the human plus the Speaker window alone can always advance or end a debate — without hand-editing files, and without the Speaker ever speaking for an absent panelist.
Live viewer
Watch a debate like a live stream:
parliament-mcp view <debate-id> # prints a random loopback URLOne read-only page, updating live over SSE: turns as they land, current mic holder, ledger statuses, stall warnings, the verdict. No controls, no writes — it is strictly a reader of room.json (reconnects resume without gaps or duplicates). parliament-mcp history lists concluded debates from the history records written at verdict time.
Viewer trust note (read before running it): the debate files are protected by file permissions (0600 — your OS user only). The viewer changes that boundary while it runs: loopback restricts by host, not by user, so on a shared machine any local account or process that can reach 127.0.0.1 on the printed port can read the entire debate, briefing included. Stop the viewer when you stop watching.
Honest telemetry
The turn board reports per-seat server_payload_bytes / est_payload_tokens: exact bytes of event-bearing tool results this server serialized to that seat (est tokens = bytes/4). It is Parliament traffic only — not billing tokens, not the agent's context usage, and it has no denominator: never read it as "% of context". Seat identity and session ids carry per-field provenance (launcher_requested / agent_observed_corroborated / agent_observed_uncorroborated / self_reported_unverified) and the tiers are never merged. A recorded session id is a correlation key into your local transcript store — treat debate/history files as referencing potentially sensitive local transcripts; redact ids before pasting into shared channels or bug reports.
Trust model — read this
Parliament has no authentication. Its trust boundary is your OS user account: any process running as you — including unrelated background agents — can read and modify every debate, briefing, and verdict under ~/.parliament (created 0700/0600, which keeps other users out, nothing more). Seat credentials prevent accidents (a confused window posting into the wrong seat), not attacks. Blind-opening sealing is advisory: a panelist agent with shell access could read the files early; nothing running as the same user can be stopped from doing so. Do not put secrets or credentials in a briefing.
Tools
Happy path — panelist: join_debate, post_opening, wait_for_mic, post_turn · speaker: create_debate, wait_for_event, produce_verdict
Speaker moderation — post_amendment, update_point_status, extend_debate (advisory log)
Recovery shelf — grant_mic, drop_seat, open_floor, get_turn_board, get_ledger, leave_debate, stop_debate
Discovery — list_debates, get_briefing
Dev
npm run smoke # e2e: 3 clients/3 processes — full lifecycle + recovery matrix
# (credentials, idempotent re-join, stall detection, drop_seat,
# forced floor-open, ruling protection, schemaVersion guard, file modes)Known-untested (tracked from the v0.2 design debate): the context-growth/compaction endurance hypothesis — whether hours of idle polling degrades panelist quality in real chat harnesses. Run a long live debate before trusting the default poll timeout.
