mcp-coordinator 0.2.1 → 0.4.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.
Files changed (60) hide show
  1. package/README.md +846 -846
  2. package/dashboard/Dockerfile +19 -19
  3. package/dashboard/public/index.html +1178 -1178
  4. package/dist/cli/dashboard.js +9 -5
  5. package/dist/cli/server/backup.d.ts +7 -0
  6. package/dist/cli/server/backup.js +162 -0
  7. package/dist/cli/server/index.js +5 -0
  8. package/dist/cli/server/restore.d.ts +2 -0
  9. package/dist/cli/server/restore.js +117 -0
  10. package/dist/cli/server/start.js +24 -1
  11. package/dist/cli/server/status.js +16 -23
  12. package/dist/src/agent-activity.js +6 -6
  13. package/dist/src/agent-registry.js +6 -6
  14. package/dist/src/announce-workflow.d.ts +52 -0
  15. package/dist/src/announce-workflow.js +91 -0
  16. package/dist/src/consultation.d.ts +22 -0
  17. package/dist/src/consultation.js +118 -45
  18. package/dist/src/database.js +126 -126
  19. package/dist/src/db-adapter.d.ts +30 -0
  20. package/dist/src/db-adapter.js +32 -1
  21. package/dist/src/dependency-map.js +5 -5
  22. package/dist/src/file-tracker.d.ts +10 -0
  23. package/dist/src/file-tracker.js +40 -8
  24. package/dist/src/http/handle-health.d.ts +23 -0
  25. package/dist/src/http/handle-health.js +86 -0
  26. package/dist/src/http/handle-rest.d.ts +23 -0
  27. package/dist/src/http/handle-rest.js +374 -0
  28. package/dist/src/http/utils.d.ts +15 -0
  29. package/dist/src/http/utils.js +39 -0
  30. package/dist/src/impact-scorer.js +87 -50
  31. package/dist/src/introspection.js +1 -1
  32. package/dist/src/metrics.d.ts +83 -0
  33. package/dist/src/metrics.js +162 -0
  34. package/dist/src/mqtt-bridge.d.ts +21 -0
  35. package/dist/src/mqtt-bridge.js +55 -5
  36. package/dist/src/mqtt-broker.d.ts +16 -0
  37. package/dist/src/mqtt-broker.js +16 -1
  38. package/dist/src/path-guard.d.ts +14 -0
  39. package/dist/src/path-guard.js +44 -0
  40. package/dist/src/reset-guard.d.ts +16 -0
  41. package/dist/src/reset-guard.js +24 -0
  42. package/dist/src/serve-http.d.ts +31 -1
  43. package/dist/src/serve-http.js +189 -446
  44. package/dist/src/server-setup.d.ts +2 -0
  45. package/dist/src/server-setup.js +25 -366
  46. package/dist/src/sse-emitter.d.ts +6 -0
  47. package/dist/src/sse-emitter.js +50 -2
  48. package/dist/src/tools/agents-tools.d.ts +8 -0
  49. package/dist/src/tools/agents-tools.js +46 -0
  50. package/dist/src/tools/consultation-tools.d.ts +21 -0
  51. package/dist/src/tools/consultation-tools.js +170 -0
  52. package/dist/src/tools/dependencies-tools.d.ts +8 -0
  53. package/dist/src/tools/dependencies-tools.js +27 -0
  54. package/dist/src/tools/files-tools.d.ts +8 -0
  55. package/dist/src/tools/files-tools.js +28 -0
  56. package/dist/src/tools/mqtt-tools.d.ts +9 -0
  57. package/dist/src/tools/mqtt-tools.js +33 -0
  58. package/dist/src/tools/status-tools.d.ts +8 -0
  59. package/dist/src/tools/status-tools.js +63 -0
  60. package/package.json +83 -80
