The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Swissmedic listing page.
Swissmedic's authorised human medicines register (Switzerland) — the "Zugelassene Arzneimittel HAM" list: every authorised product and each of its authorised dosage strengths, with the marketing-authorisation holder, medicine category, dispensing category (prescription tier) and authorisation dates. Keyless.
Part of Pipeworx — an MCP gateway connecting AI agents to 1683+ live data sources.
swissmedic_search(query?, holder?, category?, dispensing_category?, limit?, offset?) — free-text/filtered search with real offset+limit pagination and a total_matches count. Answers "is X authorised in Switzerland", "who holds the Swiss authorisation for X", "what dispensing category is X in".swissmedic_product(name) — every authorised dosage strength for one product by name (exact match preferred, falls back to substring with match_type marked). The export carries no indication text and no per-product URL, so this returns the authoritative Swissmedic download instead.swissmedic_recent_changes(since) — products first authorised, or with a dosage strength newly authorised, since a date — a real signal from the source's own date columns.swissmedic_dataset_info() — the authoritative bulk-download URL, source page, row count, and the full lists of distinct medicine categories and dispensing categories used by the filters.Keyless.
The file is ~1 MB compressed, ~4.8 MB of sheet XML. That is the same size class the sibling ema-medicines pack already parses live per request without issue, and roughly 9x smaller than the file that made the ema-article57 pack build a local R2-backed copy instead (164,314 rows / 63 MB of sheet XML there, versus 11,086 rows / 4.8 MB here). So swissmedic fetches live, same pattern as ema-medicines: one parsed copy held per isolate for 12 hours (DATASET_TTL_MS) plus a 12-hour Cloudflare edge cache on top — Swissmedic's own monthly update cadence makes anything shorter needless re-downloading of an unchanged file. If a refresh fails while an earlier copy is in hand, the earlier copy is served rather than an error.
Swissmedic's general Legal Notice is more restrictive-sounding than EMA's ("downloading or copying content... requires written consent from the copyright holders" — a page written for the site broadly, not this specific public list). That question does not need resolving here: per the proxy-vs-mirror rule (mirror-needs-grant-proxy-does-not, CLAUDE.md), a pack that calls a public, keyless upstream live on each cache-miss is a client, not a publisher, and needs no explicit grant — only bulk-copying/mirroring the dataset would. This pack does the former (fetch-and-cache, same as ema-medicines), never the latter.
Rows 1-6 are a title block (bilingual German/French, the same shape as most Swiss federal exports), row 7 is the header, rows 8+ are records. Ten columns: authorisation number, dosage-strength number, product name, marketing-authorisation holder, medicine category ("Heilmittelcode" — e.g. "Impfstoffe" = vaccines, "Blutprodukte" = blood products), dispensing category at dosage level, dispensing category at medicine level, first-authorisation date, dosage-authorisation date, validity duration ("unbegrenzt" = unlimited, for most rows in this export).
The two date columns are Excel serial dates (days since 1899-12-30, the Lotus-1-2-3-compatibility epoch Excel still carries) — converted to ISO YYYY-MM-DD on the way out (excelSerialToIso in src/index.ts). Verified against a known row: Vivotif's first_authorisation_date serial converts to 1980-12-09, consistent with its real-world authorisation history.
There is no indication text and no per-product URL beyond the authorisation number. swissmedic_product's response points to the authoritative Swissmedic download rather than paraphrasing an indication that is not in the data.
dispensing_category matches either columnEvery product carries a dispensing category at the medicine level and, separately, one per dosage strength — they usually agree but are not guaranteed to. swissmedic_search's dispensing_category filter matches a row if EITHER column equals the requested value, so a caller does not need to know which level Swissmedic happened to record a mismatch at. Distinct values observed live: A, A+, B, B/D, D, E.
swissmedic_recent_changes uses real dates, unlike ema-article57's sibling toolEMA's Article 57 export (ema-article57 pack) carries no date column at all, so that pack's recent_changes can only diff its own snapshots. This file has two real date columns, so swissmedic_recent_changes filters directly on first_authorisation_date/dosage_authorisation_date >= since — no snapshot history required. Verified live 2026-09-25: since: "2026-01-01" returned 384 real changes, including dosage-level re-authorisations from August 2026.
src/xlsx.ts is a copy of mcps/ema-medicines/src/xlsx.ts (the hand-rolled zip+XML reader) rather than a shared dependency — both files are small and a Worker pack shouldn't pull in a shared xlsx-parsing package for what is, on each side, one regex-based scanner over one file shape. Keep them in sync by eye if the underlying approach ever needs to change; the lazy [^>]*? attribute match in CELL_RE is load-bearing (see the comment above it) and must not be "simplified" to greedy.
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
tools/list at https://gateway.pipeworx.io/swissmedic/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:
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 account needed for the first calls. Inspect any tool: GET https://gateway.pipeworx.io/v1/tools/swissmedic_search. Find one: POST https://gateway.pipeworx.io/v1/tools/search_packs with {"query":"..."}.
This package also runs as a local stdio MCP server — no Pipeworx account, no gateway round-trip:
Or run it directly to confirm it starts:
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.
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:
The gateway picks the right tool and fills the arguments automatically.
MIT