devbox-mcp
by Egoushka
README.md
# devbox-mcp
MCP server that lets an LLM run a project's *real* test suite, and a
SonarQube scan, against projects it can see on disk — without giving the LLM
a raw shell.
Tools:
- `list_projects` — lists directories under `PROJECTS_ROOT`, detected
toolchain (`dotnet` / `npm` / `pytest` / none).
- `run_tests(project)` — runs that project's test suite in a throwaway,
one-shot container: the repo is bind-mounted **read-only**, a tmpfs
provides writable scratch space, and the container is removed after.
`project` must be a name `list_projects` returned — this is the only
guard against a path-traversal or prompt-injected argument escaping
`PROJECTS_ROOT`.
- `sonar_scan(project)` — runs `sonar-scanner-cli` against the project
(requires a `sonar-project.properties` file in it) and your SonarQube
server.
## Why this needs a Docker API proxy, not the raw socket
`run_tests` and `sonar_scan` need to launch containers, which normally means
mounting `/var/run/docker.sock`. **Don't do that.** A mounted Docker socket
is root on the host — anyone who can make this server run an arbitrary
command (a malicious test file, a prompt-injected tool argument) can then
talk to the Docker API directly and do anything root can do.
Put [`tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy)
in front of the real socket instead, and point this server's `DOCKER_HOST`
at the proxy. Scope the proxy to only what's needed:
```yaml
environment:
CONTAINERS: 1 # create/start/wait/logs/remove containers
POST: 1 # without this, the proxy is read-only
IMAGES: 1 # pull the toolchain images
# everything else defaults to 0/off: no EXEC, no NETWORKS, no VOLUMES,
# no access to containers this server didn't create.
```
This is a real narrowing (no `docker exec` into unrelated containers, no
host-network access, no volume mounts beyond what this server's own `docker
run` calls specify), not a sandbox: a compromised instance can still launch
containers on your host. Treat it accordingly — this is not something to
expose beyond a private network.
## Configuration
| Env var | Default | Meaning |
|---|---|---|
| `PROJECTS_ROOT` | `/srv/chargehand/repos` | Directory of project checkouts to operate on |
| `DOCKER_HOST` | (docker default) | Point at your docker-socket-proxy, e.g. `tcp://docker-socket-proxy:2375` |
| `SONAR_HOST_URL` | — | Your SonarQube server, reachable **by address**, not by a Docker Compose service name — the scan runs in its own one-shot container on the default bridge network, not your SonarQube stack's network |
| `SONAR_TOKEN` | — | SonarQube user/analysis token |
| `RUN_TIMEOUT_MS` | `600000` | Kill switch per container run |
| `PORT` | `8000` | HTTP port (Streamable HTTP MCP transport, path `/mcp`) |
## Running
```bash
docker run -p 8000:8000 \
-e PROJECTS_ROOT=/repos \
-e DOCKER_HOST=tcp://docker-socket-proxy:2375 \
-e SONAR_HOST_URL=http://your-sonarqube:9000 \
-e SONAR_TOKEN=... \
-v /path/to/your/repos:/repos:ro \
ghcr.io/egoushka/devbox-mcp:latest
```
Pin an exact tag/digest in production — `latest` is for trying it out.
## Scope / non-goals (v1)
No arbitrary path execution (only `list_projects`-returned names), no write
access to the mounted repos, no auto-remediation, no CI trigger integration.
Toolchains supported: dotnet, npm, pytest — pull requests welcome for more.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues