Skip to main content
Glama
README.md
# devenv-mcp

[![CI](https://github.com/TyllerDaniel/devenv-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/TyllerDaniel/devenv-mcp/actions/workflows/ci.yml)

A [Model Context Protocol](https://modelcontextprotocol.io) server that gives an
AI agent a safe place to do software-engineering work. The agent can browse
tasks, open a sandboxed copy of a broken repository, run commands in it, and
diagnose CI failures, all through typed MCP tools.

It is the agent-facing half of
[rl-env-benchmark](https://github.com/TyllerDaniel/rl-env-benchmark) and reads
the same task format, and it calls
[ci-doctor](https://github.com/TyllerDaniel/ci-doctor) for CI log diagnosis.

## Tools

| Tool | What it does |
| --- | --- |
| `list_tasks` | List tasks with id, title, category and difficulty |
| `get_task` | Full prompt for one task |
| `start_session` | Create a fresh sandbox holding the task's starting repo; returns `session_id` |
| `run_in_sandbox` | Run a shell command in the session's repo root; returns exit code and output |
| `end_session` | Destroy the sandbox |
| `diagnose_ci` | Classify failures in a CI log or JUnit XML and suggest a next step |
| `run_grader` | Run the hidden grader. **Off by default**, see below |

Every tool declares a zod input schema, returns `structuredContent` alongside
readable text, and reports failures in-band with `isError`, so the model can
read what went wrong and recover instead of the call failing outright.

## Design decisions

* **Isolation by default.** Sessions run in Docker with `--network none`, 1 CPU,
  1 GB of memory and a PID limit. A `local` mode exists for task authoring, but
  it runs commands on the host and is clearly labelled as not isolated.
* **Grading is opt-in.** If an agent under evaluation can call the grader, it
  can iterate against hidden tests until they pass, which measures persistence
  rather than skill. `run_grader` is only registered when
  `DEVENV_ALLOW_GRADING=1`, for task authors and human reviewers.
* **The grader never enters the workspace early.** It is copied in only when
  grading runs, and a test asserts the agent cannot find it beforehand.
* **Bounded output.** Command output is collected in a head-plus-tail buffer
  (the end of a log is usually where the error is), so a runaway process cannot
  exhaust memory or flood the model's context.
* **Real timeouts.** Commands start in their own process group and the whole
  group is killed on timeout. Killing only `sh` would leave grandchildren such
  as `pytest` holding the pipes open, and the call would hang.
* **Capped, cleaned-up sessions.** A session limit prevents runaway container
  creation, and all sandboxes are destroyed on SIGINT, SIGTERM or client
  disconnect.
* **stdout is the protocol.** All logging goes to stderr.

## Install

```bash
git clone https://github.com/TyllerDaniel/devenv-mcp
cd devenv-mcp && npm install && npm run build
```

## Use with Claude Desktop or Claude Code

```json
{
  "mcpServers": {
    "devenv": {
      "command": "node",
      "args": ["/path/to/devenv-mcp/dist/index.js"],
      "env": {
        "DEVENV_TASKS_DIR": "/path/to/rl-env-benchmark/tasks",
        "DEVENV_SANDBOX": "docker"
      }
    }
  }
}
```

With Claude Code: `claude mcp add devenv -e DEVENV_TASKS_DIR=/path/to/tasks -- node /path/to/devenv-mcp/dist/index.js`

## Configuration

| Variable | Default | Meaning |
| --- | --- | --- |
| `DEVENV_TASKS_DIR` | `./tasks` | Directory of tasks (`task.toml` + `repo/` + `grader/`) |
| `DEVENV_IMAGES_DIR` | `<tasks>/../images` | Base images, `images/<name>/Dockerfile` |
| `DEVENV_SANDBOX` | `docker` | `docker` or `local` (not isolated) |
| `DEVENV_ALLOW_GRADING` | `0` | `1` registers `run_grader` |
| `DEVENV_MAX_SESSIONS` | `4` | Concurrent sandbox limit |
| `CI_DOCTOR_BIN` | `ci-doctor` | Path to the ci-doctor binary |

Invalid values fail fast at startup with a message naming the variable.

## Example session

From `npm run build` and a client connected over stdio, against the
rl-env-benchmark tasks with grading enabled:

```
> start_session {"task_id": "004-quadratic-dedupe"}
Started session 72721590 for 004-quadratic-dedupe (local sandbox).

> run_in_sandbox {"session_id": "72721590", "command": "python3 -m pytest -q tests | tail -1"}
[exit code 0]
1 passed in 0.01s

> run_grader {"session_id": "72721590"}
FAILED                    <- visible tests pass, but the hidden 300k-event test times out

> run_in_sandbox {..., "command": "sed -i 's/seen = \\[\\]/seen = set()/; s/seen.append/seen.add/' ingest/dedupe.py"}
> run_grader {"session_id": "72721590"}
PASSED
```

## Development

```bash
npm test          # vitest: unit tests plus a real MCP client over an in-memory transport
npm run typecheck
npm run build
```

The server tests connect an actual MCP `Client` to the server, so they cover
schemas, error handling and the full start, run, grade and end loop rather than
calling handlers directly.

## License

MIT

TDQS

A4/5.0

Scored across 6 tools

Disambiguation4/5

The task-browsing pair (list_tasks/get_task) and the sandbox lifecycle trio (start_session/run_in_sandbox/end_session) are clearly delineated by resource and action. diagnose_ci sits in a slightly separate domain (CI log analysis) but its description makes its purpose distinct from run_in_sandbox.

Naming Consistency5/5

All names use snake_case verb_noun form (list_tasks, get_task, start_session, run_in_sandbox, end_session, diagnose_ci). No mixing of conventions or vague bare verbs.

Tool Count5/5

Six tools is a tight, well-scoped set that covers task discovery, sandbox execution lifecycle, and CI diagnosis without redundancy.

Completeness4/5

The core workflow (find task, start sandbox, run commands, end sandbox, diagnose failures) is fully covered. Minor gaps exist: no session status/list tool and no explicit submit/evaluate step to confirm a solution, though the latter may be out of scope.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues