Local MCP server for coordinating multiple coding agents in one repository through a shared on-disk chatroom.
The v2 model is topic-centric:
- agents coordinate inside explicit topics
- unread state is tracked per participant per topic
- handoffs are topic-scoped
- humans inspect chat through a read-only viewer/export surface
Use it when:
- multiple agents need a shared place for status, blockers, and handoffs
- you want resume context across sessions without adding a network service
- you want lightweight coordination that is visible to every agent working in the repo
Do not use it for private messaging, push delivery, or automatic orchestration.
- Quick Start
- V1 And V2
- Who This Is For
- Common Tasks
- First Successful Run
- Topic Workflow
- Viewing Chats
- Security Model
- Important Constraints
- Troubleshooting
- Monitor And Debugging
- Advanced Deliberation Conventions
- For Repository Maintainers
- Reference
This repository now ships the v2 model.
- v2 runtime state lives under
.chatroom_v2/ - v1 runtime state lived under
.chatroom/ - the v2 server and viewer do not read or migrate v1 state
If you used v1 previously, old .chatroom/ history will not appear in the v2 viewer.
Agent operators
- configure MCP clients to launch the server
- ensure agents use stable participant names
- decide how topics should be named in the repository
Agents
- join the chatroom
- choose or open a topic
- read handoff or unread state for that topic
- send messages and write summaries inside that topic
Humans
- inspect topics, participants, summaries, and messages through the read-only viewer
- use JSON output when they want to pipe chat state into other local tooling
- cannot post messages or manage topics through a dedicated human UI in this version
See what topics exist
python3 chatroom_monitor.pyThis shows the overview: active participants, visible topics, latest activity, and summary snippets.
Inspect one topic
python3 chatroom_monitor.py --topic parser-refactorThis shows only one topic's metadata, latest summary, and recent messages.
Check what one participant has not read
python3 chatroom_monitor.py --participant codex
python3 chatroom_monitor.py --participant codex --unread-only
python3 chatroom_monitor.py --topic parser-refactor --participant codex
python3 chatroom_monitor.py --participant codex --latest-topicOverview mode shows per-topic unread counts for that participant, and --unread-only narrows that overview to topics with unread messages. --latest-topic is a navigation shortcut that opens the most relevant topic for that participant. Topic mode still shows only messages visible to that participant in that topic.
Export chat state to another local tool
python3 chatroom_monitor.py --format json
python3 chatroom_monitor.py --topic parser-refactor --format jsonUse overview JSON when you want topic/status data. Use topic JSON when you want one topic's summary and messages.
Requirements:
- Python 3
mcp
Install the only dependency:
python3 -m pip install mcpConfigure your MCP client to launch chatroom_mcp_server.py.
This server is not limited to Claude Code or Codex CLI. It works with any client that supports launching a local stdio MCP server. The sections below are example client-specific configurations, not an exclusive list.
Create a .mcp.json file in the project root:
{
"mcpServers": {
"chatroom": {
"command": "python3",
"args": ["chatroom_mcp_server.py"]
}
}
}Add this to .codex/config.toml:
[mcp_servers.chatroom]
command = "python3"
args = ["chatroom_mcp_server.py"]If your client supports launching a local stdio MCP server, configure it to run:
- command:
python3 - args:
["chatroom_mcp_server.py"]
Different clients use different config files and UI flows, but they all need to launch the same server command above.
Important launch detail:
- shared state lives under
.chatroom_v2/ - by default, the server resolves the chatroom root from the directory containing
chatroom_mcp_server.py - if your client launches the server from elsewhere, use an absolute path for
chatroom_mcp_server.pyor setCHATROOM_ROOT=/absolute/path/to/repo - treat the MCP launch command and arguments as trusted static configuration; do not derive them from prompts, chat messages, web requests, or other untrusted input
Once configured:
- Start your agents in the same repository.
- Have each agent call
joinwith a unique stable name such asclaudeorcodex. - Open or choose a topic before resuming context.
- On resumed sessions, call
get_handoff(name=..., topic_id=...)before replaying chat history.
Call these tools from two agents connected to the same repository:
Agent A:
join(name="alice")
open_topic(topic_id="parser-refactor", title="Parser Refactor")
send_message(name="alice", topic_id="parser-refactor", to="all", content="Started parser fix; will post tests next.")
Agent B:
join(name="codex")
get_handoff(name="codex", topic_id="parser-refactor")
Expected result:
- both names appear in the participant list
- the topic appears in
list_topics() codexsees the message inrecent_messagesand an unread count greater than0.chatroom_v2/is created in the repository and added to.gitignoreif it was missing
If you want the raw unread message list instead of the compact handoff payload, call:
read_unread(name="codex", topic_id="parser-refactor")
Register the agent with a stable name and optional role:
join(name="codex", role="review")
Use list_participants() if you want to see who is currently active.
Open a new workstream:
open_topic(topic_id="parser-refactor", title="Parser Refactor")
List active and closed topics:
list_topics()
list_topics(status="all")
If you are resuming work, pick the topic first, then call:
get_handoff(name="codex", topic_id="parser-refactor")
That gives you the current participants, the topic record, the unread count for that topic, the latest relevant summary, and a recent message window. If you only want unread messages, use:
read_unread(name="codex", topic_id="parser-refactor")
If you only want the latest written handoff for a topic and summary scope, use:
read_latest_summary(topic_id="parser-refactor", scope="all")
Topic hygiene matters:
- keep one topic focused on one workstream
- do not collapse unrelated work into one topic just to reduce topic count
- do not create multiple overlapping topics for the same decision unless you want fragmented handoff state
- close topics when the workstream is actually done
Broadcast within a topic:
send_message(name="codex", topic_id="parser-refactor", to="all", content="Taking ownership of the monitor test failures.")
Send a directed message within a topic:
send_message(name="codex", topic_id="parser-refactor", to="alice", content="Need your decision on the retry semantics.")
Write a durable summary for the next session in the topic:
write_summary(name="codex", topic_id="parser-refactor", scope="all", content="Parser refactor is in. Remaining work: Windows path edge cases and monitor coverage.")
Use these when you need a quick status check:
list_participants()
get_status()
get_status() returns aggregate counts and the last activity timestamp. list_participants() tells you who is currently present.
Most users can ignore cursors and rely on get_handoff or read_unread for a specific topic. If you need explicit unread control:
get_cursor(name="codex", topic_id="parser-refactor")
set_cursor(name="codex", topic_id="parser-refactor", message_id=42)
Humans should use the read-only viewer instead of reading raw state files.
Overview mode:
python3 chatroom_monitor.py
python3 chatroom_monitor.py --format jsonTopic mode:
python3 chatroom_monitor.py --topic parser-refactor
python3 chatroom_monitor.py --topic parser-refactor --participant codex
python3 chatroom_monitor.py --topic parser-refactor --format jsonParticipant-focused convenience forms:
python3 chatroom_monitor.py --participant codex --unread-only
python3 chatroom_monitor.py --participant codex --latest-topicThese flags only help you navigate to the right topic. They do not turn the viewer into a room-wide conversation feed or a write surface.
Useful options:
python3 chatroom_monitor.py --status all
python3 chatroom_monitor.py --limit 50
python3 chatroom_monitor.py --interval 0.5
python3 chatroom_monitor.py --onceThe viewer is read-only.
- Overview mode shows topics and room status, not a mixed room-wide message tail.
- Topic mode shows one topic at a time.
--participant <name>applies visibility filtering and unread calculations for that participant.--unread-onlyhides overview topics with zero unread messages for the selected participant.--latest-topicresolves and opens the most relevant topic for the selected participant.--format jsonis for machine-readable export, not just terminal viewing.
What to expect in each mode:
- Overview mode:
- topic IDs and titles
- topic status such as
openorclosed - latest activity timestamps
- latest summary snippets
- unread counts if a participant filter is provided
- Topic mode:
- topic metadata
- latest relevant summary
- recent messages from that topic only
- participant-filtered visibility when requested
If --participant <name> --unread-only shows no topics, that means the participant currently has nothing unread in the visible topic set. If --participant <name> --latest-topic cannot resolve a topic, that means there is no matching topic after the current status and visibility filters are applied.
This project is a local stdio MCP server. It does not accept MCP server definitions from users, does not launch other MCP servers, and does not expose a network listener.
The stdio launch configuration itself is still sensitive because MCP clients start the configured command as a subprocess. Keep the command and arguments static and repository-owned. Do not build a UI, API, agent workflow, marketplace importer, or prompt-editable configuration path that lets untrusted input choose command, args, transport, or environment variables for this server.
If a downstream wrapper adds network access, it becomes responsible for normal network controls: bind to localhost by default, require authentication for non-local use, validate browser Origin headers, and avoid exposing the chatroom state directory to untrusted tenants. That wrapper is outside this server's current security boundary.
- This is local-only. There is no network transport, auth, or encryption.
- MCP client launch configuration must be trusted static configuration, not user-controlled data.
- Live participant names are exclusive. If one running process has joined as
codex, another running process cannot also join ascodex. joinonly registers presence. It does not instruct other agents how to behave or make them automatically respond.send_messageonly appends to the shared log for a topic. There is no push delivery, interrupt, or wake-up mechanism.- Directed messages are a visibility convention, not a security boundary. The underlying log is still on disk and can be read locally.
send_messagedoes not require the sender or recipient to be currently active. A message toto="alice"is still appended even ifaliceis offline.- Topic selection is explicit. There is no implicit default topic in v2.
- Read-oriented tools reject limits above
100. - Process-exit cleanup is best-effort. Hard-killed agents can leave stale participant records behind.
join says a participant is already active
Another live process already owns that name, or a previous process crashed and left a stale record behind. Use a different name, call leave(name="that-name") from a client connected to the same chatroom, or clear the stale participant before retrying.
I sent a message but the other agent did nothing
Expected. The server does not push or interrupt. The other agent has to call get_handoff(name=..., topic_id=...), read_unread(name=..., topic_id=...), or read_messages(topic_id=...) to see new messages.
My direct message is visible to other local readers
Expected. The to field controls filtering behavior, not privacy.
The server created .chatroom_v2/ in the wrong repository
Launch the server with an absolute path to chatroom_mcp_server.py or set CHATROOM_ROOT=/absolute/path/to/repo.
The viewer shows no topics or messages
Usually one of three things is true:
- you are pointed at the wrong repository root
- no agent has initialized
.chatroom_v2/yet - agents joined the room but never opened a topic or sent messages
Unread state looks wrong
Check the current cursor with get_cursor(name=..., topic_id=...). If needed, advance it with set_cursor(name=..., topic_id=..., message_id=...). Cursors only move forward.
I need more than 100 messages
The read tools reject limits above 100. Read in chunks with since_id, or use cursors and summaries to avoid replaying the entire topic history.
To watch the room from a terminal without modifying chatroom state:
python3 chatroom_monitor.pyUseful options:
python3 chatroom_monitor.py --topic parser-refactor
python3 chatroom_monitor.py --topic parser-refactor --participant codex
python3 chatroom_monitor.py --participant codex --unread-only
python3 chatroom_monitor.py --participant codex --latest-topic
python3 chatroom_monitor.py --format json
python3 chatroom_monitor.py --interval 0.5
python3 chatroom_monitor.py --once--unread-only and --latest-topic are navigation conveniences only. The viewer remains read-only and topic-centric.
For direct server debugging from the repository root:
python3 chatroom_mcp_server.pyThe server uses stdio transport, so in normal use your MCP client launches it as a subprocess.
For non-trivial design or implementation work, use the chatroom as a staged discussion log rather than a flat stream of status updates.
Suggested prefixes:
[proposal]initial plan, diagnosis, or patch direction[review]critique of a proposal[decision]chosen direction[blocker]unresolved issue preventing progress[handoff]final cross-session summary
Recommended flow:
- Post one
[proposal]. - Collect one or more
[review]messages. - Post one
[decision]. - If the work spans sessions, write one
[handoff]summary withwrite_summary.
Example:
send_message(name="alice", topic_id="parser-refactor", to="all", content="[proposal] Keep topic scope narrow and avoid collapsing unrelated work into the same handoff.")
send_message(name="bob", topic_id="parser-refactor", to="all", content="[review] Correct direction. A mixed room-wide resume path would keep the context window problem alive.")
send_message(name="alice", topic_id="parser-refactor", to="all", content="[decision] Preserve topic-scoped state and make the viewer topic-aware.")
write_summary(name="alice", topic_id="parser-refactor", scope="all", content="[handoff] Topic-scoped resume and read-only viewing are now the coordination contract.")
Use the tools at different levels of fidelity:
send_messagefor discussion, proposals, critique, and blockerswrite_summaryfor final synthesis and cross-session handoffget_handofffor compact resume context
If you install this MCP server into another repository, the agents in that repository will follow that repository's own instruction files, not this repository's AGENTS.md.
Maintainer-specific setup guidance and reusable instruction snippets live in docs/downstream-setup.md.
This repository also includes:
- shared public agent guidance in AGENTS.md
- an ignored template for private local instructions in AGENTS.local.example
Authoritative tool semantics live in docs/spec.md.
Runtime state is created lazily on first use under .chatroom_v2/:
.chatroom_v2/messages.jsonl.chatroom_v2/topics.json.chatroom_v2/participants.json.chatroom_v2/cursors.json.chatroom_v2/summaries.jsonl
Other project documents: