Skip to main content
ClaudeWave
ES, Spanish
pipeworx-io / MCP Servers · Registry oficial

mcp-drug-prices

US drug price benchmarks — what a pharmacy PAYS and what Medicare PAYS OUT.

  • 0 estrellas
  • 0 forks
  • 0 issues abiertos
  • Lenguaje: TypeScript
  • Licencia: MIT
  • actualizado

Instalación

remote
Terminal
claude mcp add --transport http mcp-drug-prices https://gateway.pipeworx.io/drug-prices/mcp
.mcp.json (ámbito de proyecto)
{
  "mcpServers": {
    "mcp-drug-prices": {
      "type": "http",
      "url": "https://gateway.pipeworx.io/drug-prices/mcp"
    }
  }
}

Servidor remoto, sin binario local. Si pide credenciales, sigue el README del proveedor.

  1. Ejecuta el comando en tu terminal (Claude Code) o añade la URL como conector (Claude Desktop) o en mcp.json (Cursor).
  2. Autoriza el acceso cuando el cliente lo pida.
  3. Reinicia la sesión: las herramientas del servidor aparecen solas.
README

Resumen de MCP Servers

README completo en GitHub

drug-prices

US drug price benchmarks: what a pharmacy pays to acquire a drug, and what Medicare Part B pays out for one administered by a provider. Keyless.

Part of Pipeworx — an MCP gateway connecting AI agents to 1855+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.

These are two different numbers that are routinely confused for each other, and keeping them apart is most of what this pack does.

NADACASP payment limit
Answerswhat a retail pharmacy pays a wholesalerwhat Medicare pays a clinic
Cadenceweeklyquarterly
Keyed byNDCHCPCS (J-code)
Typical druggeneric tablets, retail productsoncology, biologics, infusions, vaccines
SourceCMS via data.medicaid.govCMS ASP Pricing Files (ZIP)

A generic tablet has a NADAC and no ASP. An infused oncology drug has an ASP and usually no retail NADAC. drug_price_compare reports whichever exist and says why the other is missing, rather than returning an empty result.

Tools

  • nadac_price(drug | ndc, as_of?, limit?) — current acquisition cost per unit

  • nadac_history(ndc, weeks?) — weekly price series and % change

  • asp_payment_limit(hcpcs | drug, quarter?, limit?) — Part B limit per dosage unit

  • drug_price_compare(drug, ndc?) — both benchmarks side by side, with vintages

Plumbing

NADAC is read live — data.medicaid.gov exposes a real query API (filter, sort, paginate) and the dataset holds ~20 months, so there is nothing to mirror. ASP has no API at all: CMS publishes one ZIP per quarter, so it is ingested into asp_payment_limits by scripts/ingest-asp-payment-limits.mjs and read from Supabase.

node scripts/ingest-asp-payment-limits.mjs            # current quarter
node scripts/ingest-asp-payment-limits.mjs --back=5   # backfill

Traps

Every one of these was hit while building the pack, and each produced a confident wrong answer rather than an error.

The newest NADAC week is usually a correction file, not a week. NADAC publishes the full weekly file plus tiny off-cycle corrections sharing the same effective_date column. Measured 2026-08-28: 2026-08-26 → 77 rows, 2026-08-19 → 58,163 rows, 2026-08-12 → 6 rows. The obvious design — take max(effective_date), then filter — lands on the 77-row file and reports "no price found" for almost every drug, which is indistinguishable from a drug NADAC does not cover. Filter for the drug first, then take the newest date in the matched set.

The datastore API silently ignores unknown parameters. ?sort=-effective_date returns HTTP 200 with unsorted rows — byte-identical to ?zzznonsense=x. The only spelling that sorts is sorts[0][property] + sorts[0][order]. A typo here does not fail; it quietly answers with an arbitrary week's price.

The NADAC dataset id rotates every January, and the year in the title is not the range it covers. The dataset called "NADAC … 2026" holds effective dates from 2025-01-01 to 2026-08-26. The id is resolved by title at call time, with the last known id as a fallback, so January does not break the pack.

NADAC carries two dates. effective_date is the week the price applies to; as_of_date is when CMS published it. Prices key on the first; both are returned.

The ASP payment limit's vintage is not its quarter. The CMS file states it: the file effective 2026-07-01 is "based on 1Q26 ASP data". The limit in force is derived from ASP data roughly two quarters earlier, so comparing it against this week's NADAC spans about six months. Both quarters are always returned.

The ASP CSV is windows-1252, not UTF-8, and carries a 0xA0 inside a real value ("1 million\xa0PFU"). Reading it as UTF-8 throws — and worse, grep silently aborts on the illegal byte, which is how J9271 first appeared to be missing from a file that contains it.

The ASP CSV header is not row 1 (nine rows of notes precede it, and the count varies by quarter), and the inner filename carries a per-quarter build date (…File 061626.csv). Both are matched by pattern, never by offset or literal name. CMS is also inconsistent about a -final-file suffix on the ZIP, so both spellings are tried.

NADAC names brand products by their BRAND, not their ingredient. APIXABAN matches 0 rows; ELIQUIS matches 204. A generic-name search for a still-branded drug therefore returns a confident empty, and the tempting explanation — "must be clinic-administered" — is simply wrong for that case. The empty-result note offers both possibilities in order of likelihood rather than asserting one.

NDCs travel in three shapes — dashed 5-4-2, 11-digit, and 10-digit variants missing a leading zero — and they are not interchangeable as strings. All are normalised to canonical 11-digit.

