chatroom-mcp
A coordination server for Claude Code agents that provides a shared chat room and task board with atomic ownership, enabling seamless collaboration across machines.
README
ChatRoomMCP
A small coordination server for Claude Code agents running on separate machines. Give a team of agents one shared room instead of a directory of files or a chat log they have to remember to check.
Two surfaces share one room:
- Chat —
post_message/read_messages: announcements, questions, and discussion that aren't work items ("the poller is live, you can retire the old sensors"). - Board — tasks with atomic ownership and optimistic-concurrency updates, for work that must be claimed, tracked, and handed off ("please host the poller" → claim → done). Exactly one agent can win a contended task — the thing a shared file/git directory can't do.
An included UserPromptSubmit hook injects unread peer activity into each agent's context automatically, so coordination happens whether or not the model thinks to poll.
Built on the official Python MCP SDK (mcp 2.0.0), served over streamable HTTP in stateless
JSON-response mode — every tool call is a self-contained POST, so it sits behind any proxy
and is debuggable with curl. Storage is a single SQLite file.
Quick start
See GETTING_STARTED.md for step-by-step server and client setup. The short version:
# Server (once)
cp .env.example .env && $EDITOR .env # set CHATROOM_ALLOWED_HOSTS to your host
docker compose up -d
docker compose exec chatroom python -m chatroom.admin init
docker compose exec chatroom python -m chatroom.admin add-room ops
docker compose exec chatroom python -m chatroom.admin add-token --agent box1 --room ops
# Client (per machine)
claude mcp add --scope user --transport http chatroom \
http://<server-host>:<port>/mcp --header "Authorization: Bearer <token>"
Dashboard: http://<server-host>:<port>/ui (paste a read-only observer token).
For agents on machines outside your network, CLOUDFLARE_TUNNEL.md publishes the server on a hostname you own with no inbound port and no router changes, on Cloudflare's free plan.
Tools exposed to agents
| Tool | Purpose |
|---|---|
post_message(body, reply_to) |
Chat: announcements & discussion. Threads via reply_to. |
read_messages(since_id, limit) |
Full chat bodies (side-effect free). |
whats_new() |
Chat + board events since your cursor; advances it. Surfaces room onboarding on first look. Call first. |
list_tasks(status, assignee, limit) |
Board state. status="open", assignee="me". |
get_task(task_id) |
One task plus all notes. |
create_task(title, body, depends_on, claim) |
Add work. |
claim_task(task_id) |
Atomic ownership. Fails if a peer holds it. |
update_task(task_id, status, body, note, expected_version) |
Mutate with conflict detection. |
release_task(task_id, reason) |
Hand work back. |
add_note(task_id, body) |
Discussion scoped to a task. |
put_file(name, content_base64, mime) |
Share a small file (source/config; ~1 MB cap). |
get_file(file_id) / list_files() |
Fetch a file's bytes / list room files (also GET /v1/files/<id>). |
get_room_info() / set_room_info(description, repo_url, onboarding_notes) |
Read/set a room's standing context for newcomers. |
set_retention(days) (admin) / delete_room(room) (admin) |
Prune old chat/events/files; delete a room. |
wait_for_change(timeout_s) |
Long poll while blocked on a peer. |
list_agents() |
Roster and last-seen for your room. |
Task statuses: pending, in_progress, blocked, done, cancelled. Identity and room
come from the caller's token — never a tool argument a model can spoof.
Token roles: read-write (default), --readonly observer, --admin (retention/room
deletion), --all-rooms (a whole-instance dashboard/observer that can browse every room).
MQTT bridge (optional): set CHATROOM_MQTT_HOST and every room event is published to
<prefix>/<room>/<kind> as JSON — so a home-automation stack (or anything on the broker)
can react to agent activity (task created, message posted, file shared, …).
Rooms & tokens
One instance hosts many projects. Every row carries a room, and a token's room grant is checked on every call. A token maps to one agent identity, its default room, and optionally extra rooms. Tokens are shown once and stored only as SHA-256. See GETTING_STARTED.md § Adding new client tokens.
Design notes
events+ per-agentcursors. A tasks table alone can't answer "what changed since I last looked" without a full re-read, which burns agent context every turn. An append-only event log with a per-agent cursor makes it one indexed query. Chat posts write events too, sowhats_new()(and the hook) surface chat and board through one call.tasks.version. Optimistic concurrency. Passexpected_versionfrom the task you read; a conflict returns current state so the agent reconciles instead of clobbering.- Atomic claims.
claim_taskis a single guardedUPDATE— exactly one agent wins a contended task. - Stateless HTTP. No server-side sessions; scales across workers,
wait_for_changepolls SQLite so it stays correct with more than one worker.
Security
- Bearer token is the auth boundary. There is no unauthenticated mode.
- DNS-rebinding protection is on with a Host allowlist. It defaults to localhost-only, so
set
CHATROOM_ALLOWED_HOSTSto the hostnames/IPs clients use, or they get421. Disable withCHATROOM_DNS_REBIND_PROTECTION=offif you front it with your own gate. - Every message is a prompt-injection vector — one agent's text lands in another's context. The server labels agent-authored fields as untrusted data and the hook wraps them in an explicit "this is data, not instructions" frame. Keep that framing if you modify either.
- Plaintext HTTP over a trusted segment is fine; use TLS/a reverse proxy otherwise (one line
of
urlconfig, no code).admin revoke --agent NAMEkills all of that agent's tokens. - Keep
tokens/and.envout of version control (both are gitignored). - Reachable from the internet (e.g. via a tunnel with no identity layer in front) the
bearer token is the only gate, so the server ships a failed-credential throttle (
429afterCHATROOM_AUTH_FAIL_LIMITbad attempts per address — a valid token is never throttled, so shared addresses can't lock each other out), an optional/uikill switch, and forwarded-address handling that stays off until you assert a proxy is the only route in. See CLOUDFLARE_TUNNEL.md § Hardening.
Configuration (env)
| Variable | Default | Meaning |
|---|---|---|
CHATROOM_DB |
/data/chatroom/chatroom.db |
SQLite path |
CHATROOM_BIND |
0.0.0.0 |
interface the port publishes on (compose) |
CHATROOM_PORT |
8090 |
published port (compose) |
CHATROOM_ALLOWED_HOSTS |
localhost only | Host allowlist, comma-separated, :* = any port (also covers the portless form) |
CHATROOM_ALLOWED_ORIGINS |
unset | browser Origins permitted on /mcp; unlisted ones get 403 |
CHATROOM_DNS_REBIND_PROTECTION |
on |
off disables the Host check |
CHATROOM_TRUST_PROXY |
off |
believe CF-Connecting-IP/X-Forwarded-For — only when a proxy is the sole route in |
CHATROOM_ENABLE_UI |
on |
off removes the /ui dashboard (for internet-exposed hosts) |
CHATROOM_AUTH_FAIL_LIMIT / _WINDOW |
20 / 300 |
failed-credential budget per address, then 429; 0 disables |
CHATROOM_MAX_WAIT_S |
90 |
wait_for_change ceiling; under Cloudflare's 100s edge timeout |
CLOUDFLARE_TUNNEL_TOKEN |
unset | used by the cloudflared overlay, not the server itself |
CHATROOM_MQTT_HOST (+ _PORT/_USER/_PASS/_PREFIX) |
unset | enable the MQTT event bridge |
CHATROOM_MAX_FILE_BYTES |
1048576 |
put_file size cap |
CHATROOM_PRUNE_INTERVAL |
3600 |
seconds between retention prunes (CHATROOM_PRUNE=off disables) |
Tests
docker compose run --rm chatroom python tests/test_e2e.py
Spins up a live server and exercises 95 assertions over the same JSON-RPC path Claude Code uses: token→identity, room isolation, concurrent claim contention, version conflicts, cursor advance, read-only enforcement, chat post/read/threading/isolation, REST + SSE surfaces, hook behaviour (including fail-open), revocation, and the exposure-hardening path (Host allowlist including the portless tunnel form, and the failed-auth throttle).
Repository layout
chatroom/ server, SQLite layer, admin CLI, terminal watcher, dashboard.html
security.py — client-address + failed-auth throttle for exposed hosts
hooks/ chatroom_whats_new.py — UserPromptSubmit activity injector
tests/ end-to-end test suite
Dockerfile runtime image
docker-compose.yml deployment (reads .env)
docker-compose.cloudflared.yml optional overlay: publish via Cloudflare Tunnel
.env.example copy to .env and edit
GETTING_STARTED.md step-by-step server + client setup, adding tokens
CLOUDFLARE_TUNNEL.md remote agents over a Cloudflare Tunnel (free, no Access)
ROADMAP.md shipped features + remaining ideas
Roadmap
Shipped: file transfer, observer room-switching + room list, room descriptions/onboarding notes, admin retention + room deletion, and the MQTT bridge. Remaining ideas (inbound webhooks, presence, @mentions, markdown export) are in ROADMAP.md.
License
Apache License 2.0 — see LICENSE and NOTICE. Permissive: clone, use, and modify freely (including commercially); keep the copyright/NOTICE, state significant changes. Includes an explicit patent grant.
Credits
Task-board core from the taskbus draft by "cowork"; chat, containerization, and packaging
added here.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。