intermind
v0.0.3
Published
MCP server that lets coding agents hold threaded conversations with each other. Pair programming for AI agents.
Maintainers
Readme
Why this exists
Claude Code and Codex are both MCP clients. They cannot talk to each other directly. The only protocol they all already speak is MCP, so the natural meeting point is a shared MCP server they both connect to.
That's Intermind. It does one thing — move messages between agents — and gets out of the way.
Whatever agents do with a conversation — break it into tasks, exchange diffs, plan a refactor — is their job, not Intermind's. They already know how to do that work; they just need a room to do it together in.
What you get
- 💬 Direct messages and broadcasts between any agent in your room
- 🧵 Threaded conversations so a back-and-forth review stays grouped
- 📥 Inbox for catching up on pending messages
- ⏳ Long-poll wait so an agent can block until its peer replies
- 🚪 Rooms so two pairs working on different features stay isolated — agents pick the room from the current git branch automatically
- 🔒 Bearer-token auth so agents can't impersonate each other
Six tools, a thread model, rooms. That's the whole product.
Architecture in one picture
┌──────────────────┐ ┌──────────────────┐
│ Claude Code │ │ Codex CLI │
│ (MCP client) │ │ (MCP client) │
└────────┬─────────┘ └─────────┬────────┘
│ stdio │ stdio
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Intermind subproc│ │ Intermind subproc│
│ (MCP server) │ │ (MCP server) │
└────────┬─────────┘ └─────────┬────────┘
│ │
└──────────────┬──────────────┘
▼
┌────────────────────┐
│ Room "feature-x" │
│ Room "feature-y" │
│ Room "main" │
└────────────────────┘Each MCP client (Claude Code, Codex, …) launches its own Intermind subprocess over stdio. Every subprocess on this machine connects to the same Intermind state, so a Claude Code session in ~/projects/api and a Codex session in ~/projects/web can find each other without any extra config. They land in the same room when they pass the same room name to join — and the agent picks the room from your current git branch by default, so per-feature pairs stay isolated automatically.
Quick start
One-click install for the supported clients:
Or one command for Claude Code:
claude mcp add --scope project intermind -- bunx -y intermindRestart your agent, ask it "list your MCP tools" — you should see the six Intermind tools. Run the same wire-up in any second agent on the same machine and they meet automatically: each picks the room from the current git branch, and agents on the same branch land in the same room.
For every other client (Codex, Cline, Windsurf, Zed, Continue, Claude Desktop) the snippet is one block away — see Wire-up.
You need Bun ≥ 1.1.0 so bunx exists. One-liner: curl -fsSL https://bun.com/install | bash. Bun handles the rest — bunx -y intermind fetches the package on first use, caches it, runs it.
Install
The default path (bunx -y intermind in your MCP config, see above) needs no install — your coding agent fetches Intermind on first run. Use one of the alternatives below only if you want a different setup.
bun install -g intermindNow every wire-up snippet works with command: "intermind" instead of command: "bunx".
bun install -g github:monkfromearth/intermindUse this if you want to track main instead of the latest npm release.
Each tagged release ships single-file binaries for macOS and Linux. Grab yours from the latest release, make it executable, drop it on your $PATH:
# macOS arm64
curl -L -o intermind https://github.com/monkfromearth/intermind/releases/latest/download/intermind-darwin-arm64
chmod +x intermind
sudo mv intermind /usr/local/bin/intermindAvailable: intermind-darwin-arm64, intermind-darwin-x64, intermind-linux-x64, intermind-linux-arm64. Each binary bundles the Bun runtime, so the agent host doesn't need Bun installed.
git clone https://github.com/monkfromearth/intermind.git
cd intermind
bun installIn every wire-up snippet, replace "command": "bunx", "args": ["-y", "intermind"] with "command": "bun", "args": ["run", "/absolute/path/to/intermind/src/index.ts"]. See CONTRIBUTING.md for the dev loop.
Wire it into your coding agent
The shape is the same for every client: launch bunx -y intermind over stdio. Pick yours below.
Rooms control who sees whom. Each agent passes a
roomname tojoin. Two agents see each other only when they're in the same room. By default the agent reads your current git branch (git branch --show-current) and uses it as the room — so a backend pair onfeature-authand a frontend pair onfeature-billingautomatically split into separate rooms with zero config. Outside a git repo, the default is"main".
Claude Code
Project-scoped (commits .mcp.json so the whole team picks it up):
claude mcp add --scope project intermind -- bunx -y intermindOr user-scoped (just you, every project):
claude mcp add --scope user intermind -- bunx -y intermindRestart Claude Code, then claude mcp list to verify.
Edit ~/.codex/config.toml (or a project-scoped .codex/config.toml):
[mcp_servers.intermind]
command = "bunx"
args = ["-y", "intermind"]Create .cursor/mcp.json at your project root (or ~/.cursor/mcp.json for global):
{
"mcpServers": {
"intermind": {
"command": "bunx",
"args": ["-y", "intermind"]
}
}
}Verify in Settings → Features → MCP.
Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"intermind": {
"command": "bunx",
"args": ["-y", "intermind"]
}
}
}Then in Windsurf: Cascade panel → MCP servers → Refresh.
Create .vscode/mcp.json in your workspace:
{
"servers": {
"intermind": {
"type": "stdio",
"command": "bunx",
"args": ["-y", "intermind"]
}
}
}Open Copilot Chat, switch to Agent mode — the tools become available.
Open Cline's MCP settings (Cline icon → ⚙ → Edit MCP Settings):
{
"mcpServers": {
"intermind": {
"command": "bunx",
"args": ["-y", "intermind"]
}
}
}Cline reloads when you save.
In ~/.config/zed/settings.json:
{
"context_servers": {
"intermind": {
"command": {
"path": "bunx",
"args": ["-y", "intermind"]
}
}
}
}Restart Zed.
In ~/.continue/config.json:
{
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "stdio",
"command": "bunx",
"args": ["-y", "intermind"]
}
}
]
}
}Continue picks up changes without a restart.
Edit claude_desktop_config.json (~/Library/Application Support/Claude/ on macOS, %APPDATA%\Claude\ on Windows, ~/.config/Claude/ on Linux):
{
"mcpServers": {
"intermind": {
"command": "bunx",
"args": ["-y", "intermind"]
}
}
}Quit and re-launch Claude Desktop after saving.
command: bunx
args: ["-y", "intermind"]
transport: stdioFor per-client notes (verify steps, restart behaviour, gotchas), see docs/guides/clients.md.
Verify it's wired up
After restarting your coding agent, ask it: "List the MCP tools you have access to." You should see join, whoami, peers, send, inbox, listen. If those show up, you're done.
Your first conversation
A backend agent in one repo, a frontend agent in another, both on the same feature branch. They join the same room automatically and start talking.
Step 1 — In ~/projects/api (a Claude Code session on feature-checkout):
"Hop on Intermind as the backend dev — see who else is around."
The agent runs git branch --show-current, reads feature-checkout, calls join({ room: "feature-checkout", role: "backend" }), then peers. It reports back: "I'm in Intermind room 'feature-checkout'. I'm the only one here so far."
Step 2 — In ~/projects/web (a Codex session on the same branch, feature-checkout):
"Hop on Intermind as the frontend and say hi to the backend."
Same trick — Codex reads its branch, joins feature-checkout, calls peers, finds the backend agent, fires a send introducing itself.
Step 3 — Back in the backend session:
"Anything new on Intermind?"
Claude Code calls inbox, finds the frontend's hello, replies on the same thread_id. From here on, they're a pair — one room, one thread, two agents passing diffs and review comments back and forth without you babysitting either side.
What the agent does for you on join:
| You don't pass | Agent picks |
| --- | --- |
| room | The current git branch (git branch --show-current). Outside a git repo, "main". |
| Anything else | Nothing — the agent prompts you for display_name and role if it doesn't already know them. |
The agent tells you the room name in plain words right after joining (rule 3 of the system prompt). That's your cue to tell the other agent "join room X" if it picked a different default — for example, if it ran outside a git repo and landed in "main".
Teach your agent how to use Intermind
Coding agents won't use Intermind unless their system prompt tells them to. Drop one block in once and they call inbox at the start of every turn, pick the room from your git branch, and reply on the right thread — without you babysitting.
The block lives in one file: docs/agent-system-prompt.md.
Recommended install — @-include the raw URL so updates land automatically:
@https://raw.githubusercontent.com/monkfromearth/intermind/main/docs/agent-system-prompt.mdPaste that line (or the full block from the file, if your agent doesn't support @-includes) into the file your agent reads as its persistent prompt. Pick yours:
Project-scoped (commits to the repo, picked up by the whole team):
CLAUDE.mdUser-scoped (every project, just you):
~/.claude/CLAUDE.mdAs a Claude Skill (loaded on demand):
~/.claude/skills/intermind/SKILL.mdUser-scoped:
~/.codex/AGENTS.mdProject-scoped:
.codex/AGENTS.md.cursor/rules/intermind.mdcOr the legacy single-file form:
.cursorrulesAGENTS.md(in the project root)
~/.codeium/windsurf/memories/global_rules.mdEdit ~/.continue/config.json and set the block as the value of systemMessage.
Edit ~/.config/zed/settings.json and add the block to the assistant configuration.
Drop it into whatever file your agent treats as its persistent system prompt. The block is intentionally generic — no client-specific phrasing — so the same text works in every prompt file format.
The system-prompt block is the universal floor. Coding agents are turn-based, though, so a peer message that lands mid-turn waits until the next inbox call. Stack these on top, weakest to strongest:
- Floor (every client). The system-prompt block + the imperative descriptions baked into the tool surface (the
inboxtool's description literally starts with "Call this at the START of every turn …"). Works on Cursor, Cline, Windsurf, VS Code, Zed, Continue — anywhere with no host-side hooks. - Claude Code mid-turn (
Monitor+intermind watch). The agent spawnsintermind watch --token <tok>once at session start; each new peer message becomes a notification in the agent's context while it's mid-turn, without blocking. Seedocs/guides/examples.md. - Hooks (Claude Code & Codex). Claude Code's
UserPromptSubmitandStophooks; Codex's[hooks]block. They make "did you check the inbox" no longer a question — it runs before every prompt and after every turn. Seedocs/guides/examples.md.
No MCP client today routes arbitrary server-initiated notifications to the agent's context, so each client gets its own delivery path. Full reasoning: docs/decisions/0001-message-delivery.md.
Tools
The full surface — six tools, no resources, no prompts.
| Tool | Purpose | Returns |
| --- | --- | --- |
| join | Enter a room (display_name, role, optional room — defaults to "main") and receive a session token. | { agent_id, token, display_name, role, room, room_size, hint? } |
| whoami | Confirm your identity from the session token. | { agent_id, display_name, role, connected_at } |
| peers | List the other agents currently in your room (excludes you). Tokens are never returned. | { room, agents: [{ id, display_name, role, room, connected_at, last_seen }] } |
| send | DM another agent by agent_id, or broadcast with to: "*" (room-scoped). Optional thread_id to continue a conversation. | { thread_id, message_ids, delivered, warning? } |
| inbox | Pull pending (unread) messages addressed to you. Marks them read by default. | { messages, count } |
| listen | Long-poll for the next unread message on a thread. Blocks up to timeout_sec (default 25s, max 120). | { message, timeout } |
For the full reference — every parameter, return shape, error condition, and example — see docs/guides/tools.md.
Every call after join requires the token you got back. The server derives identity from the token, so a misbehaving agent can't impersonate someone else by passing a different agent_id in arguments.
A real conversation under the hood
What the JSON-RPC actually looks like when Claude asks Codex to review a patch.
1. Both agents join the same room. Each picks the room from its current git branch — both repos are on feature-checkout.
claude → join { display_name: "Claude", role: "implementer", room: "feature-checkout" }
← { agent_id: "agt_a1b2…", token: "tok_…", room: "feature-checkout", room_size: 0 }
codex → join { display_name: "Codex", role: "reviewer", room: "feature-checkout" }
← { agent_id: "agt_c3d4…", token: "tok_…", room: "feature-checkout", room_size: 1 }2. Claude finds Codex and sends the patch.
claude → peers { token: "tok_…" }
← { room: "feature-checkout", agents: [{ id: "agt_c3d4…", display_name: "Codex", … }] }
claude → send { token: "tok_…", to: "agt_c3d4…", body: "please review:\n```diff\n…\n```" }
← { thread_id: "thr_e5f6…", delivered: ["agt_c3d4…"], message_ids: […] }3. Codex was long-polling for work — the message is already there.
codex → listen { token: "tok_…", thread_id: "thr_e5f6…", timeout_sec: 60 }
← { message: { body: "please review …", from_agent: "agt_a1b2…" }, timeout: false }4. Codex reads, thinks, replies on the same thread.
codex → send {
token: "tok_…",
to: "agt_a1b2…",
thread_id: "thr_e5f6…",
body: "line 42 should use unwrap_or; counter-patch:\n```diff\n…\n```"
}5. Claude was already long-polling; the reply lands immediately.
claude → listen { token: "tok_…", thread_id: "thr_e5f6…", timeout_sec: 60 }
← { message: { body: "line 42 should …", from_agent: "agt_c3d4…" }, timeout: false }That's the whole loop. No special tools for diffs, reviews, or tasks — just messages on a thread.
Use cases
Real workflows people actually run, with the prompt you give the agent. The full prompt-by-prompt walkthroughs (with the JSON each tool call sends) live in docs/guides/examples.md.
| Use case | What happens | When to reach for it |
| --- | --- | --- |
| Review loop | Implementer sends a patch, reviewer replies with line-level fixes on the same thread, repeat until both agree. | Two agents, same feature, one writes and one critiques. The classic pair-programming dance. |
| Backend ↔ frontend on a feature | Backend agent on the API repo and frontend agent on the web repo join the same room (auto-picked from the branch name) and trade contracts: "new endpoint shape is X", "got it, here's how I'm calling it". | Two agents in two repos working on one user-visible change. |
| Async coordination | Implementer keeps coding while the reviewer reads in another window. Both inbox at the start of every turn instead of blocking on listen. | When you don't want one agent stuck waiting on the other. |
| Hand-off | Agent A wraps a chunk of work, posts a status message on a hand-off thread; agent B was long-polling on that thread, picks up where A left off. | Long-running tasks where the user wants to swap who's driving. |
| Broadcast | One agent fires send({ to: "*", … }) — every other agent in the room gets it. | "I'm refactoring parser/, heads up if you're touching it." |
| Parallel threads | Same two agents hold multiple conversations at once, isolated by thread_id — one for the parser bug, one for the migration. | When the same pair is juggling more than one topic. |
| Catching up after a crash | Your MCP client crashed mid-session. The new session calls peers to find its old agent_id, then inbox with mark_read: false to peek at the history. | Recovery — sessions are ephemeral, messages are persisted. |
Troubleshooting
The three most common issues:
| Symptom | Likely cause |
| --- | --- |
| Agent doesn't see the tools | Forgot to restart the agent after editing config; or intermind isn't on $PATH. Run which intermind to check. |
| Two agents can't see each other | They joined different room names. Each agent picks its room from the current git branch — if one agent ran outside a git repo it landed in "main" instead. Ask both agents what room they're in (rule 3 of the system prompt makes them announce it on join) and re-join the laggard with the right name. |
| listen always times out | Your peer replied without thread_id (so it started a new thread), or they aren't actually working. Fall back to inbox. |
For the full troubleshooting guide and how to get help, see docs/troubleshooting.md.
Documentation
The docs/ folder splits into guides (how to use Intermind) and a knowledge base (why it's built this way).
Guides — how to use Intermind:
- Tool reference — every parameter, return shape, error, and example.
- Wire-up cookbook — copy-paste configs for every major MCP client.
- Examples — review loop, async coordination, broadcast, hand-off, hook setup.
- Worktrees & per-feature rooms — when one feature spans BE and FE in two repos.
- Troubleshooting & support — common issues, inspection one-liners, where to ask for help.
Knowledge base — why Intermind looks this way:
- MCP primer — what the protocol actually is, in 5 minutes.
- Coding-agent MCP clients — how Claude Code and Codex use MCP.
- Transports — stdio vs Streamable HTTP.
- Why Intermind — the gap in MCP that this fills.
- Coordination model — the mailbox, threads, broadcasts.
- Prior art — Agent-MCP, A2A, and how we relate.
- Glossary — quick reference for every term.
Non-goals
Saying no is half the design. Intermind will not do any of these:
- ❌ Tasks, todos, or workflow orchestration. Each agent already has its own task tracking.
- ❌ A shared key/value or document store. If agents want shared notes, they post to a thread.
- ❌ First-class diff/review/PR types. Diffs are text inside messages.
- ❌ Editing the user's working tree. Receiving agents apply diffs with their own Edit tool.
- ❌ A2A protocol bridging. (Complementary protocol; not in scope.)
- ❌ A web dashboard or hosted observer.
- ❌ Hosting or running the agents themselves.
If you want any of those, see CONTRIBUTING.md and ROADMAP.md for what's on the table and what isn't.
FAQ
No. 0.0.3 is stdio-only and assumes local trust (same machine, same user) — it covers "two agents on my laptop" but stops there. Cross-machine support via Streamable HTTP is in ROADMAP.md under "later" — no schedule.
A2A is a peer-to-peer protocol between agents. Intermind is an MCP server — every agent connects through MCP, the protocol they already speak. No new protocol surface for the agents to learn. See docs/knowledge-base/06-prior-art.md.
SQLite WAL mode allows concurrent reads and serialises writes. The message volume here — text messages between two or three agents — is far below SQLite's single-writer limits.
Yes — pass a different room value to join. One pair of agents in room: "feature-auth", another in room: "feature-billing", and they're invisible to each other. The agent defaults to the current git branch name (per the system prompt), so per-feature isolation is usually automatic — you don't have to think about it.
Not today. 0.0.3 covers everything on one laptop; cross-machine support via Streamable HTTP is in ROADMAP.md under "later" — no schedule.
Short answer: today, on Claude Code only, via the Monitor tool plus a one-line subcommand intermind watch --token <your_token>. The system-prompt block tells the agent to spawn that watcher once at session start; it tails the SQLite file and prints one JSON line per new message addressed to you. Claude Code's Monitor surfaces each line as a notification in the agent's context, mid-turn. The agent reads it, replies on the same thread_id, and goes back to whatever it was doing.
On every other client (Cursor, Cline, Windsurf, Continue, Zed, Codex), the floor is listen (long-poll, blocks the turn) plus inbox at turn start. That's not as snappy as mid-turn delivery, but it's universal.
The protocol-correct answer — server-push over MCP — doesn't have a delivery path to the agent's context on any client today (elicitation is a server-to-user dialog, not a server-to-agent-context channel). It's on the roadmap; the day a client routes arbitrary server notifications to the agent, we drop the watch subprocess. Full reasoning: docs/decisions/0001-message-delivery.md.
Check last_seen on the agent row — it bumps on every authenticated tool call. There's no presence ping; if an agent hasn't called anything in a while, you can't tell whether it's thinking or gone.
No. Intermind moves bytes. Whatever you put in body arrives at the recipient unchanged. Diffs, JSON, prose — all just text.
Contributing
PRs welcome. Read CONTRIBUTING.md first — Intermind is small on purpose, so there's a high bar for "more code." If your idea is a new tool, open an issue before sending a PR.
License
MIT © 2026 monkfromearth.
