OpenFDA
This server provides FDA drug information lookup and safety reporting via seven tools.
Search by drug name: Get brand name, generic name, manufacturer, NDC, route, substance, and usage/safety details.
Search by generic name: Find all brand versions of a drug using its active ingredient.
Get adverse event reports: Retrieve reported side effects and reactions for a drug, optionally filtered by seriousness.
Search by manufacturer: List drugs produced by a specific company.
Get safety information: Retrieve warnings, contraindications, interactions, and precautions.
Lookup by NDC: Search using product NDC, package NDC, or NDC without dashes.
Lookup by product NDC: Find all package variations for a specific product NDC.
Provides a way for users to support the developer through the Buy Me A Coffee platform, with both a link and QR code included in the README.
Used for loading environment variables from a .env file, specifically for storing and accessing the OpenFDA API key securely.
The repository is hosted on GitHub, allowing users to clone the source code for local development and modification.
The MCP server is distributed as an npm package that can be installed and run using npx.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OpenFDA what are the side effects of ibuprofen?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OpenFDA MCP Server
A Model Context Protocol (MCP) server for querying drug information from the OpenFDA API.
Features
2.0.0 registers one tool per openFDA drug endpoint, each with the same shape:
a field + value search, limit/skip for paging, detail for the
returned record shape, and (where the endpoint supports it) sort and
count for ordering and frequency aggregation. Adding an endpoint means
adding a row here, not rewriting the pattern.
drug-label— Search FDA structured product labels (SPL): prescribing and OTC drug info.field:drug_name(brand/generic/substance, tiered),ndc,spl_product_data_elements,effective_time,id,set_id,brand_name,generic_name,substance_name,manufacturer_name,route,product_type,application_number,unii,rxcui.detail:summary(default; identity plus the safety narrative, every field present, empty if absent),safety(warnings, contraindications, interactions and overdosage — see the migration table below for the 1.x tool this replaces),full(the raw upstream record).count:openfda.route.exact,openfda.product_type.exact,openfda.manufacturer_name.exact.sort:effective_time:desc/effective_time:asc.limitdefault 1, max 25.drug-event— Search FAERS adverse event reports (voluntarily submitted side-effect reports; not evidence of causation).field:drug_name(unionspatient.drug.openfda.generic_name,patient.drug.openfda.substance_nameandpatient.drug.medicinalproduct),brand_name,manufacturer_name,product_ndc,pharm_class,drug_characterization,indication,reaction,reaction_outcome,serious,seriousness_death,patient_sex,reporter_qualification,country,received_date,report_id.detail:summary(default; one row per report, FAERS codes decoded, (reaction, outcome) pairs deduplicated),full. Also takesseriousness(serious/non-serious/all, defaultall).sort:receivedate:desc/receivedate:asc; withoutsort, results are a deterministic earliest-report_idslice.count:patient.reaction.reactionmeddrapt.exact,patient.reaction.reactionoutcome,serious,patient.patientsex,occurcountry.exact,patient.drug.openfda.generic_name.exact(see the migration table below for the 1.x tool this replaces).limitdefault 10, max 50. Note: the searchablereceived_datefield maps toreceivedate, but thesummaryprojection's returnedreport_datereadsreceiptdate— two different, near-duplicate FAERS date fields. Filtering by one and reading the other back will not, in general, show the same date.drug-drugsfda— Search Drugs@FDA application data: approvals, sponsors, products and submissions.field:products.brand_name(default drug-name search, 98.79% populated),application_number,sponsor_name(stored uppercase upstream; normalised automatically),products.active_ingredients.name,products.dosage_form,products.route,products.marketing_status,products.reference_drug,products.te_code,openfda.brand_name,openfda.generic_name,openfda.substance_name,openfda.manufacturer_name,openfda.route,openfda.product_ndc(theopenfda.*names are the openFDA-harmonised spelling of the same identifiers, but populated on only ~42% of applications — the precise alternative toproducts.brand_name, not the default),submissions.submission_type,submissions.submission_status,submissions.submission_status_date,submissions.submission_class_code,submissions.review_priority.detail:summary(default; application number, sponsor,openfdablock andproducts— each product carrieste_code,nullwhen absent — plus asubmission_count, with nosubmissionsarray),full(addssubmissions, capped at 10 per record, plussubmissions_truncatedwhen more were omitted).count:sponsor_name,products.marketing_status,products.dosage_form.exact.limitdefault 5, max 100. Seven search paths openFDA publishes on this endpoint are deliberately not exposed here — see Migrating from 1.x below.drug-ndc— Search the NDC Directory: every drug product currently listed with the FDA (packaging, labeler, marketing category and application number). This is the product registry, distinct fromdrug-label'sndcfield, which searches labelling text by NDC — see the migration table below for the 1.x NDC lookup this tool is not a replacement for.field:product_ndc,packaging.package_ndc,generic_name,brand_name,active_ingredients.name,openfda.manufacturer_name,marketing_category,application_number,dosage_form,route,product_type,pharm_class,marketing_start_date,openfda.unii,openfda.rxcui,openfda.spl_set_id.detail:summary(default; identity, packaging and marketing status),full(raw upstream record).count:dosage_form.exact,route.exact,product_type.exact,marketing_category,openfda.manufacturer_name.exact.limitdefault 5, max 50.drug-enforcement— Search FDA drug recall and enforcement reports.classificationis the hazard level (Class I: reasonable probability of serious harm or death; II: temporary or reversible harm; III: unlikely harm) andstatussays whether a recall is Ongoing, Completed or Terminated — a recall appearing in results does not mean it is still in effect.field:product_description(default drug-name search, 100% populated),recall_number,event_id,code_info,recalling_firm,reason_for_recall,classification,status,voluntary_mandated,state,country,recall_initiation_date,report_date,termination_date,openfda.generic_name,openfda.brand_name,openfda.product_ndc(the last three are exact but populated on only ~18% of recalls — the precise alternative toproduct_description, not the default).detail:summary(default; every field above except the threeopenfda.*names, which are bundled as oneopenfdaobject),full(raw upstream record).count:classification.exact,status.exact,state.exact,voluntary_mandated.exact,recalling_firm.exact.sort:report_date:desc/report_date:asc/recall_initiation_date:desc.limitdefault 5, max 50.drug-orangebook— Search the Orange Book: FDA-approved drug products with their therapeutic-equivalence ratings. Almost all data lives in the nestedproductsarray, which this tool flattens into one entry per product.field:products.brand_name,products.active_ingredients.name,products.application_number,products.application_type,products.application_full_name,products.application_name,products.therapeutic_equivalence_codes,products.reference_listed_drug,products.reference_standard,products.dosage_form,products.route,approval_date.detail:summary(default;approval_date,product_numberand the flattenedproductsarray —reference_listed_drugandreference_standardare booleans always returned,falsea fact rather than a missing value),full(raw upstream record).count:products.application_type,products.dosage_form.exact,products.route.exact,products.therapeutic_equivalence_codes.sort:approval_date:desc/approval_date:asc.limitdefault 5, max 50.drug-shortages— Search FDA drug shortage reports.statusis one of three values, live-verified 2026-09-21:Current(1153 records),To Be Discontinued(443), orResolved(7) — a product appearing here is not necessarily short now, andTo Be Discontinuedis neither "current" nor "resolved" but the larger of the two non-Currentstates. openFDA sends an empty string, notnull, for an absent date on this endpoint; this tool normalises those tonull.field:generic_name(default),company_name,openfda.manufacturer_name,openfda.brand_name,openfda.substance_name,package_ndc,openfda.product_ndc,status,therapeutic_category,dosage_form,update_type,initial_posting_date,update_date.detail:summary(default; theopenfda.*names are bundled as oneopenfdaobject),full(raw upstream record).count:status,dosage_form.exact,therapeutic_category,company_name.exact.sort:update_date:desc/update_date:asc/initial_posting_date:desc.limitdefault 10, max 50. Smallest drug dataset (~1,600 records); a coverage percentage here represents far fewer records than the same percentage elsewhere.
Every tool's response envelope carries matched_via (which field path
actually matched), total (the upstream match count, not the number of
records in this response), returned (how many records it does carry),
limit, dropped_for_budget (how many rows were dropped to stay within the
60,000-character response budget — 0 when none were, never omitted),
next_skip (the offset to resume paging from; null when the result set is
exhausted or the next offset would exceed SKIP_MAX) and results.
Page by next_skip, not skip + limit — the budget can drop trailing
rows, so skip + limit silently steps over exactly the rows that were
dropped. A search that matches nothing returns a plain no-results message,
not an error.
limit means two different things depending on whether count is set: for
a record search it caps rows returned, capped at that tool's max below;
for an aggregation it caps buckets, defaulting to 100 (openFDA's own bucket
ceiling) when omitted. This is an asymmetry on every tool except
drug-drugsfda (whose record max is already 100): omitting limit under
count can return up to 100 buckets, but an explicit limit is still
rejected above the tool's record max — so a caller can receive more
buckets than it can explicitly request. Record maxes: drug-label 25,
drug-event 50, drug-drugsfda 100, drug-ndc 50, drug-enforcement 50,
drug-orangebook 50, drug-shortages 50.
An aggregated response is trimmed to the same 60,000-character budget as a
record response, and reports dropped_for_budget for the buckets it dropped
— returned counts the buckets actually kept. It carries no next_skip:
an aggregation has no result total and no skip semantics, so an offset to
resume from would be a number with nothing behind it.
Route vocabularies differ across tools.
drug-label'sroutefield (openfda.route, the SPL route of administration) anddrug-drugsfda'sproducts.routefield (the Drugs@FDA product route) are different controlled vocabularies. The same insulin glargine product is reported asSUBCUTANEOUSin one andINJECTIONin the other, so joining or filtering on route across tools will silently miss matches.
Set up your OpenFDA API Key
The server reads
OPENFDA_API_KEYfrom its process environment. It is launched by your MCP client, so the key belongs in theenvblock of your client configuration (shown below) — a.envfile is not read.Get a key from OpenFDA API Key Registration. A key raises your limit from 40 to 240 requests per minute.
Without a key, every tool call returns a configuration error rather than failing confusingly upstream. To run on the unauthenticated tier anyway, set
OPENFDA_ALLOW_KEYLESS=1— note that tier reports no rate-limit headers, so exhausting it surfaces as slow, intermittent failures.Note: Never commit your real API key to version control.
Example MCP Server Configuration
If you are integrating this server with a larger MCP system, your configuration might look like:
{ "mcpServers": { "openfda": { "command": "npx", "args": [ "-y", "@ythalorossy/openfda" ], "env": { "OPENFDA_API_KEY": "*****************************************" }, "timeout": 60000, "autoApprove": [ "drug-label", "drug-event", "drug-drugsfda", "drug-ndc", "drug-enforcement", "drug-orangebook", "drug-shortages" ] } } }Replace the asterisks with your actual API key.
Related MCP server: drugbank-mcp-server
Want to run it locally?
git clone https://github.com/ythalorossy/openfda.git
cd openfda
npm install
npm run buildThen start the server:
node dist/index.jsOr use it directly with npx:
npx @ythalorossy/openfdaConfiguration
Export OPENFDA_API_KEY in your shell before running locally: export OPENFDA_API_KEY=your_key.
Migrating from 1.x
2.0.0 replaces the nine get-* tools with one tool per openFDA drug endpoint.
Pin 1.3.0 if you are not ready to migrate.
1.x tool | 2.0.0 call |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
drug-drugsfda field names carry no section prefix. 1.x grouped fields
under a section parameter (application, products, submissions,
openfda), so the table row above is illustrative, not literal: real field
names are flat where 1.x had a section for the top-level application
fields — application_number and sponsor_name carry no prefix — while
products.*, submissions.* and openfda.* keep theirs. A caller who
copies application.sponsor_name from 1.x muscle memory gets an
unknown-field error; the correct value is sponsor_name.
get-drug-by-ndc did not search /drug/ndc.json. It searched labels
by openfda.product_ndc, so it maps to drug-label ({ field: "ndc" }),
not to drug-ndc. drug-ndc searches the actual NDC Directory — new
capability this server did not previously expose — and returns different
data than the label-based lookup 1.x actually performed: use drug-ndc for
packaging, labeler and marketing-category questions, and drug-label for
label text keyed off an NDC.
get-drug-by-product-ndc returned a pre-filtered available_packages
— the label's package NDCs filtered down to the product you searched.
drug-label's summary detail returns package_ndc unfiltered, so on
a label that covers several products it mixes packages from all of them.
Nothing is lost: summary also returns product_ndc[], so filtering by
prefix is a one-line operation on the caller's side, and
active_ingredient, purpose and dosage_and_administration are reachable
via detail: "full" — but the convenience of a pre-filtered list is gone.
Seven Drugs@FDA search paths available in 1.x are deliberately not
exposed on drug-drugsfda: openfda.application_number (a 42%-populated
duplicate of the 100%-populated application_number), products.product_number
and submissions.submission_number (per-application ordinals that match
tens of thousands of unrelated records corpus-wide), and all four
submissions.application_docs.{id,url,date,type} (opaque per-document
values you must already possess in order to search by them). See
docs/superpowers/notes/2026-09-20-field-selection.md (## drugsfda →
"Deliberately not exposed") for the full reasoning. All seven remain
reachable through the field-catalog resource a later release publishes, so
nothing becomes unqueryable — they are simply no longer in the field enum.
Other behaviour changes:
Search-value injection is fixed. Every 1.x tool interpolated the caller's search value straight into the query string. A crafted value could append a clause and make the server report data for a different drug than the one named in its own
matched_via— verified live:openfda.brand_name:"Advil"returns 39 records,openfda.brand_name:"Tylenol"returns 111, and the injected combination of the two returns 150. 2.0.0 escapes every value, and query assembly is confined to one file with a test enforcing it.A search that matches nothing now returns a plain no-results message. Previously openFDA's HTTP 404 was reported as
isError: truewith "Failed to retrieve…", which was indistinguishable from an outage.'Unknown'placeholder strings are gone. Absent values arenullor[], so a placeholder can no longer be mistaken for data.get-drug-safety-info's scalardrug_nameis now the arraybrand_name(ondrug-label'sdetail: "safety").Response headers are uniform across tools; the emoji/prose headers are gone.
Every response is capped at 60,000 characters, dropping trailing records and saying how many, rather than returning an unusable wall of text.
License
MIT

Available Tools
7 toolsget-drug-adverse-eventsB
Get adverse event reports for a drug. This provides safety information about reported side effects and reactions. Use brand name or generic name.
Inputs: drugName (required), limit, seriousness.
get-drug-by-generic-nameA
Get drug information by generic (active ingredient) name. Useful when you know the generic name but not the brand name. Returns all brand versions of the generic drug.
Inputs: genericName (required), limit.
get-drug-by-nameA
Get drug by name. Use this tool to get the drug information by name. The drug name should be the brand name. It returns the brand name, generic name, manufacturer name, product NDC, product type, route, substance name, indications and usage, warnings, do not use, ask doctor, ask doctor or pharmacist, stop use, pregnancy or breast feeding.
Inputs: drugName (required).
get-drug-by-ndcB
Get drug information by National Drug Code (NDC). Accepts both product NDC (XXXXX-XXXX) and package NDC (XXXXX-XXXX-XX) formats. Also accepts NDC codes without dashes.
Inputs: ndcCode (required).
get-drug-by-product-ndcA
Get drug information by product NDC only (XXXXX-XXXX format). This ignores package variations and finds all packages for a product.
Inputs: productNDC (required).
get-drug-safety-infoB
Get comprehensive safety information for a drug including warnings, contraindications, drug interactions, and precautions. Use brand name.
Inputs: drugName (required).
get-drugs-by-manufacturerA
Get all drugs manufactured by a specific company. Useful for finding alternatives or checking manufacturer portfolios.
Inputs: manufacturerName (required), limit.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 7 tools
The tools have overlapping purposes that could cause confusion, particularly between get-drug-by-name, get-drug-by-generic-name, and get-drug-by-ndc, which all retrieve drug information but with different input parameters. Descriptions help clarify the distinctions, but an agent might misselect when the query is ambiguous about whether to use brand name, generic name, or NDC. The safety-focused tools (get-drug-adverse-events and get-drug-safety-info) are more distinct, but overall there is moderate overlap in the information retrieval tools.
All tool names follow a consistent verb_noun pattern with hyphens, specifically 'get-drug-by-X' or 'get-drug-X', where X is a specific attribute like name, ndc, or safety-info. This predictability makes it easy for an agent to understand the naming convention and infer tool purposes. There are no deviations in style, such as mixing camelCase or snake_case, ensuring high readability and consistency throughout the set.
With 7 tools, the server is well-scoped for its domain of drug information retrieval from OpenFDA. Each tool serves a distinct purpose, such as looking up drugs by different identifiers or accessing safety data, and none appear redundant or unnecessary. This count is typical for a focused API server, providing enough functionality without being overwhelming or too sparse, making it appropriate for the server's purpose.
The tool set covers the core domain of drug information retrieval comprehensively, including lookups by brand name, generic name, NDC, manufacturer, and safety data. Minor gaps exist, such as the lack of update or delete operations, but this is reasonable since OpenFDA is a read-only public database. Agents can work around these gaps, and the surface supports common queries without dead ends, though advanced features like filtering or pagination might be missing but are not essential for basic coverage.
Maintenance
Related MCP Connectors
DrugBank MCP — wraps the DrugBank Clinical API (api.drugbank.com)
RxNorm MCP — wraps the NLM RxNav REST API (free, no auth)
MCP server exposing supplements database used by iNutriPlan.com
MCP server for US nursing facility search and ownership lookup (NursingHomeDatabase).
Related MCP Servers
- FlicenseBqualityDmaintenanceA comprehensive MCP server that provides access to U.S. FDA public datasets via the openFDA API, enabling querying of drug adverse events, labeling, recalls, approvals, shortages, NDC directory, and medical device regulatory data.1026-
- AlicenseNot gradedqualityDmaintenanceModel Context Protocol server providing access to the comprehensive DB pharmaceutical database with 17,430+ drugs, supporting search, drug details, interactions, and more.11MIT
- FlicenseNot gradedqualityCmaintenanceA Model Context Protocol (MCP) server exposing the FDA Purple Book — the database of licensed biological products (reference biologics, biosimilars, and interchangeables), with BLA numbers, applicants, approval dates, and exclusivity expiry.-
- AlicenseAqualityDmaintenanceAn MCP server that enables AI assistants to query U.S. FDA public datasets, including drug adverse events, labeling, recalls, approvals, shortages, NDC directory, and medical device regulatory information.10MIT