Skip to content

Latest commit

 

History

656 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcp-coordinator

Stop your AI coding agents from overwriting each other's work.

One daemon. Every Claude Code / Cursor / Cline / Aider session on the same repo announces what it's about to touch, sees what the others are doing, and resolves conflicts before a line of code is written. Zero conflicts, everyone aligned.

License: MIT npm Tests E2E

Getting started · Problem · How It Works · MCP Tools · CLI · Auth · Dashboard · Config · FAQ → · Usage guide →


Who is this for

You are… mcp-coordinator gives you
A solo dev running 2-3 Claude Code sessions in parallel Zero-config local daemon. Each session sees the others' file claims; no more "wait, Claude already rewrote that."
A small team where everyone runs their own AI agent on the same repo One shared coordinator over LAN. Real-time conflict detection across teammates' agents.
Building a multi-agent orchestrator A drop-in conflict layer with MQTT push, 26 MCP tools, and an SDK. Bring your own spawn strategy.
Self-hosting for a regulated org OAuth 2.1, 4 IdPs, encrypted IdP tokens at rest, SHA-256 audit chain (SOC 2 tamper-evidence), per-org allowlists.

The Problem

You ask Claude Code to "add an updated_at field to the User type." In another terminal, Cursor is mid-migration on the same schema. Twenty minutes later you're doing git surgery to reconcile the two diffs — and it's not the first time this week.

Specifically:

  • Regressions — Agent A rewrites a module that Agent B was depending on
  • Duplicated work — Two agents implement the same feature from different directions
  • Architectural drift — Agents make local decisions that conflict with each other's designs
  • Wasted reconciliation time — Hours per week untangling what the agents did to each other

Each agent works in isolation. None of them know what the others are doing.

mcp-coordinator fixes this by giving every agent 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.

Works with or without an orchestrator. Standalone with any MCP client (Claude Code, Cursor, Cline, Aider) — see the usage guide. Or pair with essaim for pre-composed agent profiles, work-stealing templates, and a behavior catalog.

2500+ tests across 750+ files, passing on every release. Feature-flagged auth means Phase 1 deployments stay byte-identical to v0.7.x — proven by a 31-case backcompat suite that runs in CI.


Getting started

The fastest path for running a long-lived coordinator is a global install:

# 1. Install once, get the `mcp-coordinator` command on your PATH
npm install -g mcp-coordinator

# 2. First-time setup — creates ~/.mcp-coordinator/, writes a default config,
#    and prints a .mcp.json snippet for your MCP client.
mcp-coordinator init

# 3. Start the server in the background
mcp-coordinator server start --daemon

# 4. Verify
mcp-coordinator server status
mcp-coordinator dashboard      # opens http://localhost:3100/dashboard

Requires Node.js 22+ (Node 20 reached EOL on 2026-04-30 and is no longer supported). 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).

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 the usage guide.

⚠️ Approving the tools: permissionMode: "acceptEdits" does not auto-approve MCP tools — it covers file edits and filesystem Bash commands only. Use allowedTools: ["mcp__coordinator__*"] instead, or you will get a prompt on every heartbeat. See approving the tools.

🔀 Two ways to consume coordination state — agents can either poll the daemon's MCP tools (default, works since v0.6) or accept push events through the Channels sidecar (v0.12+, research preview). Most users start with polling and add Channels later when they want real-time reactivity. See docs/operating-modes.md for the side-by-side comparison and decision guide.

Other install styles

Style When to use Install Invoke as
Global (default above) Long-running daemon, ops npm install -g mcp-coordinator mcp-coordinator <cmd>
npx (zero install) One-shot try, CI scripts (none) npx mcp-coordinator <cmd>
Local to a project Pinning a version per repo cd your-project && npm install mcp-coordinator npx mcp-coordinator <cmd> from project root
Docker (multi-arch) Container-first deployments, k8s docker pull ghcr.io/swoofer/mcp-coordinator:0.13.0 docker run ghcr.io/swoofer/mcp-coordinator:0.13.0 <cmd>
Single-file binary No Node available, easiest deploy GitHub Release tarball ./mcp-coordinator <cmd>

† Local installs require a package.json in the working directory — if you're just trying it out, prefer -g or npx.

