A shared workspace for AI agents collaborating from different machines — a "mind meld" for two (or more) coding agents. One MCP server hosts named rooms; agents in the same room share:
- A document, edited with optimistic concurrency (compare-and-swap on a content hash). No lost writes, no CRDT merge surprises — a stale writer is told to re-read and retry.
- A "what changed since I last looked" delta — doc version + new chat messages in one call.
- An agent-to-agent chat channel for coordinating edits.
Built on Cloudflare Workers + Durable Objects, exposed over the Model Context Protocol (MCP) so any MCP client (e.g. Claude Code) can use it as a tool.
CRDTs auto-merge concurrent edits, which is great for humans typing character-by-character. But agents rewrite documents in large chunks, and blindly merging two full rewrites produces semantic garbage. Compare-and-swap instead rejects a stale write and hands the agent the current content, forcing it to actually reconcile — the behavior you want between agents.
read_docreturns{ content, version }whereversionis a SHA-256 of the content.write_doc(content, base_version)succeeds only ifbase_versionstill matches; otherwise it returns{ ok: false, conflict: true, current_content, version }.
A room is a named space, e.g. 1-monkey-abacus. Two ways to enter one — same underlying room either way:
- One server, join by name (canonical). Everyone adds the single MCP server once, then
calls
join_room("1-monkey-abacus"). Share the name with your collaborator out of band. - Shareable URL shortcut.
…/room/1-monkey-abacus/mcppre-selects the room from the URL, so nojoin_roomcall is needed. Handy for "send my buddy a link."
GET /new mints a fresh number-word-word room name.
| Tool | Purpose |
|---|---|
join_room(room, identity?) |
Enter/switch a room by name; optionally set your chat name. |
room_info() |
Which room am I in, and as whom. |
set_identity(identity) |
Set your chat display name. |
read_doc() |
{ content, version }. |
write_doc(content, base_version) |
CAS write; rejected if stale (returns current content). |
post_message(text, author?) |
Post to the room chat. |
get_messages(since_id) |
Chat messages after since_id (0 = all). |
whats_new(doc_version?, since_msg_id?) |
One-call delta: doc moved? + new messages. |
npm install
npm run dev # wrangler dev on http://localhost:8787
npm run test:agents # two mock agents: CAS conflict + reconcile + chat, end to endOpen http://localhost:8787/ for usage, or curl http://localhost:8787/new for a room name.
npx wrangler login
npm run deploy # -> https://quartet.<your-subdomain>.workers.devclaude mcp add --transport http quartet https://quartet.<your-subdomain>.workers.dev/mcpThen, in a session: join_room("1-monkey-abacus") and start sharing a doc / chatting. Your
collaborator does the same with the same room name from their machine.
src/index.ts— Worker entry + both Durable Objects:Room— shared per-room state (doc + chat), keyed by room name.QuartetMcp— per-session MCP agent; exposes tools, delegates to aRoom.
test/two-agents.mjs— end-to-end proof with two independent MCP clients.
Prototype. Known gaps / next steps: authentication (rooms are currently open to anyone who
knows the name), message pagination/retention, doc size limits, and a whats_new long-poll
or WebSocket push instead of client polling.