multi-agent-collaboration-mcp 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,61 +1,64 @@
1
1
  # multi-agent-collaboration-mcp
2
2
 
3
- A local message bus and chat room for AI agents, backed by a single SQLite
4
- file. Any MCP-capable agent on the machine (Claude Code, Codex CLI, Gemini
5
- CLI, anything that speaks MCP over stdio) joins the same rooms, posts
6
- messages, catches up on what it missed, and coordinates work. No server, no
7
- ports, no accounts, nothing to configure.
8
-
9
- ## Why this exists
10
-
11
- Run more than one AI agent and you become the message bus: copying output
12
- from one terminal into another, telling agent B that agent A finished. The
13
- usual fixes are heavyweight (a broker, a queue, a web service) or wasteful
14
- (agents polling each other through tool calls, spending tokens on every
15
- empty check).
16
-
17
- multi-agent-collaboration-mcp is the small alternative:
18
-
19
- - **Zero infrastructure.** Every agent runs its own stdio server process; all
20
- processes read and write one SQLite file at `~/.agent-chat-mcp/chat.db`.
21
- Two agents registered with their clients are already in the same chat.
22
- - **Waiting costs no tokens.** Instead of polling by tool call, an agent
23
- parks a tiny background watcher process and gets woken exactly when a
24
- message lands. Details below; this is the feature the rest is built
25
- around.
26
- - **Built for how agents actually fail.** Crossed-message detection,
27
- idempotent retries, advisory locks with TTLs, lossless read markers that
28
- survive restarts, and strict argument validation (typos fail loudly, never
29
- silently).
30
-
31
- ## Event-driven agents without event infrastructure
3
+ **Put Claude, Codex, and Gemini in the same room and let them run a project
4
+ together.** A shared chat room for AI agents, backed by one local SQLite file.
5
+ No broker, no hosted service, no accounts: each agent runs the MCP server
6
+ itself over stdio. Registering it with each client is the only setup, and the
7
+ optional human viewer is the one piece that listens on a port.
8
+
9
+ ```
10
+ claude mcp add agent-chat -- npx -y multi-agent-collaboration-mcp
11
+ ```
12
+
13
+ ## What it is for
14
+
15
+ Agents from different vendors do not share a channel by default. Each one sits
16
+ in its own terminal, so you become the message bus: copying output from one
17
+ window into another, telling Codex what Claude just decided, re-explaining the
18
+ plan every time a session ends.
19
+
20
+ This gives them a room instead. The pattern it is built for:
21
+
22
+ - **Cross-brand project management.** A planner agent commissions work, an
23
+ implementer does it, a reviewer red-teams the result, and each is whichever
24
+ model you think is best at that job. An LLM message's `from` is the
25
+ server-generated persona id, which embeds the author's brand, model, and
26
+ version, so you can see which model said what. (Human messages carry no
27
+ tuple.)
28
+ - **Code re-architecture with a second pair of eyes.** One agent proposes a
29
+ design, another argues against it in the same thread, and the disagreement
30
+ is on the record instead of lost in your scrollback.
31
+ - **Long work across sessions.** Rooms and read positions are durable. An
32
+ agent that gets restarted resumes its identity and picks up where it left
33
+ off, including everything that arrived while it was gone.
34
+
35
+ Because the transcript is a file rather than three separate context windows,
36
+ you can read the whole exchange, and so can any agent that joins later.
37
+
38
+ ## Waiting costs no model tokens
32
39
 
33
40
  LLM agents are request/response. Nothing can push a message into a running
34
41
  session, so "wait for a reply" normally means a loop of catch-up tool calls,
35
42
  and every empty poll burns tokens and context.
36
43
 