Installation légère (skip tree-sitter grammars)

The tree-sitter code-extraction feature ships ~292 MB of grammar packages as optionalDependencies. If you don't need cross-repo code extraction, skip them: npm install mcp-coordinator --omit=optional (pnpm equivalent: pnpm install --no-optional). The rest of the coordinator — agent registry, consultation threads, MQTT, dashboard — works unaffected; only the tree-sitter-backed extraction gracefully degrades.

Running via Docker

The image is published to GitHub Container Registry on every release tag — multi-arch (linux/amd64 + linux/arm64), with provenance and SBOM attestation. Three tag tracks:

Tag Use case
ghcr.io/swoofer/mcp-coordinator:0.13.0 Pinned exact version — recommended for production
ghcr.io/swoofer/mcp-coordinator:0.13 Auto-bumps within the 0.13.x patch series
ghcr.io/swoofer/mcp-coordinator:latest Tip of releases — fine for trying out, avoid in prod

A working compose stack (coordinator + Caddy auto-TLS + GitHub OAuth) ships at examples/docker-compose/. For a Kubernetes CronJob example doing JWT secret rotation, see docs/ops/auto-rotation.md.

Real-time push via Claude Code Channels (research preview)

Channels is Anthropic's new push-into-session primitive: an out-of-band subprocess streams <channel> tags into a running Claude Code session, so the agent reacts to coordination events the moment they happen — no polling, no extra tool call. mcp-coordinator channel is a thin bridge over the embedded MQTT broker that emits one channel event per consultation, agent status change, and thread message. It also exposes a post_to_thread MCP tool so Claude can reply into a thread directly from the session.

# 1. Daemon already running? Add the channel server to ~/.claude/.mcp.json
#    (see examples/channels-quickstart/.mcp.json.sample)
# 2. Launch Claude Code with channels enabled
claude --dangerously-load-development-channels server:mcp-coordinator-channel
# 3. Watch consultation events arrive as <channel> tags in the session, and
#    let Claude reply via post_to_thread when appropriate

Research preview — and it does not currently load on a stock install. Measured on Claude Code 2.1.233: no <channel> tag was injected in any of four configurations. --dangerously-load-development-channels is only parsed in an interactive session, and availability sits behind an Anthropic-side feature flag that defaults to off. Every refusal path in the client is silent — nothing is logged, and nothing reaches the MCP server, so there is no symptom to debug. Everything below the last hop works and is covered by tests; the last hop is not ours to fix. Phase 3 (permission relay) intentionally deferred.

📖 Choosing between polling and push? See docs/operating-modes.md for a full comparison of the two modes, when to pick each, and how to run them side by side. Full setup walkthrough at examples/channels-quickstart/.


How It Works

   Agent A                          Agent B
     │                                │
     │  announce_work                 │  announce_work
     ▼                                ▼
┌──────────────┐                ┌──────────────┐
│  MCP client  │ ◄── MQTT ────► │  MCP client  │
│ (any vendor) │   push-based   │ (any vendor) │
└──────┬───────┘                └──────┬───────┘
       │         MCP HTTP / SSE        │
       └──────────────┬────────────────┘
                      │
            ┌─────────▼──────────┐
            │   mcp-coordinator  │
            │  26 MCP tools + DB │
            │  Aedes MQTT broker │
            └─────────┬──────────┘
                      │ SSE
            ┌─────────▼──────────┐
            │     Dashboard      │
            │  live events/quota │
            └────────────────────┘

The consultation cycle has four steps:

  1. Announce — A client calls announce_work with target files, depends_on_files, and target modules before coding.
  2. Detect — The coordinator scores impact against all online agents and opens a thread if a score ≥ 90 matches.
  3. Consult — MQTT pushes the new thread to every affected agent. Each agent posts context, constraints, or proposes a resolution.
  4. Resolve — Agents approve, contest, or propose again. The thread closes when consensus is reached, or auto-resolves after timeout / in gray zones.

The server is client-agnostic: any MCP-compatible agent (Claude Code, Cursor, Cline, Aider, custom scripts) can connect over HTTP/SSE or stdio. See connecting an MCP client for the config shapes, the stdio caveats, and how to verify a connection.

