Skip to content

0xNoramiya/lookcloser-mcp

Repository files navigation

LookCloser MCP Server

FHIR-backed MCP server powering the LookCloser closed-loop safety net for incidental imaging findings. Stateless tool layer over HAPI FHIR R4. The four LookCloser agents (Significance Classifier, Recipient Resolver, Closure Auditor, Escalator) all share these tools.

Setup

uv sync                        # or:  python -m venv .venv && pip install -e ".[dev]"
cp .env.example .env

Defaults to https://hapi.fhir.org/baseR4 — override FHIR_BASE_URL in .env.

Run

python -m lookcloser_mcp                           # stdio (for local MCP clients)
python -m lookcloser_mcp --http --port 8080        # streamable HTTP (for Prompt Opinion)
mcp-inspector python -m lookcloser_mcp             # interactive tool exploration

Seed demo data

Seeds two patients onto HAPI sandbox: Diane Suryanto (adrenal incidentaloma, open loop) and Hari Wibowo (pulmonary nodule, closed loop). Each run produces fresh patients with run-unique identifiers — no clean-up required between runs.

python scripts/seed_fhir.py                        # phases 1+2 (patients + post-state)
python scripts/seed_fhir.py --phase 1              # just the patients
python scripts/seed_fhir.py --phase 2              # post-state for Diane (run after platform creates Task)

Resource IDs are written to demo-data/patient_ids.json. Hand the relevant IDs to your MCP client / Prompt Opinion agents.

Test

uv run pytest                              # 11 unit tests, mocked httpx (~8s)
uv run pytest -m integration               # live HAPI E2E for Diane audit flow

Tools

Tool Purpose
fhir_get_patient_summary Demographics + active conditions/meds/allergies — call first
fhir_get_diagnostic_report Full report body (decoded from base64 presentedForm)
fhir_search_diagnostic_reports Recent reports, metadata + conclusion only
fhir_get_care_team PCP + active specialists; falls back to encounter participants
fhir_search_service_requests Orders by code/intent/status/category/date
fhir_search_clinical_notes DocumentReference text — load-bearing for the Auditor
fhir_search_encounters Past or planned encounters
fhir_create_task POST a tracked Task (with structured_recommendation extension)
fhir_search_tasks Find Tasks (decodes structured_recommendation extension back to dict)
fhir_update_task_status Read-modify-write businessStatus + append note
finding_followup_guidance Naive lookup against an embedded ~7-entry corpus

All tool returns include a top-level provenance: {fhir_server, fetched_at}. FHIR errors return {"error": "fhir_unavailable", "detail": "...", "provenance": {...}} instead of raising. patient_id accepts either "Patient/123" or bare "123".

Deploy a public URL (for Prompt Opinion)

The MCP must be reachable from Prompt Opinion. Three options:

A. Cloudflared tunnel (fastest for a demo)

# Terminal 1 — run the MCP over HTTP
python -m lookcloser_mcp --http --port 8080
# Terminal 2 — expose it
cloudflared tunnel --url http://localhost:8080

Use the printed https://*.trycloudflare.com/mcp URL in Prompt Opinion.

B. Fly.io

fly launch --no-deploy        # accepts fly.toml as-is; pick org/region
fly deploy

Public URL: https://lookcloser-mcp.fly.dev/mcp

C. Railway

railway init && railway up

Set start command to uv run python -m lookcloser_mcp --http --port $PORT --host 0.0.0.0 if Railway doesn't pick it from the Dockerfile.

The MCP correctly returns HTTP 400 {"error":{"code":-32600,"message":"Bad Request: Missing session ID"}} for an unauthenticated POST to /mcp — that's the protocol-compliant response before initialize. Use the MCP Inspector to actually call tools.

Project layout

src/lookcloser_mcp/
  server.py            # FastMCP server, registers all 11 callables
  fhir_client.py       # async httpx wrapper
  schemas.py           # pydantic return models (documentation only)
  tools/               # one module per tool group
  guidance/corpus.json # 7 follow-up snippets
demo-data/             # 3 transaction Bundles + post-seed state
scripts/seed_fhir.py   # POSTs bundles, harvests assigned IDs
tests/test_tools.py    # 11 unit + 1 live integration

About

MCP server for the LookCloser closed-loop safety net — 11 FHIR R4 tools over HAPI for incidental imaging finding follow-up

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors