@pipeworx/ema-article57
MCP serverDev toolsEMA Article 57 product data, every medicine with a valid marketing
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the ask pipeworx tool from @pipeworx/ema-article57
Install @pipeworx/ema-article57
The server’s own address, for the clients that take one directly. Or connect ahel onceand every client you use reads it from one address, with the account kept on ahel rather than in each client’s config.
Claude Code
claude mcp add --transport http --scope user pipeworx-ema-article57 'https://gateway.pipeworx.io/ema-article57/mcp'Run it once in your project, then open /mcp to approve any sign-in the server asks for.
Claude Desktop
https://gateway.pipeworx.io/ema-article57/mcpAdd a custom connector in Settings, paste this address, and approve the sign-in.
Cursor
cursor://anysphere.cursor-deeplink/mcp/install?name=pipeworx-ema-article57&config=eyJ1cmwiOiJodHRwczovL2dhdGV3YXkucGlwZXdvcnguaW8vZW1hLWFydGljbGU1Ny9tY3AifQ==Open the link and Cursor adds the server at that address.
ChatGPT
https://gateway.pipeworx.io/ema-article57/mcpIn Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.
Codex
codex mcp add pipeworx-ema-article57 --url 'https://gateway.pipeworx.io/ema-article57/mcp'Run it once, then sign in with codex mcp login pipeworx-ema-article57 if the server asks for an account.
From the project's README
As published by pipeworx-io/mcp-ema-article57 in README.md.
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.
Part of Pipeworx — an MCP gateway connecting AI agents to 1683+ live data sources.
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— the full 164,314-row array, gzip (39.5 MB raw, 3.6 MB gzipped), read by every tool call.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.
Quick Start
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
{
"mcpServers": {
"ema-article57": {
"url": "https://gateway.pipeworx.io/ema-article57/mcp"
}
}
}
What this endpoint actually serves
tools/list at https://gateway.pipeworx.io/ema-article57/mcp returns the tools in the table
above plus the shared Pipeworx meta-tools — ask_pipeworx,
discover_tools, search_within, remember/recall and the rest of the
gateway-wide set. So the tool count you see is larger than this table: a
single-pack endpoint currently lists roughly 30 shared tools alongside the
pack's own. The connection's initialize response states its exact scope, and
is the authoritative answer for a given day.
This is deliberate, not multiplexing by accident. The meta-tools are what let a
scoped connection answer a question this pack does not cover — via
ask_pipeworx, which routes across the whole catalog — without you adding a
second MCP server. There is currently no way to mount a pack endpoint without
them; if the extra schemas cost you more context than the routing is worth,
connect to the full gateway once rather than to several pack endpoints.
Or connect to the full Pipeworx gateway to get every pack's tools listed directly, instead of just this one's:
{
"mcpServers": {
"pipeworx": {
"url": "https://gateway.pipeworx.io/mcp"
}
}
}
Both URLs reach the same gateway and the same 1683+ data sources. The
only difference is which pack's tools are listed directly; ask_pipeworx
reaches all of them from either one.
No MCP client? Call it over HTTP
curl -X POST https://gateway.pipeworx.io/v1/tools/ema_article57_search \
-H 'Content-Type: application/json' \
-d '{"query":"Helixor","limit":5}'
No account needed for the first calls. Inspect any tool: GET https://gateway.pipeworx.io/v1/tools/ema_article57_search. Find one: POST https://gateway.pipeworx.io/v1/tools/search_packs with {"query":"..."}.
Standalone (no gateway account)
This package also runs as a local stdio MCP server — no Pipeworx account, no gateway round-trip:
{
"mcpServers": {
"ema-article57": {
"command": "npx",
"args": ["-y", "@pipeworx/mcp-ema-article57"]
}
}
}
Or run it directly to confirm it starts:
npx -y @pipeworx/mcp-ema-article57
It speaks MCP over stdin/stdout and answers initialize/tools/list/tools/call
for only this pack's tools — none of the shared meta-tools the gateway
connection above adds. Same source, same tools, no ask_pipeworx routing.
Using with ask_pipeworx
Instead of calling tools directly, you can ask questions in plain English — this works on the pack endpoint above as well as on the full gateway:
ask_pipeworx({ question: "your question about Ema Article57 data" })
The gateway picks the right tool and fills the arguments automatically.
More
License
MIT
Tools it offers (40)
What this server listed when ahel dialed its public endpoint in Sep 2026, with no key and no account of yours. The names are the server’s own.
ask_pipeworxask_pipeworx_betaask_pipeworx_groundedsearch_withindeep_researchdiscover_toolsresolve_entitycompare_entitiessubscribeunsubscribelist_subscriptionsrecent_alertsentity_profilerecent_changesvalidate_claimcompany_factsscan_dependencybet_researchpolymarket_arbitragepolymarket_edgespolymarket_kalshi_spreadpolymarket_fill_riskpolymarket_edge_trackerkalshi_weather_edgeresolution_auditresolution_diffrelease_calendar_marketspipeworx_trendingsuggest_questionsgenerate_llms_txt
Signals
- Last commit
- Sep 2026
Advanced
- Delivery
- ema-article57 MCP server → your ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
io-github-pipeworx-io-ema-article57- Source
- github.com/pipeworx-io/mcp-ema-article57
- Hosted endpoint
https://gateway.pipeworx.io/ema-article57/mcp