Back to Directory/Developer Tools

io.github.cyanheads/medical-codes-mcp-server

Offline US medical code lookup and crosswalk — ICD-10-CM/PCS, HCPCS Level II, RxNorm. Keyless.

Developer ToolsTypeScriptv0.4.0

@cyanheads/medical-codes-mcp-server

Decode, search, validate, and crosswalk US medical codes — ICD-10-CM, ICD-10-PCS, HCPCS Level II, RxNorm — over a bundled offline index via MCP. STDIO or Streamable HTTP.

6 Tools

Version License MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


[!NOTE] Informational, not clinical or coding advice. This server returns official code descriptions and billable/validity flags from public-domain federal releases to help you decode and look up codes. It is not medical advice, and a valid_billable result is not a coding or reimbursement decision. Always verify codes against the official source releases (CMS, CDC/NCHS, NLM) and your payer's rules before submitting a claim. The bundled data is only as current as the release baked into the build — call medcode_list_systems to see exactly which releases are active.

Overview

US medical codes — ICD-10-CM, ICD-10-PCS, HCPCS Level II, and RxNorm — from a bundled offline SQLite index built from public-domain CDC/NCHS, CMS, and NLM federal releases. Decode, search, validate billability, and crosswalk codes and drugs (including NDC lookups) from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.

Tools

ToolDescription
medcode_get_codeDecode 1–50 codes to their official descriptions. Auto-detects the system per code; partial-success found / notFound.
medcode_search_codesFull-text search over official descriptions — go from a clinical description to the code.
medcode_check_codeValidate a code's existence, currency, and billability, with a whyNot for non-billable/terminated cases.
medcode_map_codesCrosswalk a code within its hierarchy (parents/children) or a drug across RxNorm (name ↔ RXCUI, NDC ↔ RXCUI, RXCUI → ingredients/brands).
medcode_browse_hierarchyWalk a system's hierarchy for discovery without a search term.
medcode_list_systemsList bundled systems with release identifiers, effective dates, and code counts (provenance).

How it works

Only freely-redistributable, public-domain US federal code sets are bundled, baked into a single SQLite + FTS5 database at package-build time and opened read-only at startup.

Bundled code systems

SystemSourceCovers
ICD-10-CMCDC/NCHS — US federal, public domainDiagnoses (billable leaf codes + non-billable category headers)
ICD-10-PCSCMS — US federal, public domainInpatient procedures (axis-based 7-character codes)
HCPCS Level IICMS — US federal, public domainSupplies, drugs, and non-physician services
RxNormNLM RxNav — public domainDrugs: name ↔ RXCUI, NDC ↔ RXCUI crosswalk, ingredients, and brands

RxNorm bundles the current normalized drug vocabulary — ingredients, brand names, clinical & branded drugs, and packs, with their NDC and ingredient/brand crosswalks — sourced at build time from the keyless RxNav REST API, which serves the public-domain normalized layer only. The full UMLS-licensed RxNorm release is intentionally excluded, so the package stays freely redistributable.

CPT (AMA copyright) and SNOMED CT / LOINC (UMLS-license-gated) are intentionally absent — not freely redistributable, so they cannot ship in an offline package.

US scope. ICD-10-CM and ICD-10-PCS are the US clinical modifications, not the WHO ICD-10/ICD-11 base or another country's national modification.

Capability reference

medcode_get_code tool

  • Accepts 1–50 codes; mixed systems are fine — each code's system is detected independently from its shape
  • Decodes a National Drug Code (NDC) directly to its RxNorm product — hyphenated FDA segment configurations (4-4-2, 5-3-2, 5-4-1, or the 11-digit 5-4-2) or bare 10/11 digits — offline via the bundled NDC↔RxNorm map, tagged source: "NDC"
  • Partial success: resolved codes in found, unresolved in notFound with a per-code reason
  • An explicit system overrides auto-detection when a value is genuinely ambiguous (an ambiguous code lists its candidateSystems); includeHierarchy attaches each code's parent and immediate children
  • alsoInSystems names other bundled systems holding the same code string — it is a different code in each
  • Errors: no_codes_found when none of the requested codes resolve in any bundled system

