mcps-docker-compose/ENVIRONMENT.md

239 lines
14 KiB
Markdown

# Environment variables
Everything an operator can set for the MCP stack, in three parts:
1. [The stack's `.env`](#1-the-stacks-env) - what you write in `.env` next to `mcp-stack.compose.yml`.
Split into **required** and **optional**.
2. [What the compose file sets itself](#2-what-the-compose-file-sets-itself) - do not put these in `.env`.
3. [Per-server variables](#3-per-server-variables-running-a-server-by-hand) - only for starting one
image by hand with `docker run`, outside Compose.
`mcp-stack.env.example` is the template: `cp mcp-stack.env.example .env`. `.env` is ignored by git
and holds secrets, so it stays on the machine that runs the stack.
> Compose substitutes an unset variable with an empty string and only warns. A "required" variable
> below is one whose absence makes the service refuse to start or the stack unusable - not one
> Compose itself rejects.
## 1. The stack's `.env`
### Required
| Variable | What it is | Notes |
|---|---|---|
| `CODING_AGENT_MCP_API_KEYS` | Bearer tokens that may call the coding-agent MCP. Comma-separated, optionally `subject=token`. | Each token at least 32 characters. The server refuses to start with none. This is the `key` in the node's catalog entry. |
| `DEV_SERVER_MCP_API_KEYS` | Same, for the dev-server MCP. | Use a different token from the other servers. |
| `BROWSER_MCP_API_KEYS` | Same, for the browser MCP. | |
| `MINIO_MCP_API_KEYS` | Same, for the MinIO MCP. | |
| `DEV_SERVER_WORKER_TOKEN` | Shared secret between `dev-server-mcp` and `dev-server-worker`. | At least 32 characters; both refuse to start otherwise. Never leaves the stack, so it is not a catalog value. |
| `MCP_WORKSPACE_HOST_PATH` | Absolute path of the host directory mounted as `/workspace`. | Bind-mount source, so empty fails. A dedicated project directory - never a home directory or `/`. |
Generate a token with `openssl rand -hex 32`.
### Required in practice
| Variable | Default | Why |
|---|---|---|
| `MCP_UID`, `MCP_GID` | `10001` / `10001` | Keep them. The images run as 10001:10001, and the dev-server's two volumes take their owner from the image, so another value leaves the worker unable to write to them. Give `MCP_WORKSPACE_HOST_PATH` to `10001:10001` instead. This file is read literally, no shell runs over it. |
### Optional
**Images**
| Variable | Default | Meaning |
|---|---|---|
| `MCP_IMAGE_TAG` | the hash named in `mcp-stack.compose.yml` | Which build of the five `luciolelii/*` images to run: a commit hash of the `mcps` repository, or `latest`. Bumping it is a deploy. |
**MinIO MCP**
| Variable | Default | Meaning |
|---|---|---|
| `MINIO_MCP_INTERNAL_BUCKET_PREFIX` | `exec-` | Every internal-mode bucket is `<prefix><execution id>`. Cannot be empty; at most 27 lowercase letters, digits or `-`. It is the boundary that keeps the API key inside buckets this server made. |
| `MINIO_MCP_INTERNAL_BUCKET_EXPIRE_DAYS` | `7` | Lifecycle rule set on an execution's bucket when this server creates it: objects are deleted that many days after they were written. `0` sets none. The empty bucket stays. Needs permission to set a bucket lifecycle; if refused, the session still opens and a warning is logged. |
| `MINIO_MCP_ALLOWED_ENDPOINTS` | empty (public addresses) | Comma-separated S3 origins, e.g. `https://s3.example.org`. In external mode the endpoint comes from the node's configuration (`x-minio-endpoint`); empty, any **public** address is reachable and private, loopback and metadata ones are refused at every connection; set, exactly the listed origins, private ones included. It applies to both modes, so when set the internal MinIO's endpoint must be on it. |
| `MINIO_MCP_DEFAULT_REGION` | `us-east-1` | Region when a session names none. |
| `MINIO_MCP_MAX_OBJECT_BYTES` | `1048576` (1 MiB) | Limit for one read and one write. Ceiling 32 MiB. |
| `MINIO_MCP_MAX_CONNECTIONS_PER_SESSION` | `8` | External mode only. Ceiling 64. |
The internal MinIO's endpoint and keys are **not** variables here: they are headers of the
`minio-mcp-internal` entry in the workflow manager's catalog (`x-minio-endpoint`,
`x-minio-access-key`, `x-minio-secret-key`, optionally `x-minio-region`).
**PostgreSQL MCP**
| Variable | Default | Meaning |
|---|---|---|
| `POSTGRES_MCP_API_KEYS` | none, **required** | Bearer tokens, as for the other servers; at least 32 characters each. |
| `POSTGRES_MCP_INTERNAL_DATABASE_PREFIX` | `exec_` | Every internal-mode database, and its user, is `<prefix><execution id>` with the id's dashes as underscores. Cannot be empty; at most 26 lowercase letters, digits or `_`, starting with a letter. It is the boundary that keeps the API key inside databases this server made. |
| `POSTGRES_MCP_ALLOWED_HOSTS` | empty (public addresses) | Comma-separated host names this server may connect to. In external mode the host and port come from the node's configuration (`x-postgres-host`, `x-postgres-port`); empty, any **public** address is reachable and private, loopback and metadata ones are refused; set, exactly the listed hosts, private ones included. For the internal mode it is optional; when set, the platform's PostgreSQL host has to be on it. |
| `POSTGRES_MCP_MAX_ROWS` | `1000` | Most rows one query returns, whatever the model asks. Ceiling 10000. |
| `POSTGRES_MCP_MAX_RESULT_BYTES` | `1048576` (1 MiB) | Most bytes of rows one statement returns; the rest is cut and the answer says so. Ceiling 16 MiB. |
| `POSTGRES_MCP_STATEMENT_TIMEOUT_MS` | `30000` | A statement running longer is stopped by the server. Ceiling 10 minutes. |
| `POSTGRES_MCP_MAX_CONNECTIONS_PER_SESSION` | `4` | External mode only. Ceiling 32. |
The internal PostgreSQL's host and credentials are **not** variables here: they are headers of the
`postgres-mcp-internal` entry in the workflow manager's catalog (`x-postgres-host`, `x-postgres-port`,
`x-postgres-database`, `x-postgres-user`, `x-postgres-password`, `x-postgres-sslmode`, and
`x-postgres-role-secret`). The user has to be able to make databases and users - `CREATEDB` and
`CREATEROLE`, and nothing more - and the secret, at least 32 characters, is what the password of each
execution's own user is derived from. **It has to be the same as the `executionRoleSecret` of the
workflow manager's own `storages.json` entry for that PostgreSQL**, and so does the prefix, or the
storage nodes and the models would not see the same database. The databases are dropped when they are
older than the entry's `executionDatabaseExpireDays` by the workflow manager, not by this server.
**Dev server**
| Variable | Default | Meaning |
|---|---|---|
| `DEV_SERVER_SERVICES_CONFIG` | `./dev-server-mcp/services.example.json` | The services a caller may start. Copy `services.example.json` and edit the copy. Mounted read-only. |
| `DEV_SERVER_ALLOWED_COMMANDS` | `node,npm,python3,./mvnw` | What a service may run. `npx` is left out on purpose: it runs an arbitrary package by name. |
| `DEV_SERVER_PORT_RANGE` | `5200-5219` | Ports given to per-execution instances. Keep aligned with `BROWSER_MCP_ALLOWED_ORIGINS`. |
| `DEV_SERVER_MAX_INSTANCES` | `4` | Concurrent instances. Size by memory: two frontend builds saturate the worker's 2 GB. |
| `DEV_SERVER_MAX_WORKSPACE_BYTES` | `536870912` (512 MiB) | Largest workspace copied for an instance. |
| `DEV_SERVER_IDLE_TIMEOUT_SECONDS` | `7200` | Idle time after which an instance is stopped (its copy is kept). |
| `DEV_SERVER_MAX_LIFETIME_SECONDS` | `86400` | Absolute lifetime; then everything, copy included, is discarded. |
| `DEV_SERVER_PREVIEW_BASE_URL` | empty | Public address a person opens a preview at. Empty means no preview proxy and no preview links. |
| `DEV_SERVER_PREVIEW_PORT` | `4500` | Port of the preview proxy inside the worker. |
| `DEV_SERVER_EGRESS_PROXY` | `http://egress-proxy:3128` | The only route out the worker has, for installs. |
| `DEV_SERVER_NO_PROXY` | `localhost,127.0.0.1,dev-server-worker` | Hosts that bypass that proxy. |
**Browser**
| Variable | Default | Meaning |
|---|---|---|
| `BROWSER_MCP_ALLOWED_ORIGINS` | `http://dev-server-worker:5173,http://dev-server-worker:5200-5219` | Origins the browser may open, exact match. Keep the range aligned with `DEV_SERVER_PORT_RANGE`, or a preview looks like a broken app. |
| `BROWSER_MCP_MAX_SESSIONS` | `8` | Concurrent browser sessions. |
**Published ports** (all on loopback, `127.0.0.1`)
| Variable | Default | |
|---|---|---|
| `MCP_GATEWAY_PORT` | `3100` | One port serving every server by path (`/coding-agent/`, `/dev-server/`, `/browser/`, `/minio/`). What the catalog expects. |
| `CODING_AGENT_MCP_PORT` | `3101` | Per-server ports, kept for clients configured before the gateway. |
| `DEV_SERVER_MCP_PORT` | `3102` | |
| `BROWSER_MCP_PORT` | `3103` | |
| `MINIO_MCP_PORT` | `3104` | |
**Dedicated VM** (with the overlay `mcp-stack.vm.compose.yml`)
| Variable | Default | Meaning |
|---|---|---|
| `MCP_GATEWAY_CADDYFILE` | `./mcp-stack.Caddyfile` | Set to `./mcp-stack.vm.Caddyfile` on the VM: it serves HTTPS. |
| `MCP_SITE_ADDRESS` | `:3100` | The public host name Caddy gets a certificate for, e.g. `mcp-stack.example.org`. Required with the VM Caddyfile. |
| `MCP_TLS_CONTACT` | empty | Contact e-mail for the certificate authority. Set it on the VM. |
| `MCP_BIND_ADDRESS` | `0.0.0.0` | Address ports 80 and 443 bind to. |
## 2. What the compose file sets itself
Fixed in `mcp-stack.compose.yml`. They are not read from `.env`, and overriding them there has no
effect.
| Variable | Set on | Value |
|---|---|---|
| `CODING_AGENT_MCP_EXECUTION_BACKEND` | coding-agent-mcp | `disabled`, so no command-running tool is offered. |
| `DEV_SERVER_WORKER_URL` | dev-server-mcp | `http://dev-server-worker:4000` |
| `DEV_SERVER_CONFIG` | dev-server-worker | `/config/services.json` |
| `DEV_SERVER_WORKSPACE_ROOT` | dev-server-worker | `/workspace` |
| `DEV_SERVER_INSTANCES_ROOT`, `DEV_SERVER_INSTANCE_HOME`, `DEV_SERVER_NPM_CACHE` | dev-server-worker | volumes `/instances`, `/instances/.shared-home`, `/npm-cache` |
Container paths, user ids of the browser (`1000:1000`), memory and CPU limits are fixed in the file
too.
## 3. Per-server variables (running an image by hand)
Only for `docker run` of one image, outside Compose. The stack does not forward these, so they
cannot be set through `.env`. Defaults shown are the ones in the code.
### coding-agent-mcp
| Variable | Default | |
|---|---|---|
| `CODING_AGENT_MCP_API_KEYS` | none, **required** over HTTP | Also read from `MCP_API_KEYS` when unset. |
| `CODING_AGENT_MCP_ROOTS` | the working directory | Workspace root(s), when no `--root` flag is given. |
| `CODING_AGENT_MCP_EXECUTION_BACKEND` | `local` on stdio, `disabled` on HTTP | Whether command execution is offered. |
| `CODING_AGENT_MCP_CORS_ORIGINS` | empty | Comma-separated browser origins allowed. |
| `CODING_AGENT_MCP_AUDIT_LOG` | none | Path of an audit log file. |
| `MCP_DEBUG_REQUESTS` | off | `1`/`true`/`yes`/`on` logs requests. Leave it off in production. |
### dev-server-mcp
| Variable | Default | |
|---|---|---|
| `DEV_SERVER_MCP_API_KEYS` | none, **required** | |
| `DEV_SERVER_WORKER_TOKEN` | none, **required** | At least 32 characters. |
| `DEV_SERVER_WORKER_URL` | `http://dev-server-worker:4000` | |
| `DEV_SERVER_MCP_HOST` / `_PORT` | `0.0.0.0` / `3000` | |
| `DEV_SERVER_MCP_MAX_SESSIONS` | `64` | |
| `DEV_SERVER_MCP_SESSION_TTL_SECONDS` | `1800` | |
| `DEV_SERVER_MCP_CORS_ORIGINS` | empty | |
### dev-server-worker
| Variable | Default | |
|---|---|---|
| `DEV_SERVER_WORKER_TOKEN` | none, **required** | At least 32 characters. |
| `DEV_SERVER_CONFIG` | `/config/services.json` | The services file. |
| `DEV_SERVER_WORKSPACE_ROOT` | `/workspace` | |
| `DEV_SERVER_ALLOWED_COMMANDS` | none, **required** | The worker refuses to start with it empty, and every service's `command` must be on it. |
| `DEV_SERVER_INSTANCES_ROOT` | none | Where per-execution copies live. |
| `DEV_SERVER_INSTANCE_HOME` | `/tmp/dev-server` | |
| `DEV_SERVER_NPM_CACHE` | none | |
| `DEV_SERVER_NPM_REGISTRY` | none | Registry override. |
| `DEV_SERVER_PORT_RANGE` | `5200-5219` | Ports for instances: a rising range above 1023. |
| `DEV_SERVER_MAX_INSTANCES` | `4` | |
| `DEV_SERVER_MAX_WORKSPACE_BYTES` / `_ENTRIES` | 512 MiB / `20000` | |
| `DEV_SERVER_IDLE_TIMEOUT_SECONDS` / `_MAX_LIFETIME_SECONDS` | `7200` / `86400` | |
| `DEV_SERVER_EGRESS_PROXY` / `_NO_PROXY` | none | Proxy for installs. |
| `DEV_SERVER_PREVIEW_BASE_URL` | none | |
| `DEV_SERVER_PREVIEW_HOST` / `_PORT` | `0.0.0.0` / `4500` | |
| `DEV_SERVER_CHILD_PATH` | `/opt/java/openjdk/bin:/usr/local/bin:/usr/bin:/bin` | `PATH` of started services. |
| `JAVA_HOME` | none | Passed to started services. |
| `DEV_SERVER_WORKER_HOST` / `_PORT` | `0.0.0.0` / `4000` | |
| `DEV_SERVER_WORKER_REQUEST_TIMEOUT_MS` | `620000` | |
### browser-mcp
| Variable | Default | |
|---|---|---|
| `BROWSER_MCP_API_KEYS` | none, **required** | |
| `BROWSER_MCP_ALLOWED_ORIGINS` | none, **required** | The server refuses to start with it empty. Exact origins, plus ranges such as `http://host:5200-5219`. |
| `BROWSER_MCP_MAX_SESSIONS` | `16` | |
| `BROWSER_MCP_DEFAULT_TIMEOUT_MS` | `15000` | |
| `BROWSER_MCP_HOST` / `_PORT` | `0.0.0.0` / `3000` | |
| `BROWSER_MCP_SESSION_TTL_SECONDS` | `1800` | |
| `BROWSER_MCP_CORS_ORIGINS` | empty | |
### minio-mcp
| Variable | Default | |
|---|---|---|
| `MINIO_MCP_API_KEYS` | none, **required** | |
| `MINIO_MCP_INTERNAL_BUCKET_PREFIX` | `exec-` | See above. |
| `MINIO_MCP_INTERNAL_BUCKET_EXPIRE_DAYS` | `7` | See above. |
| `MINIO_MCP_ALLOWED_ENDPOINTS` | empty (public addresses) | |
| `MINIO_MCP_DEFAULT_REGION` | `us-east-1` | |
| `MINIO_MCP_MAX_OBJECT_BYTES` | `1048576` | Ceiling 32 MiB. |
| `MINIO_MCP_MAX_CONNECTIONS_PER_SESSION` | `8` | Ceiling 64. |
| `MINIO_MCP_MAX_SESSIONS` | `32` | |
| `MINIO_MCP_SESSION_TTL_SECONDS` | `1800` | |
| `MINIO_MCP_REQUEST_MAX_BYTES` | derived from `MAX_OBJECT_BYTES` | HTTP body limit; base64 adds a third. |
| `MINIO_MCP_HOST` / `_PORT` | `0.0.0.0` / `3000` | |
| `MINIO_MCP_CORS_ORIGINS` | empty | |
### PostgreSQL MCP
| Variable | Default | |
|---|---|---|
| `POSTGRES_MCP_API_KEYS` | none, **required** | |
| `POSTGRES_MCP_INTERNAL_DATABASE_PREFIX` | `exec_` | See above. |
| `POSTGRES_MCP_ALLOWED_HOSTS` | empty (public addresses) | See above. |
| `POSTGRES_MCP_MAX_ROWS` | `1000` | Ceiling 10000. |
| `POSTGRES_MCP_MAX_RESULT_BYTES` | `1048576` | Ceiling 16 MiB. |
| `POSTGRES_MCP_STATEMENT_TIMEOUT_MS` | `30000` | |
| `POSTGRES_MCP_MAX_CONNECTIONS_PER_SESSION` | `4` | Ceiling 32. |
| `POSTGRES_MCP_MAX_SESSIONS` | `16` | |
| `POSTGRES_MCP_SESSION_TTL_SECONDS` | `1800` | |
| `POSTGRES_MCP_REQUEST_MAX_BYTES` | `1048576` | HTTP body limit. |
| `POSTGRES_MCP_HOST` / `_PORT` | `0.0.0.0` / `3000` | |
| `POSTGRES_MCP_CORS_ORIGINS` | empty | |