Metadata-Version: 2.4
Name: openfda-mcp
Version: 0.1.0
Summary: MCP server for FDA drug and device regulatory metadata via openFDA (CDER, CBER, CDRH).
Project-URL: Homepage, https://github.com/Black-Swan-Causal-Labs/openfda-mcp
Project-URL: Repository, https://github.com/Black-Swan-Causal-Labs/openfda-mcp
Project-URL: Issues, https://github.com/Black-Swan-Causal-Labs/openfda-mcp/issues
Author: Black Swan Causal Labs
License: MIT License
        
        Copyright (c) 2026 Black Swan Causal Labs
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: fda,mcp,medical-devices,model-context-protocol,openfda,pharmacoepidemiology,real-world-evidence,regulatory
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Healthcare Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.10
Requires-Dist: fastmcp>=2.0
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == 'dev'
Description-Content-Type: text/markdown

<!-- mcp-name: com.blackswancausallabs/openfda-mcp -->

# openFDA MCP

An MCP server exposing FDA regulatory metadata — drugs, biologics, and medical
devices — through the [openFDA](https://open.fda.gov/) API.

Built by [Black Swan Causal Labs](https://blackswancausallabs.com) as the identifier-resolution
layer for a real-world-evidence (RWE) case roster: given an FDA application
number or a product name, resolve it to authoritative regulatory metadata.

## Why this exists

There are other openFDA MCP servers, and several are broader. This one is
narrow on purpose: it is the instrument that resolved the application numbers in
a specific published RWE dataset, and it exists so that dataset can name the
tool that produced it.

That matters more than it might sound. Whether `BLA 125123/2058` resolves to a
particular product, or `DEN160026` to a particular device class, is a decision
made by a piece of software — and a different wrapper can yield a different
roster. "We used openFDA" is not a sufficient methods statement; **"openfda-mcp
v0.1.0"** is. If you use this in research, pin the version.

Its practical edge over a general openFDA client is the **device half**:
resolving a CDRH submission number to a risk class takes a three-hop chain
(number → product code → classification) with two non-obvious traps, both
handled here.

## Tools

**Drugs and biologics** (`/drug/*` — CDER, CBER)

| Tool | Purpose |
|---|---|
| `search_drug_label` | Search SPL label text, optionally scoped to a section |
| `lookup_drugsfda_application` | Drugs@FDA record for an NDA/BLA/ANDA number |
| `resolve_drug_to_application` | Brand or generic name → application number(s) |
| `screen_for_rwe_signals` | **Experimental.** Sweep labels for RWE signals |

**Devices** (`/device/*` — CDRH)

| Tool | Purpose |
|---|---|
| `lookup_device_submission` | K / DEN / P / H number → device record |
| `classify_device_product_code` | Product code → device class + medical specialty |
| `validate_device_application` | Full chain: number → class, specialty, category |

## Install

```bash
pip install openfda-mcp
```

Add to your MCP client config:

```json
{
  "mcpServers": {
    "openfda": {
      "command": "openfda-mcp",
      "env": { "OPENFDA_API_KEY": "${OPENFDA_API_KEY:-}" }
    }
  }
}
```

**The API key is optional.** Without one, openFDA allows 40 requests/min and
1,000/day, which is enough for interactive use. A [free
key](https://open.fda.gov/apis/authentication/) raises it to 240/min and
120,000/day — worth having for bulk sweeps.

## Two findings worth knowing

Both were established empirically and are not obvious from FDA's docs.

**De Novo grants live in the 510(k) endpoint.** `DEN######` numbers are stored
in the `k_number` field of `/device/510k`. There is no De Novo endpoint, and
looking for one leads to the wrong conclusion that De Novo numbers can't be
resolved. They can.

**HDE numbers are not in openFDA at all.** Neither the 510(k) nor the PMA
endpoint carries `H######`, so no product code — and therefore no classification
— is retrievable. This server still reports `device_class: "III"` for them, by
regulatory inference: HDE is by definition the pathway for devices that would
otherwise require a PMA. `medical_specialty` stays null, because that one really
is unavailable, and `device_class_source` says which is which.

## Transient failures are never silent

A genuine absence and a failed request are different things, and this package
keeps them different:

- **not found** (HTTP 404, or 200 with no results) → returns `None`; safe to cache
- **transient failure** (timeout, connection error, 429, 5xx) → retried with
  backoff, then raises `OpenFDATransientError`; **never** cache this
- **rejected request** (other 4xx) → raises `OpenFDARequestError`

This is a direct response to a real defect: an earlier version swallowed every
exception and returned `None`, so a single read timeout on one application
number was cached as a real miss and silently blanked two fields on that record
for weeks. Cached failures are indistinguishable from real absences, which makes
them the worst kind of silent data loss.

## On `screen_for_rwe_signals`

It is **unvalidated**. There is no ground-truth oracle for a discovery sweep, and
below the strongest hits the results are dominated by applications whose labels
use "registry" in an unrelated sense. Treat its output as candidates for human
review — not as a finding, and not as a count to report. Establishing recall
against a held-out set of known cases is open work.

## Development

```bash
pip install -e ".[dev]"
pytest              # unit tests, offline
pytest -m live      # live checks against api.fda.gov
```

Live tests assert against known-good fixtures (`K203571` → class II Ophthalmic,
`DEN160026` → class II Immunology, `BLA761180` → LEO Pharma) so a change on
FDA's side surfaces as a test failure rather than as quietly wrong data.

## License

MIT
