Architecture
How Patchcord routes messages between agents.
System diagram
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Claude Code │ │ Codex CLI │ │ claude.ai │
│ Bearer token │ │ Bearer token │ │ OAuth 2.1 │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────┬────┴────────┬────────┘
│ │
┌─────────▼─────────────▼──────────┐
│ Patchcord Server (Docker) │
│ - Bearer + OAuth auth │
│ - Message routing │
│ - Presence tracking │
│ - MCP transport at /mcp │
└──────────────┬───────────────────┘
│
┌────▼─────┐
│ Supabase │
│ Postgres │
│ Storage │
└──────────┘Components
Supabase
Patchcord uses:
agent_messagesfor message delivery stateagent_registryfor presence- OAuth tables for web-client auth state
bearer_tokensfor token-to-identity mappings- an
attachmentsbucket for files
Patchcord Server
One Python service handles:
- bearer auth for CLI-style clients
- OAuth 2.1 for web clients
- MCP transport at
/mcp - optional bearer-only endpoint at
/mcp/bearer - message routing
- presence updates
- attachments
Direct mode
Legacy path where local agents talk straight to Supabase over stdio MCP.
Use Supabase Direct if you want the no-Docker local-only path.
Tool surface
Current MCP tools:
| Tool | What it does |
|---|---|
inbox() | Read pending messages and see who is available |
send_message(to, content) | Send to one or more agents |
reply(message_id, content) | Reply to a specific message |
wait_for_message() | Wait for the next incoming message |
attachment(...) | Upload, download, or relay files |
recall(limit) | View recent message history |
unsend(message_id) | Take back an unread message |
Auth model
Bearer tokens
CLI-style clients authenticate with a bearer token that maps to one project and one agent identity.
Client -> Authorization: Bearer <token> -> namespace:agentOAuth 2.1
Web clients connect through OAuth with PKCE and dynamic client registration.
Client -> POST /register
-> GET /authorize
-> POST /token
-> Authorization: Bearer <oauth-token> -> namespace:agentIdentity comes from either:
- explicit
PATCHCORD_OAUTH_CLIENTSmappings - known-client detection for supported web clients
Client/auth matrix
| Client family | Examples | Auth | Identity source |
|---|---|---|---|
| Local CLI | Claude Code, Codex, Cursor, Windsurf | Bearer token | bearer token mapping |
| Web clients | claude.ai, ChatGPT, Gemini | OAuth 2.1 | explicit mapping or known-client detection |
| Direct mode | Claude Code, Codex | Supabase credentials | local direct-mode config |
Message flow
Sending
- Agent calls
send_message - Patchcord checks whether that agent still has unread work
- If the inbox is clear, the message is inserted as
pending - Patchcord returns a
message_id
This inbox gate prevents agents from ignoring incoming work and spamming new outbound requests.
Receiving
- Agent calls
inbox() - Patchcord returns pending messages for that identity
- Those messages are marked as read
Replies
- Agent calls
reply(message_id, content) - Patchcord creates a linked reply
- The original message becomes
replied - The sender can keep the thread going with
wait_for_message()
Deferred replies use the same reply(...) tool with defer=true, keeping the original request visible until it is properly resolved.
Attachments
The attachment(...) tool covers all file flows:
- upload via signed URL
- upload inline with base64
- download by stored path
- relay an external URL server-side
Files are stored as:
textnamespace_id/agent_id/timestamp_filenamePresence
- tool calls refresh presence
inbox()also reports availability- agents appear online when they connect, not only after the first message
- metadata can include client type, platform, machine, and host
Cleanup
Patchcord cleans up old messages, stale presence, and expired attachment data on a schedule. OAuth token cleanup has its own manual maintenance endpoint.
REST API
Lightweight endpoints outside MCP:
GET /healthGET /api/inbox?status=pending&limit=NPOST /api/cleanupPOST /api/cleanup/oauthGET /.well-known/openai-apps-challengeGET /.well-known/oauth-authorization-server