From 921d54cb06b5d4961e7b6d89b02e91873e638f90 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Fri, 2 Oct 2026 09:41:04 +0200 Subject: [PATCH] Document every environment variable of the MCP stack One place for what an operator can set, split the way an operator needs it: the stack's .env as required, required-in-practice and optional; what the compose file fixes itself and .env cannot override; and the per-server variables for starting a server by hand, which the stack does not forward. "Required" here means the service refuses to start or the stack is unusable without it - Compose itself only substitutes an empty string and warns. That was checked against each server's startup code, not assumed: the API keys, the worker token, the dev-server command allowlist and the browser's allowed origins all fail at start when empty. Co-Authored-By: Claude Sonnet 5.5 --- ENVIRONMENT.md | 191 +++++++++++++++++++++++++++++++++++++++++++++++++ MCP-STACK.md | 2 +- 2 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 ENVIRONMENT.md diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md new file mode 100644 index 0000000..3f3ca29 --- /dev/null +++ b/ENVIRONMENT.md @@ -0,0 +1,191 @@ +# 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 + server 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` | Must be the numeric owner of `MCP_WORKSPACE_HOST_PATH`, or the containers cannot write to it. Write what `id -u` and `id -g` print: this file is read literally, no shell runs over it. | + +### Optional + +**MinIO MCP** + +| Variable | Default | Meaning | +|---|---|---| +| `MINIO_MCP_INTERNAL_BUCKET_PREFIX` | `exec-` | Every internal-mode bucket is ``. 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_ALLOWED_ENDPOINTS` | empty (any) | Comma-separated S3 origins, e.g. `https://s3.example.org`. **Set it on an exposed server.** It applies to both modes, so 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`). + +**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 a server by hand) + +Only for `node src/index.js` and similar, 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_ALLOWED_ENDPOINTS` | empty (any) | | +| `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 | | diff --git a/MCP-STACK.md b/MCP-STACK.md index cef35ad..145b8e0 100644 --- a/MCP-STACK.md +++ b/MCP-STACK.md @@ -12,7 +12,7 @@ The MCP client orchestrates them; servers do not share MCP sessions or credentia ## Start -1. Copy `mcp-stack.env.example` to `.env` and replace every token with an independent random value. +1. Copy `mcp-stack.env.example` to `.env` and replace every token with an independent random value. Every variable, required or optional, is documented in [ENVIRONMENT.md](ENVIRONMENT.md). 2. Set `MCP_WORKSPACE_HOST_PATH` to one dedicated project directory. 3. Copy and edit `dev-server-mcp/services.example.json`. It ships four worked service definitions: a shared Vite tree, and per-execution ones for Node, Java (through the project's own `./mvnw`) and Python. Keep `BROWSER_MCP_ALLOWED_ORIGINS` aligned with `DEV_SERVER_PORT_RANGE`: the browser reaches a preview only on a port that range covers.