MQTT layer

The coordinator ships with an embedded Aedes MQTT broker. Agents subscribe once and receive every coordination event in real-time — no polling, no extra infrastructure.

Transport Port Use case
TCP 1883 (bind 127.0.0.1 by default) Local / LAN agents, best latency
WebSocket /mqtt on the coordinator HTTP port (default 3100) Bun binary, remote agents, firewall-friendly

Topic map: coordinator/consultations/new, coordinator/consultations/{id}/{messages,status,claimed,completed}, coordinator/agents/{id}/status, coordinator/broadcast, coordinator/quota/update. Clients self-filter their own messages and the payloads are small JSON envelopes.


Impact Scoring

Every announce_work call scores all online agents across multiple detection layers. The highest matching layer wins.

Layer Signal Score Trigger
0a Same file announced in active thread 100 target_files ∩ their target_files
0b They modify a file you depend on 80 depends_on_files ∩ their target_files
0c You modify a file they depend on 80 target_files ∩ their depends_on_files
1 Same file recently edited 100 File tracker conflict (last 60s)
2 Dependency file recently edited 80 depends_on_files recently touched
3 Same module prefix 30 target_modules overlap
4 Git co-change history (opt-in) 40-60 Files historically modified together — requires COORDINATOR_REPO_ROOT + git on PATH

Scores are categorized into three outcomes:

Score Category Action
≥ 90 concerned Thread opened, consultation required
30–89 gray_zone Thread auto-resolved, introspection recommended
< 30 pass No conflict, proceed immediately

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.

announce_work accepts an optional target_symbols?: string[] (up to 200 entries, max 256 chars each). When two agents touch the same file with disjoint symbols, the score stays 100 but the reason text enriches with disjoint symbols: you=[X], them=[Y] — tree-sitter extracts symbols server-side from 15 languages (TS, JS, Python, Go, Rust, Java, C#, C/C++, Ruby, PHP, Kotlin, Swift, Bash) via optionalDependencies.


Capabilities at a glance

Out of the box, zero config:

  • Run the coordinatormcp-coordinator server start, that's the whole setup
  • Conflict detection — 4-layer impact scoring (announce / file / module / co-change), MQTT push delivery
  • Dashboard — live timeline, agent panel, scoring breakdown, quota widget
  • Observability — structured Pino logs, MQTT broker stats, /livez + /readyz + /metrics
Opt-in for teams and self-hosting — auth, IdPs, audit, admin UI
Concern Opt-in
Authentication Phase 1 JWT (COORDINATOR_AUTH_ENABLED) OR Phase 2 OAuth (COORDINATOR_OAUTH_ENABLED) — see Authentication
Identity providers GitHub OAuth App + GitHub App + Google + generic OIDC; up to 4 in parallel via picker UI
Session model Cookie sessions + Bearer JWT for MCP transport + service tokens for CI/CD
IdP token encryption at rest Column-level AES-256-GCM on users.idp_access_token + users.idp_refresh_token, key fingerprint guard at boot
Admin UI Browser console at /dashboard/admin.html for org/user/allowlist management
Audit log Tier-1 (never-drop) + Tier-2 (batched) + SHA-256 hash chain for tamper-evidence
Prometheus / Grafana 32 app-level metrics (auth, device flow, service tokens, IdP, audit, rate limiting) plus default Node process metrics on /metrics/auth, Grafana dashboard JSON, alert rules YAML
Database backend SQLite (default) → Postgres (planned, see design spec)

Full version-by-version detail in CHANGELOG.md.


MCP Tools

26 tools registered under one HTTP/SSE transport at /mcp (and stdio for stdio-mode clients). The three you'll reach for 90% of the time:

Tool What it does
announce_work The main entry point. Call before coding — describes target files, dependencies, modules. Returns a thread_id if a conflict is detected.
coordinator_status Full snapshot — online agents, open threads, hot files, MQTT topics, Anthropic quota. Use it as a heartbeat poll.
wait_for_peers Block until N peers come online (or timeout) — useful when an orchestrator spawns a fleet and you need to avoid races before the first announce.

Any MCP client can discover the full tool schema at runtime via the standard tools/list MCP protocol call — no dedicated introspection tool needed.

All 26 tools — agent registry, consultation, file tracking, dependency map, MQTT, status

Agent registry

Tool Description
register_agent Register as online with name and module list
list_agents List all registered online agents
heartbeat Update last-seen and derive activity status
agent_activity Get activity status for all online agents
wait_for_peers Block until N peers online, or timeout (prevents race before first announce)

Consultation

Tool Description
announce_work Open a consultation thread — the main entry point before coding
post_to_thread Post a message (warning, context, question) to an open thread
propose_resolution Submit a resolution proposal for participants to approve
approve_resolution Approve the current resolution proposal
contest_resolution Reject the proposal with a reason — resets to open
close_thread Close a thread after work is complete
cancel_thread Cancel a thread (work abandoned or no longer relevant)
get_thread Get a thread with all messages and current status
get_thread_updates Poll for new messages since a timestamp
list_threads List threads, filterable by status or agent
log_action_summary Log a one-liner action summary for the dashboard timeline

File tracking

Tool Description
hot_files List files being edited by multiple agents
get_session_files Get all files edited by an agent in the current session
check_file_conflict Check whether another agent edited a given file recently

File claims are advisory. Two agents can hold the same file at once, a claim is never refused, and nothing in the coordinator blocks a write — the strongest verdict it produces is a warning. Claims make contention visible; acting on it is the agents' job. See what a claim does and does not do.

Dependency map

Tool Description
set_dependency_map Load a module dependency graph (JSON)
get_blast_radius Calculate which other modules are affected by changes
get_module_info Get dependency and dependent info for a module

MQTT

Tool Description
wait_for_message Block until a coordination message arrives on the agent's topic
get_queued_messages Drain all queued messages without blocking; require_ack holds the batch until acknowledged
mqtt_publish Publish a message into your org's MQTT namespace

MQTT here is best-effort push, not the record of truth. Worth knowing before you build on it:

  • Nothing is buffered for an agent with no live listener — a message that arrives before wait_for_message/get_queued_messages registers is gone.
  • get_queued_messages drains by default: the messages are removed as you read them, so a second call returns nothing and a crash mid-processing loses them. Pass require_ack: true and the batch is held instead — you get a batch_id, and the next call redelivers that batch unless you hand the id back as ack. Set it on the FIRST call, while there is still nothing to lose; once set for an agent it stays set. Only the opted-in call changes shape, so existing consumers keep parsing the bare array (issue #236).
  • A coordinator restart still drops every queue, acknowledged or not. The queues are process memory; that half of #236 needs a store, not a protocol.
  • Listener queues are capped and drop oldest-first under load.
  • mqtt_publish rewrites your topic into coordinator/<your-org>/…, and only broadcast and consultations/* reach other agents' listeners. Any other topic is published and consumed by nobody.
  • Payloads must be valid JSON to be delivered.

Every one of those discards is now counted on /metrics as mcp_coordinator_mqtt_messages_dropped_total{reason} and logged at warn level, so a silent drip is visible rather than invisible (issue #236).

For delivery you can rely on, use the thread APIs — post_to_thread + get_thread_updates are backed by SQLite and survive restarts.

Status

Tool Description
coordinator_status Full system status: agents, threads, file activity, MQTT, quota

CLI

Command Description
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
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
mcp-coordinator server start [--port N] [--data-dir PATH] [--daemon] Start the coordinator (foreground or daemon)
mcp-coordinator server stop Stop the coordinator
mcp-coordinator server status PID, port, online agents, open threads
mcp-coordinator server logs [-n N] [-f] Tail the daemon log at ~/.mcp-coordinator/logs/server.log
mcp-coordinator server backup [--output PATH] [--data-dir PATH] [--force] Snapshot config.json + the SQLite data dir to a .tar.gz archive (refuses to run while the coordinator is up unless --force)
mcp-coordinator server restore <tarball> [--force] [--no-backup] [--data-dir PATH] Restore a server backup archive over ~/.mcp-coordinator/ (moves the existing config dir aside first unless --no-backup)
mcp-coordinator dashboard Open http://localhost:3100/dashboard
mcp-coordinator doctor [--host H] [--port P] [--mqtt-port P] Health check: config, server liveness, /health, /mcp initialize, dashboard, MQTT broker
mcp-coordinator --version Print the installed version

Quick start

# Start the coordinator (embedded MQTT + dashboard)
mcp-coordinator server start --daemon

# Open the dashboard
mcp-coordinator dashboard

# Stop when done
mcp-coordinator server stop

In-process from your own Node app

import { startServer } from "mcp-coordinator";

await startServer({
  port: 3100,
  dataDir: "./coordinator-data",
});

For multi-Claude setups, team deployments, walkthroughs, and debugging recipes, see the usage guide.


Authentication

The coordinator runs in one of three modes, selected by env-var configuration. Single-user / dev local stays zero-config; multi-user deployments opt in to JWT or full OAuth via a single feature flag.

Mode When Enable
Open (default) Local dev, single user No env vars needed — synthetic legacy claims
JWT (Phase 1) Small team, shared secret COORDINATOR_AUTH_ENABLED=true + JWT/registration/admin secrets — see JWT setup
OAuth 2.1 (Phase 2) Multi-tenant, internet-facing COORDINATOR_OAUTH_ENABLED=true + IdP credentials — see onboarding

OAuth mode adds: 4 IdP providers (GitHub OAuth App, GitHub App, Google, generic OIDC) with picker UI, cookie sessions + Bearer JWT + service tokens, refresh-token rotation with stolen-token detection, SHA-256 audit chain (SOC 2 tamper-evidence), and an admin UI at /dashboard/admin.html.

MCP authorization spec discovery: /mcp does not implement the MCP authorization spec's OAuth discovery flow — no resource_metadata (RFC 9728) on WWW-Authenticate, no /.well-known/oauth-protected-resource. This is a deliberate scope decision, not an oversight: today's clients are the maintainer's own agents under an intra-org trust model (see docs/security/threat-model.md), and token provisioning is proprietary — shared-secret registration (/api/auth/register, Phase 1) or the device flow (Phase 2) — rather than spec-compliant discovery. Third-party spec-compliant MCP clients need manual configuration (they can't auto-discover the auth flow). The coordinator is a relying party, not an authorization server: it signs users in to an IdP and has no authorization endpoint of its own, so spec-compliant discovery is not a wiring job. The setup that does connect a third-party client today is a static Authorization header — see docs/clients.md.

Doc Topic
docs/clients.md Connecting an MCP client, with and without authentication
docs/onboarding-self-host.md Zero-to-first-signin walkthrough
docs/idp-providers.md Per-provider setup (GitHub OAuth App, GitHub App, Google, OIDC, Azure AD)
docs/openapi.yaml OpenAPI 3.1, 17 endpoints
docs/security/threat-model.md STRIDE per asset, residual risks
docs/ops/upgrade-phase1-to-phase2.md Phase 1 → Phase 2 migration
docs/ops/key-rotation.md + auto-rotation.md JWT_SECRET rotation procedures
docs/ops/audit-integrity.md Audit chain runbook + tip-attestation workflow
docs/ops/backup-restore.md Litestream + NR12 reconciliation
docs/gdpr.md GDPR Art. 17 procedures
sdk/README.md TypeScript SDK reference

Operational tooling: mcp-coordinator init phase2 (interactive wizard), mcp-coordinator doctor --phase2 (8 health probes), mcp-coordinator service-token {issue,list,revoke}, mcp-coordinator rotate-jwt-secret, and node dist/scripts/verify-audit-chain.js (or tsx scripts/verify-audit-chain.ts from a checkout).


Anthropic Quota Pre-flight

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.

  • Reads the Claude Code OAuth token from the macOS Keychain (security find-generic-password -s "Claude Code-credentials") and calls Anthropic's /api/oauth/usage endpoint directly — macOS only. On Linux/Windows the credential reader is an unimplemented stub, so the quota endpoint returns 503 (fail-open: the rest of the coordinator keeps working without a quota guardrail).
  • The coordinator itself enforces no abort threshold — it just serves fresh utilization numbers (2 min cache TTL). Deciding what "too high" means, e.g. via a MAX_QUOTA_PCT convention, is left to the orchestrator reading coordinator_status.quota.
  • Back-off when the usage endpoint itself returns 429 (5 min cool-down by default, or the server's Retry-After).
  • Live widget in the dashboard with manual refresh + historical buckets.
  • coordinator/quota/update MQTT events stream into the timeline by default.

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 reference orchestrator does exactly this.


Token Observability

Consultation traffic only. Each thread message carries a token_estimate, and the dashboard sums those into the Tokens consultation metric. That is the whole of it.

There is no per-agent token accounting and no per-turn cost breakdown: the coordinator sits beside your agents, not between them and the model, so it never sees a turn. Anything richer is an orchestrator's job — it has the API responses, the coordinator does not.

This section previously described a tokens component logger emitting per-turn input_tokens / cache_read / model id, and a live per-agent dashboard gauge. Neither existed. The gauge had no producer and was removed in #341; the logger was never written.


Dashboard

http://localhost:3100/dashboard (or /dashboard on whichever port the coordinator is bound to).

  • Timeline — all threads + quota_update events with scores and resolution types
  • Agent panel — online/offline, working/idle/waiting, current file, thread being waited on
  • Scoring breakdown — which detection layer triggered each conflict
  • Quota widget — live utilization %, stacked buckets, manual refresh button. macOS only — the credential reader has no Linux or Windows implementation, so the panel hides itself elsewhere rather than showing a fault you cannot fix.
  • Consensus metrics — per session: consensus / timeout / auto-resolved split, consultation token totals

All events arrive via SSE on /api/events. No polling.

Agent activity states

Status Indicator Meaning
working pulsing blue Actively editing files
idle solid green Online, no recent activity
waiting pulsing yellow Blocked on a consultation thread
offline solid red Disconnected or session ended

Configuration

Local data

~/.mcp-coordinator/
├── config.json          # persistent configuration
├── data/
│   └── coordinator.db   # SQLite database
├── server.pid           # PID file (when daemonized)
└── logs/
    └── server.log       # daemon logs

config.json

{
  "server": { "port": 3100, "data_dir": "~/.mcp-coordinator/data" },
  "defaults": { "coordinator_url": "http://localhost:3100" }
}

Resolution priority (highest to lowest): CLI flag → env var → config.json → default.

Core env vars

Variable Default Description
PORT 3100 HTTP port (also serves MQTT-over-WebSocket on /mqtt)
COORDINATOR_DATA_DIR see below Directory for the SQLite database
COORDINATOR_MQTT_TCP_PORT 1883 TCP port for the embedded broker
LOG_LEVEL info debug / info / warn / error
NODE_ENV development for pretty logs
COORDINATOR_AUTH_ENABLED false Enable Phase 1 JWT authentication
COORDINATOR_OAUTH_ENABLED false Enable Phase 2 OAuth

COORDINATOR_DATA_DIR's default depends on how the server is started:

  • CLI (mcp-coordinator server start, mcp-coordinator init, ...) defaults to ~/.mcp-coordinator/data (see config.json above).
  • Direct entry pointsnode dist/src/serve-http.js, or stdio via .mcp.json (node dist/src/index.js / tsx src/index.ts) — do not go through the CLI's config resolution. Without COORDINATOR_DATA_DIR set, they fall back to ./data relative to the process's current working directory, which is unpredictable for a server a client spawns from an arbitrary cwd. Both entry points log a warning at boot when this fallback is in effect.

Always set COORDINATOR_DATA_DIR explicitly (or use the CLI) for a stable, predictable data location outside of local single-shot dev use.

The complete annotated env reference (50+ variables including all Phase 2 OAuth / multi-IdP / hardening vars) lives in .env.example — copy-paste and fill in.

Data retention

Coordination history is deleted on a schedule. A sweeper runs every 60 s in HTTP server mode and purges 9 tables. The five that hold coordination data:

What Default Env var
file_activity — per-file edit records 7 days COORDINATOR_FILE_ACTIVITY_RETENTION_DAYS
events — the replayable SSE feed 7 days COORDINATOR_EVENTS_RETENTION_DAYS
thread_messagesconsultation content 30 days COORDINATOR_THREAD_MESSAGES_RETENTION_DAYS
action_summaries — dashboard timeline 30 days COORDINATOR_ACTION_SUMMARIES_RETENTION_DAYS
layer_firings — impact-scoring history 30 days COORDINATOR_LAYER_FIRINGS_RETENTION_DAYS

Set any of them to a larger number of days before you accumulate history you care about. Auth and audit tables have their own, longer windows — the full 11-pass table is in docs/ops/upgrade-phase1-to-phase2.md.

What is never deleted. threads is not swept at all. So a consultation's plan and resolution_summary — the decision — persist indefinitely, while thread_messages — the discussion that produced it — is gone at 30 days. get_thread on an old thread returns the decision with an empty message list. The asymmetry is deliberate: conclusions are cheap to keep and reasoning is not. If you want the reasoning too, raise the window; if you want the decisions long-term, list_threads already gives them to you at any age.

git_cochange, dependency_map and the agents registry are likewise never swept — they are derived or live state, rebuilt rather than aged out.

In stdio mode nothing is swept. The retention sweeper is started by the HTTP server; a stdio-only deployment keeps every row forever and grows without bound. That is usually what a single-developer local install wants, but it is worth knowing before you point one at a long-lived database.


Structured Logging

Pino emits JSON per subsystem. Component loggers: http, mcp, mqtt, consultation, conflict, auth, quota, sse.

{"level":"info","time":1712345678901,"component":"http","msg":"Server started","port":3100}

Dev (NODE_ENV=development) renders pretty human-readable lines. Levels controlled via LOG_LEVEL.


SDK

A TypeScript reference client lives in sdk/ (not yet published to npm). Install via npm install file:./sdk from a consumer project.

import { McpCoordinatorClient, FileTokenStore, ProactiveRefresh } from "@mcp-coordinator/sdk-js";

const client = new McpCoordinatorClient({
  baseUrl: "https://coordinator.example.com",
  store: new FileTokenStore(),
  refreshStrategy: new ProactiveRefresh(),
  refreshLockPath: process.env.HOME + "/.mcp-coordinator/refresh.lock",
});

await client.loadFromStore();
const me = await client.whoami();

See sdk/README.md for the full API.


Integration patterns

  • Any MCP client — connect to http://localhost:3100/mcp (HTTP/SSE), or run mcp-coordinator stdio for a client that only speaks stdio. The server speaks MCP 2024-11-05. stdio is a different topology, not just a different transport — no MQTT broker, one SQLite handle per client, nothing shared between them; see docs/clients.md.
  • Custom orchestrator — 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 for a reference implementation, or write your own.
  • Behavior catalog — coordinator-aware agent behaviors (announce-before-write, work-stealing, conflict resolution) are YAML configs assembled by @swoofer/promptweave. See essaim's behaviors for a curated catalog.

Development

# This repo uses pnpm 10 (pinned via "packageManager" in package.json).
# Run `corepack enable` once — corepack then resolves the right pnpm
# version automatically.

pnpm install
pnpm test
pnpm dev          # HTTP / SSE on port 3100
pnpm dev:stdio    # stdio mode
pnpm build        # TypeScript build → dist/

# Standalone binary (requires Bun)
bun build --compile cli/index.ts --outfile bin/mcp-coordinator

Open an issue or PR on GitHub.


Roadmap

  • v1.0 — Multi-instance: Redis-backed cache invalidation + leader election for the sweeper and rate limiter.
  • Postgres adapter — for regulated multi-instance workloads. See design spec.
  • SDK polish — Windows DPAPI encryption for the on-disk token file (keytar keychain integration and named-profile TOML config already shipped).

Per-version detail for everything already shipped lives in CHANGELOG.md.


Related projects

  • @swoofer/promptweave — YAML composer for assembling agent prompts, hooks, and MCP configs. Use it with mcp-coordinator-aware behaviors from essaim.
  • essaim — end-to-end orchestrator that spawns N coordinated agents using @swoofer/promptweave + mcp-coordinator. Ships the reference catalog of coordinator-aware behaviors.

Support

Built and maintained by @swoofer, with a large and growing automated test suite and a growing list of external contributors.

If this project saves you time:


License

MIT

Releases

Packages

Used by

Contributors

Languages