Skip to main content
Glama
iNileshW

Passport Casework MCP Server

by iNileshW
README.md
# Build a research assistant over HM Passport Office casework

A passport-casework assistant answers two kinds of question. Simple lookups —
_"what's the status of application 500123?"_ — are handled by governed MCP tools. But most
management questions are analytical — _"what's our median processing time by office, and
where is the backlog worst?"_ — and no one can pre-build a tool for each one, so the
assistant writes and runs its own Python. Running model-written code is the dangerous part —
which is what this morning was about.

**You edit one file.** Everything here is built and runs, except the containment inside
`analysis_tool.py`, which you finish. Then you run the assistant and watch it work.

## Setup

Plain Python — no Docker, no Git.

```
pip install -r requirements.txt
python seed.py            # builds the caseload: data/passport_applications.csv
python test_analysis.py   # 3 of 4 pass until you finish the task below
```

## The task — finish `analysis_tool.py`

This is the **only file you change.** It already runs the model's code in a child process,
over the caseload, under a wall-clock timeout. Three containment pieces are missing, each
marked `# TODO (you)` in the file:

| #   | Add                                                                                             | Where in `analysis_tool.py`                                                                                                            |
| --- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | **No network** — wrap the child in `unshare -r -m -n` (the control whose absence let "Sol" out) | `_unshare_available()` — make the probe return `True`; **and** the `argv` branch in `run_analysis()` marked `# <- replace this branch` |
| 2   | **CPU-time cap** — `RLIMIT_CPU`                                                                 | `_set_limits()`                                                                                                                        |
| 3   | **File-size cap** — `RLIMIT_FSIZE`                                                              | `_set_limits()`                                                                                                                        |

For the two caps, copy the shape of the `RLIMIT_AS` / `RLIMIT_NPROC` lines already sitting in
`_set_limits()`. You do not touch `server.py`, `assistant.py`, or `test_analysis.py`.

You are done when:

```
python test_analysis.py   # 4/4 — the outbound call is now BLOCKED, and a real analysis still returns 640
```

## Then — run the assistant

`assistant.py` is written for you. Once your containment is in place, just run it:

```
python assistant.py "Which two processing offices are most in need of extra staff this quarter, and what is the evidence?"
```

Watch it work: it searches the caseload with the governed MCP tools, computes the medians and
backlogs with your `run_analysis`, cites the service standard, and answers. That is the whole
point — **the governed tools run trusted, in-process; the one open-ended capability, arbitrary
code, is the only thing contained.**

### If you want to go further (optional)

- Add to the research surface in `server.py` — a filter by date range or fraud flag, a
  per-office resource. Watch the boundary: lookups belong on the server, aggregates stay in
  the sandbox.
- Give it an open brief — _"write the quarterly processing note for the board"_ — and see how
  far the tool-plus-sandbox pattern carries a multi-step answer.

## Where containment stops

The child process, `setrlimit` and the network namespace stop resource exhaustion and close
egress — but do not fully confine the filesystem. For genuinely hostile code you would add a
container (`--network=none --read-only --cap-drop=ALL` + seccomp), then gVisor or a microVM.
Here the code is the department's own model, not an adversary, so Tier 2 is a defensible floor.

## Files

| File                              |                                                                                        |
| --------------------------------- | -------------------------------------------------------------------------------------- |
| **`analysis_tool.py`**            | **The one file you edit** — finish the containment.                                    |
| `test_analysis.py`                | The battery that tells you when you're done. Run it; don't edit it.                    |
| `assistant.py`                    | The assistant — provided and runnable.                                                 |
| `server.py`                       | The governed MCP server (lookups + guidance). Provided; extend only if you go further. |
| `seed.py`, `data/`, `guidance.md` | The caseload, and the service-standard resource.                                       |