package/README.md CHANGED
@@ -1,846 +1,846 @@
1
- <div align="center">
2
-
3
- # mcp-coordinator
4
-
5
- **Embedded MQTT broker + MCP server for multi-agent coordination. Zero conflicts, everyone aligned.**
6
-
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
- [![npm](https://img.shields.io/npm/v/mcp-coordinator.svg)](https://www.npmjs.com/package/mcp-coordinator)
9
- [![Tests](https://github.com/swoofer/mcp-coordinator/actions/workflows/test.yml/badge.svg)](https://github.com/swoofer/mcp-coordinator/actions)
10
-
11
- [Getting started](#getting-started) · [Problem](#the-problem) · [How It Works](#how-it-works) · [MQTT Layer](#mqtt-communication-layer) · [Scoring](#impact-scoring) · [MCP Tools](#mcp-tools) · [CLI](#cli) · [Standalone use](#standalone-use--without-an-orchestrator) · [Quota](#anthropic-quota-pre-flight) · [Dashboard](#dashboard) · [Config](#configuration) · [Auth](#authentication)
12
-
13
- </div>
14
-
15
- ---
16
-
17
- ## The Problem
18
-
19
- When multiple developers each use an AI coding agent in parallel on the same repo, things break:
20
-
21
- - **Regressions** — Agent A rewrites a module that Agent B was depending on
22
- - **Duplicated work** — Two agents implement the same feature from different directions
23
- - **Architectural drift** — Agents make local decisions that conflict with each other's designs
24
- - **Wasted reconciliation time** — Developers spend hours untangling what the agents did
25
-
26
- Each agent works in isolation. None of them know what the others are doing.
27
-
28
- mcp-coordinator fixes this by giving agents a **shared nervous system over MQTT** — they announce intentions before coding, conflicts are detected before a single line is written, and agents see each other's actions in real-time to agree on an approach.
29
-
30
- It works **with or without** an orchestrator on top. Use it standalone with any MCP client (Claude Code, Cursor, Cline, Aider) — see [Standalone use](#standalone-use--without-an-orchestrator). Or pair it with [essaim](https://github.com/swoofer/essaim) when you want pre-composed agent profiles, work-stealing templates, and a behavior catalog.
31
-
32
- ---
33
-
34
- ## Getting started
35
-
36
- ```bash
37
- # 1. Install
38
- npm install -g mcp-coordinator
39
-
40
- # 2. First-time setup — creates ~/.mcp-coordinator/, writes a default config,
41
- # and prints a .mcp.json snippet for your MCP client.
42
- mcp-coordinator init
43
-
44
- # 3. Start the server (foreground or --daemon for background)
45
- mcp-coordinator server start --daemon
46
-
47
- # 4. Verify
48
- mcp-coordinator server status
49
- mcp-coordinator dashboard # opens http://localhost:3100/dashboard
50
- ```
51
-
52
- Step 2 is idempotent — re-running `init` won't overwrite an existing config. The snippet it prints goes into your MCP client's config (e.g., `~/.claude/.mcp.json` for Claude Code). If you'd rather not copy-paste, run `mcp-coordinator init --write-mcp-config <project-path>` and the snippet is written to `<project-path>/.mcp.json` (merging if the file already exists).
53
-
54
- After step 4, every Claude Code (or other MCP-compatible) session connected to this coordinator can call all 26 tools (`register_agent`, `announce_work`, `post_to_thread`, `coordinator_status`, ...). For the full multi-Claude or team setup, see [Standalone use](#standalone-use--without-an-orchestrator).
55
-
56
- ---
57
-
58
- ## How It Works
59
-
60
- ```
61
- Agent A Agent B
62
- │ │
63
- │ announce_work │ announce_work
64
- ▼ ▼
65
- ┌──────────────┐ ┌──────────────┐
66
- │ MCP client │ ◄── MQTT ────► │ MCP client │
67
- │ (any vendor) │ push-based │ (any vendor) │
68
- └──────┬───────┘ └──────┬───────┘
69
- │ MCP HTTP / SSE │
70
- └──────────────┬────────────────┘
71
-
72
- ┌─────────▼──────────┐
73
- │ mcp-coordinator │
74
- │ 26 MCP tools + DB │
75
- │ Aedes MQTT broker │
76
- └─────────┬──────────┘
77
- │ SSE
78
- ┌─────────▼──────────┐
79
- │ Dashboard │
80
- │ live events/quota │
81
- └────────────────────┘
82
- ```
83
-
84
- The **consultation cycle** has four steps:
85
-
86
- 1. **Announce** — A client calls `announce_work` with target files, `depends_on_files`, and target modules before coding.
87
- 2. **Detect** — The coordinator scores impact against all online agents and opens a thread if a score ≥ 90 matches.
88
- 3. **Consult** — MQTT pushes the new thread to every affected agent. Each agent posts context, constraints, or proposes a resolution.
89
- 4. **Resolve** — Agents approve, contest, or propose again. The thread closes when consensus is reached, or auto-resolves after timeout / in gray zones.
90
-
91
- The server is **client-agnostic**: any MCP-compatible agent (Claude Code, Cursor, Cline, Aider, custom scripts) can connect over HTTP/SSE or stdio.
92
-
93
- ---
94
-
95
- ## MQTT Communication Layer
96
-
97
- The coordinator ships with an **embedded [Aedes](https://github.com/moscajs/aedes) MQTT broker**. Agents subscribe once and receive every coordination event in real-time — no polling, no extra infrastructure.
98
-
99
- ### Broker
100
-
101
- | Transport | Port | Use case |
102
- |-----------|------|----------|
103
- | TCP | `1883` (bind `127.0.0.1` by default) | Local / LAN agents, best latency |
104
- | WebSocket | `/mqtt` on the coordinator HTTP port (default `3100`) | Bun binary, remote agents, firewall-friendly |
105
-
106
- One coordinator = one broker. Nothing external to install.
107
-
108
- ### Topic map
109
-
110
- Every coordinator event is published on a well-known topic. Clients subscribe to the full set on connect.
111
-
112
- | Topic | Emitted when | Payload highlights |
113
- |-------|--------------|--------------------|
114
- | `coordinator/consultations/new` | A thread is opened | `thread_id`, `subject`, `initiator_id`, `target_modules`, `target_files` |
115
- | `coordinator/consultations/{id}/messages` | Anyone posts to a thread | `agent_id`, `name`, `content`, `type` (warning/context/proposal) |
116
- | `coordinator/consultations/{id}/status` | Thread transitions state | `status` ∈ `open` / `resolving` / `resolved` / `timeout` |
117
- | `coordinator/consultations/{id}/claimed` | An agent atomically claims a task (work-stealing) | `claimed_by`, `thread_id` |
118
- | `coordinator/consultations/{id}/completed` | Claimed task finishes | `agent_id`, `thread_id`, `resolution` |
119
- | `coordinator/agents/{id}/status` | Agent goes online / offline | `status`, `name`, `modules` |
120
- | `coordinator/broadcast` | System-wide announcements | arbitrary JSON |
121
- | `coordinator/quota/update` | Anthropic quota refresh | `usage`, `limit`, `utilization_pct` |
122
-
123
- ### Push delivery flow
124
-
125
- ```
126
- COORDINATOR BROKER (Aedes) CLIENT
127
- ─────────── ────────────── ──────
128
-
129
- announce_work() ──────────► publish subscribe
130
- coordinator/ ─► event
131
- consultations/new ─────────► classify topic
132
- self-msg filter
133
- ─► handler
134
- ```
135
-
136
- Key guarantees:
137
-
138
- - **Self-filter** — clients drop messages where `payload.agent_id` equals the local agent's id, so agents never wake on their own actions.
139
- - **Bun compatibility** — when consumed from a Bun-compiled client, a Duplex stream bridges the `mqtt` client to the native WebSocket API (the `ws` package receiver doesn't work under Bun).
140
- - **Backpressure-free** — messages are small JSON envelopes.
141
-
142
- ---
143
-
144
- ## Impact Scoring
145
-
146
- Every `announce_work` call scores all online agents across multiple detection layers. The highest matching layer wins.
147
-
148
- | Layer | Signal | Score | Trigger |
149
- |-------|--------|------:|---------|
150
- | 0a | Same file announced in active thread | 100 | `target_files` ∩ their `target_files` |
151
- | 0b | They modify a file you depend on | 80 | `depends_on_files` ∩ their `target_files` |
152
- | 0c | You modify a file they depend on | 80 | `target_files` ∩ their `depends_on_files` |
153
- | 1 | Same file recently edited | 100 | File tracker conflict (last 60s) |
154
- | 2 | Dependency file recently edited | 80 | `depends_on_files` recently touched |
155
- | 3 | Same module prefix | 30 | `target_modules` overlap |
156
-
157
- Scores are categorized into three outcomes:
158
-
159
- | Score | Category | Action |
160
- |-------|----------|--------|
161
- | ≥ 90 | `concerned` | Thread opened, consultation required |
162
- | 30–89 | `gray_zone` | Thread auto-resolved, introspection recommended |
163
- | < 30 | `pass` | No conflict, proceed immediately |
164
-
165
- > **Layer 0 is critical.** Without announced intentions, a two-agent scenario where both work in `src/auth/` would score only 30 (gray zone, auto-resolved). With `announce_work`, the same scenario scores 100 and triggers a full consultation.
166
-
167
- ---
168
-
169
- ## MCP Tools
170
-
171
- 26 tools organized by function. All registered under one HTTP/SSE transport at `/mcp` (and stdio for stdio-mode clients).
172
-
173
- ### Agent registry
174
-
175
- | Tool | Description |
176
- |------|-------------|
177
- | `register_agent` | Register as online with name and module list |
178
- | `list_agents` | List all registered online agents |
179
- | `heartbeat` | Update last-seen and derive activity status |
180
- | `agent_activity` | Get activity status for all online agents |
181
- | `wait_for_peers` | Block until N peers online, or timeout (prevents race before first announce) |
182
-
183
- ### Consultation
184
-
185
- | Tool | Description |
186
- |------|-------------|
187
- | `announce_work` | Open a consultation thread — the main entry point before coding |
188
- | `post_to_thread` | Post a message (warning, context, question) to an open thread |
189
- | `propose_resolution` | Submit a resolution proposal for participants to approve |
190
- | `approve_resolution` | Approve the current resolution proposal |
191
- | `contest_resolution` | Reject the proposal with a reason — resets to `open` |
192
- | `close_thread` | Close a thread after work is complete |
193
- | `cancel_thread` | Cancel a thread (work abandoned or no longer relevant) |
194
- | `get_thread` | Get a thread with all messages and current status |
195
- | `get_thread_updates` | Poll for new messages since a timestamp |
196
- | `list_threads` | List threads, filterable by status or agent |
197
- | `log_action_summary` | Log a one-liner action summary for the dashboard timeline |
198
-
199
- ### File tracking
200
-
201
- | Tool | Description |
202
- |------|-------------|
203
- | `hot_files` | List files being edited by multiple agents |
204
- | `get_session_files` | Get all files edited by an agent in the current session |
205
- | `check_file_conflict` | Check whether another agent edited a given file recently |
206
-
207
- ### Dependency map
208
-
209
- | Tool | Description |
210
- |------|-------------|
211
- | `set_dependency_map` | Load a module dependency graph (JSON) |
212
- | `get_blast_radius` | Calculate which other modules are affected by changes |
213
- | `get_module_info` | Get dependency and dependent info for a module |
214
-
215
- ### MQTT
216
-
217
- | Tool | Description |
218
- |------|-------------|
219
- | `wait_for_message` | Block until a coordination message arrives on the agent's topic |
220
- | `get_queued_messages` | Drain all queued messages without blocking |
221
- | `mqtt_publish` | Publish a raw message to any MQTT topic |
222
-
223
- ### Status
224
-
225
- | Tool | Description |
226
- |------|-------------|
227
- | `coordinator_status` | Full system status: agents, threads, file activity, MQTT, quota |
228
-
229
- The in-server `introspection` tool returns the full schema for every tool — point any MCP client at it for live discovery.
230
-
231
- ---
232
-
233
- ## CLI
234
-
235
- Two distribution channels:
236
-
237
- - **npm** — `npm install -g mcp-coordinator`. Requires Node.js 20+.
238
- - **Single-file binary** — Bun-compiled, no Node required. Download the matching tarball from a [GitHub Release](https://github.com/swoofer/mcp-coordinator/releases).
239
-
240
- ### Commands
241
-
242
- | Command | Description |
243
- |---------|-------------|
244
- | `mcp-coordinator init [--url <url>] [--write-mcp-config <path>] [--write-claude-md <path>]` | First-time setup — create config dir, default `config.json`, print/write the `.mcp.json` snippet, optionally scaffold a sample `CLAUDE.md` |
245
- | `mcp-coordinator uninstall [--mcp-config <path>] [--claude-md <path>] [--purge] [--force]` | Remove integrations: drop `coordinator` entry from a `.mcp.json`, strip the coordination section from a `CLAUDE.md`, or `--purge` the `~/.mcp-coordinator/` directory entirely |
246
- | `mcp-coordinator server start [--port N] [--data-dir PATH] [--daemon]` | Start the coordinator (foreground or daemon) |
247
- | `mcp-coordinator server stop` | Stop the coordinator |
248
- | `mcp-coordinator server status` | PID, port, online agents, open threads |
249
- | `mcp-coordinator server logs [-n N] [-f]` | Tail the daemon log at `~/.mcp-coordinator/logs/server.log` |
250
- | `mcp-coordinator dashboard` | Open `http://localhost:3100/dashboard` |
251
- | `mcp-coordinator doctor [--host H] [--port P] [--mqtt-port P]` | Health check: config, server liveness, `/health`, `/mcp` initialize, dashboard, MQTT broker |
252
- | `mcp-coordinator --version` | Print the installed version |
253
-
254
- ### Quick start
255
-
256
- ```bash
257
- # Start the coordinator (embedded MQTT + dashboard)
258
- mcp-coordinator server start --daemon
259
-
260
- # Open the dashboard
261
- mcp-coordinator dashboard
262
-
263
- # Stop when done
264
- mcp-coordinator server stop
265
- ```
266
-
267
- ### In-process from your own Node app
268
-
269
- ```ts
270
- import { startServer } from "mcp-coordinator";
271
-
272
- await startServer({
273
- port: 3100,
274
- dataDir: "./coordinator-data",
275
- });
276
- ```
277
-
278
- ---
279
-
280
- ## Standalone use — without an orchestrator
281
-
282
- You don't need an orchestrator. mcp-coordinator works on its own with any MCP-compatible client — Claude Code, Cursor, Cline, Aider, custom scripts. The two most common setups:
283
-
284
- ### Solo developer, multiple Claude Code sessions
285
-
286
- You're running 2-3 Claude Code sessions in parallel on the same repo and want them to see each other's work. One coordinator instance handles all of them.
287
-
288
- ```bash
289
- # In one terminal: start the coordinator
290
- mcp-coordinator server start --daemon
291
- ```
292
-
293
- Then add the coordinator to each Claude Code session's `.mcp.json` (located at `~/.claude/.mcp.json` for the global config, or `<your-project>/.mcp.json` for per-project):
294
-
295
- ```json
296
- {
297
- "mcpServers": {
298
- "coordinator": {
299
- "type": "http",
300
- "url": "http://localhost:3100/mcp"
301
- }
302
- }
303
- }
304
- ```
305
-
306
- Each Claude session now has access to all 26 coordination tools (`register_agent`, `announce_work`, `post_to_thread`, etc.). Open `mcp-coordinator dashboard` in a browser to watch real-time activity across your sessions.
307
-
308
- ### Team setup — shared coordinator on LAN
309
-
310
- One person hosts the coordinator on a shared machine; teammates point their Claude at it.
311
-
312
- Host:
313
-
314
- ```bash
315
- # Bind to all interfaces; default is 127.0.0.1
316
- COORDINATOR_BIND=0.0.0.0 mcp-coordinator server start --daemon
317
- ```
318
-
319
- Each teammate's `.mcp.json` points to the host's IP:
320
-
321
- ```json
322
- {
323
- "mcpServers": {
324
- "coordinator": {
325
- "type": "http",
326
- "url": "http://192.168.1.42:3100/mcp"
327
- }
328
- }
329
- }
330
- ```
331
-
332
- For internet-facing or multi-tenant deployments, enable JWT auth (see [Authentication](#authentication)). Each teammate registers via `POST /api/auth/register` with the team's `COORDINATOR_REGISTRATION_SECRET`, gets a Bearer token, and adds it to their `.mcp.json`:
333
-
334
- ```json
335
- {
336
- "mcpServers": {
337
- "coordinator": {
338
- "type": "http",
339
- "url": "https://coordinator.example.com/mcp",
340
- "headers": { "Authorization": "Bearer <your-token>" }
341
- }
342
- }
343
- }
344
- ```
345
-
346
- ### Telling Claude to use the coordinator tools
347
-
348
- Without a behavior catalog (which is what [essaim](https://github.com/swoofer/essaim) ships), you instruct Claude manually. Easiest path:
349
-
350
- ```bash
351
- # In your project root — scaffolds CLAUDE.md with coordinator instructions
352
- mcp-coordinator init --write-claude-md ~/my-repo --write-mcp-config ~/my-repo
353
- ```
354
-
355
- This appends a clearly-marked `mcp-coordinator:coordination-section` block to `~/my-repo/CLAUDE.md` (creating it if absent, replacing the section if it already exists). Combined with `--write-mcp-config`, your project is fully wired in one command.
356
-
357
- If you'd rather embed the instructions yourself (or you're not using Claude Code), the section reads roughly:
358
-
359
- > Before modifying any source file, register with the coordinator MCP server:
360
- >
361
- > 1. Call `register_agent` with your name and the modules you'll touch
362
- > 2. Call `announce_work` describing what you'll do, listing target files (and `depends_on_files` if applicable)
363
- > 3. If a thread is created (consultation triggered), wait for the resolution before writing code
364
- > 4. After a meaningful change, call `log_action_summary` to update the dashboard timeline
365
- > 5. If another agent is already working on a file you need to touch, post a question to the thread via `post_to_thread` and wait for their response before proceeding
366
- >
367
- > Use the `coordinator_status` tool to see current activity at any time.
368
-
369
- That's all you need to start coordinating. The dashboard shows live who's doing what; the SQLite database persists threads across sessions; conflicts are detected before code is written.
370
-
371
- ### Push vs polling — important architectural note
372
-
373
- Vanilla Claude Code talks to mcp-coordinator over MCP (HTTP/stdio request-response). It **does not subscribe to MQTT**. That means events the coordinator publishes on MQTT (`coordinator/consultations/new`, etc.) are not auto-delivered to a Claude Code session — Claude has to **poll** the coordinator to discover new activity. The polling pattern is:
374
-
375
- - `announce_work` returns the thread ID immediately if a conflict is detected — that's the most important checkpoint
376
- - After that, periodic calls to `coordinator_status` / `list_threads` / `get_thread_updates` surface new posts on threads you're a participant in
377
- - The CLAUDE.md scaffolded by `mcp-coordinator init --write-claude-md` instructs Claude to do exactly this polling
378
-
379
- If you want **real-time push** (every coordination event interrupting Claude between turns instead of waiting for a poll), use [essaim](https://github.com/swoofer/essaim). essaim ships an agent-loop wrapper that subscribes to the MQTT broker and injects events into the turn flow automatically. mcp-coordinator alone supports the polling model — which is sufficient for most use cases (2-3 Claude sessions on a small team) and zero-config to set up.
380
-
381
- ### End-to-end example: two Claudes coordinating (polling model)
382
-
383
- Two terminals, same repo, both Claude Code sessions wired to the same local coordinator. Both sessions have a `CLAUDE.md` scaffolded by `mcp-coordinator init --write-claude-md`, which instructs Claude to register, announce, and poll. The conversation below is what each Claude does — the human user just asks each Claude to make a change.
384
-
385
- ```
386
- TERMINAL 1 (Alice) TERMINAL 2 (Bob)
387
-
388
- $ claude $ claude
389
- > "Add updated_at to User type in > "Migrate User schema"
390
- src/models/user.ts" (touches src/models/user.ts)
391
-
392
- [Alice's Claude] [Bob's Claude]
393
- register_agent(name="Alice", ...) register_agent(name="Bob", ...)
394
- announce_work(
395
- target_files: ["src/models/user.ts"]
396
- )
397
- → response: { thread_id: null,
398
- concerned_agents: [] } announce_work(
399
- target_files: ["src/models/user.ts",
400
- "migrations/004.sql"]
401
- )
402
- → response: { thread_id: "T-1",
403
- concerned_agents: ["alice"],
404
- score: 100, layer: "0a" }
405
- [Bob sees the conflict in the response]
406
- get_thread("T-1")
407
- post_to_thread("T-1", type: "context",
408
- content: "full schema migration; can
409
- wait for your field to land first")
410
-
411
- [Alice writes the field, then before
412
- next major action the CLAUDE.md says
413
- "poll coordinator_status"]
414
- coordinator_status()
415
- → response: shows T-1 with Bob's post
416
- get_thread("T-1")
417
- post_to_thread("T-1", type: "context",
418
- content: "adding 1 field at line 42,
419
- no rename. Done in 5 min.")
420
- propose_resolution("T-1",
421
- content: "Alice's field first,
422
- Bob runs migration after")
423
-
424
- [Bob's CLAUDE.md polling step]
425
- coordinator_status()
426
- → shows T-1 in 'resolving' state
427
- get_thread("T-1")
428
- approve_resolution("T-1")
429
-
430
- [Alice's next poll]
431
- coordinator_status()
432
- → T-1 status = 'resolved'
433
- [Alice writes the field] [Bob writes the migration]
434
- log_action_summary(...) log_action_summary(...)
435
- ```
436
-
437
- The dashboard at `http://localhost:3100/dashboard/` plays the entire timeline live. `mcp-coordinator server logs -f` (in a third terminal) tails the daemon log if you want to see the protocol-level events. If polling cadence is too coarse and you find Claude missing posts, switch to essaim's agent-loop, which delivers MQTT events automatically.
438
-
439
- ### Team setup walkthrough — shared coordinator with JWT
440
-
441
- Full step-by-step for a team running a coordinator on a shared host with internet-facing or multi-tenant access. Adjust to your network/TLS reality.
442
-
443
- **Step 1 (host) — generate secrets**
444
-
445
- ```bash
446
- # 32+ char shared secret; put in your secrets manager and inject as env vars
447
- JWT_SECRET=$(openssl rand -hex 32)
448
- REGISTRATION_SECRET=$(openssl rand -hex 32)
449
- ADMIN_SECRET=$(openssl rand -hex 32)
450
- ```
451
-
452
- **Step 2 (host) — start the coordinator with auth enabled**
453
-
454
- ```bash
455
- COORDINATOR_AUTH_ENABLED=true \
456
- COORDINATOR_JWT_SECRET="$JWT_SECRET" \
457
- COORDINATOR_REGISTRATION_SECRET="$REGISTRATION_SECRET" \
458
- COORDINATOR_ADMIN_SECRET="$ADMIN_SECRET" \
459
- COORDINATOR_BIND=0.0.0.0 \
460
- mcp-coordinator server start --daemon --port 3100
461
- ```
462
-
463
- (Front the server with TLS via nginx/Caddy/etc. for internet exposure. Local LAN can use plain HTTP.)
464
-
465
- **Step 3 (each teammate) — request a token**
466
-
467
- ```bash
468
- curl -X POST https://coordinator.example.com/api/auth/register \
469
- -H "Content-Type: application/json" \
470
- -d '{"agent_name":"alice","registration_secret":"<REGISTRATION_SECRET shared via team channel>"}'
471
- # Response: { "agent_id": "alice-abc123", "token": "eyJ...", "expires_at": "...", "role": "agent" }
472
- ```
473
-
474
- **Step 4 (each teammate) — wire `.mcp.json`**
475
-
476
- ```json
477
- {
478
- "mcpServers": {
479
- "coordinator": {
480
- "type": "http",
481
- "url": "https://coordinator.example.com/mcp",
482
- "headers": { "Authorization": "Bearer <paste-token-here>" }
483
- }
484
- }
485
- }
486
- ```
487
-
488
- **Step 5 (each teammate) — run `init --write-claude-md` to scaffold project instructions**, OR add the coordination section to their existing `CLAUDE.md`.
489
-
490
- **Step 6 (each teammate) — verify**: `mcp-coordinator doctor --host coordinator.example.com --port 443` should show all checks green from any laptop.
491
-
492
- **Token rotation**: tokens expire per `COORDINATOR_JWT_EXPIRY` (default 24h). Refresh via `POST /api/auth/refresh` with the current Bearer token. The admin can revoke a specific agent via `POST /api/auth/revoke` (admin token required).
493
-
494
- ### Logs and debugging
495
-
496
- The daemon writes to `~/.mcp-coordinator/logs/server.log`. Tail it:
497
-
498
- ```bash
499
- mcp-coordinator server logs # last 50 lines
500
- mcp-coordinator server logs -n 200 # last 200 lines
501
- mcp-coordinator server logs -f # follow (Ctrl+C to stop)
502
- ```
503
-
504
- For a one-shot check that everything is wired up correctly (config valid, server up, MCP responds, dashboard reachable, MQTT accepting connections), use the doctor:
505
-
506
- ```bash
507
- mcp-coordinator doctor
508
- ```
509
-
510
- `doctor` exits non-zero if any check fails and prints actionable hints next to each failure. Probe a remote coordinator with `--host` and `--port`:
511
-
512
- ```bash
513
- mcp-coordinator doctor --host coordinator.example.com --port 443 --mqtt-port 1883
514
- ```
515
-
516
- Logging level is controlled by `LOG_LEVEL` (`debug`, `info`, `warn`, `error` — default `info`). Set `NODE_ENV=development` for human-readable pretty logs:
517
-
518
- ```bash
519
- NODE_ENV=development LOG_LEVEL=debug mcp-coordinator server start
520
- ```
521
-
522
- ### Removing the integration (per-project or globally)
523
-
524
- Symmetric to `init`, the `uninstall` command undoes what was added without touching anything you wrote yourself.
525
-
526
- ```bash
527
- # Remove coordinator from a project's .mcp.json AND strip its section from CLAUDE.md
528
- mcp-coordinator uninstall --mcp-config ~/my-repo --claude-md ~/my-repo
529
-
530
- # Wipe the global config dir (~/.mcp-coordinator/) entirely — config + data + logs + pid file
531
- mcp-coordinator uninstall --purge # asks for confirmation
532
- mcp-coordinator uninstall --purge --force # skip the prompt, useful in scripts
533
- ```
534
-
535
- `--mcp-config <path>` reads `<path>/.mcp.json`, removes only the `coordinator` server entry (other servers untouched), and deletes the file if it ends up empty. `--claude-md <path>` removes only the block between the `<!-- mcp-coordinator:coordination-section -->` sentinels — it never touches text you authored. Combine flags as needed; if the resulting `CLAUDE.md` is empty, it's deleted.
536
-
537
- To remove the npm package itself: `npm uninstall -g mcp-coordinator`.
538
-
539
- ### Running multiple coordinators on the same machine
540
-
541
- Useful for per-project isolation — every project gets its own ephemeral coordinator with no cross-contamination. Pick distinct ports + data dirs:
542
-
543
- ```bash
544
- # Project A
545
- PORT=3110 \
546
- COORDINATOR_MQTT_TCP_PORT=11883 \
547
- mcp-coordinator server start --daemon --data-dir ./.mcp-coordinator-A
548
-
549
- # Project B (different terminal)
550
- PORT=3120 \
551
- COORDINATOR_MQTT_TCP_PORT=12883 \
552
- mcp-coordinator server start --daemon --data-dir ./.mcp-coordinator-B
553
- ```
554
-
555
- The default `~/.mcp-coordinator/server.pid` only tracks ONE daemon at a time. For multi-instance runs, pass `--data-dir` explicitly to each instance — the PID file lives next to the data dir, so multiple instances don't fight over the same file. To stop a specific instance, `cd` to its data dir's parent and run `mcp-coordinator server stop` from there, OR `kill $(cat ./.mcp-coordinator-A/../server.pid)`.
556
-
557
- In each project's `.mcp.json`, point at the project's coordinator:
558
-
559
- ```json
560
- {
561
- "mcpServers": {
562
- "coordinator": {
563
- "type": "http",
564
- "url": "http://localhost:3110/mcp"
565
- }
566
- }
567
- }
568
- ```
569
-
570
- This pattern works well alongside `essaim`, which uses Strategy A (in-process) and starts its own ephemeral coordinator per `essaim run` — there's no port conflict because essaim picks an isolated dir by default.
571
-
572
- ---
573
-
574
- ## Anthropic Quota Pre-flight
575
-
576
- The coordinator tracks Anthropic workspace quota live and exposes it on MQTT, the dashboard, and the `coordinator_status` MCP tool — so MCP clients can decide whether to abort, throttle, or proceed before launching expensive turns.
577
-
578
- - Reads usage from the Anthropic API using the key in the environment.
579
- - Threshold via `MAX_QUOTA_PCT` env var (default `95`).
580
- - Back-off when the usage endpoint itself returns 429.
581
- - Live widget in the dashboard with manual refresh + historical buckets.
582
- - `coordinator/quota/update` MQTT events stream into the timeline by default.
583
-
584
- Orchestrators that spawn N agents at once can read `coordinator_status.quota` and abort their run if utilization is over a configured threshold — the [essaim](https://github.com/swoofer/essaim) reference orchestrator does exactly this.
585
-
586
- ---
587
-
588
- ## Token Observability
589
-
590
- Every MCP tool call and agent turn is logged with token breakdown.
591
-
592
- - **Logs** — component logger `tokens` emits `input_tokens`, `output_tokens`, `cache_read`, `cache_creation`, `thinking`, model id, turn index.
593
- - **Dashboard** — live per-agent token gauge, cumulative session total, quota widget.
594
-
595
- Aggregating across runs (e.g., `reports/YYYY-MM-DD-<run-id>.md`) is an orchestrator responsibility — the coordinator emits the events, the orchestrator consumes them.
596
-
597
- ---
598
-
599
- ## Dashboard
600
-
601
- `http://localhost:3100/dashboard` (or `/dashboard` on whichever port the coordinator is bound to).
602
-
603
- - **Timeline** — all threads + `quota_update` events with scores and resolution types
604
- - **Agent panel** — online/offline, working/idle/waiting, current file, thread being waited on. Resizable drag handle.
605
- - **Scoring breakdown** — which detection layer triggered each conflict
606
- - **Quota widget** — live utilization %, stacked buckets, manual refresh button
607
- - **Version banner** — server version shown in the header (dynamic, not hardcoded)
608
- - **Consensus metrics** — per session: consensus / timeout / auto-resolved split, token totals
609
-
610
- All events arrive via SSE on `/api/events`. No polling.
611
-
612
- ---
613
-
614
- ## Agent Activity States
615
-
616
- | Status | Indicator | Meaning |
617
- |--------|-----------|---------|
618
- | working | pulsing blue | Actively editing files |
619
- | idle | solid green | Online, no recent activity |
620
- | waiting | pulsing yellow | Blocked on a consultation thread |
621
- | offline | solid red | Disconnected or session ended |
622
-
623
- Activity is derived from heartbeats enriched with the current file/thread context from the file tracker.
624
-
625
- ---
626
-
627
- ## Configuration
628
-
629
- ### Local data
630
-
631
- ```
632
- ~/.mcp-coordinator/
633
- ├── config.json # persistent configuration
634
- ├── data/
635
- │ └── coordinator.db # SQLite database
636
- ├── server.pid # PID file (when daemonized)
637
- └── logs/
638
- └── server.log # daemon logs
639
- ```
640
-
641
- ### config.json
642
-
643
- ```json
644
- {
645
- "server": { "port": 3100, "data_dir": "~/.mcp-coordinator/data" },
646
- "defaults": { "coordinator_url": "http://localhost:3100" }
647
- }
648
- ```
649
-
650
- Resolution priority (highest to lowest): CLI flag → env var → config.json → default.
651
-
652
- ### Server env vars
653
-
654
- | Variable | Default | Description |
655
- |----------|---------|-------------|
656
- | `PORT` | `3100` | HTTP port (also serves MQTT-over-WebSocket on `/mqtt`) |
657
- | `COORDINATOR_DATA_DIR` | `~/.mcp-coordinator/data` | Directory for the SQLite database |
658
- | `COORDINATOR_MQTT_TCP_PORT` | `1883` | TCP port for the embedded broker |
659
- | `COORDINATOR_MQTT_WS_PATH` | `/mqtt` | WebSocket path on the same HTTP port |
660
- | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
661
- | `NODE_ENV` | — | `development` for pretty logs |
662
- | `COORDINATOR_AUTH_ENABLED` | `false` | Enable JWT authentication |
663
- | `COORDINATOR_JWT_SECRET` | — | HMAC signing key (min 32 chars) |
664
- | `COORDINATOR_JWT_EXPIRY` | `24h` | Token lifetime (e.g., `1h`, `7d`) |
665
- | `COORDINATOR_REGISTRATION_SECRET` | — | Shared secret for agent auto-register |
666
- | `COORDINATOR_ADMIN_SECRET` | — | Separate secret for admin token creation |
667
- | `MAX_QUOTA_PCT` | `95` | Pre-flight abort threshold for Anthropic quota |
668
-
669
- ---
670
-
671
- ## Structured Logging
672
-
673
- [Pino](https://getpino.io/) emits JSON per subsystem. Component loggers: `http`, `mcp`, `mqtt`, `consultation`, `conflict`, `auth`, `tokens`, `quota`.
674
-
675
- Production (default):
676
-
677
- ```json
678
- {"level":"info","time":1712345678901,"component":"http","msg":"Server started","port":3100}
679
- ```
680
-
681
- Dev (`NODE_ENV=development`):
682
-
683
- ```
684
- [14:21:03.456] INFO (http): Server started
685
- port: 3100
686
- ```
687
-
688
- Levels controlled via `LOG_LEVEL`.
689
-
690
- ---
691
-
692
- ## Authentication
693
-
694
- Opt-in JWT (HS256 via [jose](https://github.com/panva/jose)). Set `COORDINATOR_AUTH_ENABLED=true` plus the required secrets to enable.
695
-
696
- ### Setup
697
-
698
- ```bash
699
- export COORDINATOR_AUTH_ENABLED=true
700
- export COORDINATOR_JWT_SECRET="your-secret-at-least-32-characters-long"
701
- export COORDINATOR_REGISTRATION_SECRET="team-shared-secret"
702
- export COORDINATOR_ADMIN_SECRET="admin-only-secret"
703
- ```
704
-
705
- ### Agent self-register
706
-
707
- ```bash
708
- curl -X POST http://localhost:3100/api/auth/register \
709
- -H "Content-Type: application/json" \
710
- -d '{"agent_name":"my-agent","registration_secret":"team-shared-secret"}'
711
- # → { agent_id, token, expires_at, role }
712
- ```
713
-
714
- ### Refresh
715
-
716
- ```bash
717
- curl -X POST http://localhost:3100/api/auth/refresh \
718
- -H "Authorization: Bearer <current-token>"
719
- ```
720
-
721
- ### Revoke (admin)
722
-
723
- ```bash
724
- curl -X POST http://localhost:3100/api/auth/revoke \
725
- -H "Authorization: Bearer <admin-token>" \
726
- -H "Content-Type: application/json" \
727
- -d '{"agent_id":"agent-to-revoke"}'
728
- ```
729
-
730
- ### Exempt routes
731
-
732
- `GET /health`, `POST /api/auth/register`, `POST /api/auth/refresh`, `GET /api/events` (SSE).
733
-
734
- ---
735
-
736
- ## Test Results
737
-
738
- All four coordination scenarios are validated end-to-end by the test suite:
739
-
740
- | Scenario | Layer | Score | Category | Outcome |
741
- |----------|-------|------:|----------|---------|
742
- | S1 — Same file | 0a | 100 | concerned | Thread opened → consensus |
743
- | S2 — Same module | 3 | 30 | gray_zone | Auto-resolved, introspection |
744
- | S3 — Dependency | 0b | 80 | gray_zone | Auto-resolved, introspection |
745
- | S4 — No overlap | — | 0 | pass | Auto-resolved immediately |
746
-
747
- **Performance:**
748
-
749
- | Component | Time |
750
- |-----------|------|
751
- | Conflict detection (no LLM) | < 5 ms |
752
- | MQTT push delivery | < 50 ms end-to-end |
753
- | Full consultation cycle (S1) | 30–45 s |
754
-
755
- ---
756
-
757
- ## Integration patterns
758
-
759
- ### Any MCP client
760
-
761
- Connect to `http://localhost:3100/mcp` (HTTP/SSE) or stdio. The server speaks MCP 2024-11-05.
762
-
763
- ### Custom orchestrator
764
-
765
- Spawn agents that connect to the MQTT broker and register via the MCP `register_agent` tool. The orchestrator decides spawn count, lifecycle, and quota gating; the coordinator handles the protocol. See [essaim](https://github.com/swoofer/essaim) for a reference implementation, or write your own.
766
-
767
- ### Reference catalog of coordinator-aware behaviors
768
-
769
- The behaviors that make agents announce-before-write, resolve conflicts, and participate in work-stealing are YAML configs assembled by [@swoofer/promptweave](https://github.com/swoofer/promptweave). See [essaim's behaviors](https://github.com/swoofer/essaim/tree/main/behaviors) for a curated catalog.
770
-
771
- ---
772
-
773
- ## Development
774
-
775
- ```bash
776
- # Tests (216 passing across 18 files)
777
- npm test
778
- npm run test:watch
779
-
780
- # Dev coordinator (tsx, hot reload)
781
- npm run dev # HTTP / SSE on port 3100
782
- npm run dev:stdio # stdio mode
783
-
784
- # CLI in dev
785
- npm run cli -- server start
786
- npm run cli -- dashboard
787
-
788
- # TypeScript build → dist/
789
- npm run build
790
-
791
- # Standalone binary (requires Bun)
792
- bun build --compile cli/index.ts --outfile bin/mcp-coordinator
793
- ```
794
-
795
- ### Project structure
796
-
797
- ```
798
- src/ # Coordinator (npm package surface)
799
- serve-http.ts # HTTP/SSE/MCP server entry
800
- server-setup.ts # 26 MCP tool registrations
801
- impact-scorer.ts # multi-layer conflict detection
802
- consultation.ts # Thread lifecycle
803
- agent-registry.ts # Online agents
804
- file-tracker.ts # File edit history
805
- dependency-map.ts # Module graph
806
- agent-activity.ts # working/idle/waiting/offline
807
- mqtt-broker.ts # Embedded Aedes (TCP + WS)
808
- mqtt-bridge.ts # Coordinator → broker fanout
809
- quota/ # Anthropic quota pre-flight + refresh
810
- auth.ts # Optional JWT
811
- index.ts # Stdio entry + programmatic re-exports
812
-
813
- cli/ # CLI binary (mcp-coordinator)
814
- index.ts # Entry point
815
- server/ # start / stop / status
816
- dashboard.ts # Open dashboard URL
817
- config.ts # Config loader
818
- version.ts # package.json version helper
819
-
820
- tests/unit/ # Vitest — 216 tests, 18 files
821
- dashboard/public/ # Single-file web dashboard
822
- ```
823
-
824
- ---
825
-
826
- ## Related projects
827
-
828
- - **[@swoofer/promptweave](https://github.com/swoofer/promptweave)** — YAML composer for assembling agent prompts, hooks, and MCP configs. Use it with mcp-coordinator-aware behaviors from essaim.
829
- - **[essaim](https://github.com/swoofer/essaim)** — end-to-end orchestrator that spawns N coordinated agents using `@swoofer/promptweave` + `mcp-coordinator`. Ships the reference catalog of coordinator-aware behaviors.
830
-
831
- ---
832
-
833
- ## Support
834
-
835
- Solo maintainer. If this project saves you time, consider supporting development:
836
-
837
- - [GitHub Sponsors](https://github.com/sponsors/swoofer)
838
- - [Buy Me A Coffee](https://buymeacoffee.com/swoofer)
839
-
840
- A star on the repo also helps surface the project to other developers.
841
-
842
- ---
843
-
844
- ## License
845
-
846
- MIT
1
+ <div align="center">
2
+
3
+ # mcp-coordinator
4
+
5
+ **Embedded MQTT broker + MCP server for multi-agent coordination. Zero conflicts, everyone aligned.**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+ [![npm](https://img.shields.io/npm/v/mcp-coordinator.svg)](https://www.npmjs.com/package/mcp-coordinator)
9
+ [![Tests](https://github.com/swoofer/mcp-coordinator/actions/workflows/test.yml/badge.svg)](https://github.com/swoofer/mcp-coordinator/actions)
10
+
11
+ [Getting started](#getting-started) · [Problem](#the-problem) · [How It Works](#how-it-works) · [MQTT Layer](#mqtt-communication-layer) · [Scoring](#impact-scoring) · [MCP Tools](#mcp-tools) · [CLI](#cli) · [Standalone use](#standalone-use--without-an-orchestrator) · [Quota](#anthropic-quota-pre-flight) · [Dashboard](#dashboard) · [Config](#configuration) · [Auth](#authentication)
12
+
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## The Problem
18
+
19
+ When multiple developers each use an AI coding agent in parallel on the same repo, things break:
20
+
21
+ - **Regressions** — Agent A rewrites a module that Agent B was depending on
22
+ - **Duplicated work** — Two agents implement the same feature from different directions
23
+ - **Architectural drift** — Agents make local decisions that conflict with each other's designs
24
+ - **Wasted reconciliation time** — Developers spend hours untangling what the agents did
25
+
26
+ Each agent works in isolation. None of them know what the others are doing.
27
+
28
+ mcp-coordinator fixes this by giving agents a **shared nervous system over MQTT** — they announce intentions before coding, conflicts are detected before a single line is written, and agents see each other's actions in real-time to agree on an approach.
29
+
30
+ It works **with or without** an orchestrator on top. Use it standalone with any MCP client (Claude Code, Cursor, Cline, Aider) — see [Standalone use](#standalone-use--without-an-orchestrator). Or pair it with [essaim](https://github.com/swoofer/essaim) when you want pre-composed agent profiles, work-stealing templates, and a behavior catalog.
31
+
32
+ ---
33
+
34
+ ## Getting started
35
+
36
+ ```bash
37
+ # 1. Install
38
+ npm install -g mcp-coordinator
39
+
40
+ # 2. First-time setup — creates ~/.mcp-coordinator/, writes a default config,
41
+ # and prints a .mcp.json snippet for your MCP client.
42
+ mcp-coordinator init
43
+
44
+ # 3. Start the server (foreground or --daemon for background)
45
+ mcp-coordinator server start --daemon
46
+
47
+ # 4. Verify
48
+ mcp-coordinator server status
49
+ mcp-coordinator dashboard # opens http://localhost:3100/dashboard
50
+ ```
51
+
52
+ Step 2 is idempotent — re-running `init` won't overwrite an existing config. The snippet it prints goes into your MCP client's config (e.g., `~/.claude/.mcp.json` for Claude Code). If you'd rather not copy-paste, run `mcp-coordinator init --write-mcp-config <project-path>` and the snippet is written to `<project-path>/.mcp.json` (merging if the file already exists).
53
+
54
+ After step 4, every Claude Code (or other MCP-compatible) session connected to this coordinator can call all 26 tools (`register_agent`, `announce_work`, `post_to_thread`, `coordinator_status`, ...). For the full multi-Claude or team setup, see [Standalone use](#standalone-use--without-an-orchestrator).
55
+
56
+ ---
57
+
58
+ ## How It Works
59
+
60
+ ```
61
+ Agent A Agent B
62
+ │ │
63
+ │ announce_work │ announce_work
64
+ ▼ ▼
65
+ ┌──────────────┐ ┌──────────────┐
66
+ │ MCP client │ ◄── MQTT ────► │ MCP client │
67
+ │ (any vendor) │ push-based │ (any vendor) │
68
+ └──────┬───────┘ └──────┬───────┘
69
+ │ MCP HTTP / SSE │
70
+ └──────────────┬────────────────┘
71
+
72
+ ┌─────────▼──────────┐
73
+ │ mcp-coordinator │
74
+ │ 26 MCP tools + DB │
75
+ │ Aedes MQTT broker │
76
+ └─────────┬──────────┘
77
+ │ SSE
78
+ ┌─────────▼──────────┐
79
+ │ Dashboard │
80
+ │ live events/quota │
81
+ └────────────────────┘
82
+ ```
83
+
84
+ The **consultation cycle** has four steps:
85
+
86
+ 1. **Announce** — A client calls `announce_work` with target files, `depends_on_files`, and target modules before coding.
87
+ 2. **Detect** — The coordinator scores impact against all online agents and opens a thread if a score ≥ 90 matches.
88
+ 3. **Consult** — MQTT pushes the new thread to every affected agent. Each agent posts context, constraints, or proposes a resolution.
89
+ 4. **Resolve** — Agents approve, contest, or propose again. The thread closes when consensus is reached, or auto-resolves after timeout / in gray zones.
90
+
91
+ The server is **client-agnostic**: any MCP-compatible agent (Claude Code, Cursor, Cline, Aider, custom scripts) can connect over HTTP/SSE or stdio.
92
+
93
+ ---
94
+
95
+ ## MQTT Communication Layer
96
+
97
+ The coordinator ships with an **embedded [Aedes](https://github.com/moscajs/aedes) MQTT broker**. Agents subscribe once and receive every coordination event in real-time — no polling, no extra infrastructure.
98
+
99
+ ### Broker
100
+
101
+ | Transport | Port | Use case |
102
+ |-----------|------|----------|
103
+ | TCP | `1883` (bind `127.0.0.1` by default) | Local / LAN agents, best latency |
104
+ | WebSocket | `/mqtt` on the coordinator HTTP port (default `3100`) | Bun binary, remote agents, firewall-friendly |
105
+
106
+ One coordinator = one broker. Nothing external to install.
107
+
108
+ ### Topic map
109
+
110
+ Every coordinator event is published on a well-known topic. Clients subscribe to the full set on connect.
111
+
112
+ | Topic | Emitted when | Payload highlights |
113
+ |-------|--------------|--------------------|
114
+ | `coordinator/consultations/new` | A thread is opened | `thread_id`, `subject`, `initiator_id`, `target_modules`, `target_files` |
115
+ | `coordinator/consultations/{id}/messages` | Anyone posts to a thread | `agent_id`, `name`, `content`, `type` (warning/context/proposal) |
116
+ | `coordinator/consultations/{id}/status` | Thread transitions state | `status` ∈ `open` / `resolving` / `resolved` / `timeout` |
117
+ | `coordinator/consultations/{id}/claimed` | An agent atomically claims a task (work-stealing) | `claimed_by`, `thread_id` |
118
+ | `coordinator/consultations/{id}/completed` | Claimed task finishes | `agent_id`, `thread_id`, `resolution` |
119
+ | `coordinator/agents/{id}/status` | Agent goes online / offline | `status`, `name`, `modules` |
120
+ | `coordinator/broadcast` | System-wide announcements | arbitrary JSON |
121
+ | `coordinator/quota/update` | Anthropic quota refresh | `usage`, `limit`, `utilization_pct` |
122
+
123
+ ### Push delivery flow
124
+
125
+ ```
126
+ COORDINATOR BROKER (Aedes) CLIENT
127
+ ─────────── ────────────── ──────
128
+
129
+ announce_work() ──────────► publish subscribe
130
+ coordinator/ ─► event
131
+ consultations/new ─────────► classify topic
132
+ self-msg filter
133
+ ─► handler
134
+ ```
135
+
136
+ Key guarantees:
137
+
138
+ - **Self-filter** — clients drop messages where `payload.agent_id` equals the local agent's id, so agents never wake on their own actions.
139
+ - **Bun compatibility** — when consumed from a Bun-compiled client, a Duplex stream bridges the `mqtt` client to the native WebSocket API (the `ws` package receiver doesn't work under Bun).
140
+ - **Backpressure-free** — messages are small JSON envelopes.
141
+
142
+ ---
143
+
144
+ ## Impact Scoring
145
+
146
+ Every `announce_work` call scores all online agents across multiple detection layers. The highest matching layer wins.
147
+
148
+ | Layer | Signal | Score | Trigger |
149
+ |-------|--------|------:|---------|
150
+ | 0a | Same file announced in active thread | 100 | `target_files` ∩ their `target_files` |
151
+ | 0b | They modify a file you depend on | 80 | `depends_on_files` ∩ their `target_files` |
152
+ | 0c | You modify a file they depend on | 80 | `target_files` ∩ their `depends_on_files` |
153
+ | 1 | Same file recently edited | 100 | File tracker conflict (last 60s) |
154
+ | 2 | Dependency file recently edited | 80 | `depends_on_files` recently touched |
155
+ | 3 | Same module prefix | 30 | `target_modules` overlap |
156
+
157
+ Scores are categorized into three outcomes:
158
+
159
+ | Score | Category | Action |
160
+ |-------|----------|--------|
161
+ | ≥ 90 | `concerned` | Thread opened, consultation required |
162
+ | 30–89 | `gray_zone` | Thread auto-resolved, introspection recommended |
163
+ | < 30 | `pass` | No conflict, proceed immediately |
164
+
165
+ > **Layer 0 is critical.** Without announced intentions, a two-agent scenario where both work in `src/auth/` would score only 30 (gray zone, auto-resolved). With `announce_work`, the same scenario scores 100 and triggers a full consultation.
166
+
167
+ ---
168
+
169
+ ## MCP Tools
170
+
171
+ 26 tools organized by function. All registered under one HTTP/SSE transport at `/mcp` (and stdio for stdio-mode clients).
172
+
173
+ ### Agent registry
174
+
175
+ | Tool | Description |
176
+ |------|-------------|
177
+ | `register_agent` | Register as online with name and module list |
178
+ | `list_agents` | List all registered online agents |
179
+ | `heartbeat` | Update last-seen and derive activity status |
180
+ | `agent_activity` | Get activity status for all online agents |
181
+ | `wait_for_peers` | Block until N peers online, or timeout (prevents race before first announce) |
182
+
183
+ ### Consultation
184
+
185
+ | Tool | Description |
186
+ |------|-------------|
187
+ | `announce_work` | Open a consultation thread — the main entry point before coding |
188
+ | `post_to_thread` | Post a message (warning, context, question) to an open thread |
189
+ | `propose_resolution` | Submit a resolution proposal for participants to approve |
190
+ | `approve_resolution` | Approve the current resolution proposal |
191
+ | `contest_resolution` | Reject the proposal with a reason — resets to `open` |
192
+ | `close_thread` | Close a thread after work is complete |
193
+ | `cancel_thread` | Cancel a thread (work abandoned or no longer relevant) |
194
+ | `get_thread` | Get a thread with all messages and current status |
195
+ | `get_thread_updates` | Poll for new messages since a timestamp |
196
+ | `list_threads` | List threads, filterable by status or agent |
197
+ | `log_action_summary` | Log a one-liner action summary for the dashboard timeline |
198
+
199
+ ### File tracking
200
+
201
+ | Tool | Description |
202
+ |------|-------------|
203
+ | `hot_files` | List files being edited by multiple agents |
204
+ | `get_session_files` | Get all files edited by an agent in the current session |
205
+ | `check_file_conflict` | Check whether another agent edited a given file recently |
206
+
207
+ ### Dependency map
208
+
209
+ | Tool | Description |
210
+ |------|-------------|
211
+ | `set_dependency_map` | Load a module dependency graph (JSON) |
212
+ | `get_blast_radius` | Calculate which other modules are affected by changes |
213
+ | `get_module_info` | Get dependency and dependent info for a module |
214
+
215
+ ### MQTT
216
+
217
+ | Tool | Description |
218
+ |------|-------------|
219
+ | `wait_for_message` | Block until a coordination message arrives on the agent's topic |
220
+ | `get_queued_messages` | Drain all queued messages without blocking |
221
+ | `mqtt_publish` | Publish a raw message to any MQTT topic |
222
+
223
+ ### Status
224
+
225
+ | Tool | Description |
226
+ |------|-------------|
227
+ | `coordinator_status` | Full system status: agents, threads, file activity, MQTT, quota |
228
+
229
+ The in-server `introspection` tool returns the full schema for every tool — point any MCP client at it for live discovery.
230
+
231
+ ---
232
+
233
+ ## CLI
234
+
235
+ Two distribution channels:
236
+
237
+ - **npm** — `npm install -g mcp-coordinator`. Requires Node.js 20+.
238
+ - **Single-file binary** — Bun-compiled, no Node required. Download the matching tarball from a [GitHub Release](https://github.com/swoofer/mcp-coordinator/releases).
239
+
240
+ ### Commands
241
+
242
+ | Command | Description |
243
+ |---------|-------------|
244
+ | `mcp-coordinator init [--url <url>] [--write-mcp-config <path>] [--write-claude-md <path>]` | First-time setup — create config dir, default `config.json`, print/write the `.mcp.json` snippet, optionally scaffold a sample `CLAUDE.md` |
245
+ | `mcp-coordinator uninstall [--mcp-config <path>] [--claude-md <path>] [--purge] [--force]` | Remove integrations: drop `coordinator` entry from a `.mcp.json`, strip the coordination section from a `CLAUDE.md`, or `--purge` the `~/.mcp-coordinator/` directory entirely |
246
+ | `mcp-coordinator server start [--port N] [--data-dir PATH] [--daemon]` | Start the coordinator (foreground or daemon) |
247
+ | `mcp-coordinator server stop` | Stop the coordinator |
248
+ | `mcp-coordinator server status` | PID, port, online agents, open threads |
249
+ | `mcp-coordinator server logs [-n N] [-f]` | Tail the daemon log at `~/.mcp-coordinator/logs/server.log` |
250
+ | `mcp-coordinator dashboard` | Open `http://localhost:3100/dashboard` |
251
+ | `mcp-coordinator doctor [--host H] [--port P] [--mqtt-port P]` | Health check: config, server liveness, `/health`, `/mcp` initialize, dashboard, MQTT broker |
252
+ | `mcp-coordinator --version` | Print the installed version |
253
+
254
+ ### Quick start
255
+
256
+ ```bash
257
+ # Start the coordinator (embedded MQTT + dashboard)
258
+ mcp-coordinator server start --daemon
259
+
260
+ # Open the dashboard
261
+ mcp-coordinator dashboard
262
+
263
+ # Stop when done
264
+ mcp-coordinator server stop
265
+ ```
266
+
267
+ ### In-process from your own Node app
268
+
269
+ ```ts
270
+ import { startServer } from "mcp-coordinator";
271
+
272
+ await startServer({
273
+ port: 3100,
274
+ dataDir: "./coordinator-data",
275
+ });
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Standalone use — without an orchestrator
281
+
282
+ You don't need an orchestrator. mcp-coordinator works on its own with any MCP-compatible client — Claude Code, Cursor, Cline, Aider, custom scripts. The two most common setups:
283
+
284
+ ### Solo developer, multiple Claude Code sessions
285
+
286
+ You're running 2-3 Claude Code sessions in parallel on the same repo and want them to see each other's work. One coordinator instance handles all of them.
287
+
288
+ ```bash
289
+ # In one terminal: start the coordinator
290
+ mcp-coordinator server start --daemon
291
+ ```
292
+
293
+ Then add the coordinator to each Claude Code session's `.mcp.json` (located at `~/.claude/.mcp.json` for the global config, or `<your-project>/.mcp.json` for per-project):
294
+
295
+ ```json
296
+ {
297
+ "mcpServers": {
298
+ "coordinator": {
299
+ "type": "http",
300
+ "url": "http://localhost:3100/mcp"
301
+ }
302
+ }
303
+ }
304
+ ```
305
+
306
+ Each Claude session now has access to all 26 coordination tools (`register_agent`, `announce_work`, `post_to_thread`, etc.). Open `mcp-coordinator dashboard` in a browser to watch real-time activity across your sessions.
307
+
308
+ ### Team setup — shared coordinator on LAN
309
+
310
+ One person hosts the coordinator on a shared machine; teammates point their Claude at it.
311
+
312
+ Host:
313
+
314
+ ```bash
315
+ # Bind to all interfaces; default is 127.0.0.1
316
+ COORDINATOR_BIND=0.0.0.0 mcp-coordinator server start --daemon
317
+ ```
318
+
319
+ Each teammate's `.mcp.json` points to the host's IP:
320
+
321
+ ```json
322
+ {
323
+ "mcpServers": {
324
+ "coordinator": {
325
+ "type": "http",
326
+ "url": "http://192.168.1.42:3100/mcp"
327
+ }
328
+ }
329
+ }
330
+ ```
331
+
332
+ For internet-facing or multi-tenant deployments, enable JWT auth (see [Authentication](#authentication)). Each teammate registers via `POST /api/auth/register` with the team's `COORDINATOR_REGISTRATION_SECRET`, gets a Bearer token, and adds it to their `.mcp.json`:
333
+
334
+ ```json
335
+ {
336
+ "mcpServers": {
337
+ "coordinator": {
338
+ "type": "http",
339
+ "url": "https://coordinator.example.com/mcp",
340
+ "headers": { "Authorization": "Bearer <your-token>" }
341
+ }
342
+ }
343
+ }
344
+ ```
345
+
346
+ ### Telling Claude to use the coordinator tools
347
+
348
+ Without a behavior catalog (which is what [essaim](https://github.com/swoofer/essaim) ships), you instruct Claude manually. Easiest path:
349
+
350
+ ```bash
351
+ # In your project root — scaffolds CLAUDE.md with coordinator instructions
352
+ mcp-coordinator init --write-claude-md ~/my-repo --write-mcp-config ~/my-repo
353
+ ```
354
+
355
+ This appends a clearly-marked `mcp-coordinator:coordination-section` block to `~/my-repo/CLAUDE.md` (creating it if absent, replacing the section if it already exists). Combined with `--write-mcp-config`, your project is fully wired in one command.
356
+
357
+ If you'd rather embed the instructions yourself (or you're not using Claude Code), the section reads roughly:
358
+
359
+ > Before modifying any source file, register with the coordinator MCP server:
360
+ >
361
+ > 1. Call `register_agent` with your name and the modules you'll touch
362
+ > 2. Call `announce_work` describing what you'll do, listing target files (and `depends_on_files` if applicable)
363
+ > 3. If a thread is created (consultation triggered), wait for the resolution before writing code
364
+ > 4. After a meaningful change, call `log_action_summary` to update the dashboard timeline
365
+ > 5. If another agent is already working on a file you need to touch, post a question to the thread via `post_to_thread` and wait for their response before proceeding
366
+ >
367
+ > Use the `coordinator_status` tool to see current activity at any time.
368
+
369
+ That's all you need to start coordinating. The dashboard shows live who's doing what; the SQLite database persists threads across sessions; conflicts are detected before code is written.
370
+
371
+ ### Push vs polling — important architectural note
372
+
373
+ Vanilla Claude Code talks to mcp-coordinator over MCP (HTTP/stdio request-response). It **does not subscribe to MQTT**. That means events the coordinator publishes on MQTT (`coordinator/consultations/new`, etc.) are not auto-delivered to a Claude Code session — Claude has to **poll** the coordinator to discover new activity. The polling pattern is:
374
+
375
+ - `announce_work` returns the thread ID immediately if a conflict is detected — that's the most important checkpoint
376
+ - After that, periodic calls to `coordinator_status` / `list_threads` / `get_thread_updates` surface new posts on threads you're a participant in
377
+ - The CLAUDE.md scaffolded by `mcp-coordinator init --write-claude-md` instructs Claude to do exactly this polling
378
+
379
+ If you want **real-time push** (every coordination event interrupting Claude between turns instead of waiting for a poll), use [essaim](https://github.com/swoofer/essaim). essaim ships an agent-loop wrapper that subscribes to the MQTT broker and injects events into the turn flow automatically. mcp-coordinator alone supports the polling model — which is sufficient for most use cases (2-3 Claude sessions on a small team) and zero-config to set up.
380
+
381
+ ### End-to-end example: two Claudes coordinating (polling model)
382
+
383
+ Two terminals, same repo, both Claude Code sessions wired to the same local coordinator. Both sessions have a `CLAUDE.md` scaffolded by `mcp-coordinator init --write-claude-md`, which instructs Claude to register, announce, and poll. The conversation below is what each Claude does — the human user just asks each Claude to make a change.
384
+
385
+ ```
386
+ TERMINAL 1 (Alice) TERMINAL 2 (Bob)
387
+
388
+ $ claude $ claude
389
+ > "Add updated_at to User type in > "Migrate User schema"
390
+ src/models/user.ts" (touches src/models/user.ts)
391
+
392
+ [Alice's Claude] [Bob's Claude]
393
+ register_agent(name="Alice", ...) register_agent(name="Bob", ...)
394
+ announce_work(
395
+ target_files: ["src/models/user.ts"]
396
+ )
397
+ → response: { thread_id: null,
398
+ concerned_agents: [] } announce_work(
399
+ target_files: ["src/models/user.ts",
400
+ "migrations/004.sql"]
401
+ )
402
+ → response: { thread_id: "T-1",
403
+ concerned_agents: ["alice"],
404
+ score: 100, layer: "0a" }
405
+ [Bob sees the conflict in the response]
406
+ get_thread("T-1")
407
+ post_to_thread("T-1", type: "context",
408
+ content: "full schema migration; can
409
+ wait for your field to land first")
410
+
411
+ [Alice writes the field, then before
412
+ next major action the CLAUDE.md says
413
+ "poll coordinator_status"]
414
+ coordinator_status()
415
+ → response: shows T-1 with Bob's post
416
+ get_thread("T-1")
417
+ post_to_thread("T-1", type: "context",
418
+ content: "adding 1 field at line 42,
419
+ no rename. Done in 5 min.")
420
+ propose_resolution("T-1",
421
+ content: "Alice's field first,
422
+ Bob runs migration after")
423
+
424
+ [Bob's CLAUDE.md polling step]
425
+ coordinator_status()
426
+ → shows T-1 in 'resolving' state
427
+ get_thread("T-1")
428
+ approve_resolution("T-1")
429
+
430
+ [Alice's next poll]
431
+ coordinator_status()
432
+ → T-1 status = 'resolved'
433
+ [Alice writes the field] [Bob writes the migration]
434
+ log_action_summary(...) log_action_summary(...)
435
+ ```
436
+
437
+ The dashboard at `http://localhost:3100/dashboard/` plays the entire timeline live. `mcp-coordinator server logs -f` (in a third terminal) tails the daemon log if you want to see the protocol-level events. If polling cadence is too coarse and you find Claude missing posts, switch to essaim's agent-loop, which delivers MQTT events automatically.
438
+
439
+ ### Team setup walkthrough — shared coordinator with JWT
440
+
441
+ Full step-by-step for a team running a coordinator on a shared host with internet-facing or multi-tenant access. Adjust to your network/TLS reality.
442
+
443
+ **Step 1 (host) — generate secrets**
444
+
445
+ ```bash
446
+ # 32+ char shared secret; put in your secrets manager and inject as env vars
447
+ JWT_SECRET=$(openssl rand -hex 32)
448
+ REGISTRATION_SECRET=$(openssl rand -hex 32)
449
+ ADMIN_SECRET=$(openssl rand -hex 32)
450
+ ```
451
+
452
+ **Step 2 (host) — start the coordinator with auth enabled**
453
+
454
+ ```bash
455
+ COORDINATOR_AUTH_ENABLED=true \
456
+ COORDINATOR_JWT_SECRET="$JWT_SECRET" \
457
+ COORDINATOR_REGISTRATION_SECRET="$REGISTRATION_SECRET" \
458
+ COORDINATOR_ADMIN_SECRET="$ADMIN_SECRET" \
459
+ COORDINATOR_BIND=0.0.0.0 \
460
+ mcp-coordinator server start --daemon --port 3100
461
+ ```
462
+
463
+ (Front the server with TLS via nginx/Caddy/etc. for internet exposure. Local LAN can use plain HTTP.)
464
+
465
+ **Step 3 (each teammate) — request a token**
466
+
467
+ ```bash
468
+ curl -X POST https://coordinator.example.com/api/auth/register \
469
+ -H "Content-Type: application/json" \
470
+ -d '{"agent_name":"alice","registration_secret":"<REGISTRATION_SECRET shared via team channel>"}'
471
+ # Response: { "agent_id": "alice-abc123", "token": "eyJ...", "expires_at": "...", "role": "agent" }
472
+ ```
473
+
474
+ **Step 4 (each teammate) — wire `.mcp.json`**
475
+
476
+ ```json
477
+ {
478
+ "mcpServers": {
479
+ "coordinator": {
480
+ "type": "http",
481
+ "url": "https://coordinator.example.com/mcp",
482
+ "headers": { "Authorization": "Bearer <paste-token-here>" }
483
+ }
484
+ }
485
+ }
486
+ ```
487
+
488
+ **Step 5 (each teammate) — run `init --write-claude-md` to scaffold project instructions**, OR add the coordination section to their existing `CLAUDE.md`.
489
+
490
+ **Step 6 (each teammate) — verify**: `mcp-coordinator doctor --host coordinator.example.com --port 443` should show all checks green from any laptop.
491
+
492
+ **Token rotation**: tokens expire per `COORDINATOR_JWT_EXPIRY` (default 24h). Refresh via `POST /api/auth/refresh` with the current Bearer token. The admin can revoke a specific agent via `POST /api/auth/revoke` (admin token required).
493
+
494
+ ### Logs and debugging
495
+
496
+ The daemon writes to `~/.mcp-coordinator/logs/server.log`. Tail it:
497
+
498
+ ```bash
499
+ mcp-coordinator server logs # last 50 lines
500
+ mcp-coordinator server logs -n 200 # last 200 lines
501
+ mcp-coordinator server logs -f # follow (Ctrl+C to stop)
502
+ ```
503
+
504
+ For a one-shot check that everything is wired up correctly (config valid, server up, MCP responds, dashboard reachable, MQTT accepting connections), use the doctor:
505
+
506
+ ```bash
507
+ mcp-coordinator doctor
508
+ ```
509
+
510
+ `doctor` exits non-zero if any check fails and prints actionable hints next to each failure. Probe a remote coordinator with `--host` and `--port`:
511
+
512
+ ```bash
513
+ mcp-coordinator doctor --host coordinator.example.com --port 443 --mqtt-port 1883
514
+ ```
515
+
516
+ Logging level is controlled by `LOG_LEVEL` (`debug`, `info`, `warn`, `error` — default `info`). Set `NODE_ENV=development` for human-readable pretty logs:
517
+
518
+ ```bash
519
+ NODE_ENV=development LOG_LEVEL=debug mcp-coordinator server start
520
+ ```
521
+
522
+ ### Removing the integration (per-project or globally)
523
+
524
+ Symmetric to `init`, the `uninstall` command undoes what was added without touching anything you wrote yourself.
525
+
526
+ ```bash
527
+ # Remove coordinator from a project's .mcp.json AND strip its section from CLAUDE.md
528
+ mcp-coordinator uninstall --mcp-config ~/my-repo --claude-md ~/my-repo
529
+
530
+ # Wipe the global config dir (~/.mcp-coordinator/) entirely — config + data + logs + pid file
531
+ mcp-coordinator uninstall --purge # asks for confirmation
532
+ mcp-coordinator uninstall --purge --force # skip the prompt, useful in scripts
533
+ ```
534
+
535
+ `--mcp-config <path>` reads `<path>/.mcp.json`, removes only the `coordinator` server entry (other servers untouched), and deletes the file if it ends up empty. `--claude-md <path>` removes only the block delimited by the `mcp-coordinator:coordination-section` sentinels (rendered as HTML comments around the section) — it never touches text you authored. Combine flags as needed; if the resulting `CLAUDE.md` is empty, it's deleted.
536
+
537
+ To remove the npm package itself: `npm uninstall -g mcp-coordinator`.
538
+
539
+ ### Running multiple coordinators on the same machine
540
+
541
+ Useful for per-project isolation — every project gets its own ephemeral coordinator with no cross-contamination. Pick distinct ports + data dirs:
542
+
543
+ ```bash
544
+ # Project A
545
+ PORT=3110 \
546
+ COORDINATOR_MQTT_TCP_PORT=11883 \
547
+ mcp-coordinator server start --daemon --data-dir ./.mcp-coordinator-A
548
+
549
+ # Project B (different terminal)
550
+ PORT=3120 \
551
+ COORDINATOR_MQTT_TCP_PORT=12883 \
552
+ mcp-coordinator server start --daemon --data-dir ./.mcp-coordinator-B
553
+ ```
554
+
555
+ The default `~/.mcp-coordinator/server.pid` only tracks ONE daemon at a time. For multi-instance runs, pass `--data-dir` explicitly to each instance — the PID file lives next to the data dir, so multiple instances don't fight over the same file. To stop a specific instance, `cd` to its data dir's parent and run `mcp-coordinator server stop` from there, OR `kill $(cat ./.mcp-coordinator-A/../server.pid)`.
556
+
557
+ In each project's `.mcp.json`, point at the project's coordinator:
558
+
559
+ ```json
560
+ {
561
+ "mcpServers": {
562
+ "coordinator": {
563
+ "type": "http",
564
+ "url": "http://localhost:3110/mcp"
565
+ }
566
+ }
567
+ }
568
+ ```
569
+
570
+ This pattern works well alongside `essaim`, which uses Strategy A (in-process) and starts its own ephemeral coordinator per `essaim run` — there's no port conflict because essaim picks an isolated dir by default.
571
+
572
+ ---
573
+
574
+ ## Anthropic Quota Pre-flight
575
+
576
+ The coordinator tracks Anthropic workspace quota live and exposes it on MQTT, the dashboard, and the `coordinator_status` MCP tool — so MCP clients can decide whether to abort, throttle, or proceed before launching expensive turns.
577
+
578
+ - Reads usage from the Anthropic API using the key in the environment.
579
+ - Threshold via `MAX_QUOTA_PCT` env var (default `95`).
580
+ - Back-off when the usage endpoint itself returns 429.
581
+ - Live widget in the dashboard with manual refresh + historical buckets.
582
+ - `coordinator/quota/update` MQTT events stream into the timeline by default.
583
+
584
+ Orchestrators that spawn N agents at once can read `coordinator_status.quota` and abort their run if utilization is over a configured threshold — the [essaim](https://github.com/swoofer/essaim) reference orchestrator does exactly this.
585
+
586
+ ---
587
+
588
+ ## Token Observability
589
+
590
+ Every MCP tool call and agent turn is logged with token breakdown.
591
+
592
+ - **Logs** — component logger `tokens` emits `input_tokens`, `output_tokens`, `cache_read`, `cache_creation`, `thinking`, model id, turn index.
593
+ - **Dashboard** — live per-agent token gauge, cumulative session total, quota widget.
594
+
595
+ Aggregating across runs (e.g., `reports/YYYY-MM-DD-<run-id>.md`) is an orchestrator responsibility — the coordinator emits the events, the orchestrator consumes them.
596
+
597
+ ---
598
+
599
+ ## Dashboard
600
+
601
+ `http://localhost:3100/dashboard` (or `/dashboard` on whichever port the coordinator is bound to).
602
+
603
+ - **Timeline** — all threads + `quota_update` events with scores and resolution types
604
+ - **Agent panel** — online/offline, working/idle/waiting, current file, thread being waited on. Resizable drag handle.
605
+ - **Scoring breakdown** — which detection layer triggered each conflict
606
+ - **Quota widget** — live utilization %, stacked buckets, manual refresh button
607
+ - **Version banner** — server version shown in the header (dynamic, not hardcoded)
608
+ - **Consensus metrics** — per session: consensus / timeout / auto-resolved split, token totals
609
+
610
+ All events arrive via SSE on `/api/events`. No polling.
611
+
612
+ ---
613
+
614
+ ## Agent Activity States
615
+
616
+ | Status | Indicator | Meaning |
617
+ |--------|-----------|---------|
618
+ | working | pulsing blue | Actively editing files |
619
+ | idle | solid green | Online, no recent activity |
620
+ | waiting | pulsing yellow | Blocked on a consultation thread |
621
+ | offline | solid red | Disconnected or session ended |
622
+
623
+ Activity is derived from heartbeats enriched with the current file/thread context from the file tracker.
624
+
625
+ ---
626
+
627
+ ## Configuration
628
+
629
+ ### Local data
630
+
631
+ ```
632
+ ~/.mcp-coordinator/
633
+ ├── config.json # persistent configuration
634
+ ├── data/
635
+ │ └── coordinator.db # SQLite database
636
+ ├── server.pid # PID file (when daemonized)
637
+ └── logs/
638
+ └── server.log # daemon logs
639
+ ```
640
+
641
+ ### config.json
642
+
643
+ ```json
644
+ {
645
+ "server": { "port": 3100, "data_dir": "~/.mcp-coordinator/data" },
646
+ "defaults": { "coordinator_url": "http://localhost:3100" }
647
+ }
648
+ ```
649
+
650
+ Resolution priority (highest to lowest): CLI flag → env var → config.json → default.
651
+
652
+ ### Server env vars
653
+
654
+ | Variable | Default | Description |
655
+ |----------|---------|-------------|
656
+ | `PORT` | `3100` | HTTP port (also serves MQTT-over-WebSocket on `/mqtt`) |
657
+ | `COORDINATOR_DATA_DIR` | `~/.mcp-coordinator/data` | Directory for the SQLite database |
658
+ | `COORDINATOR_MQTT_TCP_PORT` | `1883` | TCP port for the embedded broker |
659
+ | `COORDINATOR_MQTT_WS_PATH` | `/mqtt` | WebSocket path on the same HTTP port |
660
+ | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
661
+ | `NODE_ENV` | — | `development` for pretty logs |
662
+ | `COORDINATOR_AUTH_ENABLED` | `false` | Enable JWT authentication |
663
+ | `COORDINATOR_JWT_SECRET` | — | HMAC signing key (min 32 chars) |
664
+ | `COORDINATOR_JWT_EXPIRY` | `24h` | Token lifetime (e.g., `1h`, `7d`) |
665
+ | `COORDINATOR_REGISTRATION_SECRET` | — | Shared secret for agent auto-register |
666
+ | `COORDINATOR_ADMIN_SECRET` | — | Separate secret for admin token creation |
667
+ | `MAX_QUOTA_PCT` | `95` | Pre-flight abort threshold for Anthropic quota |
668
+
669
+ ---
670
+
671
+ ## Structured Logging
672
+
673
+ [Pino](https://getpino.io/) emits JSON per subsystem. Component loggers: `http`, `mcp`, `mqtt`, `consultation`, `conflict`, `auth`, `tokens`, `quota`.
674
+
675
+ Production (default):
676
+
677
+ ```json
678
+ {"level":"info","time":1712345678901,"component":"http","msg":"Server started","port":3100}
679
+ ```
680
+
681
+ Dev (`NODE_ENV=development`):
682
+
683
+ ```
684
+ [14:21:03.456] INFO (http): Server started
685
+ port: 3100
686
+ ```
687
+
688
+ Levels controlled via `LOG_LEVEL`.
689
+
690
+ ---
691
+
692
+ ## Authentication
693
+
694
+ Opt-in JWT (HS256 via [jose](https://github.com/panva/jose)). Set `COORDINATOR_AUTH_ENABLED=true` plus the required secrets to enable.
695
+
696
+ ### Setup
697
+
698
+ ```bash
699
+ export COORDINATOR_AUTH_ENABLED=true
700
+ export COORDINATOR_JWT_SECRET="your-secret-at-least-32-characters-long"
701
+ export COORDINATOR_REGISTRATION_SECRET="team-shared-secret"
702
+ export COORDINATOR_ADMIN_SECRET="admin-only-secret"
703
+ ```
704
+
705
+ ### Agent self-register
706
+
707
+ ```bash
708
+ curl -X POST http://localhost:3100/api/auth/register \
709
+ -H "Content-Type: application/json" \
710
+ -d '{"agent_name":"my-agent","registration_secret":"team-shared-secret"}'
711
+ # → { agent_id, token, expires_at, role }
712
+ ```
713
+
714
+ ### Refresh
715
+
716
+ ```bash
717
+ curl -X POST http://localhost:3100/api/auth/refresh \
718
+ -H "Authorization: Bearer <current-token>"
719
+ ```
720
+
721
+ ### Revoke (admin)
722
+
723
+ ```bash
724
+ curl -X POST http://localhost:3100/api/auth/revoke \
725
+ -H "Authorization: Bearer <admin-token>" \
726
+ -H "Content-Type: application/json" \
727
+ -d '{"agent_id":"agent-to-revoke"}'
728
+ ```
729
+
730
+ ### Exempt routes
731
+
732
+ `GET /health`, `POST /api/auth/register`, `POST /api/auth/refresh`, `GET /api/events` (SSE).
733
+
734
+ ---
735
+
736
+ ## Test Results
737
+
738
+ All four coordination scenarios are validated end-to-end by the test suite:
739
+
740
+ | Scenario | Layer | Score | Category | Outcome |
741
+ |----------|-------|------:|----------|---------|
742
+ | S1 — Same file | 0a | 100 | concerned | Thread opened → consensus |
743
+ | S2 — Same module | 3 | 30 | gray_zone | Auto-resolved, introspection |
744
+ | S3 — Dependency | 0b | 80 | gray_zone | Auto-resolved, introspection |
745
+ | S4 — No overlap | — | 0 | pass | Auto-resolved immediately |
746
+
747
+ **Performance:**
748
+
749
+ | Component | Time |
750
+ |-----------|------|
751
+ | Conflict detection (no LLM) | < 5 ms |
752
+ | MQTT push delivery | < 50 ms end-to-end |
753
+ | Full consultation cycle (S1) | 30–45 s |
754
+
755
+ ---
756
+
757
+ ## Integration patterns
758
+
759
+ ### Any MCP client
760
+
761
+ Connect to `http://localhost:3100/mcp` (HTTP/SSE) or stdio. The server speaks MCP 2024-11-05.
762
+
763
+ ### Custom orchestrator
764
+
765
+ Spawn agents that connect to the MQTT broker and register via the MCP `register_agent` tool. The orchestrator decides spawn count, lifecycle, and quota gating; the coordinator handles the protocol. See [essaim](https://github.com/swoofer/essaim) for a reference implementation, or write your own.
766
+
767
+ ### Reference catalog of coordinator-aware behaviors
768
+
769
+ The behaviors that make agents announce-before-write, resolve conflicts, and participate in work-stealing are YAML configs assembled by [@swoofer/promptweave](https://github.com/swoofer/promptweave). See [essaim's behaviors](https://github.com/swoofer/essaim/tree/main/behaviors) for a curated catalog.
770
+
771
+ ---
772
+
773
+ ## Development
774
+
775
+ ```bash
776
+ # Tests (216 passing across 18 files)
777
+ npm test
778
+ npm run test:watch
779
+
780
+ # Dev coordinator (tsx, hot reload)
781
+ npm run dev # HTTP / SSE on port 3100
782
+ npm run dev:stdio # stdio mode
783
+
784
+ # CLI in dev
785
+ npm run cli -- server start
786
+ npm run cli -- dashboard
787
+
788
+ # TypeScript build → dist/
789
+ npm run build
790
+
791
+ # Standalone binary (requires Bun)
792
+ bun build --compile cli/index.ts --outfile bin/mcp-coordinator
793
+ ```
794
+
795
+ ### Project structure
796
+
797
+ ```
798
+ src/ # Coordinator (npm package surface)
799
+ serve-http.ts # HTTP/SSE/MCP server entry
800
+ server-setup.ts # 26 MCP tool registrations
801
+ impact-scorer.ts # multi-layer conflict detection
802
+ consultation.ts # Thread lifecycle
803
+ agent-registry.ts # Online agents
804
+ file-tracker.ts # File edit history
805
+ dependency-map.ts # Module graph
806
+ agent-activity.ts # working/idle/waiting/offline
807
+ mqtt-broker.ts # Embedded Aedes (TCP + WS)
808
+ mqtt-bridge.ts # Coordinator → broker fanout
809
+ quota/ # Anthropic quota pre-flight + refresh
810
+ auth.ts # Optional JWT
811
+ index.ts # Stdio entry + programmatic re-exports
812
+
813
+ cli/ # CLI binary (mcp-coordinator)
814
+ index.ts # Entry point
815
+ server/ # start / stop / status
816
+ dashboard.ts # Open dashboard URL
817
+ config.ts # Config loader
818
+ version.ts # package.json version helper
819
+
820
+ tests/unit/ # Vitest — 216 tests, 18 files
821
+ dashboard/public/ # Single-file web dashboard
822
+ ```
823
+
824
+ ---
825
+
826
+ ## Related projects
827
+
828
+ - **[@swoofer/promptweave](https://github.com/swoofer/promptweave)** — YAML composer for assembling agent prompts, hooks, and MCP configs. Use it with mcp-coordinator-aware behaviors from essaim.
829
+ - **[essaim](https://github.com/swoofer/essaim)** — end-to-end orchestrator that spawns N coordinated agents using `@swoofer/promptweave` + `mcp-coordinator`. Ships the reference catalog of coordinator-aware behaviors.
830
+
831
+ ---
832
+
833
+ ## Support
834
+
835
+ Solo maintainer. If this project saves you time, consider supporting development:
836
+
837
+ - [GitHub Sponsors](https://github.com/sponsors/swoofer)
838
+ - [Buy Me A Coffee](https://buymeacoffee.com/swoofer)
839
+
840
+ A star on the repo also helps surface the project to other developers.
841
+
842
+ ---
843
+
844
+ ## License
845
+
846
+ MIT