NPI Registry MCP Server
NPI Registry MCP Server
An MCP (Model Context Protocol) server that wraps the public NPPES NPI Registry API — the US registry of healthcare providers — and exposes it to LLM clients as callable tools. No API key is required; NPPES is a free public endpoint.
Beyond straight API passthrough, the server adds a derived-intelligence layer: offline NPI checksum validation, a composite referral-eligibility assessment, and plain-English specialty search that NPPES itself cannot answer.
Guide
Follow the guide, to build the server from scratch, deploy and test it, all using Claude or similar AI coding assitant tools.
Tools
# | Tool | What it does |
1 |
| Finds providers by name and/or 2-letter state. The only way to turn a name into an NPI. Searches individual surnames and organization names. |
2 |
| Returns the complete NPPES record for one 10-digit NPI: name, credential, status, addresses, specialties. |
3 |
| Returns just the NUCC taxonomies for an NPI — code, description, primary flag, license state. |
4 |
| Offline CMS Luhn checksum validation, no network call. Run it before spending a lookup on unverified input; it names the expected check digit when validation fails. |
5 |
| Composite referral-eligibility judgment across five checks: checksum, registry existence, active status, primary taxonomy, and a complete practice address. Returns actionable concerns. |
6 |
| Maps a plain-English specialty — cardiology, orthopedic, pediatrics, neurology, family medicine, psychiatry, dermatology, oncology — to NUCC taxonomy codes, then returns matching providers in a state. |
Tools 4–6 never raise. Every failure path — bad checksum, unknown NPI, NPPES outage, unrecognized keyword — returns a structured result the calling model can read and recover from.
Run with Docker
docker build -t npi-mcp .
docker run --rm -p 8000:8000 npi-mcp
curl http://localhost:8000/healthRun locally
pip install -e .
npi-mcp # or: uvicorn npi_mcp.server:appConfiguration
Variable | Purpose |
| Comma-separated public hostnames this server is reached by, e.g. |
FastMCP enables DNS-rebinding protection by default and answers 421 Misdirected
Request to any Host header it was not told about. localhost and 127.0.0.1
(with any port) are always allowed, so local development needs no configuration;
set MCP_ALLOWED_HOSTS for any other hostname. Each entry H is added to
allowed_hosts, and https://H and http://H to allowed_origins. Entries may
be given as bare hostnames or full URLs. The resolved list is logged at startup.
docker run --rm -p 8000:8000 \
-e MCP_ALLOWED_HOSTS="my-space.hf.space" npi-mcpEndpoints
Path | Purpose |
| MCP streamable-HTTP endpoint. |
| Liveness probe: status, version, uptime. |
| Root probe returning server name, version, and MCP endpoint path. |
Deployment notes
Hugging Face Spaces: the frontmatter above selects the Docker SDK and routes traffic to
app_port: 8000. Push this repo to a Space and it builds and serves unchanged. SetMCP_ALLOWED_HOSTSto the Space hostname (<user>-<space>.hf.space) in the Space's variables, or requests fail with 421.Cloudflare tunnel:
cloudflared tunnel --url http://localhost:8000puts the same container behind a public hostname. The server binds0.0.0.0, so the only per-target difference isMCP_ALLOWED_HOSTS-- which is why the hostname is read at runtime rather than baked into the image, since a quick tunnel gets a new hostname on every run.
Implementation notes
Requires
mcp<2: version 2.x renamedFastMCPtoMCPServerand moved it out ofmcp.server.fastmcp.find_providers_by_specialtyis bounded by the NPPES 200-record page cap, so in a populous state it returns a sample of matching providers, not a census.NPPES matches individuals on
last_nameand organizations onorganization_name, and ANDs its query parameters. Whenlast_nameis given withoutfirst_name, the client issues both queries concurrently and merges the deduplicated results.