Skip to main content
Glama
iNileshW

Passport Casework MCP Server

by iNileshW

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 capRLIMIT_CPU

_set_limits()

3

File-size capRLIMIT_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.