medcode_search_codes tool

  • Every search term must appear — matched first as a token prefix, then as a substring, so inflected and compound forms are also found
  • Filter by system, billableOnly (exclude headers/categories), and chapter
  • Ranked by full-text relevance; results echo the resolved system per row
  • Paginates via cursor/limit (default MEDCODE_MAX_RESULTS, ceiling 200); discloses truncated/nextCursor, and returns a notice with the parsed query when nothing matches

medcode_check_code tool

  • Discriminated status: valid_billable, valid_not_billable, valid_header, or terminated
  • whyNot explains non-billable/terminated cases — a non-billable or terminated code is a successful result, not an error
  • alsoInSystems names other bundled systems holding the same code string, since the verdict applies only to the resolved system
  • Errors: unknown_code (absent from every bundled system) and ambiguous_system (present in multiple systems, no system given)

medcode_map_codes tool

  • Hierarchy directions parents/children walk one level per call (depth-1); ICD-10-PCS codes have no prefix parent
  • Drug directions (RxNorm): name_to_rxcui, ndc_to_rxcui/rxcui_to_ndc (NDC accepted hyphenated or as bare 10/11 digits), rxcui_to_ingredients/rxcui_to_brands (each hit carries conceptType: IN/PIN/MIN/BN)
  • children, name_to_rxcui, and rxcui_to_ndc paginate via cursor/limit — one RXCUI can carry thousands of package NDCs
  • Every hit carries source provenance so a chained call uses the right identifier; a resolvable source with no edge in the requested direction is a successful empty result with a notice, not an error
  • Errors: no_mapping (source doesn't resolve), direction_unavailable (RxNorm not bundled in this build), ambiguous_system

medcode_browse_hierarchy tool

  • With no node: top-level entries (ICD-10-CM categories, HCPCS range buckets, ICD-10-PCS first-axis values); with a node: its immediate children
  • ICD-10-CM/HCPCS use a prefix hierarchy; ICD-10-PCS is axis-based and only the top-level Section axis is browsable — positions 2–7 are context-dependent and not enumerable from a flat partial code
  • Paginates via cursor/limit (default MEDCODE_MAX_RESULTS, ceiling 200)
  • Errors: unknown_node when the node doesn't exist (or, for ICD-10-PCS, uses an out-of-alphabet character or begins no bundled code)

medcode_list_systems tool

  • No input; returns one entry per bundled system with releaseId, effectiveStart/effectiveEnd, codeCount, sourceUrl, and builtAt
  • Confirms exactly which ICD-10-CM/PCS fiscal year, HCPCS release, and RxNorm snapshot are baked into the running build

Features

Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.

ICD-10 / HCPCS / RxNorm-specific:

  • Bundled SQLite + FTS5 index — offline, keyless, deterministic; no runtime network I/O, no rate limit
  • Code-shape auto-detection routes a code to its system automatically; an explicit system disambiguates collisions
  • Real billable/validity signal from the source releases — the order-file billable flag drives medcode_check_code, not a heuristic

Agent-friendly output:

  • Provenance on every response — the resolved system is echoed for chaining, alsoInSystems flags a code string that means something different in another bundled system, and medcode_list_systems reports exactly which release is baked into the build
  • Graceful partial failure — medcode_get_code returns per-code found/notFound rows instead of failing the batch
  • Discriminated output contracts — medcode_check_code's typed status and medcode_map_codes' source let callers branch on data, not string parsing

Getting started

This server ships with the code database bundled — there is no API key to obtain and nothing to download at runtime.

Public Hosted Instance

A public instance is available at https://medical-codes.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP, with this client config:

{
  "mcpServers": {
    "medical-codes-mcp-server": {
      "type": "streamable-http",
      "url": "https://medical-codes.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file.

{
  "mcpServers": {
    "medical-codes-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/medical-codes-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "medical-codes-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/medical-codes-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info"
      }
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "medical-codes-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "MCP_TRANSPORT_TYPE=stdio",
        "ghcr.io/cyanheads/medical-codes-mcp-server:latest"
      ]
    }
  }
}

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Refer to "your MCP client configuration file" generically — different clients use different config paths, and the server isn't client-specific.

Prerequisites

  • Bun v1.4 or higher (or Node.js v24+ — the server falls back to the better-sqlite3 optional dependency when not run under Bun).
  • No API key, account, or network access required.

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/medical-codes-mcp-server.git
  1. Navigate into the directory:
cd medical-codes-mcp-server
  1. Install dependencies:
bun install
  1. Configure environment (optional):
cp .env.example .env
# all runtime vars are optional — the server runs as-is

Configuration

The server is offline and keyless — there are no required variables. Two server-specific knobs and the standard framework vars apply:

VariableDescriptionDefault
MEDCODE_DB_PATHAbsolute path override for the bundled SQLite index. Set only to point at a custom-built or externally-mounted database.packaged data/medical-codes.db
MEDCODE_MAX_RESULTSCap on rows returned by medcode_search_codes / medcode_browse_hierarchy.50 (ceiling 200)
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for the HTTP server.3010
MCP_HTTP_ENDPOINT_PATHEndpoint path where the MCP server is mounted./mcp
MCP_SESSION_MODEHTTP session handling: stateless, stateful, or auto (which resolves to stateful). src/index.ts declares stateless — no tool asks the caller for input mid-call — and this variable overrides that declaration.stateless
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
OTEL_ENABLEDEnable OpenTelemetry instrumentation.false

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run:

    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Building the bundled index

The bundled data/medical-codes.db ships in the npm package and Docker image but, at >100 MB, is not committed to git — fetch it from the GitHub Release assets or rebuild it locally with the build script. You only rebuild when refreshing to a new federal release. The script never downloads: extract the canonical .gov source files (ICD-10-CM/PCS order files, HCPCS ANWEB.txt — URLs in the script header) into a directory, then point the script at it:

bun run scripts/build-index.ts --from-dir <dir-with-source-files> --fy 2026

It parses the source files and emits the single .db file. It runs at build time only — the server never downloads anything.

Docker

docker build -t medical-codes-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=stdio medical-codes-mcp-server

The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/medical-codes-mcp-server. It copies the bundled data/medical-codes.db into the image so the server is fully self-contained. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers the six tools and opens the bundled index in setup().
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (*.tool.ts).
src/services/code-indexThe code-index service — read-only SQLite handle, code-shape detection, FTS5 query translation.
scripts/build-index.tsBuild-time ingest pipeline that bakes the federal source files into data/medical-codes.db.
data/medical-codes.dbThe bundled SQLite + FTS5 code index, opened read-only at runtime.
tests/Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging; the code index is a read-only global, not tenant state
  • Register new tools via the createApp() array in src/index.ts
  • The bundled DB is the source of truth — surface real billable/validity flags from the source releases; never fabricate a code or a billability decision

Contributing

Issues are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Installation

Source-derived launch command. Check the maintainer’s required arguments and credentials before running:

bash
npx -y @cyanheads/medical-codes-mcp-server

Set up in your AI client

Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.

json
{
  "mcpServers": {
    "io-github-cyanheads-medical-codes-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/medical-codes-mcp-server"
      ]
    }
  }
}

Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.

Claude Desktop setup reference

Package

@cyanheads/medical-codes-mcp-servernpm

Compatible MCP Clients

io.github.cyanheads/medical-codes-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.

  • Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.
  • Cursor~/.cursor/mcp.jsonRestart Cursor for changes to take effect.
  • VS Code.vscode/mcp.jsonReload VS Code window for changes to take effect.
  • Windsurf~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect.
  • Claude Code.mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.

Learn More