payment_limit is per its dosage unit. J9271 is $60.645 per 1 MG, so a 200 MG administration is 200×. Returning the limit without the unit invites a 200-fold error, so the two always travel together.

drug_price_compare deliberately does no arithmetic between the two benchmarks. NADAC is per pricing unit (EA/ML/GM) and ASP is per HCPCS dosage — different denominators, measured months apart. Dividing one by the other would produce a confident, meaningless "margin".

340B covered entities

b340_covered_entities and b340_contract_pharmacies read a mirror of HRSA OPAIS: 94,363 covered entity registrations (64,413 currently participating) and 398,441 contract-pharmacy links, as of the 2026-09-07 export.

A 340B ID is per SITE, not per organisation — one health system holds many, and parent_id340b links a child site to its registered parent hospital. Roughly a third of the roster is terminated registrations, so both tools exclude them unless you ask; a "no match" answer says so explicitly rather than implying the entity never existed.

type takes plain words — "hospital", "health center", "Ryan White", "critical access" — as well as HRSA's 23 codes, and state takes "Texas" as happily as "TX". An unrecognised value is REFUSED with the list of valid ones rather than silently ignored, because an ignored filter returns a full unfiltered result set with a clean 200, which reads as a real answer.

No contact people. The export carries authorizingOfficial and primaryContact — 94,363 named individuals with direct phone numbers. Neither is ingested and there is no column for them, so no query can return one. See the migration header.

How it stays fresh — a person clicks a button

The 340B data here is refreshed by a human, on purpose, roughly monthly:

  1. .github/workflows/340b-refresh-reminder.yml runs on the 1st and files a fleet task only if no load has landed in 25 days and no such ask is already open.

  2. Someone clicks Covered Entity Daily Export (JSON) at https://340bopais.hrsa.gov/Reports and runs bash scripts/340b-drop.sh ~/Downloads/340B_CoveredEntityJson_Daily_*.zip.

  3. .github/workflows/340b-ingest.yml parses, loads and closes the task.

as_of comes from the export FILENAME, never from the load date, so a file that sat in a Downloads folder for a week cannot overstate freshness. The loader refuses to swap in a roster under 50,000 entities, so a truncated download fails loudly instead of quietly shrinking the data. Every response carries as_of.

Why it is not automated

Specified in fleet #618, probed in #633: the export cannot be fetched by anything we run. Re-verified 2026-09-01 (fleet #804) and 2026-09-07 (fleet #633): still true.

HRSA OPAIS (340bopais.hrsa.gov) is Blazor Server, not client-rendered as first recorded here. /Reports ships _framework/blazor.web.js (a .NET 8+ Blazor Web App), and the SignalR circuit endpoint is live.

The 411 this file used to cite as proof proves nothing — do not repeat that probe. A bodyless POST to any path on the host answers 411 Length Required from the edge, including a path that does not exist. Send an empty body with Content-Length: 0 and the paths separate cleanly (measured 2026-09-01):

PathBodyless POSTPOST -d ''
/_blazor/negotiate?negotiateVersion=1411200 {"negotiateVersion":1,"connectionId":"…","connectionToken":"…","availableTransports":[WebSockets, ServerSentEvents, LongPolling]}
/DXXRDV411404, 0 bytes
/nonexistentpath123411404, 0 bytes

/DXXRDV is worth naming because /Reports loads the DevExpress Web Document Viewer scripts, and that control normally does mount an HTTP controller under that path — an inviting-looking REST door that OPAIS has not opened.

There is no request to reproduce. Loading /Reports in a browser, clearing the network log and clicking Covered Entity Daily Export (JSON) records zero HTTP requests — filtered on everything, and on _blazor specifically. The file comes back over the WebSocket that was already open from page load, and a WebSocket frame is not an HTTP request. HRSA's own FAQ says the same in words: no direct download URL, and "postbacks and button clicks must be done in the context of a current session and view state".

Correction, measured 2026-09-07 (fleet #633, Alaric): the blocker is the STATEFUL CIRCUIT, not the WebSocket. This file used to say a WebSocket transport is "why curl, a Worker and a cron all have nothing to call." That reason is wrong, and it is wrong in the same way as the 411 it replaced — it names a symptom the next re-prober will disprove in one command. negotiate advertises three transports, not one: WebSockets, ServerSentEvents and LongPolling. LongPolling is ordinary HTTP POST/GET, so a Worker can drive this circuit. The reason not to is what a caller would have to do once connected: replay the Blazor

Topics
drug-pricesmcpmcp-servermodel-context-protocolpipeworx
Preguntas frecuentes

Lo que la gente pregunta sobre mcp-drug-prices

¿Qué es pipeworx-io/mcp-drug-prices?

pipeworx-io/mcp-drug-prices es mcp servers para el ecosistema de Claude AI. US drug price benchmarks — what a pharmacy PAYS and what Medicare PAYS OUT. Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-10-21.

¿Cómo se instala mcp-drug-prices?

Puedes instalar mcp-drug-prices clonando el repositorio (https://github.com/pipeworx-io/mcp-drug-prices) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar pipeworx-io/mcp-drug-prices?

Nuestro agente de seguridad ha analizado pipeworx-io/mcp-drug-prices y le ha asignado un Trust Score de 95/100 (tier: Verified). Revisa el desglose completo de comprobaciones superadas y flags en esta página.

¿Quién mantiene pipeworx-io/mcp-drug-prices?

pipeworx-io/mcp-drug-prices es mantenido por pipeworx-io. La última actividad registrada en GitHub es del 2026-10-21, con 0 issues abiertos.

¿Hay alternativas a mcp-drug-prices?

Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.

Alternativas