dev-mcps/MCP-STACK.md

51 lines
3.5 KiB
Markdown

# Secure MCP stack
This workspace contains three 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 only container with a route to the internet. It allows CONNECT to the package registries and refuses everything else, which is how the worker installs dependencies without being on the network itself.
- `browser-mcp`: Playwright automation restricted to exact allowed origins.
The MCP client orchestrates them; servers 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.
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.
4. Run `docker compose --env-file .env -f mcp-stack.compose.yml up --build`.
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
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"
```
Set those three 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 MCP and worker containers stay exclusively on internal networks and therefore have no general Internet egress. For remote use, replace or front this local gateway with a TLS reverse proxy and rate limiting.
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.