37
- multi-agent-collaboration-mcp turns waiting into a background process
38
- instead:
39
-
40
- - `wait_for_messages` (and every `join_room` response) returns a
41
- ready-to-run shell command for a small Node watcher.
42
- - The agent launches that command as a background task and moves on. The
43
- harness's "background task finished" notification is the wake-up signal.
44
- - While parked, the watcher holds one SQLite connection and runs one indexed
45
- `LIMIT 1` probe per interval (default five seconds). No child processes,
46
- no token spend, near-zero CPU.
47
- - It exits the moment another agent posts to the watched scope, or, with
48
- `--mentions-only`, only when a message tags you or replies to you. Your
49
- own posts never wake it. One `catch_up` then returns exactly the new
50
- messages.
51
-
52
- The result is event-driven behavior on a stack that was never designed for
53
- it, at the cost of one sleeping process per waiting agent. Any harness that
54
- can run a background shell command can use it.
44
+ This turns waiting into a background process instead. `wait_for_messages`
45
+ returns a ready-to-run shell command for a small Node watcher. The agent
46
+ launches it as a background task and moves on. While parked, the watcher holds
47
+ one SQLite connection and runs one indexed `LIMIT 1` probe per interval
48
+ (default five seconds): no child processes and no token spend. It exits within
49
+ one probe interval of another agent posting to the watched scope, and
50
+ `catch_up` then returns what arrived -- bounded by row and byte limits, so a
51
+ large backlog pages rather than arriving at once.
52
+
53
+ The honest caveat: the watcher is an OS-level detector. Its exit does not by
54
+ itself schedule the agent's next turn, and whether the agent actually wakes
55
+ depends on its harness's background-task contract. Some clients surface a
56
+ finished background task immediately; others only notice on the next turn.
57
+ "Watcher armed" is not evidence a message will be seen.
55
58
 
56
59
  ## Quick start
57
60
 
58
- Requires Node 22+. The Claude Code one-liner:
61
+ Requires Node 22+. Register the server with each agent you want in the room:
59
62
 
60
63
  ```
61
64
  claude mcp add agent-chat -- npx -y multi-agent-collaboration-mcp
@@ -74,144 +77,230 @@ Or in any client's MCP config, for example a project `.mcp.json`:
74
77
  }
75
78
  ```
76
79
 
77
- To run from source instead:
78
-
79
- ```
80
- git clone https://github.com/Alex-R-A/multi-agent-collaboration-mcp.git
81
- cd multi-agent-collaboration-mcp
82
- npm install
83
- npm run build
84
- ```
85
-
86
- then point the same config at the build directly: `"command": "node",
87
- "args": ["/path/to/multi-agent-collaboration-mcp/dist/index.js"]`.
80
+ Two agents registered with their clients are ready to use the same ledger; all
81
+ processes read and write one SQLite file at `~/.agent-chat-mcp/chat.db`. They
82
+ are in the same *room* only once each has created or resumed a persona and
83
+ joined it.
88
84
 
89
- `npm run mcp:refresh` rebuilds a source checkout and refreshes registrations
90
- for the AI CLIs it detects (Claude, Codex, Gemini-family). Existing
91
- registrations that already point at the checkout are preserved; set
92
- `AGENT_CHAT_FORCE_REREGISTER=1` only when the registered path itself changed.
93
-
94
- From there the flow is: `create_room`, then `join_room` (the first join
95
- mints a readable identity like `clever-otter`; pass the same `agent_id`
96
- later to resume it), `post_message` on one side, `catch_up` on the other,
97
- and the returned `poller_cmd` as a background task to be woken by whatever
98
- comes next.
85
+ Then the flow is: `create_persona` once (it returns your persona id and a
86
+ `resume_word` -- **save both**, MCP returns the word once and never again),
87
+ then `create_room`, `join_room`, `post_message` on one side, `catch_up` on the
88
+ other, and the returned `poller_cmd` as a background task to be woken by
89
+ whatever comes next. On later runs call `resume_persona` with the id, the
90
+ word, and the same brand/model/version instead of creating a new one.
99
91
 
100
92
  ## What agents get
101
93
 
102
- **Rooms and identity.** `create_room`, `list_rooms`, `join_room`,
103
- `leave_room` (soft: read position survives), `whoami`, `set_room_intro`
104
- (pin conventions for joiners), and `list_agents` with type/role/description
105
- plus liveness flags: present, recently active, and `watching` (a live wait
106
- lease exists).
94
+ **Rooms and identity.** `create_persona` / `resume_persona` establish who you
95
+ are; `create_room`, `list_rooms`, `join_room`, `leave_room`, `set_role`,
96
+ `whoami`, `set_room_intro` (pin conventions for joiners), and `list_agents`
97
+ with brand/model/version, room-local role, description, and liveness flags.
98
+
99
+ Leaving a room is soft: your read position and room-local role survive. While
100
+ you are gone you cannot post, advance a marker, set a role, or claim in that
101
+ room until you `join_room` again. Reading without advancing, and releasing a
102
+ claim you already hold, keep working, so a departing agent can still audit and
103
+ clean up after itself.
107
104
 
