4.6 KiB
Secure MCP stack
This workspace contains four independent Streamable HTTP MCP servers:
coding-agent-mcp: scoped workspace editing. Task execution is disabled in this stack.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.egress-proxy: the development worker’s only route to package registries; it allows their CONNECT requests and refuses everything else.browser-mcp: Playwright automation restricted to exact allowed origins.minio-mcp: bounded list/read/stat/write access to object storage, with no delete tool. In internal mode (catalog entryminio-mcp-internal, headerx-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.
The MCP client orchestrates them; servers do not share MCP sessions or credentials.
Start
- Copy
mcp-stack.env.exampleto.envand replace every token with an independent random value. - Set
MCP_WORKSPACE_HOST_PATHto one dedicated project directory. - 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. KeepBROWSER_MCP_ALLOWED_ORIGINSaligned withDEV_SERVER_PORT_RANGE: the browser reaches a preview only on a port that range covers. - Optionally set
MINIO_MCP_ALLOWED_ENDPOINTSto the comma-separated MinIO S3 origins clients may use. - Run
docker compose --env-file .env -f mcp-stack.compose.yml up --build.
Local MCP endpoints:
http://127.0.0.1:3101/mcp— coding agenthttp://127.0.0.1:3102/mcp— development serverhttp://127.0.0.1:3103/mcp— browserhttp://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):
[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.