agent-coord-mcp 0.16.0 → 0.17.1

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
@@ -303,7 +303,7 @@ Either way, `list_agents` will show the agent with `transport: "tmux-push"` so p
303
303
 
304
304
  Under the hood: [`hooks/tmux-pusher.mjs`](./hooks/tmux-pusher.mjs) is the daemon. It watches `~/agent-coord/inbox/<id>.jsonl` (and, when room delivery is on, every channel the agent has joined), debounces bursts (1s default), drops self-posts and `/`-prefixed text (except allowlisted control commands — see [below](#clearing-sub-agent-context-send_command)), optionally enforces the peer allowlist, then pastes batches via `tmux load-buffer` → `paste-buffer -d` → `send-keys Enter`. Single-flight so two batches never overlap.
305
305
 
306
- **Delivery tiers.** Not every message deserves an agent turn. Only push-now traffic wakes the pane: `BLOCKER:`, `DAVID_DECISION:`, literal `GO:` work orders, `SCOPE:`/`SCOPE CHANGE:` from a trusted sender (coordinator/gate ids resolved from the registry), `DONE:` when this agent is a gate runner (QA/coordinator — from its registry role, re-resolved every 30s, overridable with `AGENT_COORD_GATE_RUNNER=1|0`), control commands, and server-flagged messages like the post-`/clear` identity reminder. Prefixes are literal and case-sensitive. Everything else (`FYI:`, `AGENT_ACTION:`, `RISK:`, chatter) queues silently and rides the next urgent push as one coalesced digest block (banner `[agent-coord] +N routine (pre-consumed, FYI, no reply):`). Routine backlog is age-bounded: once its oldest message has queued longer than `AGENT_COORD_MAX_QUEUE_MS` (default 5min; `0` disables), the backlog flushes on its own as a routine-only digest, so a quiet bus never sits on undelivered traffic indefinitely. The on-disk cursor (shared with `read_messages`) advances only **after** a paste is confirmed, so a crash or `SIGTERM` rewinds and redelivers (at-least-once) — queued traffic stays **unread** and is never lost. An idle agent with only routine backlog is never woken; routine ops cost zero tokens. `AGENT_COORD_TIERS=0` restores legacy push-everything. The classifier and queue are pure ([`hooks/tier.mjs`](./hooks/tier.mjs)). Known trade-offs: routine messages get their delivery receipt only when their digest lands; a digest can arrive without the banner if a push carries only queued routine after a control command; within one paste, urgent lines precede the digest regardless of arrival order.
306
+ **Delivery tiers.** Not every message deserves an agent turn. All DMs push now — they're point-to-point asks, not broadcast noise. Channel traffic wakes the pane only when push-now: `BLOCKER:`, `DAVID_DECISION:`, literal `GO:` work orders, `SCOPE:`/`SCOPE CHANGE:` from a trusted sender (coordinator/gate ids resolved from the registry), `DONE:` when this agent is a gate runner (QA/coordinator — from its registry role, re-resolved every 30s, overridable with `AGENT_COORD_GATE_RUNNER=1|0`), control commands, and server-flagged messages like the post-`/clear` identity reminder. Prefixes are literal and case-sensitive. Everything else (`FYI:`, `AGENT_ACTION:`, `RISK:`, chatter) queues silently and rides the next urgent push as one coalesced digest block (banner `[agent-coord] +N routine (pre-consumed, FYI, no reply):`). Routine backlog is age-bounded: once its oldest message has queued longer than `AGENT_COORD_MAX_QUEUE_MS` (default 15s; `0` disables), the backlog flushes on its own as a routine-only digest, so a quiet bus never sits on undelivered traffic indefinitely. The on-disk cursor (shared with `read_messages`) advances only **after** a paste is confirmed, so a crash or `SIGTERM` rewinds and redelivers (at-least-once) — queued traffic stays **unread** and is never lost. An idle agent with only routine backlog is never woken; routine ops cost zero tokens. `AGENT_COORD_TIERS=0` restores legacy push-everything. The classifier and queue are pure ([`hooks/tier.mjs`](./hooks/tier.mjs)). Known trade-offs: routine messages get their delivery receipt only when their digest lands; a digest can arrive without the banner if a push carries only queued routine after a control command; within one paste, urgent lines precede the digest regardless of arrival order.
307
307
 
308
308
  **Caveats — read these.**
309
309
 
@@ -359,7 +359,7 @@ AGENT_COORD_DIR=~/agent-coord \
359
359
  agent-coord-mcp
360
360
  ```
361
361
 
362
- Defaults to `127.0.0.1`. To bind to a LAN address (Tailscale, WireGuard, etc.) set `AGENT_COORD_BIND=10.x.y.z`; the process logs a warning if it binds to anything non-loopback so you don't accidentally serve unauthenticated traffic. `GET /healthz` is unauthenticated (for reverse-proxy probes); everything else requires `Authorization: Bearer <AGENT_COORD_TOKEN>`. TLS is out of scope — front with Caddy/nginx, or skip TLS entirely on a private overlay network.
362
+ Defaults to `127.0.0.1`. To bind to a LAN / overlay address (Tailscale, WireGuard, etc.) set `AGENT_COORD_BIND=10.x.y.z`; a non-loopback bind trips the fail-closed gate below the server **refuses to start** (rather than warns) unless identity and transport are both secured. `GET /healthz` is unauthenticated (for reverse-proxy / orchestrator probes); everything else requires `Authorization: Bearer <token>`. TLS is out of scope — front with Caddy/nginx, or skip TLS entirely on a private overlay network.
363
363
 
364
364
  The server can run as a long-lived daemon on one machine (the "host") while local agents on that host keep using stdio per-session; the two modes don't conflict — they're separate processes.
365
365
 
@@ -370,9 +370,31 @@ The server can run as a long-lived daemon on one machine (the "host") while loca
370
370
 
371
371
  Mint the per-agent tokens with `coord-token` (below). Loopback binds are exempt from both checks, so single-host use needs no extra setup.
372
372
 
373
+ **Example — bind to a Tailscale IP.** Tailscale/WireGuard *is* the secured transport (WireGuard-encrypted), so `AGENT_COORD_INSECURE=1` is the correct acknowledgement here, not a hack. On the host, after minting at least one token (see [Identity binding](#identity-binding-v070)):
374
+
375
+ ```bash
376
+ node scripts/coord-token.mjs add worker-2 # creates ~/agent-coord/tokens.json, prints the token
377
+ AGENT_COORD_HTTP_PORT=8765 \
378
+ AGENT_COORD_BIND=100.x.y.z \ # this host's Tailscale IP (`tailscale ip -4`)
379
+ AGENT_COORD_INSECURE=1 \ # ack: WireGuard is the encryption layer
380
+ node dist/server.js
381
+ ```
382
+
383
+ Remote nodes then reach it at `http://100.x.y.z:8765/mcp`. Add or rotate tokens later without a restart: `coord-token add <id>` then `kill -HUP <bus-pid>` reloads the map. `GET http://100.x.y.z:8765/healthz` (no auth) is a quick reachability check from another tailnet device.
384
+
385
+ ### Joining from another machine
386
+
387
+ A remote participant is just an **MCP client with a bearer token** — there is nothing to build or clone. What you need depends on how you connect:
388
+
389
+ - **Claude Code (or any MCP client that speaks HTTP): nothing to install.** The client already speaks MCP; a config entry (below) is the entire setup. You do *not* need this package on the client to use the tools.
390
+ - **Optional real-time push into a tmux pane:** install the package globally (`npm i -g agent-coord-mcp`) for the `coord-pusher` bin (the MCP SDK ships with it — no repo needed). See [`coord-pusher`](#coord-pusher--real-time-push-cross-machine) below. Without it you simply poll with `read_messages` / `wait_for_message`.
391
+ - **Operator-side tools** (`coord-token`, `coord-node.sh`) live in the repo and run on the **bus host**, not on a pure client. `coord-node.sh` is not a published bin, so a client without the repo can't (and needn't) run it.
392
+
393
+ > ⚠️ **Installing the package globally does not connect you to a remote bus.** The `agent-coord-mcp` bin is the *stdio server* — pointing a Claude config at it spins up a **separate, local, file-backed bus on your own machine**, not a connection to anyone else's. To join a networked bus you must add the **HTTP** entry below (with `"type": "http"` and a URL), never the stdio server.
394
+
373
395
  ### Point a Claude Code session at the remote server
374
396
 
375
- In `~/.claude.json`:
397
+ In `~/.claude.json` (or a project `.mcp.json`) — the token must be the one minted for *this* agent's id; the bus enforces that the id you `join` as matches the token:
376
398
 
377
399
  ```json
378
400
  {
@@ -380,12 +402,14 @@ In `~/.claude.json`:
380
402
  "agent-coord": {
381
403
  "type": "http",
382
404
  "url": "http://host:8765/mcp",
383
- "headers": { "Authorization": "Bearer <AGENT_COORD_TOKEN>" }
405
+ "headers": { "Authorization": "Bearer <this-agent's-token>" }
384
406
  }
385
407
  }
386
408
  }
387
409
  ```
388
410
 
411
+ Or via the CLI (no hand-editing): `claude mcp add --transport http --scope user agent-coord http://host:8765/mcp --header "Authorization: Bearer <token>"`.
412
+
389
413
  Then `join({agentId:"me"})` and call any tool exactly as you would locally. `send_message`, `read_messages`, `wait_for_message`, `list_rooms`, `join_room`, etc. all work identically.
390
414
 
391
415
  ### `coord-node` — one-command onboarding (recommended)
@@ -464,7 +488,7 @@ Backward-compat: if `tokens.json` is absent, the legacy single shared `AGENT_COO
464
488
  ### Auth posture, briefly
465
489
 
466
490
  - Threat model: misbehaving / buggy / compromised same-LAN cooperator can no longer assert another agent's identity. Not a hostile-attacker model (TLS + per-message signing is a separate, larger task).
467
- - Don't bind to a public address without TLS. The server prints a warning if you do anyway.
491
+ - Don't bind to a public address without TLS. A non-loopback bind is refused outright unless per-agent tokens are configured **and** `AGENT_COORD_INSECURE=1` is set — and that acknowledgement is meant for a private overlay (Tailscale/WireGuard) or a TLS reverse proxy, not the open internet.
468
492
 
469
493
  ### Other clients
470
494
 
package/hooks/tier.mjs CHANGED
@@ -14,6 +14,11 @@ export function classifyTier(m, opts = {}) {
14
14
  // Server-set push-now override (post-/clear reminder). send_message builds
15
15
  // Messages from fixed fields, so a peer cannot smuggle this flag in.
16
16
  if (m.urgent === true) return "urgent";
17
+ // DMs are always push-now: they're addressed to this agent by a peer who
18
+ // wants it specifically, and DM volume is tiny next to room traffic. The
19
+ // tiers exist to absorb broadcast noise, not point-to-point asks (the
20
+ // liaison relaying a David question must not sit in a digest queue).
21
+ if (m.kind === "DM") return "urgent";
17
22
  if (typeof m.text !== "string") return "routine";
18
23
  const text = m.text.trimStart();
19
24
  // Control/slash commands are injected raw and must fire immediately.
@@ -19,9 +19,9 @@
19
19
  * AGENT_COORD_DEBOUNCE_MS coalesce window for bursts (default 1000)
20
20
  * AGENT_COORD_POLL_MS fallback poll interval (default 1000)
21
21
  * AGENT_COORD_TARGET_GRACE missed pane probes before self-exit (default 3)
22
- * AGENT_COORD_MAX_QUEUE_MS max time routine traffic may queue without an
23
- * urgent trigger before it flushes as a digest
24
- * (default 300000 = 5min; 0 disables)
22
+ * AGENT_COORD_MAX_QUEUE_MS max time routine (channel) traffic may queue
23
+ * without an urgent trigger before it flushes as
24
+ * a digest (default 15000 = 15s; 0 disables)
25
25
  *
26
26
  * Safety:
27
27
  * - drops messages where from === AGENT_COORD_ID (no self-echo)
@@ -89,8 +89,9 @@ const TIERS_ENABLED = process.env.AGENT_COORD_TIERS !== "0";
89
89
  // Max-age flush: routine traffic never waits longer than this for an urgent
90
90
  // trigger — once the oldest queued message is overdue, the backlog flushes as
91
91
  // a routine-only digest. Bounds the silent-fleet failure mode where FYI:/DONE:
92
- // chatter queues forever because nothing urgent ever arrives.
93
- const MAX_QUEUE_MS = parseInt(process.env.AGENT_COORD_MAX_QUEUE_MS || "300000", 10);
92
+ // chatter queues forever because nothing urgent ever arrives. Only channel
93
+ // traffic queues (DMs are push-now), so the default is short.
94
+ const MAX_QUEUE_MS = parseInt(process.env.AGENT_COORD_MAX_QUEUE_MS || "15000", 10);
94
95
  // Gate runners (QA/coordinator) receive DONE: as push-now; countersigned
95
96
  // SCOPE: changes are honored only from these trusted ids. Re-resolved from
96
97
  // the registry every 30s so a role change doesn't strand a stale pusher;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-coord-mcp",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "File-backed MCP server for coordinating multiple AI coding agents (Claude Code, Cursor, Cline, etc.). Local stdio or networked over Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {