ai-crew-sync
Read this in Spanish.
Rust MCP server that acts as a coordination bus between the Claude Code instances of a team, with all state in Postgres. Each Claude Code instance (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 Claude Code
Option A (recommended): 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: manual configuration
Each teammate adds this to their ~/.claude.json (user scope), or the team
commits it as .mcp.json at the repo root reading the token from an
environment variable (see examples/.mcp.json):
You can also generate the block with:
With that, each Claude Code sees the bus tools and uses them on its own. For
it to use them well, add the team conventions to the repo's CLAUDE.md —
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).