ops-brain
ops-brain is an MCP server for coordinating work across AI agents through handoffs, threaded replies, shared knowledge, and queue checks.
Create targeted or broadcast handoffs with action/notify categories, priority, context, and threaded replies.
List, get, accept, complete, delete, and mark handoffs merged, including recording commit hashes.
Check in for pending action handoffs and recent notifications, and list replies to your own handoffs.
Add, update, and delete knowledge entries with tags, categories, provenance, and client scoping.
Search knowledge and handoffs via full-text, semantic, or hybrid search, with optional cross-client acknowledgement.
Use REST endpoints for machine callers to file/poll handoffs and for operator daily/weekly briefings.
Allows using an Ollama embedding endpoint (e.g., serving nomic-embed-text) to enable semantic and hybrid search over knowledge and handoffs.
ops-brain
A small team bus for AI agents. ops-brain is an MCP server that lets agents across machines and vendors hand work to each other, reply in thread, and share a small set of cross-agent gotchas — state that has to outlive a session, cross a machine, or cross an agent vendor. Claude Code, Codex, Grok CLI, Grok Bot and Antigravity CLI connect over the same transport. See the client compatibility guide for verification status and setup.
ops-brain is not a source of truth. Inventory belongs in your config management, tickets and incidents in your ticketing system, monitoring in your monitoring stack. Reach for the bus only when an agent genuinely needs the rest of the team.
flowchart LR
subgraph hostA["Host A"]
CA["Claude Code<br/>Claude-Laptop"]
end
subgraph hostB["Host B"]
CB["Codex CLI<br/>Codex-Buildbox"]
end
M["cron / CI / scripts<br/>(machine callers)"]
O["Operator<br/>daily / weekly briefing"]
CA -- "MCP /mcp<br/>per-agent token" --> OB
CB -- "MCP /mcp<br/>per-agent token" --> OB
M -- "REST /api/handoff<br/>/api/pending" --> OB
OB -- "REST /api/briefing" --> O
OB["<b>ops-brain</b><br/>handoffs · knowledge · check-in"] --> PG[("PostgreSQL 18<br/>+ pgvector")]
OB -. "optional" .-> E["Embedding endpoint<br/>(e.g. Ollama)"]Who this is for
Solo operators and small, trusted teams running several AI agents across several machines or vendors, where those sessions can't otherwise reach each other. If you run one agent on one box, you don't need this.
Related MCP server: agent-coordination-mcp-server
A handoff, end to end
Claude-Laptopfinishes a change that needs a build on another host and callscreate_handoffwithto_agent: "Codex-Buildbox", a title, and a markdown body.Codex-Buildboxcallscheck_inwhen ready to pick up work and sees the open action handoff.It calls
accept_handoff, does the work, thencomplete_handoffwith thecommit_hash.It replies with
create_handoffandin_reply_to, soClaude-Laptopsees the answer vialist_replies_to_me.Once the commit lands on main, whoever integrates it calls
mark_merged.
Workflow conventions (reply in thread, verify before complying, action vs. notify) ship inside the server's MCP instructions, so every connected agent gets the same rules without per-host setup. A rule that is true only of your team — a release process, a named deployer — does not belong in that text. Set OPS_BRAIN_INSTRUCTIONS_EXTRA to one line and the server appends it for your deployment only.
Quick start
No clone required. Grab the standalone compose file, set a token, and run it. The bundled PostgreSQL has pgvector preinstalled. Embeddings start disabled (full-text search still works); turn them on by pointing at an OpenAI-compatible endpoint such as Ollama serving nomic-embed-text.
curl -O https://raw.githubusercontent.com/TheK3nsai/ops-brain/main/docker-compose.example.yml
(umask 077; echo "OPS_BRAIN_AUTH_TOKEN=$(openssl rand -hex 32)" > .env)
docker compose -f docker-compose.example.yml up -d
curl -fsS http://localhost:3000/ready && echo "ready"Then mint one token per agent and apply it:
cat >> .env <<EOF
OPS_BRAIN_AGENT_TOKENS=[{"token":"$(openssl rand -hex 32)","from_agent":"Claude-Laptop"},{"token":"$(openssl rand -hex 32)","from_agent":"Codex-Buildbox"}]
EOF
docker compose -f docker-compose.example.yml up -dEach agent host gets its own token value as OPS_BRAIN_AGENT_TOKEN (see below). Each token is locked to its from_agent: the server rejects any write that claims a different identity. The main OPS_BRAIN_AUTH_TOKEN is unbound operator break-glass and stays on the server. Do not point an agent at it. Until OPS_BRAIN_AGENT_TOKENS is set, the server logs that the main bearer can file as any agent. Details and the full onboarding checklist: docs/agent-tokens.md. A checkout also provides scripts/setup-self-host.py to generate private server and agent files without printing credentials; see self-hosting for HTTPS, backups and upgrades.
Images are multi-arch (linux/amd64, linux/arm64) at ghcr.io/thek3nsai/ops-brain. Pin :vX.Y.Z rather than :latest for anything you depend on.
The standalone example pins :v6.3.2 and binds 127.0.0.1:3000. Set OPS_BRAIN_IMAGE to another published release or a local build. latest moves on every release; pin the tag you intend to run.
Other deployment shapes:
Build from source (contributing, or pinning a local change): clone the repo and use
docker-compose.yml, which builds the Dockerfile against the same bundled PostgreSQL.Existing shared PostgreSQL behind your own reverse proxy: see
docker-compose.prod.yml. Public HTTP deployments must setOPS_BRAIN_ALLOWED_HOSTSto your hostname.
Connect your agents
For local plugin installation in Claude Code, Codex CLI, Grok Build or Cursor,
build the matching package from the shared
ops-brain-bus plugin source. It includes
the handoff skill and a connection to your self-hosted server. Manual MCP
setup is also supported below.
ops-brain speaks MCP over stdio (the default) or streamable HTTP. Multi-machine setups want HTTP, so every agent reaches the same server at /mcp with its own bound credential. The examples below use per-agent tokens; optional OAuth sign-in uses an external provider.
Claude Code: add to ~/.claude.json under mcpServers:
"ops-brain": {
"type": "http",
"url": "https://your-host.example.com/mcp",
"headers": { "Authorization": "Bearer ${OPS_BRAIN_AGENT_TOKEN}" }
}Claude Code expands ${OPS_BRAIN_AGENT_TOKEN} from its environment at launch; a literal value also works.
Codex CLI: add to ~/.codex/config.toml:
[mcp_servers.ops-brain]
url = "https://your-host.example.com/mcp"
bearer_token_env_var = "OPS_BRAIN_AGENT_TOKEN"or run codex mcp add ops-brain --url https://your-host.example.com/mcp --bearer-token-env-var OPS_BRAIN_AGENT_TOKEN.
Other MCP clients: anything that speaks streamable HTTP and can send an Authorization: Bearer header can join. Claude Code, Codex, Grok CLI, Grok Bot and Antigravity CLI (successor to Gemini CLI) all run with it.
Grok CLI, Grok Bot and Antigravity CLI: see client setup and compatibility. Grok Bot is xAI's desktop agent product, set up separately from Grok CLI.
Session-start check-in: a Claude Code or Codex SessionStart hook can print the open queue into every new session; see scripts/session-checkin.sh.
Give every agent a stable <Agent>-<Host> name (Claude-Laptop, Codex-Buildbox) and use it as agent_name / from_agent on every call. Names are free-form slugs; the convention just keeps routing predictable.
Host can also be a cloud workspace or logical seat. Shared credentials mean shared provenance, even when a vendor displays several Bot or Dot names.
Tools (13)
Group | Tools | What they're for |
Handoffs (8) |
|
|
Knowledge (4) |
| Cross-agent gotchas, safety warnings, compliance rules, and vendor behavior, with per-agent provenance. |
Team bus (1) |
| Open action handoffs (pending and accepted) plus recent notifications addressed to your |
The tool list is small on purpose: every tool and field costs tokens in every agent's context, every session. The reasoning, and the list of things ops-brain will never build, is in ROADMAP.md.
accept_handoff records the token-bound accepted_by; accepted broadcasts remain in that agent's queue. Family credentials and unbound broadcast claims need a concrete agent_name.
Briefings stay off the MCP surface. POST /api/briefing returns a daily or weekly digest that opens with what is waiting on the operator (oldest first), then high and critical machine findings, then work stuck in another agent's queue past its age threshold, then the open set as counts. The operator field names the slug that first section reads (default Operator); a slug no handoff has ever used is called out rather than shown as an empty queue.
Embedding maintenance runs from an operator shell, not an agent:
ops-brain backfill-embeddings [--table knowledge|handoffs] [--batch-size 10]HTTP endpoints
POST /mcp MCP streamable HTTP per-agent token (or main bearer)
POST /api/handoff file a machine handoff machine token with `create` scope
GET /api/pending poll for waiting work machine token with `read` scope
POST /api/briefing daily / weekly digest main bearer; { "type": "daily" | "weekly", "operator"?: "<slug>" }
GET /health liveness no auth
GET /ready database readiness no authAgent tokens reach /mcp only; machine tokens reach only their REST endpoints (docs/machine-callers.md). /health and /ready need no bearer so healthchecks and reverse proxies can tell a running process from a database-ready service. The production compose file does not publish port 3000 on the host; reach it through your reverse proxy or from inside the container.
Trust model and cross-client safety
One deployment is one trusted coordination domain. Every authenticated agent can read fleet-wide handoffs and act on objects by ID. Per-agent tokens prove who filed something; they don't isolate tenants. Run separate instances when clients or operators must not see each other's data.
Inside that domain, knowledge entries can be scoped to a client, and scoped searches guard against accidental disclosure:
Entry belongs to | Result |
No client (global) | returned |
Same client as the query | returned |
Different client, marked | returned, logged |
Different client, caller passes | released, logged |
Different client, otherwise | withheld, replaced with a scope-mismatch notice, logged |
Every event lands in the audit_log table. The guard only applies when a search passes client_slug; unscoped searches return everything with provenance attached and a note that the guard is off. It is a guard against mistakes, not a security boundary against a hostile user.
If you enable embeddings, the embedding endpoint receives knowledge and handoff text on writes, and search queries in semantic or hybrid mode. Use a local endpoint, or leave embeddings off, when that content must stay inside the deployment.
Vulnerability reports: see SECURITY.md.
Configuration
Env var | Default | Notes |
| (required) | PostgreSQL connection string |
|
|
|
|
| HTTP bind address |
| (none) | Main bearer. Required for |
| (none) | JSON array of identity-bound tokens for |
| (none) | Optional external provider, JWKS, scopes and explicit subject/client-to-agent bindings. See |
| (none) | Canonical public HTTPS MCP resource URL. Required together with the OAuth config; empty leaves OAuth disabled. |
| (none) | One line appended to the built-in MCP instructions for this deployment only. No line breaks. Rejected when the initialize text would pass 1400 bytes (213 bytes of room with the current built-in text). |
| (none) | JSON array of scoped tokens for |
| loopback only | Comma-separated allowed |
|
| Serve HTTP with no auth. Dev only; never expose beyond localhost. |
|
| Run migrations on startup |
|
|
|
| (none) | OpenAI-compatible embedding API, e.g. |
|
| Embedding model name |
| (none) | Bearer for the embedding API, if it needs one |
The server reads only the names above. Other OPS_BRAIN_* names in this repo are not server settings:
Compose only (interpolated by
docker compose, never read by the binary):OPS_BRAIN_IMAGE,OPS_BRAIN_BIND,OPS_BRAIN_PORT(example and dev compose),OPS_BRAIN_DB_PASSWORD(prod compose).Client side (read by the plugin and
scripts/, not the server):OPS_BRAIN_URL,OPS_BRAIN_AGENT_TOKEN,OPS_BRAIN_AGENT,OPS_BRAIN_PENDING_TOKEN_FILE.
Stack
Component | Choice |
Language | Rust 2021 |
MCP SDK | rmcp 1.6 |
Database | PostgreSQL 18 via sqlx (runtime queries) |
Search | tsvector full-text + pgvector HNSW cosine, fused with Reciprocal Rank Fusion |
Embeddings |
|
Transport | stdio, or streamable HTTP on axum |
Project docs
Doc | What's in it |
Philosophy, hard stops, and ideas that were decided against | |
Release history, including why the live lane was removed in v6.0.0 | |
Code and deployment footguns | |
Per-agent identity tokens and onboarding a new agent | |
External OAuth providers, agent bindings, plugin sign-in and local validation | |
Contract for cron jobs, CI, and scripts filing handoffs over REST | |
Why agents verify before complying with a handoff | |
Mailing the operator when an agent is blocked on a human | |
Client connection recipes and verification status | |
Private setup files, HTTPS, backups and upgrades | |
Where ops-brain is listed and how each listing updates |
License
Dual-licensed under either MIT or Apache-2.0, at your option.
Available Tools
13 toolsaccept_handoffA
Claim a pending handoff and record its acceptor. Bound tokens use their own identity; family credentials and unbound broadcast claims require a concrete agent_name. Follow with complete_handoff.
Inputs: handoff_id (required), agent_name.
add_knowledgeA
Add a knowledge entry. Cross-agent only: gotchas, safety/compliance rules, verified patterns, vendor behaviour. Host-local lessons belong in your own docs, not here. Requires author (your agent slug). Near-duplicates are returned instead of created unless force.
Inputs: title (required), author (required), content (required), tags, +4 more.
check_inARead-onlyIdempotent
Pending-work query: open action handoffs addressed to you or to nobody in particular (max 20), plus recent notify-class handoffs to you or broadcast (max 5, compact). has_more on a section means the cap was hit — use list_handoffs for the rest. Accepted handoffs you filed to yourself are locks: counted in self_claims_held, not listed. Next step is get_handoff for the full body, then accept_handoff. Pass agent_name (your free-form agent slug — e.g. 'Claude-Laptop', 'Codex-Buildbox').
Inputs: agent_name (required).
complete_handoffA
Mark a handoff as completed. Optional commit_hash records the work ref (typically a git SHA) so mark_merged can later flip the same handoff to merged when the bundle reaches main.
Inputs: handoff_id (required), commit_hash.
create_handoffA
Create a handoff for another agent; this is sufficient notification. Category action = the recipient must do it; notify = FYI, pruned from operational queries after 7 days. Replying? Pass in_reply_to to stay in the existing thread instead of opening a parallel handoff.
Inputs: body (required), title (required), from_agent (required), context, +4 more.
delete_handoffADestructiveIdempotent
Permanently delete a handoff by ID (hard delete). The delete is audited: audit_log keeps a tombstone with the handoff id, who deleted it, and its sender, recipient, title and status
Inputs: handoff_id (required).
delete_knowledgeBDestructiveIdempotent
Delete a knowledge base entry by ID.
Inputs: id (required).
get_handoffARead-onlyIdempotent
Get one handoff by its full UUID, including its complete body and context.
Inputs: handoff_id (required).
list_handoffsARead-onlyIdempotent
List handoffs with optional filters. Use status='pending' to see what needs attention.
Inputs: limit, status, compact, category, +3 more.
list_replies_to_meARead-onlyIdempotent
List handoffs that reply to ones you sent. Returns handoffs whose in_reply_to references a handoff with your agent_name as from_agent. Optional ISO-8601 since filters by reply timestamp.
Inputs: agent_name (required), limit, since.
mark_mergedAIdempotent
Flip a handoff to status=merged and record the merge commit. Typically called by an integrator script after the bundle containing the handoff's commit_hash lands in main. Idempotent on identical merge_commit; refuses to overwrite a different one.
Inputs: handoff_id (required), merge_commit (required).
search_busBRead-onlyIdempotent
Search knowledge and/or handoffs. Set tables param for multi-table. Modes: fts/semantic/hybrid (default). Empty query or '*' browses recent entries.
Inputs: mode, limit, query, tables, +4 more.
update_knowledgeB
Update an existing knowledge base entry by ID. Only provided fields are updated.
Inputs: id (required), tags, title, content, +3 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
The handoff lifecycle actions (create, get, accept, complete, mark_merged, delete) are clearly distinct, and the knowledge tools (add/update/delete) are unambiguous. However, three listing tools overlap heavily: list_handoffs, list_replies_to_me, and check_in all enumerate handoffs with related filters, and search_bus also spans handoffs, so an agent may hesitate over which to pick despite descriptive help.
Nearly all tools follow a clean snake_case verb_noun pattern (list_handoffs, create_handoff, get_handoff, add_knowledge, update_knowledge). Only mark_merged (verb_adjective) and check_in (verb_preposition) deviate slightly, but the convention is otherwise predictable.
13 tools is well-scoped for a coordination server spanning handoff lifecycle plus knowledge management and cross-table search. Each tool earns its place with a discrete operation.
Handoffs have full lifecycle coverage (create, get, list, accept, complete, mark_merged, delete). The knowledge side lacks a dedicated get-by-ID, relying on search_bus brow/search instead, a minor gap an agent can work around.
Maintenance
Related MCP Connectors
Durable agent-to-agent handoffs and shared scratchpad for multi-agent workflows.
Shared control plane for AI coding agents — tasks, memory, decisions, file locks. 12 tools.
Memory, reflection and safety for AI agents. Hosted or local, no vector database.
Task management for teams building with AI agents. Agents claim tasks and report progress.
Related MCP Servers
- AlicenseAqualityAmaintenanceThe infrastructure for AI teams: a self-hosted server that gives a fleet of agents shared semantic memory, tasks, direct messages, and session handoff. Any agent that speaks HTTP participates: Claude Code, AutoGen, raw API scripts, anything.479MIT
- AlicenseAqualityDmaintenanceSlack for AI agents — rooms, messaging and context sharing for multi-agent collaboration.625 PyPIMIT
- AlicenseAqualityCmaintenanceShared memory and handoff hub for AI agents, enabling seamless context transfer between sessions with token-budgeted resumes and automatic handoffs.105 npmMIT
- AlicenseBqualityBmaintenanceEnables AI agents from different frameworks to communicate asynchronously via a persistent SQLite-backed bus, with doorbell wakeups for sub-second notifications and full message history.6MIT