86 lines
5.9 KiB
Markdown
86 lines
5.9 KiB
Markdown
# Secure MCP stack
|
|
|
|
The Docker Compose deployment of the MCP servers: the compose files, the gateway's Caddyfiles, the
|
|
egress proxy's configuration and the example `.env`. The servers themselves live in the `mcps`
|
|
repository, which builds their images and publishes them to Docker Hub; this repository only pulls
|
|
them, so a server running it needs neither the sources nor a build toolchain.
|
|
|
|
| Image | What it is |
|
|
|---|---|
|
|
| `luciolelii/coding-agent-mcp` | Scoped workspace editing. Task execution is disabled in this stack. |
|
|
| `luciolelii/dev-server-mcp` | Authenticated facade for a private, allowlisted development-server worker. One instance per execution, each in a throwaway copy of that execution's tree and on its own port. The same image runs the facade and the worker. |
|
|
| `luciolelii/browser-mcp` | Playwright automation restricted to exact allowed origins. |
|
|
| `luciolelii/minio-mcp` | Bounded list/read/stat/write access to object storage, with no delete tool. In internal mode (catalog entry `minio-mcp-internal`, header `x-minio-scope`) it uses the platform's MinIO whose endpoint and keys the catalog entry sends as headers, and one bucket per execution, `<MINIO_MCP_INTERNAL_BUCKET_PREFIX><execution id>`, created when the session opens; in external mode the model opens session-scoped connections to MinIO deployments itself. |
|
|
|
|
`egress-proxy` is the development worker's only route to package registries: it allows their CONNECT
|
|
requests and refuses everything else.
|
|
|
|
The MCP client orchestrates the servers; they do not share MCP sessions or credentials.
|
|
|
|
## Start
|
|
|
|
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, owned by `10001:10001`
|
|
(`sudo chown -R 10001:10001 <dir>`): that is the user the images run as.
|
|
3. Copy and edit `dev-server-mcp/services.example.json`, and point `DEV_SERVER_SERVICES_CONFIG` at the copy. 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.
|
|
4. Optionally set `MINIO_MCP_ALLOWED_ENDPOINTS` to the comma-separated MinIO S3 origins clients may use.
|
|
5. If the Docker Hub repositories are private, run `docker login` on this host first.
|
|
6. Pull and run:
|
|
|
|
```sh
|
|
docker compose --env-file .env -f mcp-stack.compose.yml pull
|
|
docker compose --env-file .env -f mcp-stack.compose.yml up -d
|
|
```
|
|
|
|
On a dedicated VM add the overlay to **both** commands, or the gateway loses ports 80 and 443:
|
|
`-f mcp-stack.compose.yml -f mcp-stack.vm.compose.yml`.
|
|
|
|
## Updating
|
|
|
|
A deploy is a new image tag. Images are published from `mcps` and tagged with its commit hash and
|
|
`latest`. Set `MCP_IMAGE_TAG` in `.env` (or edit the default in `mcp-stack.compose.yml`) to the new
|
|
hash, then `pull` and `up -d` as above. To restart only some services without touching the others,
|
|
name them and add `--no-deps`: `up -d --no-deps minio-mcp mcp-gateway`.
|
|
|
|
Local MCP endpoints:
|
|
|
|
- `http://127.0.0.1:3101/mcp` — coding agent
|
|
- `http://127.0.0.1:3102/mcp` — development server
|
|
- `http://127.0.0.1:3103/mcp` — browser
|
|
- `http://127.0.0.1:3104/mcp` — MinIO
|
|
|
|
The same endpoints are also available through the unified gateway at `/coding-agent/mcp`,
|
|
`/dev-server/mcp`, `/browser/mcp` and `/minio/mcp` on port 3100 (or the configured HTTPS host on
|
|
the VM deployment).
|
|
|
|
Example Codex client configuration (`.codex/config.toml` in a trusted project):
|
|
|
|
```toml
|
|
[mcp_servers.coding_agent]
|
|
url = "http://127.0.0.1:3101/mcp"
|
|
bearer_token_env_var = "CODING_AGENT_MCP_TOKEN"
|
|
|
|
[mcp_servers.dev_server]
|
|
url = "http://127.0.0.1:3102/mcp"
|
|
bearer_token_env_var = "DEV_SERVER_MCP_TOKEN"
|
|
|
|
[mcp_servers.browser]
|
|
url = "http://127.0.0.1:3103/mcp"
|
|
bearer_token_env_var = "BROWSER_MCP_TOKEN"
|
|
|
|
[mcp_servers.minio]
|
|
url = "http://127.0.0.1:3104/mcp"
|
|
bearer_token_env_var = "MINIO_MCP_TOKEN"
|
|
```
|
|
|
|
Set those four variables in the MCP client's environment to the token portions configured for the corresponding servers. Do not reuse a token between servers.
|
|
|
|
Only the minimal Caddy gateway publishes loopback ports. The coding, development and browser services stay exclusively on internal networks. `minio-mcp` has outbound connectivity because each MCP session can open connections to remote S3 endpoints. Set `MINIO_MCP_ALLOWED_ENDPOINTS` whenever those endpoints are known; leaving it empty gives authenticated MCP clients an intentional network-request capability. For remote use, use the included VM overlay so Caddy terminates TLS, and add rate limiting at the network edge when appropriate.
|
|
|
|
The worker and browser networks are marked internal, no service uses host networking or the Docker socket, and the development-server workspace mount is read-only. Container isolation reduces the blast radius but is not a substitute for an ephemeral VM/microVM when executing hostile multi-tenant code.
|
|
|
|
Installing dependencies means fetching and running other people's code, so three things stand between it and the rest of the stack: the tree runs in a copy of itself and never in the workspace mount, the install runs with package scripts disabled, and the only way out of the worker is a proxy that allows the registries and nothing else. Each execution also gets a `HOME` of its own, which is what keeps Maven's `~/.m2` from becoming a channel between executions — npm's shared cache is safe because `npm ci` verifies every package against the lockfile, and Maven has no lockfile to verify against.
|
|
|
|
`coding-agent-mcp` intentionally retains write access to the selected project because editing is its purpose. Point `MCP_WORKSPACE_HOST_PATH` only at a dedicated, backed-up directory on storage with a disk quota; never point it at a home directory, repository collection, or filesystem root.
|