@pipeworx/ema-article57
Connect: https://pipeworx.io/mcp — every tool in the catalog, including @pipeworx/ema-article57’s. Install: one-click buttons
Connect to just the @pipeworx/ema-article57 pack
https://gateway.pipeworx.io/ema-article57/mcp — only @pipeworx/ema-article57’s own tools, nothing else in the catalog.
No MCP client? Skip the connection: POST https://gateway.pipeworx.io/v1/tools/search_packs {"query":"..."} to find a tool below, GET /v1/tools/<name> for its schema, POST the same URL with arguments for the data — see For AI agents.
Tools: 4
EMA Article 57 product data — every medicine with a valid marketing authorisation anywhere in the European Economic Area (centrally, mutual-recognition, decentralised AND purely nationally authorised), one row per product x authorisation-country, with the marketing-authorisation holder and pharmacovigilance system location for each. Keyless.
This is EMA’s WIDE register. The sibling ema-medicines pack covers only the EU centralised-procedure medicines (~1 row per medicine, richer fields — status, indications, approval dates). Article 57 is the opposite trade-off: thinner fields, but every EEA country’s nationally authorised products too, which ema-medicines explicitly does not carry.
Tools
ema_article57_search(query?, active_substance?, holder?, country?, route?, limit?, offset?)— free-text/filtered search with real offset+limit pagination and atotal_matchescount. Answers “which EEA countries is X authorised in”, “who is the marketing-authorisation holder for X in country Y”, “which products does company Z hold in the EEA”.ema_article57_product(name, country?)— every authorisation-country record for one product by name (exact match preferred, falls back to substring withmatch_typemarked). Article 57 carries no indication text and no per-product URL, so this returns the authoritative EMA source document URL instead of a paraphrased indication.ema_article57_recent_changes(since)— products added to / removed from the register since a date, computed by diffing this pack’s own dated snapshots (the source has no change-date column at all — see Gotchas). Returnscomparable: falsewith a stated reason rather than a guessed empty diff when no snapshot covers the requested window.ema_article57_dataset_info()— the authoritative EMA bulk-download URL, current row count, this pack’s snapshot dates on record, and the full list of distinct authorisation-country values (for exact spelling before filtering bycountry).
Auth
Keyless.
Data sources
- https://www.ema.europa.eu/en/documents/other/article-57-product-data_en.xlsx — the Article 57 database export, linked from EMA’s “Public data from Article 57 database” page. Verified live 2026-09-25: HTTP 200,
Last-Modified: Thu, 24 Sep 2026 13:38:24 GMT, 7,870,020 bytes, 164,314 data rows. The page states the file carries “EMA/518502/2018 Rev. 88” and is updated “periodically” — there is no published cadence, soLast-Modifiedis the only real freshness signal.
Architecture — this pack does NOT fetch EMA live
Measured 2026-09-25 against the real file: 7.87 MB compressed unzips to 63.0 MB of sheet1.xml + 4.7 MB of sharedStrings.xml (164,314 rows x 8 columns). Parsing a much smaller EMA export with the same regex-scan approach (ema-medicines’s ~0.9 MB report) already measured 123 MB of V8 heap; this file is ~9x bigger and would not fit in a Cloudflare Worker’s 128 MB ceiling. So this pack reads a pre-built, pre-compacted dataset from the shared pipeworx-datasets R2 bucket instead of calling EMA per request. scripts/ingest-ema-article57.mjs does the actual fetch + XLSX parse (offline, not in a Worker) and writes:
-
ema-article57/latest.json.gz— all 164,314 rows, dictionary-encoded (schema 2: each column is a table of unique strings plus one integer index per row), 6.35 MB raw / 1.42 MB gzipped, read once per isolate.Why columns, not row objects: schema 1 stored one object per row (29 MB of JSON) and three of the four tools died with Cloudflare error 1102 (resource limits) inside the gateway isolate on 2026-09-25. Every column repeats heavily — 15.9M characters of values, 2.8M unique, 32 countries, ~4,000 holders — so filters are decided once per unique value and looked up per row by index. Measured locally against the real file: search peaks ~33 MB above baseline, recent_changes (two snapshots) ~59 MB, 15 MB held once cached. The pack refuses any other schema rather than misreading it.
-
ema-article57/snapshots/YYYY-MM-DD.json.gz— one dated copy per ingest run, kept forema_article57_recent_changesto diff against. -
ema-article57/snapshots-index.json— small, uncompressed list of snapshot dates on record. -
ema-article57/meta.json— generation metadata.
Re-run the ingest periodically (node scripts/ingest-ema-article57.mjs) to track EMA’s own updates and to give ema_article57_recent_changes more than one snapshot to diff — as shipped, only 2026-09-25 is on record, so recent_changes cannot yet answer for any earlier since date and says so explicitly rather than guessing zero.
Without the gateway’s _r2 binding (a bare npm install of this package) every tool returns {reason: "dataset_unavailable", hint: ...} naming the upstream URL to fetch directly — there is no live fallback, because a live per-request fetch+parse is exactly the thing this architecture note explains is not viable.
Licence — EMA’s Legal Notice explicitly permits this
EMA’s Legal Notice: “Information and documents made available on EMA’s webpages are public and may be reproduced and/or distributed, totally or in part, irrespective of the means and/or the formats used, for non-commercial and commercial purposes, provided that EMA is always acknowledged as the source of the material.” An explicit reuse grant, not silence — building a copy here does not depend on the mirror-needs-grant-proxy-does-not proxy-only fallback.
Schema — eight columns, nothing else
Row 20 is the header (rows 1-19 are a provenance banner + blank rows, same shape as the medicines-output-* reports), rows 21+ are data. In order: product name, active substance, route of administration, authorisation country, marketing authorisation holder, pharmacovigilance system master file location, PV enquiries email, PV enquiries phone.
We keep the first six and drop the PV email and phone before anything is stored. They are contact details — at small holders often a named person’s own address — and this pack serves a copy, not a live proxy, so CLAUDE.md’s personal-data carve-out applies. ema_article57_dataset_info lists them under fields_withheld. Do not add them back without a ruling.
There is no date field, no per-product id, and no per-product URL. Article 57 is a thin regulatory cross-reference (who holds what, where) — not a clinical register. Do not expect indication text, approval dates, or a status field; ema_article57_dataset_info lists exactly what is and is not present (fields / fields_not_present).
Grain — one row per product x country, by design
The same product name legitimately repeats once per EEA country it is authorised in (e.g. “Helixor A” has separate rows for Latvia and Lithuania, each with its own holder/PV contact — in this case the same holder, but that is not guaranteed for every product). ema_article57_product returns every matching row, not one collapsed record, and countries_found lists them.
Country names are EMA’s own English spellings, not ISO codes
country values are free text as EMA publishes them — "Germany", "United Kingdom (Northern Ireland)", "Czech Republic", "European Union" (yes, that’s a real value in the country column, alongside "Iceland", "Liechtenstein", "Norway" for the wider EEA). ema_article57_search’s country filter does an exact (case-insensitive) match, so get the spelling from ema_article57_dataset_info’s distinct_countries first rather than guessing an ISO code or abbreviation.
ema_article57_recent_changes needs at least two snapshots — it has one as shipped
The source publishes no per-row change date at all, so “what changed” can only ever be answered by diffing two of this pack’s own dated snapshots. As shipped there is exactly one (2026-09-25), so every since request currently returns comparable: false, reason: "no_snapshot_at_or_before_since" — correct behaviour, not a bug, and the tool says so rather than returning a fabricated empty diff. Re-running the ingest script on a schedule is what makes this tool answer for real; see “Architecture” above.
Known gap in add-pack.mjs’s automated smoke test for R2-backed packs
scripts/lib/pack-smoke.mjs (used by scripts/add-pack.mjs step 3) calls each tool with only the arguments in smoke.json — it has no mechanism to inject a _r2 binding, so its recorded smoke results for this pack (and for every other injectR2 pack: court-listener, crs-reports, esef-filings, fda-drug-competition) are the honest “no binding” answer (dataset_unavailable), not real data. That payload is not literally empty by payload-emptiness.mjs’s heuristic (it has non-array keys), so the automated gate passes mechanically without ever exercising the real data path. This pack’s tools were independently verified against the live production R2 dataset with a hand-built binding shim before shipping (real search/product/dataset_info/recent_changes calls against 164,314 real rows) — see the fleet #2421 close notes for the transcript. Worth fixing generally in pack-smoke.mjs at some point; out of scope for this pack alone.
Tools
- ema_article57_search — Search the EMA Article 57 database — every medicine with a valid marketing authorisation anywhere
- ema_article57_product — The full Article 57 record(s) for one product by name — every EEA country it is authorised in,
- ema_article57_recent_changes — Products added to or removed from the Article 57 register since a given date, computed by diffing
- ema_article57_dataset_info — Dataset metadata: the authoritative EMA bulk-download URL, current row count, this pack’s snapshot
Tools
ema_article57_dataset_info— Dataset metadata: the authoritative EMA bulk-download URL, current row count, this pack's snapshotema_article57_product— The full Article 57 record(s) for one product by name — every EEA country it is authorised in,ema_article57_recent_changes— Products added to or removed from the Article 57 register since a given date, computed by diffingema_article57_search— Search the EMA Article 57 database — every medicine with a valid marketing authorisation anywhere