- Files:
/work, mounted in memory for scratch files and clones, and/repo, Mirage’s repository mounted read-only with the GitHub VFS, with no clone. To keep/work, mount a disk folder, an S3 bucket or another VFS there instead. - Python:
python3, in-process on Monty. - CLIs:
ghcalls GitHub’s API with a token the server holds, andgitclones a repository into/workwith nogitprogram on the host. DeepWiki, an MCP server, is installed as a CLI nameddeepwiki, with one subcommand for each tool.
- Mount, for a large repository: a pull request in Mirage’s own
repository, number 2240,
which stops the
ntnCLI printing its token. Mirage’s repository is over 100 MB, more than a terminal should hold, so the agent reads the few files it needs throughghand/repo, and nothing is cloned. - Clone, for a small one: a dependency bump of
Pydantic Monty, the runtime Mirage
runs
python3on, from 1.4.0 to 1.6.0. Comparing two releases needs the history but not the files, so the agent clones only the history, 24 MB, withgit clone -n.
Server
This is the server from the Overview with three additions: it registersgh and git, it mounts Mirage’s
repository read-only at /repo, and at start it installs the DeepWiki MCP
server as a CLI. The mount is pinned to the commit the pull request merged
as, so /repo holds the code as merged. To review your own repository,
change owner, repo and ref, and the repo of gh.
Before you start the server, set GITHUB_TOKEN to a token that can read
public repositories. GitHub says how to create one.
The server uses it for gh and for the mount, and the agent cannot read it
from the terminal.
- FastAPI
- Express
app.py
Usage
Create a terminal, and definerun, which sends commands to it and prints
what they print. It needs jq:
SH and SH in the blocks below run in the terminal
on the server, not in the shell that calls run.
An MCP server as a CLI
The server installed DeepWiki at start, so every terminal has adeepwiki
CLI. Each tool of an
MCP server is a subcommand that takes its arguments as one
JSON object, and --help prints the tool’s schema. For example:
- The tool list is read once, at registration, and no schema reaches the
model until it runs
--help. - The server needs DeepWiki to start;
mcp_cliraises when an MCP server cannot be reached. Add an entry toMCP_SERVERSto install another one. - A profile can deny a tool, and a
network:rule can limit the hosts a terminal reaches. See From MCP. - DeepWiki is a third-party service with rate limits. An MCP server that
needs an API key takes it in its config, as
auth: {"type": "header", ...}.
Review a pull request
First the agent reads what the pull request is and which files it touches.--json with --jq prints only the fields it asks for:
integ/,
and two reference pages. The agent reads the Python handler’s part of the
diff. awk prints from that file’s diff --git line to the next one:
ntn the only CLI
that did this? /repo holds the code as merged, so the agent searches every
bundled CLI for the call that reads a secret:
gh builds a masked copy for
gh auth status, gh builds the header it sends to GitHub for a clone, and
himalaya logs in to the mail server. Last, the agent counts the test files
in the change and reads the result of CI:
- The pull request does one thing:
ntn auth tokenno longer reads or prints the token. It answers with an error and exit code 1. - The change is made in Python and in TypeScript, and it comes with tests: one test file for each, and the test cases the two share.
- No other bundled CLI prints its secret. The three calls that still read one mask it or send it to the service it belongs to.
- CI passed: 89 checks, with 5 skipped.
Review a dependency bump
Mirage pins its Python runtime exactly, and a pull request movespydantic-monty from 1.4.0 to 1.6.0. Before it merges, a reviewer checks
whether anything Mirage uses has changed. The agent starts on Mirage’s side:
/repo, still on 1.4.0, shows the pin and every name Mirage imports from
Monty:
AbstractOS matters most: Mirage subclasses it, as MontyFs,
to give Monty its files.
Next the agent clones Monty. -n skips the checkout, so the terminal holds
24 MB of history instead of 41 MB of history and files:
path_create_exclusive. The agent looks for it in
Mirage, then reads it in 1.6.0:
MontyFs does not define the method, and the method is not abstract: by
default it raises NotImplementedError, and Monty answers open(path, "x")
with exclusive creation mode is not supported, as 1.4.0 already did. So
nothing breaks, and exclusive creation stays unsupported until MontyFs
adds the method. Last, the agent looks for commits that change errors or
rename things:
MontyRuntimeError, and its code uses neither of the
renamed names. For background, the agent asks DeepWiki about the new method,
into a file:
- The bump is safe to merge: 85 commits, and Monty’s Python API removes nothing.
- The file protocol gains
path_create_exclusive, with a default that refuses.MontyFsdoes not implement it, soopen(path, "x")stays unsupported, as on 1.4.0. Adding it is a follow-up. MontyRuntimeError, which Mirage catches, now also covers a result that cannot be converted to Python.