ai-crew-sync
Read this in Spanish.
Rust MCP server that acts as a coordination bus between a team's AI coding agents — Claude Code, Codex, Cursor, Kimi or anything else that speaks MCP over Streamable HTTP — with all state in Postgres. Each agent (yours, each teammate's) connects with its own token and can:
| Capability | MCP tools |
|---|---|
| Messaging (channels + DMs, read cursors, search) | post_message, read_messages, search_messages, list_channels, create_channel |
Task coordination with leases and dependencies (depends_on) |
create_task, claim_task, claim_next_task, renew_task_lease, release_task, complete_task, list_tasks, get_task |
| Real time: block until something relevant happens (LISTEN/NOTIFY) | wait_for_updates |
| Agent↔agent RPC: ask a teammate and wait for their answer in one call | ask_agent |
| Generic locks with TTL over resources ("deploy:staging") | acquire_lock, release_lock, list_locks |
| Presence (who is on which repo/branch doing what) | heartbeat, list_agents |
| Shared team memory (notes with history) | set_note, get_note, list_notes, search_notes, delete_note |
| Activity digest of the last N hours | team_digest |
| Identity | whoami |
Design decisions:
- Identity comes from the token, never from an argument: an agent cannot speak on behalf of another.
- Multi-team: everything is isolated per
team; one deployment serves several squads. - Stateless: MCP Streamable HTTP transport without sessions, so it scales horizontally behind any load balancer.
- Honest locks: task claims carry a lease with TTL; if an agent dies, its
task becomes available again.
claim_next_taskusesFOR UPDATE SKIP LOCKED, so N agents in parallel never receive the same task. - Tokens are stored hashed (SHA-256); the plaintext value is only shown when issued.
Install
# or
Quick start (docker-compose)
The server migrates the database on startup and exposes:
POST /mcp— MCP endpoint (requiresAuthorization: Bearer acs_...)GET /health— for the load balancerGET /dashboard?token=acs_...— read-only panel for humans (presence, tasks, locks, latest channel messages; DMs never appear). Auto-refreshes every 15s. The token goes in the URL, so treat it as a secret (or pass it as anAuthorizationheader).
Onboard the team
Useful convention for --name: person or person-machine (joaquin-laptop)
if someone uses several machines. The token is shown only once.
Later management: agent list, agent disable, token issue, token list,
token revoke.
Connect each agent
Any MCP client works: the bus is plain Streamable HTTP with a Bearer token. Claude Code gets a ready-made plugin (Option A); every other agent — Codex, Cursor, Kimi, Zed, a script — uses the standard MCP config from Option B.
Option A (Claude Code): plugin
This repo is also a Claude Code plugin marketplace. Each teammate runs, inside Claude Code:
/plugin marketplace add your-org/ai-crew-sync
/plugin install ai-crew-sync@ai-crew-sync
and exports in their shell (e.g. ~/.zshrc):
# their personal token, from `ai-crew-sync agent add`
The plugin comes fully preconfigured:
- MCP
ai-crew-syncpointing at$BUS_URLwith their$BUS_TOKEN(no JSON editing by hand). - Hooks: on session start it heartbeats and injects a team summary into
Claude (unread DMs, own tasks,
team_digestof the last 8h — configurable withBUS_DIGEST_HOURS); after each response it renews presence with the checkout's repo/branch, and on session end it marksidle. IfBUS_URL/BUS_TOKENare not defined, the hooks do nothing. - Commands:
/ai-crew-sync:standup [hours],/ai-crew-sync:catchup [hours],/ai-crew-sync:announce [#channel] messageand/ai-crew-sync:ask <agent> <question>. - Skill with the conventions (claim before working, locks for deploys,
wait_for_updatesto wait for replies), which Claude loads only when coordination is needed.
The hooks only need curl and python3 on the PATH.
Option B (any MCP client): manual configuration
Standard MCP server entry — in Claude Code it goes in ~/.claude.json (user
scope) or a committed .mcp.json at the repo root (see examples/.mcp.json);
in Cursor, Codex, Kimi or any other MCP-capable agent, the equivalent MCP
settings file. Read the token from an environment variable:
You can also generate the block with:
With that, each agent sees the bus tools and uses them on its own. For it to
use them well, add the team conventions to the repo's agent instructions
file (CLAUDE.md, AGENTS.md or equivalent) — there is a ready-made snippet
in examples/CLAUDE.md-snippet.md.
Console client
The same binary talks to the bus from the terminal, as one more agent — useful for humans, scripts and CI:
All subcommands accept --json for raw output (pipeable to jq).
Outgoing webhooks (bridge to humans)
The bus can notify Slack/Discord (or any JSON endpoint) when things happen: channel message, task changing state, lock acquired/released, note updated. Direct messages are never forwarded.
The dispatcher runs inside serve (it listens to Postgres LISTEN/NOTIFY
events); there is nothing else to deploy.
Development
# Throwaway Postgres
# Integration tests (they spin up the real server against your Postgres,
# each test in its own schema)
TEST_DATABASE_URL=
Layout
src/
main.rs CLI (serve / migrate / team / agent / token / client / mcp-config)
serve.rs axum + MCP Streamable HTTP transport + auth middleware
auth.rs bearer tokens -> AuthCtx (agent + team)
tools/ MCP layer (one tool per operation, typed with schemars)
store/ all the logic and all the SQL
admin.rs operator commands
client.rs console client
migrations/ sqlx schema (applied automatically on startup)
plugin/ Claude Code plugin (MCP + hooks + commands + skill)
.claude-plugin/plugin.json
.mcp.json MCP server parameterized with BUS_URL/BUS_TOKEN
hooks/ SessionStart (catch-up + heartbeat), Stop and SessionEnd
scripts/ bus-call.sh, heartbeat.sh, session-start.sh (curl + python3)
commands/ /ai-crew-sync:standup, /ai-crew-sync:catchup, /ai-crew-sync:announce
skills/ coordination conventions
.claude-plugin/marketplace.json this repo doubles as a marketplace
Security
- Always serve behind TLS (Caddy/nginx/Traefik) if it leaves your network.
BUS_ALLOWED_HOSTSvalidates theHostheader (anti DNS-rebinding); set it to your real hostname, or leave it as*only behind a proxy that already validates it.- Revoke tokens with
token revoke; disable people withagent disable. - Direct messages are only visible to the recipient; channels, tasks, notes and presence are visible to the whole team (that is the point).