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/health

Database 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

VariableDescription
PATCHCORD_PUBLIC_URLPublic-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

VariableDefaultDescription
PATCHCORD_PORT8000Server listen port
PATCHCORD_HOST0.0.0.0Server bind address
PATCHCORD_PUBLIC_URLhttp://localhost:{port}Public URL for OAuth discovery
PATCHCORD_MCP_PATH/mcpMain MCP endpoint
PATCHCORD_BEARER_PATH/mcp/bearerBearer-only MCP endpoint
PATCHCORD_STATELESS_HTTPtrueRemove in-memory session coupling
PATCHCORD_NAMEpatchcordService name in health output
PATCHCORD_DEFAULT_NAMESPACEdefaultDefault namespace for bearer-token agents without an explicit namespace
PATCHCORD_ACTIVE_WINDOW_SECONDS3600Presence activity window
PATCHCORD_PRESENCE_WRITE_INTERVAL_SECONDS10Presence write throttle
PATCHCORD_OAUTH_DEFAULT_NAMESPACEdefaultDefault 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_SECONDS86400OAuth access-token lifetime
PATCHCORD_OAUTH_REFRESH_TOKEN_TTL_SECONDS31536000OAuth refresh-token lifetime
PATCHCORD_ATTACHMENTS_BUCKETattachmentsStorage bucket for attachments
PATCHCORD_ATTACHMENT_MAX_BYTES10485760Attachment size limit
PATCHCORD_ATTACHMENT_URL_EXPIRY_SECONDS86400Signed URL lifetime
PATCHCORD_ATTACHMENT_ALLOWED_MIME_TYPEStext/*,...Allowed attachment MIME types
PATCHCORD_RATE_LIMIT_PER_MINUTE100Per-token request limit
PATCHCORD_ANON_RATE_LIMIT_PER_MINUTE20Per-IP request limit for unauthenticated requests
PATCHCORD_RATE_BAN_SECONDS60Persisted ban duration
PATCHCORD_CLEANUP_MAX_AGE_DAYS7Message retention
PATCHCORD_CIRCUIT_BREAKER_SECONDS300Circuit breaker timeout for database subsystem recovery
PATCHCORD_CLEANUP_INTERVAL_HOURS6Cleanup schedule

Breaking change: env token mappings removed

This release no longer reads bearer tokens from:

  • PATCHCORD_TOKENS
  • PATCHCORD_TOKEN_FILE
  • TOKEN_*

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=8100

Then deploy with:

bashPATCHCORD_PORT=8100 docker compose up -d --build

Compose 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.app

The 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

  1. Point the DNS record at your server.
  2. Terminate TLS at your reverse proxy or edge provider.
  3. Set PATCHCORD_PUBLIC_URL to 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 --build

Client 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_name for 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@latest

Pair 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

ProblemFix
Port conflictSet PATCHCORD_PORT in .env.server and shell env or repo-root .env
OAuth metadata uses HTTP instead of HTTPSSet PATCHCORD_PUBLIC_URL to your HTTPS URL
Session not found errorsUse PATCHCORD_STATELESS_HTTP=true (default)
Clients lose connection after restartExpected with stateful mode; use stateless
Stop hook shows as errorCosmetic; matches Claude Code issue #12667
mcp package import failsPython 3.10+ required. Check python3 --version.
Web clients don't see new tools after deployChatGPT and claude.ai cache tool schemas per session. Start a new chat.