Deployment
Run the Patchcord server with Docker or standalone Python. Docker is recommended but not required.
Prerequisites
- Python 3.10+ for standalone operation
- A domain with HTTPS for production
- A database and object-storage service configured for the backend (see the backend-service documentation for those settings)
Quick deploy
bashgit clone https://github.com/ppravdin/patchcord.git
cd patchcord
cat > .env.server << 'EOF'
PATCHCORD_PORT=8000
PATCHCORD_PUBLIC_URL=https://patchcord.yourdomain.com
EOF
# Create bearer tokens for each agent.
python3 -m patchcord.cli.manage_tokens add --namespace myproject frontend
python3 -m patchcord.cli.manage_tokens add --namespace myproject backend
python3 -m patchcord.cli.manage_tokens add --namespace myproject ds
docker compose --env-file .env.server up -d --build
curl https://patchcord.yourdomain.com/healthDatabase schema and object-storage setup are backend concerns. Configure them before deploying, following the backend-service documentation.
What success looks like
json{ "status": "ok", "service": "patchcord" }Environment variables
Required
| Variable | Description |
|---|---|
PATCHCORD_PUBLIC_URL | Public-facing base URL used for OAuth discovery |
Agent bearer tokens are managed in the database, not via environment variables. Use the token management CLI:
bashpython3 -m patchcord.cli.manage_tokens add [--namespace ns] agent_id
python3 -m patchcord.cli.manage_tokens add [--namespace ns] --token <existing> agent_id
python3 -m patchcord.cli.manage_tokens list
python3 -m patchcord.cli.manage_tokens revoke <token>Optional
| Variable | Default | Description |
|---|---|---|
PATCHCORD_PORT | 8000 | Server listen port |
PATCHCORD_HOST | 0.0.0.0 | Server bind address |
PATCHCORD_PUBLIC_URL | http://localhost:{port} | Public URL for OAuth discovery |
PATCHCORD_MCP_PATH | /mcp | Main MCP endpoint |
PATCHCORD_BEARER_PATH | /mcp/bearer | Bearer-only MCP endpoint |
PATCHCORD_STATELESS_HTTP | true | Remove in-memory session coupling |
PATCHCORD_NAME | patchcord | Service name in health output |
PATCHCORD_DEFAULT_NAMESPACE | default | Default namespace for bearer-token agents without an explicit namespace |
PATCHCORD_ACTIVE_WINDOW_SECONDS | 3600 | Presence activity window |
PATCHCORD_PRESENCE_WRITE_INTERVAL_SECONDS | 10 | Presence write throttle |
PATCHCORD_OAUTH_DEFAULT_NAMESPACE | default | Default namespace for OAuth web clients |
PATCHCORD_OAUTH_CLIENTS | (empty) | Explicit OAuth client_id=namespace:agent mappings |
PATCHCORD_KNOWN_OAUTH_CLIENTS | (built-in) | Extend known OAuth clients: agent:domain1,domain2;agent2:domain3 |
PATCHCORD_OAUTH_ACCESS_TOKEN_TTL_SECONDS | 86400 | OAuth access-token lifetime |
PATCHCORD_OAUTH_REFRESH_TOKEN_TTL_SECONDS | 31536000 | OAuth refresh-token lifetime |
PATCHCORD_ATTACHMENTS_BUCKET | attachments | Storage bucket for attachments |
PATCHCORD_ATTACHMENT_MAX_BYTES | 10485760 | Attachment size limit |
PATCHCORD_ATTACHMENT_URL_EXPIRY_SECONDS | 86400 | Signed URL lifetime |
PATCHCORD_ATTACHMENT_ALLOWED_MIME_TYPES | text/*,... | Allowed attachment MIME types |
PATCHCORD_RATE_LIMIT_PER_MINUTE | 100 | Per-token request limit |
PATCHCORD_ANON_RATE_LIMIT_PER_MINUTE | 20 | Per-IP request limit for unauthenticated requests |
PATCHCORD_RATE_BAN_SECONDS | 60 | Persisted ban duration |
PATCHCORD_CLEANUP_MAX_AGE_DAYS | 7 | Message retention |
PATCHCORD_CIRCUIT_BREAKER_SECONDS | 300 | Circuit breaker timeout for database subsystem recovery |
PATCHCORD_CLEANUP_INTERVAL_HOURS | 6 | Cleanup schedule |
Breaking change: env token mappings removed
This release no longer reads bearer tokens from:
PATCHCORD_TOKENSPATCHCORD_TOKEN_FILETOKEN_*
All bearer tokens now live in the database (bearer_tokens table). If you are
upgrading an existing deployment, import each live token with
manage_tokens before deploying this version.
Docker Compose
The included docker-compose.yml runs with:
read_only: true- dropped capabilities
- memory and PID limits
- a health check
Port conflicts
If port 8000 is taken:
envPATCHCORD_PORT=8100Then deploy with:
bashPATCHCORD_PORT=8100 docker compose up -d --buildCompose host-port interpolation comes from the shell or repo-root .env, not
from .env.server alone.
Stateless HTTP
PATCHCORD_STATELESS_HTTP=true is the recommended setting. Stateful
Streamable HTTP keeps MCP session IDs in process memory; clients can receive
Session not found after a server restart when they reuse an old
mcp-session-id. Stateless mode removes that failure mode.
Running without Docker
If you prefer to run the server directly:
bashgit clone https://github.com/ppravdin/patchcord.git
cd patchcord
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
# Create bearer tokens
.venv/bin/python -m patchcord.cli.manage_tokens add --namespace myproject frontend
# Run
PATCHCORD_PUBLIC_URL=https://patchcord.yourdomain.com \
.venv/bin/python -m patchcord.server.appThe server listens on 0.0.0.0:8000 by default. Put a reverse proxy (nginx,
Caddy, or Cloudflare Tunnel) in front for HTTPS. All environment variables
from the configuration table apply the same way.
HTTPS
- Point the DNS record at your server.
- Terminate TLS at your reverse proxy or edge provider.
- Set
PATCHCORD_PUBLIC_URLto the HTTPS URL.
Updating
If you are upgrading from env-backed bearer tokens, complete the token mapping migration first.
bashgit pull
docker compose up -d --buildClient endpoints
- Default MCP endpoint:
https://patchcord.yourdomain.com/mcp - Bearer-only endpoint:
https://patchcord.yourdomain.com/mcp/bearer
Bearer-token clients can still use /mcp; /mcp/bearer is the dedicated
bearer-only path.
OAuth deployment stance
Patchcord supports:
- explicit client mappings with
PATCHCORD_OAUTH_CLIENTS - known-client detection, extended with
PATCHCORD_KNOWN_OAUTH_CLIENTS - derived fallback from
client_namefor otherwise-unknown clients
For internet-exposed deployments, prefer explicit mappings for sensitive identities and treat known-client detection as convenience, not your only trust boundary.
Attachments
attachment(upload=true, ...)creates the configured bucket on first use if needed- Files are stored under a namespace, agent, timestamp, and filename
attachment(path_or_url=...)only accepts signed URLs for the configured host and bucket
The backend-service documentation owns database and object-storage configuration, including bucket credentials and lifecycle policy.
Plugin Hook Endpoint
Claude plugin hooks use:
bashcurl -H "Authorization: Bearer <token>" \
"https://patchcord.yourdomain.com/api/inbox?status=pending&limit=1"Supported statuses: pending, read, deferred.
Claude Code Plugin
Install with:
bashnpx patchcord@latestPair the plugin with project-local .mcp.json. Do not rely on global
PATCHCORD_* shell exports. See Plugin docs for details.
Expected behavior:
- Patchcord project: statusline and hook active
- Unrelated project: plugin no-ops
Common failure modes
Wrong identity
Usually caused by:
- wrong token
- wrong namespace casing
- ancestor
.mcp.json - stale session after config change
- stale user-scope MCP registration
Failed to connect
Check:
curl https://patchcord.yourdomain.com/health- bearer token in project config
- correct public URL
- fresh client session after config changes
Monitoring
bashcurl https://patchcord.yourdomain.com/health
docker ps --filter name=patchcord-server
docker logs patchcord-server --tail 100
docker logs patchcord-server 2>&1 | grep "OAuth client registered"Troubleshooting
| Problem | Fix |
|---|---|
| Port conflict | Set PATCHCORD_PORT in .env.server and shell env or repo-root .env |
| OAuth metadata uses HTTP instead of HTTPS | Set PATCHCORD_PUBLIC_URL to your HTTPS URL |
Session not found errors | Use PATCHCORD_STATELESS_HTTP=true (default) |
| Clients lose connection after restart | Expected with stateful mode; use stateless |
Stop hook shows as error | Cosmetic; matches Claude Code issue #12667 |
mcp package import fails | Python 3.10+ required. Check python3 --version. |
| Web clients don't see new tools after deploy | ChatGPT and claude.ai cache tool schemas per session. Start a new chat. |