@Cyanheads/Openfda Server
БесплатноНе проверенQuery FDA data on drugs, food, devices, and recalls via openFDA. Provides 12 tools for searching adverse events, drug labels, recalls, and more.
Описание
Query FDA data on drugs, food, devices, and recalls via openFDA. Provides 12 tools for searching adverse events, drug labels, recalls, and more.
README
@cyanheads/openfda-mcp-server
Query FDA data on drugs, food, devices, and recalls via openFDA. STDIO or Streamable HTTP.
Public Hosted Server: https://openfda.caseyjhand.com/mcp
Overview
FDA data on drugs, food, devices, and recalls from the openFDA public API. Search adverse events, recalls, drug approvals, and device clearances; look up NDC codes and drug labels; aggregate field counts across any endpoint. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
| Tool | Description |
|---|---|
openfda_drug_profile |
One drug name → consolidated FDA profile: identity, label, adverse events, recalls, approval, shortage |
openfda_search_adverse_events |
Search adverse event reports across drugs, food, and devices |
openfda_search_animal_events |
Search adverse event reports for veterinary drugs and devices |
openfda_search_drug_shortages |
Search FDA drug shortage records — status, availability, therapeutic category, manufacturer |
openfda_search_tobacco_reports |
Search problem reports for tobacco products, e-cigarettes, and vaping devices |
openfda_search_recalls |
Search enforcement reports and recall actions across drugs, food, and devices |
openfda_count_values |
Aggregate and tally unique values for any field across any openFDA endpoint |
openfda_describe_fields |
Return searchable field paths for an openFDA endpoint, grouped by category |
openfda_get_drug_label |
Look up FDA drug labeling (package inserts / SPL documents) |
openfda_search_drug_approvals |
Search the Drugs@FDA database for NDA/ANDA application approvals |
openfda_search_device_clearances |
Search FDA device premarket notifications — 510(k) clearances and PMA approvals |
openfda_lookup_ndc |
Look up drugs in the NDC (National Drug Code) Directory |
openfda_dataframe_query |
Run read-only SQL over a result set staged on a DataCanvas (opt-in) |
openfda_dataframe_describe |
List tables and column schemas staged on a DataCanvas (opt-in) |
Capability reference
openfda_drug_profile tool
- Resolves a brand or generic drug name to canonical FDA identifiers (generic name, NDC, RxCUI, SPL set ID) once, then keys every sub-query off that identity — avoids the identifier drift that breaks naive tool chaining
- Fans out in parallel across
drug/label,drug/event,drug/enforcement,drug/drugsfda, anddrug/shortages; each section (label,adverse_events,recalls,approval,shortage) is best-effort and returnsnullon a miss rather than failing the call - A single-drug query resolves only to a single-ingredient product, never a combination
degraded[]names any section whose sub-query failed upstream (rate limit, 5xx, query error) — a section listed there is unknown, not confirmed absent- Auth, configuration, and cancellation failures abort the whole call rather than degrading silently
openfda_search_adverse_events tool
categoryselectsdrug,food, ordevice— each returns a different field schemalimitup to 1000 andskipup to openFDA's 25000-record ceiling (past it: typedpagination_limit_reached); the page is also bounded by a shared ~24 KB serialized-byte budget —drug/eventreports run tens of KB each against a few hundred bytes forfood/event, so an oversized page returns fewer records than requested and reports the cut viapage_omitted- Sortable date field is category-specific (
receivedatefor drug,date_createdfor food,date_receivedfor device) — a field from another category causes a query error - Optional
stage: true(orcanvas_id) drains the matched set onto a DataCanvas table for SQL viaopenfda_dataframe_query
openfda_search_animal_events tool
- Covers FDA Center for Veterinary Medicine reports — animal species/breed/age/weight, drug, VeDDRA reaction terms, outcome
limitup to 1000, bounded by the shared ~24 KB page-byte budget (page_omittedreports any cut);skipcapped at 25000- Optional
stage: true(orcanvas_id) stages the matched set for SQL viaopenfda_dataframe_query - Filter examples:
animal.species,drug.brand_name,reaction.veddra_term_name,serious_ae
openfda_search_drug_shortages tool
- Filter by
status(Current/Resolved),therapeutic_category,generic_name, orcompany_name - Each record's
openfdablock carriesbrand_name,product_ndc, andrxcuifor chaining intoopenfda_get_drug_labeloropenfda_lookup_ndc limitup to 1000, bounded by the shared ~24 KB page-byte budget;skipcapped at 25000- Optional
stage: true(orcanvas_id) for DataCanvas SQL viaopenfda_dataframe_query
openfda_search_tobacco_reports tool
- Filter by
tobacco_products,reported_health_problems,reported_product_problems, ornonuser_affected - Each report carries
number_tobacco_products/number_health_problems/number_product_problemscounts alongside the arrays limitup to 1000, bounded by the shared ~24 KB page-byte budget;skipcapped at 25000- Optional
stage: true(orcanvas_id) for DataCanvas SQL viaopenfda_dataframe_query
openfda_search_recalls tool
category(drug/food/device) plusendpoint—enforcementcovers all categories,recallis device-only and rejects a non-device category as a typedrecall_endpoint_non_deviceerror- Filter by
classification(Class I/II/III),recalling_firm,reason_for_recall,status limitup to 1000, bounded by the shared ~24 KB page-byte budget — a device record runs several KB against roughly one for drug/food- Optional
stage: true(orcanvas_id) for DataCanvas SQL viaopenfda_dataframe_query
openfda_count_values tool
- Works across all 20 openFDA endpoints (drug, food, device, animal/veterinary, tobacco, other) — the same set
openfda_describe_fieldscovers counttakes a dotted field path; append.exactfor whole-phrase counting on analyzed text fields — identifier fields already indexed as keywords (product_ndc,application_number,pma_number) reject.exactasnot_aggregatable- Optional
searchscopes the aggregation; returns up to 1000 top terms ranked by count descending - Pairs with the search/label/recall tools when sample records help interpret an aggregate
- Runs against the live API even when the local bulk mirror is enabled — a partial mirror can't produce complete aggregates
openfda_describe_fields tool
- Covers all 20 cataloged openFDA endpoints — the same set
openfda_count_valuesaccepts - Returns field paths grouped by category, each with type and a one-line description, plus
queryTipscovering quoting, AND/OR,.exact, and date-range syntax - Call before constructing a
searchquery — field paths differ per endpoint and aren't derivable from a tool's own schema
openfda_get_drug_label tool
searchtargets label fields (openfda.brand_name,openfda.generic_name,openfda.manufacturer_name, orset_idfor a specific SPL revision); defaultlimit5, up to 1000- A page over the ~24 KB inline budget returns
kind: "outline"— section names and their serialized size, largest first — instead of label text; re-call withsections: [...]for the ones needed - Outline sizes are summed across the whole page, so cost scales with
limit; asectionsselection is always returned whole even when it overflows the budget, with its size disclosed sectionsnarrows each record to the requested keys plus identity metadata (openfda,set_id,id,effective_time,version)skipcapped at openFDA's 25000-record pagination ceiling
openfda_search_drug_approvals tool
- Filter by brand/generic name (
openfda.brand_name),sponsor_name(stored uppercase — a lowercase quoted value matches nothing), orsubmissions.submission_type/submissions.review_priority - Each record carries the application's full submission history, so
limitup to 1000 is bounded by the shared ~24 KB page-byte budget — a long-running application is an order of magnitude larger than a recent one page_omittedreports any cut with the routes to the rest;skipcapped at 25000- Optional
stage: true(orcanvas_id) for DataCanvas SQL viaopenfda_dataframe_query
openfda_search_device_clearances tool
pathwayselects510k(174K+ records, most common) orpma(higher-risk devices) — one pathway per call- Filter by
applicant,product_code,advisory_committee_description, oropenfda.device_name limitup to 1000, bounded by the shared ~24 KB page-byte budget — a 510(k) record carries a summary narrative and runs several times the size of a PMA record- Optional
stage: true(orcanvas_id) for DataCanvas SQL viaopenfda_dataframe_query
openfda_lookup_ndc tool
- Search by
product_ndc,brand_name,generic_name,openfda.manufacturer_name, oractive_ingredients.name - Pair with
openfda_get_drug_labelvia the returnedbrand_nameorset_idto read the package insert limitup to 1000, bounded by the shared ~24 KB page-byte budget — a product with many packaging configurations is several times the size of one with a single package- Optional
stage: true(orcanvas_id) for DataCanvas SQL viaopenfda_dataframe_query;skipcapped at 25000
openfda_dataframe_query tool
- Runs a single read-only
SELECTagainst a table staged by a search tool'sstage: true— DDL, DML, COPY, and file-reading functions are rejected - Scalar fields are stored as text (
CASTfor numeric math); nested openFDA objects/arrays are JSON columns, queryable with DuckDB JSON functions - Results are capped at the canvas row limit;
truncated: truemeans page the rest withORDER BYplusLIMIT/OFFSET - Requires
CANVAS_PROVIDER_TYPE=duckdband the optional@duckdb/node-apidependency — errorscanvas_disabledotherwise
openfda_dataframe_describe tool
- Lists every table on a canvas by
canvas_id— name, kind (table/view), full staged row count (not the inline preview count), and column name/DuckDB-type/nullable for each - Nested openFDA objects/arrays are stored as JSON columns — query them with DuckDB JSON functions
- Call before
openfda_dataframe_queryto get exact table and column names - Errors
canvas_disabledwhenCANVAS_PROVIDER_TYPEis unset,canvas_not_foundwhen thecanvas_idhas expired or never existed
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.
openFDA-specific:
- Generic API client for all 20 openFDA endpoints with retry (exponential backoff) and rate-limit awareness
- Automatic error normalization — 404 returns empty results, 429/5xx retries, 400 surfaces an actionable message
- Optional API key — works without one (1K requests/day), increases to 120K/day with a free key
- Optional DataCanvas staging (
CANVAS_PROVIDER_TYPE=duckdb, per call withstage: true) — stage large result sets as DuckDB tables and run SQL viaopenfda_dataframe_query - Optional local bulk mirror (
OPENFDA_MIRROR_ENABLED=true) — a self-refreshing SQLite copy of four drug datasets that answers exact-key lookups without spending API budget, with live fallback
Agent-friendly output:
- Byte-budget disclosure — oversized pages are bounded by a shared ~24 KB serialized budget and disclosed via
page_omitted/page_byteson bothcontent[]andstructuredContent, never silently truncated and never emptied to zero records - Typed failure contracts —
errors[]declarations keyctx.failby reason (rate_limited,query_error,pagination_limit_reached,canvas_disabled, ...) so callers can branch onerror.data.reasoninstead of parsing messages - Best-effort degradation —
openfda_drug_profilereturnsnullper section on a miss rather than failing the whole call, and names which sections failed upstream (vs. genuinely absent) indegraded[] - Empty-result guidance — a no-match search returns a notice pointing at
openfda_describe_fieldsand broader query terms rather than a bare empty array
Getting started
Public Hosted Instance
A public instance is available at https://openfda.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"openfda-mcp-server": {
"type": "streamable-http",
"url": "https://openfda.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file:
{
"mcpServers": {
"openfda-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/openfda-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"OPENFDA_API_KEY": "your-key-here"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"openfda-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/openfda-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"OPENFDA_API_KEY": "your-key-here"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"openfda-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/openfda-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
Prerequisites
- Bun v1.3.0 or higher.
- Optional: openFDA API key for higher rate limits (120K requests/day vs 1K/day).
Installation
- Clone the repository:
git clone https://github.com/cyanheads/openfda-mcp-server.git
- Navigate into the directory:
cd openfda-mcp-server
- Install dependencies:
bun install
Configuration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE |
Transport: stdio or http |
stdio |
MCP_HTTP_PORT |
HTTP server port | 3010 |
MCP_AUTH_MODE |
Authentication: none, jwt, or oauth |
none |
MCP_LOG_LEVEL |
Log level (debug, info, warning, error, etc.) |
info |
LOGS_DIR |
Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE |
Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 |
in-memory |
OPENFDA_API_KEY |
Free API key from open.fda.gov. Increases daily limit from 1K to 120K requests. | none |
OPENFDA_BASE_URL |
Base URL override for testing against a proxy or mock. | https://api.fda.gov |
OPENFDA_MIRROR_ENABLED |
Answer exact-key lookups from a local copy of the openFDA bulk downloads instead of the API. See Local bulk mirror. | false |
OPENFDA_MIRROR_PATH |
Directory holding one SQLite file per mirrored dataset. | ./data/openfda-mirror |
OPENFDA_MIRROR_REFRESH_CRON |
Cron expression for the in-process mirror refresh (HTTP transport only). Unset means no scheduled refresh. | none |
OPENFDA_MIRROR_FALLBACK_LIVE |
Fall back to the live API when the mirror is cold, missing the record, or failing. | true |
OPENFDA_MIRROR_REFRESH_TIMEOUT_MS |
Wall-clock budget for one refresh before it is aborted. | 21600000 (6h) |
OPENFDA_MIRROR_BASE_URL |
Host serving the bulk download manifest (download.json). |
https://api.fda.gov |
CANVAS_PROVIDER_TYPE |
Set to duckdb to enable DataCanvas staging — analytical SQL over result sets staged with stage: true and queried via openfda_dataframe_query. Requires the optional @duckdb/node-api dependency. |
none (disabled) |
OTEL_ENABLED |
Enable OpenTelemetry | false |
See .env.example for the full list of optional overrides.
Local bulk mirror
openFDA publishes whole-dataset JSON dumps alongside the API. With OPENFDA_MIRROR_ENABLED=true the server keeps a local SQLite copy of four of them — drug/label, drug/ndc, drug/enforcement, drug/drugsfda — and answers eligible lookups from it, leaving the API budget for everything else.
The mirror is deliberately narrow. openFDA's search runs server-side in Elasticsearch, which tokenises and ranks; a local corpus cannot reproduce that. A query is answered locally only when all of the following hold, and is sent to the API otherwise:
- the search is a single quoted
field:"value"term — no boolean operators, wildcards, or ranges; - the field is one of
id,set_id,product_id,product_ndc,recall_number,event_id,application_number, and the value is a whole identifier in its canonical spelling and case; - there is no
countand nosort, andskipis 0; - the value matches exactly one record.
The last condition is what keeps a mirrored answer identical to the API's rather than merely equivalent. Four of the seven lookup fields are primary keys and always match one record. The other three — set_id, product_ndc, event_id — can address several, and openFDA returns those in relevance order, which a local corpus cannot recompute; such a lookup routes to the API whatever the requested page size.
openfda_count_values therefore always runs against the API — a partial mirror would return plausible but incomplete aggregates.
The initial harvest runs out-of-band, never at startup:
bun run mirror:init # all four datasets
bun run mirror:init drug/enforcement # one dataset (~3.8 MB compressed)
bun run mirror:status # sync state per dataset
bun run mirror:verify # integrity check + row counts
bun run mirror:refresh # re-harvest datasets whose dump has advanced
openFDA publishes no incremental API for these endpoints, so a refresh re-reads the whole dump and tombstones records the new export no longer carries. It is idempotent and resumable — re-running after an interrupt continues from the persisted cursor. Set OPENFDA_MIRROR_REFRESH_CRON to run it in-process on the HTTP transport; on stdio, run bun run mirror:refresh from the host.
meta.lastUpdated on a mirrored response reports the last_updated stamp of the dump being served, which can differ from the live API's — the API index and the published dumps advance on separate schedules.
On Node, install the optional better-sqlite3 peer dependency; Bun uses its built-in bun:sqlite. OPENFDA_MIRROR_REFRESH_CRON additionally needs the optional node-cron peer dependency — without it the server refuses to start rather than run with a schedule it cannot honour.
Running the server
Local development
Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdioRun checks and tests:
bun run devcheck # Lints, formats, type-checks, and more bun run test # Runs the test suite
Docker
docker build -t openfda-mcp-server .
docker run --rm -p 3010:3010 openfda-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/openfda-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them. Mount a volume over /usr/src/app/data/openfda-mirror to persist an OPENFDA_MIRROR_ENABLED=true harvest across container replacement.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts |
Entry point — createApp() with tool registration and service setup. |
src/config/ |
Server-specific env var parsing and validation with Zod. |
src/services/openfda/ |
openFDA API client with retry, rate-limit handling, and error normalization. |
src/services/openfda/mirror/ |
Opt-in local bulk mirror — dataset registry, dump reader, sync ingester, and the query gate that decides mirror vs live. |
src/services/canvas/ |
DataCanvas accessor — resolves the active canvas provider for staging and SQL. |
src/mcp-server/tools/definitions/ |
Tool definitions (*.tool.ts). Fourteen openFDA tools. |
tests/ |
Unit and integration tests mirroring src/. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging - Register new tools in
src/mcp-server/tools/definitions/index.ts - Validate raw upstream data → normalize to the output schema → never fabricate a field openFDA didn't return
Data attribution
Data is served from openFDA, a U.S. Food and Drug Administration service. Under the openFDA license the data is dedicated to the public domain under CC0 1.0, with one exception: GMDN® device-classification content — Term Code, Term Name, and Term Definition — is licensed from The GMDN Agency, and redistributing it or using it to train AI requires a separate licence from the Agency.
The local mirror therefore covers drug datasets only. device/classification and every other device endpoint are excluded from it, and the ingester rejects any record carrying a GMDN-bearing field rather than writing it to disk. Extending the mirror to device data requires clearing that licence first.
FDA does not endorse this project. Do not rely on openFDA to make decisions regarding medical care.
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
Установка @Cyanheads/Openfda Server
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/cyanheads/openfda-mcp-serverFAQ
@Cyanheads/Openfda Server MCP бесплатный?
Да, @Cyanheads/Openfda Server MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для @Cyanheads/Openfda Server?
Нет, @Cyanheads/Openfda Server работает без API-ключей и переменных окружения.
@Cyanheads/Openfda Server — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить @Cyanheads/Openfda Server в Claude Desktop, Claude Code или Cursor?
Открой @Cyanheads/Openfda Server на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
автор: duxiaohuiSupabase
Database, auth and storage
автор: SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare @Cyanheads/Openfda Server with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
