Deployment
Configuration
MCP Ambassador is configured via environment variables. All configuration has sensible defaults for development — production deployments should set secrets and TLS options explicitly.
Environment variables
Core
| Variable | Default | Description |
|---|---|---|
NODE_ENV | production | production or development. The reference compose file sets production: no dev client key is auto-created, so create client keys in the UI, and a config-file load failure aborts startup. For a local trial, set NODE_ENV=development (it enables the dev client key bootstrap). It does not change logging (use MCP_AMBASSADOR_LOG_LEVEL=debug). |
MCP_AMBASSADOR_LOG_LEVEL | info | debug, info, warn, error. The reference compose file passes it from LOG_LEVEL (LOG_LEVEL=debug docker compose up -d). |
MCP_AMBASSADOR_PORT | 8443 | Ambassador Client API port. The admin and user web UI listens on 9443; change the published host port in the compose ports mapping instead. |
PUBLIC_URL | empty in the reference compose | Set PUBLIC_URL to the https URL users reach the web UI on (for example https://localhost:9443 for a local trial). In 0.10.0 the reference compose file passes it empty, which disables approval gates (gated calls fail closed and startup logs an error) and leaves the agent-endpoint issuer empty unless agent_endpoint.issuer_url is set. If set, it must be https, or http only on loopback. |
Secrets
| Variable | Default | Description |
|---|---|---|
ADMIN_SESSION_SECRET | auto-generated | 32-byte hex secret for admin session HMAC. Auto-generated in development, persisted to ./data/. Must be set explicitly in production. |
CREDENTIAL_MASTER_KEY | auto-generated | 32-byte hex key for AES-256-GCM credential vault encryption. Auto-generated in development, persisted to ./data/credential_master_key. Must be set explicitly in production. |
ADMIN_KEY | auto-generated | Admin API key (amb_ak_…). It is never logged; on first boot it is written to ./data/initial-credentials.txt. |
TLS
| Variable | Default | Description |
|---|---|---|
TLS_CERT_PATH | /data/certs/server.crt | Path to TLS certificate |
TLS_KEY_PATH | /data/certs/server.key | Path to TLS private key |
TLS_DISABLED | false | Set to true to disable TLS (for reverse proxy deployments) |
Database
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | SQLite at /data/ambassador.db | SQLite file path or PostgreSQL URL (Pro tier) |
Session
| Variable | Default | Description |
|---|---|---|
SESSION_TTL_SECONDS | 28800 | Client session token TTL (8 hours) |
SESSION_HEARTBEAT_INTERVAL | 120 | Expected heartbeat interval in seconds |
LOGIN_RATE_LIMIT_ATTEMPTS | 5 | Max login attempts per IP per window |
LOGIN_RATE_LIMIT_WINDOW_SECONDS | 300 | Rate limit window (5 minutes) |
Downstream MCP isolation
| Variable | Default | Description |
|---|---|---|
MCP_AMBASSADOR_CHILD_UID_RANGE | 20000-20999 | min-max range of uids handed out to MCP instances (gid = uid). Minimum is 10000, at most 10000 ids, and it must not contain the server's uid or gid. The image creates passwd entries for the default range; the entrypoint warns if your range falls outside it. |
MCP_AMBASSADOR_CHILD_NPROC | 256 | Per-instance process limit (RLIMIT_NPROC of the instance's own uid). It counts threads, so raise it for browser MCPs. 0 disables the limit. Never raised above a stricter hard limit the server already runs under. |
MCP_AMBASSADOR_CHILD_UID | unset | Legacy single-uid mode: all MCPs share one uid. The image sets a default range, so to use this mode empty the range (MCP_AMBASSADOR_CHILD_UID_RANGE=). Setting both is an error and the entrypoint exits. |
Never disable isolation in production
If MCP_AMBASSADOR_CHILD_UID_RANGE is emptied and MCP_AMBASSADOR_CHILD_UID is unset, isolation is off: MCP children run as the server's uid and can read /data (database, credential key, TLS keys). This is for local development only. Never clear the range in production.
If the container lacks the SETUID, SETGID and KILL capabilities (or setpriv/prlimit is missing), MCP children refuse to start and the server logs a clear message. See Docker for the required compose settings.
Data paths
Inside the container, all persistent data lives at /data/ (mounted as a Docker volume or bind mount, owned by uid 1000). Downstream MCP processes cannot read it.
/data/
ambassador.db # SQLite database (WAL mode)
certs/
server.crt # TLS certificate (auto-generated or provided)
server.key # TLS private key
ca.crt # CA certificate (returned to Ambassador Clients)
credential_master_key # 32-byte hex master key (permissions 0600)
child-uids.json # uid assigned to each MCP instance (permissions 0600)
Mount these to a persistent volume:
volumes:
ambassador-data:
driver: local
services:
ambassador:
volumes:
- ambassador-data:/data
Generating secrets
# Generate ADMIN_SESSION_SECRET
openssl rand -hex 32
# Generate CREDENTIAL_MASTER_KEY
openssl rand -hex 32
Full docker-compose.yml example
services:
ambassador:
image: mcpambassador-server:latest
ports:
- '8443:8443'
- '9443:9443'
volumes:
- ambassador-data:/data
environment:
- NODE_ENV=production
- ADMIN_SESSION_SECRET=your_64_char_hex_session_secret
- CREDENTIAL_MASTER_KEY=your_64_char_hex_master_key
- LOG_LEVEL=info
restart: unless-stopped
healthcheck:
test: ['CMD', 'curl', '-fk', 'https://localhost:8443/health']
interval: 30s
timeout: 10s
retries: 3
volumes:
ambassador-data:
Log format
MCP Ambassador emits structured JSON logs to stdout:
{
"level": "info",
"timestamp": "2026-02-19T14:32:01.123Z",
"correlation_id": "corr_uuid",
"message": "Tool invocation completed",
"user_id": "user_uuid",
"tool": "github.search_code",
"duration_ms": 234
}
Set LOG_LEVEL=debug to see verbose output including request details and MCP spawn events.
Timing and performance
MCP Ambassador's internal timers are tuned for production workloads with hundreds of concurrent sessions. You can adjust these in the YAML config file (ambassador-server.yaml) or via environment variables.
Server-side timing
| Setting | Default | Config Path | Description |
|---|---|---|---|
| Session TTL | 28800s (8h) | session.ttl_seconds | How long a client session token is valid before the client must re-authenticate |
| Evaluation interval | 120s | session.evaluation_interval_seconds | How often the server evaluates sessions for idle timeout |
| Sweep interval | 1800s (30m) | session.sweep_interval_seconds | How often the server purges expired sessions from memory |
| Heartbeat expected | 120s | session.heartbeat_expected_interval_seconds | How often the server expects a heartbeat from each client |
| Heartbeat rate limit | 1 per 10s | Hardcoded | Maximum heartbeat frequency per client (prevents misconfigured clients from flooding) |
| Health check interval | 120000ms | mcp.health_check_interval_ms | How often the server pings downstream MCPs to verify they are alive |
Admin SPA polling
The web dashboard polls the server for live status updates. These intervals balance responsiveness with server load.
| Endpoint | Interval | What it shows |
|---|---|---|
| Health status | 60s | MCP instance health indicators |
| MCP list | 60s | Admin MCP catalog |
| Catalog | 60s | User marketplace |
| Logs | 30s | MCP instance log stream |
Client-side timing
The Ambassador Client (@mcpambassador/client) has its own performance settings. See the client documentation for details.
| Setting | Default | Description |
|---|---|---|
| Heartbeat interval | 120s | How often the client sends a heartbeat to keep the session alive |
| Cache TTL | 300s | How long the client caches the tool catalog before refreshing |
| Cache enabled | true | Whether tool catalog caching is active (recommended for production) |
Deployment profiles
Choose the profile that matches your security and scale requirements.
| Profile | Session TTL | Heartbeat | Eval interval | Best for |
|---|---|---|---|---|
| Default | 8h | 120s | 120s | Most production deployments |
| High-security | 4h | 60s | 60s | Regulated environments, SOC2 compliance |
| Development | 24h | 30s | 30s | Local development and testing |
| High-scale | 8h | 180s | 180s | 1000+ concurrent sessions, minimize server load |