skip to content
Jobs

An MCP Server for Watching and Steering a Gantry Run

Expose one build's status, artifacts, and controls to an agent — confined to that run

Gantry milestones
Expose one build's status, artifacts, and controls to an agent — confined to that run

Coding agents like Claude can call external tools through a protocol called MCP. Gantry builds software autonomously, but until this job an agent had no structured way to look inside a build — to read its progress ledger, its plan, its logs, or the git state of the working tree it runs in — let alone to act on it. This job built an MCP server that presents a single Gantry run to an agent as a set of tools: ask for the run's status, read its artifacts, inspect its worktree, run a shell command inside the run's sandbox, and answer a question the build is waiting on. Every tool is confined to the one run the server is scoped to, so an agent inspecting a build cannot read another build's files. The command tool reuses the same containment Gantry already wraps around the coding agents it launches, so letting an agent run a shell command carries the same bounded blast radius as an ordinary build step.

Build

The build split reading a run from acting on one, and made confinement a foundation proven alone before any tool stood on it — a decomposition that never went red.

The work divided along the line between reading a run and acting on one, and each side became a milestone. The read surface came first because it needs nothing from the action surface. Inside it, the very first piece was the path guard: the single function that turns an untrusted path argument into a real file only when it stays inside the run the server is scoped to. The brief made this the security foundation everything else stands on and required it be proven in isolation — with tests for traversal, absolute paths, a sibling run's directory, and symlink escape — before any tool existed to call it. Centralizing that confinement once, provably, is what lets every later read tool accept a path without re-checking it.

The protocol loop was stood up next and proven with exactly the two tools that need no guard: job_status reads the resolved run, and list_runs is the one deliberate exception allowed to see every run. That pair exercised the full request-dispatch-response path while depending only on the run context and the existing registry. The remaining read tools, and later the shell and answer-prompt actions, then register as entries in a single tool-descriptor table that both the advertised manifest and the call dispatch derive from, so the wire schema cannot drift from the handler — a test asserts the two sets are equal.

The run never went red: no gate sent a sprint back, and nothing forced a re-plan across either milestone. The reason is legible in the briefs. Each names the existing Gantry machinery its sprint must reuse rather than reinvent — the registry's forgiving name resolver, the agent sandbox wrapper, the escaped-write backstop that catches a leaked write on an unsandboxed host, the front-end-to-engine command channel. The action milestone even grounds itself in what the engine honors today, treating "no live engine attached" as the normal production path rather than a gap to paper over. The difficulty was dissolved in advance by routing every risky capability through a mechanism that was already tested, instead of building a new one to test.

Feature

Before this, an agent could not inspect or steer a Gantry run through any structured interface. It left an mcp module of confined tools that still stands — and the once-absent live-engine attach is now wired in.

Before this job there was no way for an agent to reach into a Gantry run and read what it was doing, or to nudge it, except by being handed context by hand. The job answers that with a new module: a run context that resolves a human-typed run name to exactly one build and carries its worktree and orchestration directory; the path guard that keeps every file argument inside that build; a stdio JSON-RPC loop speaking MCP; and a manifest-driven set of tools — status, ledger, plan, sprint, report, log, and history readers, a git-state tool for the worktree, a sandboxed shell command, and a prompt-answering action.

Standing in the tree today, the module is there and the tools are still registered from the one descriptor table. The standalone integration test file the run wrote is gone, its coverage moved into the modules' own inline tests, which is where the bulk of the server's checks now live.

The most telling change is at the seam the action milestone deliberately left open. That milestone introduced the attach point for a live engine as an optional handle that was absent on the real launch, because no in-process transport existed to fill it then. It has since been filled: a live engine now attaches, and a second host serves the very same tool surface in-process to Gantry's conversational assistant, so an assistant turn reaches the whole surface — including a working prompt-answering action — without bespoke context-stuffing. The standalone server and the in-process assistant host share one implementation rather than growing two. What this job cut as a read-and-act contract for one run has been folded into a larger design without being rebuilt.

16 sessions
peak 159,666 · median execute 135,787 · heaviest 159,666 (02-action-tools-sandbox-gate / 01) context
112,132 in / 341,104 out tokens
$41.65 cost
2 x 7 milestones x sprints
81 edits
299 commands
1h 31m duration
unavailable roles
0 x 0 fixes x replans
claude harness