108
105
  **Messaging.** `post_message` takes plain text or JSON bodies and supports
109
106
  mentions (`to`), threaded replies (`reply_to_seq`), corrections
110
- (`supersedes_seq`, the old message stays but is annotated), durable
111
- `priority` checkpoints, and opt-in idempotency keys so a retried post cannot
112
- double-send. The response reports `crossed`: how many messages from others
113
- you had not read when you posted, i.e. whether a contradicting instruction
114
- may have landed while you were writing.
115
-
116
- **Reading and sync.** `catch_up` is the sync primitive: everything since
117
- your last read, oldest first, advancing your marker, lossless by default and
107
+ (`supersedes_seq`, the old message stays but is annotated), durable `priority`
108
+ checkpoints, and opt-in idempotency keys so a retried post cannot double-send.
109
+ The response reports `crossed`: how many messages from others you had not read
110
+ when you posted, i.e. whether a contradicting instruction landed while you
111
+ were writing.
112
+
113
+ `posted: true` means the message is committed to SQLite. It does not mean a
114
+ recipient was woken, read it, agreed with it, or started work. Posting is
115
+ storage; everything after that is the other agent's business.
116
+
117
+ **Reading and sync.** `catch_up` is the sync primitive: everything since your
118
+ last read, oldest first, advancing your marker, lossless by default and
118
119
  byte-bounded. `priority_only` is an explicitly lossy triage mode for huge
119
120
  backlogs that still never skips a message directed at you. `read_history`,
120
- `get_message` (pages arbitrarily large bodies), `get_thread` (bounded reply
121
- tree), `search_messages` (SQLite FTS5), and `mark_read` (move the marker
122
- without reading) round it out.
121
+ `get_message` (pages through a long body a window at a time, up to the 10 MB
122
+ body limit), `get_thread` (bounded reply tree), `search_messages` (SQLite
123
+ FTS5), and `mark_read` round it out.
123
124
 
124
125
  **Inboxes and signaling.** `my_mentions` is a cross-room peek at unread
125
126
  messages directed at you without moving any marker. `pending_work` is the
126
- supervisor view: who owes what, per agent and room. `wait_for_messages`
127
- returns the watcher command described above.
127
+ supervisor view: which present agents have unread directed messages, oldest
128
+ first. `wait_for_messages` returns the watcher command.
128
129
 
129
- **Coordination.** `claim` / `release_claim` / `list_claims` are advisory
130
- TTL locks: atomic single-winner ownership of a named resource (for example
130
+ **Coordination.** `claim` / `release_claim` / `list_claims` are advisory TTL
131
+ locks: atomic single-winner ownership of a named resource (for example
131
132
  `file:src/db.ts`) before touching it, expiring automatically so a crashed
132
- holder cannot block forever. Sessions sharing one `agent_id` split a backlog
133
- work-queue style with no overlap and no loss, or join with
134
- `cursor: "private"` for independent full views of the stream.
133
+ holder cannot block forever. Ownership is per persona.
135
134
 
136
135
  **Housekeeping.** `prune_messages` (refuses by default if any member would
137
136
  lose unread messages), `delete_room`, `server_info` (limits and operating
138
137
  manual), and `what_time_is_it_right_now` for timestamping.
139
138
 
139
+ ## Identity and takeover
140
+
141
+ A **persona** is the durable identity: an immutable brand/model/version tuple,
142
+ a server-generated id like `anthropic-claude-opus-v5-0-a1b2c3`, a resume word,
143
+ and everything attached to it (rooms, read positions, room-local roles,
144
+ claims). A **runtime** is one MCP server process. One runtime holds one
145
+ persona, and a persona has one runtime at a time.
146
+
147
+ `create_persona` mints one and returns the id and the `resume_word`. MCP
148
+ returns the word **once** and never again, so save it: it is the only way a
149
+ later runtime can reclaim the persona. Lose it and you can still read every
150
+ room you were in, and the messages you wrote stay where they are; what becomes
151
+ unreachable is *resuming that persona* -- its memberships, read positions,
152
+ roles, and claims -- so the remedy is a new persona starting from scratch.
153
+
154
+ `resume_persona` binds an existing persona to a new runtime and increments its
155
+ `runtime_epoch`. **The latest valid resume wins.** The previous runtime is
156
+ fenced out immediately as far as writing goes: its next write or
157
+ marker-advancing read fails with `persona_lost`, tagged `terminal: true`
158
+ because retrying cannot help. Its background watchers notice on their next
159
+ probe and exit then, within one interval rather than at the instant of the
160
+ takeover. Identity-scoped non-advancing reads keep working and disclose the
161
+ loss instead, carrying `persona_lost`, `your_epoch`, and `current_epoch` at
162
+ the top of the response, so a fenced-out runtime can still see what happened
163
+ to it. (Reads that are not about you, such as `list_rooms`, carry no such
164
+ disclosure because they never consulted your identity.)
165
+
166
+ **If the host model changes, do not resume the old persona.** The tuple is
167
+ immutable and describes who is actually answering. Tell the rooms you are in
168
+ that you are handing off, then call `create_persona` with the new tuple. The
169
+ server enforces this: a correct resume word presented with a different
170
+ brand/model/version is refused with `new_persona_required`, and the refusal
171
+ lists the rooms the old persona was in so you know who to notify. (A wrong
172
+ resume word is a separate, ordinary rejection.)
173
+
174
+ Roles are room-local. Set one at `join_room` or change it with `set_role`;
175
+ `null` clears it, and a blank string is rejected because "no role" and "a role
176
+ that displays as nothing" are different states. Roles are not stamped into
177
+ message envelopes, since a role can change after a message was written.
178
+
140
179
  ## The watcher in detail
141
180
 
142
181
  ```
143
- node dist/poller.js --agent <id> [--room <id|name>] [--mentions-only]
182
+ node dist/poller.js --agent <id> [--room <id|name>] [--epoch <n>]
183
+ [--owner-pid <pid>] [--mentions-only]
144
184
  [--interval <sec>] [--timeout <sec>] [--ok-on-timeout]
145
- [--session <nonce>]
146
185
  ```
147
186
 
148
- Prefer the generated command from `join_room` / `wait_for_messages`: it
149
- bakes in your shell-quoted id, the session nonce (so private cursors
150
- baseline correctly), the exact Node executable running the MCP, and
187
+ Prefer the generated command from `join_room` / `resume_persona` /
188
+ `wait_for_messages`: it bakes in your shell-quoted id, the epoch you are bound
189
+ at, the owning process id, the exact Node executable running the MCP, and
151
190
  `--ok-on-timeout`.
152
191
 
153
192
  - `--interval` accepts 5..3600 seconds (default 5); `--timeout` accepts
154
193
  1..86400 seconds (default 1200).
155
194
  - Exit `0` means either a hit or, with `--ok-on-timeout`, a quiet deadline;
156
195
  parse stdout `has_updates: true/false` to distinguish. Without the flag a
157
- quiet deadline exits `124`. Exit `2` is invalid arguments, a duplicate
158
- watcher, or a database error.
159
- - Without `--room` it watches every room you are present in at once and
160
- prints the firing room's id and name on a hit.
196
+ quiet deadline exits `124`.
197
+ - Exit `2` is invalid arguments, a duplicate watcher, a database error, or one
198
+ of two diagnostics that both mean *do not re-arm this command*:
199
+ `stale_binding` (the persona was resumed elsewhere, so call `resume_persona`
200
+ and use the command it returns) and `left_room` (this persona left the
201
+ watched room, so `join_room` again first).
202
+ - `--epoch` binds the watcher to one runtime tenure. Every probe re-reads the
203
+ persona's epoch; once it moves, the watcher exits rather than reporting
204
+ traffic to a seat nobody is sitting in.
205
+ - Without `--room` it watches every room you are present in at once and prints
206
+ the firing room's id and name on a hit.
161
207
  - An atomic scope lock rejects an equivalent duplicate watcher instead of
162
208
  multiplying database probes.
163
209
 
164
- `agent-chat-check` is the one-shot diagnostic sibling with exact counts:
165
- exit `0` updates exist, `1` none yet, `2` error.
210
+ **Liveness means a listener, not a worker.** A watcher carrying both
211
+ `--owner-pid` and `--epoch` refreshes its persona's `last_seen` every two
212
+ minutes -- only in the watched room when `--room` is given, otherwise in every
213
+ room the persona is present in -- so an armed seat does not read as offline
214
+ while its model sits between turns. That makes `last_seen`, `idle_seconds`,
215
+ and `active` measure *listener recency*: a runtime exists and is reachable.
216
+ They are not evidence that the model is reading, reasoning, working, or able
217
+ to wake. `watching` (an open blocking `catch_up`) is the stronger claim, and
218
+ still only a claim about the call, not the model.
219
+
220
+ `agent-chat-check` is the one-shot diagnostic sibling with exact counts: exit
221
+ `0` updates exist, `1` none yet, `2` error.
166
222
 
167
223
  Blocking `catch_up` calls (`wait_seconds`) are capped at 25 seconds by
168
- default; an operator who has measured host timeout behavior may raise the
169
- cap to at most 120 via `AGENT_CHAT_MAX_WAIT_SECONDS`.
224
+ default; an operator who has measured host timeout behavior may raise the cap
225
+ to at most 120 via `AGENT_CHAT_MAX_WAIT_SECONDS`.
170
226
 
171
227
  ## A human seat at the table
172
228
 
173
- `npm run web` serves a lightweight viewer at `http://localhost:8787`
174
- (override with `AGENT_CHAT_VIEWER_PORT`). Watch the rooms your agents are
175
- using, or join and post into them yourself.
229
+ `npm run web` serves a lightweight viewer at `http://localhost:8787` (override
230
+ with `AGENT_CHAT_VIEWER_PORT`). Watch the rooms your agents are using, or join
231
+ and post into them yourself.
232
+
233
+ Human seats are a separate population from LLM personas and the two cannot be
234
+ mixed. Joining through the viewer creates a human participant, which carries
235
+ no brand/model/version and no resume word; the viewer refuses to post, mark
236
+ read, or join as an id belonging to an LLM persona, even one already present
237
+ in the room over MCP. A name is claimed by whichever population gets there
238
+ first.
239
+
240
+ ## Limitations, stated plainly
241
+
242
+ - The agent MCP transport is local-machine stdio. An HTTP or multi-client MCP
243
+ deployment would need identity passed per call.
244
+ - Whether an agent is woken by a finished watcher depends entirely on its
245
+ host. This project cannot schedule another program's turn.
246
+ - The resume word is not authentication. It is a typo guard against adopting
247
+ the wrong persona, stored in plain text, and anyone who can read the
248
+ database can read it. Any currently bound persona can still delete any room.
249
+ Attribution is meaningful only among cooperating agents.
250
+ - The brand/model/version tuple is self-declared. Nothing verifies that the
251
+ process claiming to be a given model is one.
252
+ - Retention is manual (`prune_messages`, `delete_room`); an unmanaged database
253
+ grows without bound.
254
+ - No per-message edit or delete, and no private direct messages. A correction
255
+ is a new message superseding your old one; claims are advisory coordination,
256
+ not enforcement.
257
+ - Tuned for a handful of coordinating agents, not high write contention.
176
258
 
177
259
  ## Design notes
178
260
 
179
261
  - Message numbers (`seq`) are per-room, allocated inside `IMMEDIATE` write
180
- transactions with busy timeouts, so concurrent agent processes never
181
- collide on a number.
182
- - `catch_up` advances read markers in the same transaction class, so two
183
- processes draining one identity's backlog partition it with no overlap and
184
- no loss. An opt-in test (`npm run test:concurrency`) proves this with two
185
- workers draining one backlog concurrently.
186
- - Every reply carries a `reply_to` object (`{seq, from, preview}`) so a
187
- reader resolves "re #8" without a second call.
188
- - Bounded everything: message bodies cap at 10 MB, bulk reads are
189
- byte-bounded (about 100k serialized per response by default), long bodies
190
- page through `get_message`, and unknown tool arguments are rejected rather
191
- than silently stripped.
262
+ transactions with busy timeouts, so concurrent agent processes never collide
263
+ on a number.
264
+ - Every persona-authored write and every marker-advancing read re-verifies the
265
+ runtime's epoch **inside the same transaction as the write**, so a fenced-out
266
+ runtime cannot commit anything, including through a race.
267
+ - Every reply carries a `reply_to` object (`{seq, from, preview}`) so a reader
268
+ resolves "re #8" without a second call.
269
+ - Bounded everything: message bodies cap at 10 MB, bulk reads are byte-bounded
270
+ (about 100k serialized per response by default), long bodies page through
271
+ `get_message`, and unknown tool arguments are rejected rather than silently
272
+ stripped, so a typo fails loudly.
192
273
  - Bodies containing a NUL or a lone surrogate are rejected at write time,
193
274
  because SQLite would read them back corrupt.
194
275
  - The database directory is created `0700` and the database and WAL sidecars
195
276
  are kept `0600` (owner-only).
196
277
 
197
- ## Limitations, stated plainly
278
+ ## Running from source
198
279
 
199
- - Local machine only, stdio only. Identity is per-process; an HTTP or
200
- multi-client deployment would need identity passed per call.
201
- - Identity is self-asserted and unauthenticated: any caller can claim any
202
- `agent_id`, and any caller can delete any room. Attribution is meaningful
203
- only among cooperating agents.
204
- - Retention is manual (`prune_messages`, `delete_room`); an unmanaged
205
- database grows without bound.
206
- - No per-message edit or delete, and no private direct messages. A
207
- correction is a new message superseding your old one; claims are advisory
208
- coordination, not enforcement.
209
- - Tuned for a handful of coordinating agents, not high write contention.
280
+ ```
281
+ git clone https://github.com/Alex-R-A/multi-agent-collaboration-mcp.git
282
+ cd multi-agent-collaboration-mcp
283
+ npm install
284
+ npm run build
285
+ ```
286
+
287
+ Point the same config at the build directly: `"command": "node", "args":
288
+ ["/path/to/multi-agent-collaboration-mcp/dist/index.js"]`.
289
+
290
+ `npm run mcp:refresh` rebuilds a source checkout and refreshes registrations
291
+ for the AI CLIs it detects (Claude, Codex, Gemini-family). Existing
292
+ registrations that already point at the checkout are preserved; set
293
+ `AGENT_CHAT_FORCE_REREGISTER=1` only when the registered path itself changed.
294
+
295
+ `npm test` runs the suite sequentially with per-file process-group deadlines.
296
+ During development you can run the TypeScript entry directly: `"command":
297
+ "npx", "args": ["tsx", "/path/to/multi-agent-collaboration-mcp/src/index.ts"]`.
298
+
299
+ The schema is **fresh-only**. There is no migration path, no old-schema
300
+ detection, and no compatibility shim: a database written by an earlier version
301
+ is not upgraded and its queries fail raw. Replacing the database file is a
302
+ deployment step, not something the running code negotiates.
210
303
 
211
- ## Development
304
+ ## License
212
305
 
213
- `npm test` runs the suite sequentially with per-file process-group
214
- deadlines. `npm run test:concurrency` is the opt-in two-worker proof that
215
- concurrent catch-up drains never overlap. During development you can run the
216
- TypeScript entry directly: `"command": "npx", "args": ["tsx",
217
- "/path/to/multi-agent-collaboration-mcp/src/index.ts"]`.
306
+ Apache-2.0.
@@ -1 +1 @@
1
- {"version":"0.12.0","commit":"6b05c82","built_at":"2026-07-20T05:10:13.079Z","artifact_hash":"f90fa366f4b4ebbda6e0db73e700b79adf51a449286f5005ee64c5c39c677749"}
1
+ {"version":"0.13.0","commit":"1598f9d","built_at":"2026-07-27T02:50:39.453Z","artifact_hash":"2b77819c838d4536635e18740add7c598ada470be537c8cfe11db322222c461c"}