Skip to main content
Glama
iNileshW

hmpo-mcp-server-practice

by iNileshW

Extend the HM Passport Office application-status assistant

HM Passport Office — examination support. HMPO processes millions of passport applications a year, and applicants (and MPs' offices) constantly ask "where is application 5001, and what happens next?". The assistant answers those questions directly instead of a caseworker looking each one up by hand. This MCP server is how it reaches the applications database: it exposes the database to the model as tools, over the Model Context Protocol.

Today it exposes one tool. Your job is to add the ones the assistant needs — read-only, parameterised, and returning nothing from which an applicant's identity could be rebuilt. Work through it on your own.

Get it running

Plain Python — no Docker, no Git.

pip install -r requirements.txt
python seed.py                 # builds passport.db (five applications, four applicants)
python test_server.py          # in-process check: lists tools, calls get_application_status

server.py speaks MCP over stdio — nothing to open in a browser. To poke it by hand, use the MCP Inspector's command-line mode (its web UI does not come through the training VM's port proxy):

# list the tools the model would see
npx @modelcontextprotocol/inspector --cli python server.py --method tools/list

# call one
npx @modelcontextprotocol/inspector --cli python server.py \
  --method tools/call --tool-name get_application_status --tool-arg application_id=5001

What you're doing, in order

  1. Run it and look at it. Build the database, run test_server.py, and list the tools with the CLI above. Call get_application_status for 5001, and for an id that does not exist.

  2. Add at least two new tools over the dataset. Good candidates:

    • list_applications_for_applicant(applicant_id) — every application from one person

    • count_applications_by_status() — a queue summary (in examination, printed…)

    • find_applications_by_service(service) — e.g. all "Renewal" applications

    For each, keep the three disciplines from the starter:

    • parameterised query — bind arguments; never build SQL by string interpolation

    • open the database read-only (mode=ro)

    • return only application columns — never a passport_number or date_of_birth from the applicants table

  3. Check your change worked. Re-list the tools and confirm the new ones appear, then call them:

    npx @modelcontextprotocol/inspector --cli python server.py --method tools/list \
      | python -c "import sys,json; print([t['name'] for t in json.load(sys.stdin)['tools']])"
    
    npx @modelcontextprotocol/inspector --cli python server.py \
      --method tools/call --tool-name count_applications_by_status

Stretch

  • Expose the processing guidance as a resource. Serve guidance.md at a stable URI (for example guidance://passport-stages) so the assistant cites the live document rather than its training memory. Check it from the CLI:

    npx @modelcontextprotocol/inspector --cli python server.py --method resources/list
    
    npx @modelcontextprotocol/inspector --cli python server.py \
      --method resources/read --uri "guidance://passport-stages"
  • Add audit logging. Append every tool call — name, arguments, timestamp — to a log file.

Files

File

Contents

server.py

The MCP server. One tool today: get_application_status. Add yours below the marker.

seed.py

Builds passport.db — the applications and applicants tables.

test_server.py

In-process FastMCP client check; a template for verifying your tools.

guidance.md

Processing-stages extract (for the resource stretch).

requirements.txt

fastmcp.

Notes from the previous developer

Status: in a pilot with the examination team. The connection is opened read-only on purpose — a status surface has no business writing to application data. Every query is parameterised; the application id is bound, never formatted into the SQL. And get_application_status returns only application columns — nothing from applicants. That is deliberate: applicants carries passport numbers and dates of birth, and the shortest join from an application to a passport number is a single hop. Whatever tools you add, that hop must stay closed.

Questions worth asking yourself

  • Which of your tools would you leave connected to a live model overnight, unsupervised — and which would you not, and why?

  • What is the shortest join from an application to a passport number, and what stops the model asking for it?

  • A tool's description is read by the model as an instruction, not just documentation. Look at what you have written in yours — then ask what a malicious server could write in the description of a tool your agent connects to. (That question is where this afternoon begins.)