fda-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fda_mcp-0.1.0/.claude/settings.json +5 -0
- fda_mcp-0.1.0/.claude/settings.local.json +6 -0
- fda_mcp-0.1.0/.gitignore +10 -0
- fda_mcp-0.1.0/CLAUDE.md +62 -0
- fda_mcp-0.1.0/LICENSE +21 -0
- fda_mcp-0.1.0/PKG-INFO +291 -0
- fda_mcp-0.1.0/README.md +263 -0
- fda_mcp-0.1.0/pyproject.toml +47 -0
- fda_mcp-0.1.0/src/fda_mcp/__init__.py +1 -0
- fda_mcp-0.1.0/src/fda_mcp/config.py +25 -0
- fda_mcp-0.1.0/src/fda_mcp/documents/__init__.py +1 -0
- fda_mcp-0.1.0/src/fda_mcp/documents/fetcher.py +94 -0
- fda_mcp-0.1.0/src/fda_mcp/documents/urls.py +82 -0
- fda_mcp-0.1.0/src/fda_mcp/errors.py +58 -0
- fda_mcp-0.1.0/src/fda_mcp/openfda/__init__.py +1 -0
- fda_mcp-0.1.0/src/fda_mcp/openfda/client.py +108 -0
- fda_mcp-0.1.0/src/fda_mcp/openfda/endpoints.py +90 -0
- fda_mcp-0.1.0/src/fda_mcp/openfda/summarizer.py +500 -0
- fda_mcp-0.1.0/src/fda_mcp/resources/__init__.py +1 -0
- fda_mcp-0.1.0/src/fda_mcp/resources/endpoints_resource.py +19 -0
- fda_mcp-0.1.0/src/fda_mcp/resources/field_definitions.py +813 -0
- fda_mcp-0.1.0/src/fda_mcp/resources/query_syntax.py +44 -0
- fda_mcp-0.1.0/src/fda_mcp/server.py +21 -0
- fda_mcp-0.1.0/src/fda_mcp/tools/__init__.py +1 -0
- fda_mcp-0.1.0/src/fda_mcp/tools/count.py +47 -0
- fda_mcp-0.1.0/src/fda_mcp/tools/decision_documents.py +42 -0
- fda_mcp-0.1.0/src/fda_mcp/tools/fields.py +44 -0
- fda_mcp-0.1.0/src/fda_mcp/tools/search.py +273 -0
- fda_mcp-0.1.0/tests/__init__.py +0 -0
- fda_mcp-0.1.0/tests/conftest.py +379 -0
- fda_mcp-0.1.0/tests/integration/__init__.py +0 -0
- fda_mcp-0.1.0/tests/integration/test_live_documents.py +41 -0
- fda_mcp-0.1.0/tests/integration/test_live_openfda.py +165 -0
- fda_mcp-0.1.0/tests/test_document_fetcher.py +142 -0
- fda_mcp-0.1.0/tests/test_document_urls.py +111 -0
- fda_mcp-0.1.0/tests/test_endpoints.py +62 -0
- fda_mcp-0.1.0/tests/test_error_handling.py +153 -0
- fda_mcp-0.1.0/tests/test_openfda_client.py +142 -0
- fda_mcp-0.1.0/tests/test_resources.py +117 -0
- fda_mcp-0.1.0/tests/test_summarizer.py +232 -0
- fda_mcp-0.1.0/tests/test_tool_count_records.py +86 -0
- fda_mcp-0.1.0/tests/test_tool_decision_documents.py +108 -0
- fda_mcp-0.1.0/tests/test_tool_list_fields.py +94 -0
- fda_mcp-0.1.0/tests/test_tool_search_adverse_events.py +64 -0
- fda_mcp-0.1.0/tests/test_tool_search_device_submissions.py +76 -0
- fda_mcp-0.1.0/tests/test_tool_search_device_udi.py +39 -0
- fda_mcp-0.1.0/tests/test_tool_search_drug_labels.py +49 -0
- fda_mcp-0.1.0/tests/test_tool_search_drug_shortages.py +38 -0
- fda_mcp-0.1.0/tests/test_tool_search_drugs.py +50 -0
- fda_mcp-0.1.0/tests/test_tool_search_other.py +56 -0
- fda_mcp-0.1.0/tests/test_tool_search_recalls.py +82 -0
- fda_mcp-0.1.0/tests/test_tool_search_substances.py +63 -0
- fda_mcp-0.1.0/uv.lock +1093 -0
fda_mcp-0.1.0/.gitignore
ADDED
fda_mcp-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
FDA MCP — an MCP server providing access to FDA data via the OpenFDA API. Wraps all 21 OpenFDA endpoints plus FDA decision document retrieval into 12 MCP tools optimized for LLM consumption.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
- **Install dependencies**: `uv sync --all-extras`
|
|
12
|
+
- **Run server**: `uv run fda-mcp`
|
|
13
|
+
- **Run unit tests**: `uv run pytest`
|
|
14
|
+
- **Run integration tests** (hits real FDA API): `uv run pytest -m integration`
|
|
15
|
+
- **Run specific test file**: `uv run pytest tests/test_endpoints.py -v`
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
- **Language**: Python 3.11+, async throughout
|
|
20
|
+
- **MCP SDK**: `mcp` with FastMCP decorator API
|
|
21
|
+
- **HTTP Client**: `httpx` (async)
|
|
22
|
+
- **PDF Extraction**: `pdfplumber` + `pytesseract`/`pdf2image` OCR fallback
|
|
23
|
+
- **Transport**: stdio (standard for Claude Desktop / Claude Code)
|
|
24
|
+
|
|
25
|
+
### Source Layout
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
src/fda_mcp/
|
|
29
|
+
├── server.py -- FastMCP server entry point
|
|
30
|
+
├── config.py -- Environment-based configuration
|
|
31
|
+
├── errors.py -- Custom error types
|
|
32
|
+
├── openfda/ -- OpenFDA API client layer
|
|
33
|
+
│ ├── endpoints.py -- Enum of all 21 endpoints
|
|
34
|
+
│ ├── client.py -- Async HTTP client with rate limiting
|
|
35
|
+
│ └── summarizer.py -- Response summarization per endpoint
|
|
36
|
+
├── documents/ -- FDA decision document retrieval
|
|
37
|
+
│ ├── urls.py -- URL pattern construction
|
|
38
|
+
│ └── fetcher.py -- PDF download + text extraction
|
|
39
|
+
├── tools/ -- 12 MCP tool handlers
|
|
40
|
+
│ ├── search.py -- 9 search tools
|
|
41
|
+
│ ├── count.py -- count_records tool
|
|
42
|
+
│ ├── fields.py -- list_searchable_fields tool
|
|
43
|
+
│ └── decision_documents.py
|
|
44
|
+
└── resources/ -- 3 MCP resources
|
|
45
|
+
├── query_syntax.py
|
|
46
|
+
├── endpoints_resource.py
|
|
47
|
+
└── field_definitions.py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
- `OPENFDA_API_KEY` — optional API key for higher rate limits (240 req/min vs 40)
|
|
53
|
+
- `OPENFDA_TIMEOUT` — request timeout in seconds (default: 30)
|
|
54
|
+
- `OPENFDA_MAX_CONCURRENT` — max concurrent API requests (default: 4)
|
|
55
|
+
- `FDA_PDF_TIMEOUT` — PDF download timeout in seconds (default: 60)
|
|
56
|
+
- `FDA_PDF_MAX_LENGTH` — default max text chars from PDFs (default: 8000)
|
|
57
|
+
|
|
58
|
+
## Code Standards
|
|
59
|
+
|
|
60
|
+
- Python 3.11+ with type hints throughout
|
|
61
|
+
- Async functions for all I/O operations
|
|
62
|
+
- Tests use pytest + pytest-asyncio + respx for HTTP mocking
|
fda_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Limecooler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fda_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fda-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server providing access to FDA data via the OpenFDA API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: fda,healthcare,llm,mcp,openfda
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: httpx>=0.27.0
|
|
18
|
+
Requires-Dist: mcp[cli]>=1.26.0
|
|
19
|
+
Requires-Dist: pdf2image>=1.17.0
|
|
20
|
+
Requires-Dist: pdfplumber>=0.11.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
Requires-Dist: pytesseract>=0.3.10
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: respx>=0.22.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# FDA MCP Server
|
|
30
|
+
|
|
31
|
+
An MCP (Model Context Protocol) server that provides LLM-optimized access to FDA data through the [OpenFDA API](https://open.fda.gov/) and direct FDA document retrieval. Covers all 21 OpenFDA endpoints plus regulatory decision documents (510(k) summaries, De Novo decisions, PMA approval letters).
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **12 MCP tools** covering drug, device, food, and other FDA data
|
|
36
|
+
- **3 MCP resources** for query syntax help, endpoint reference, and field discovery
|
|
37
|
+
- **All 21 OpenFDA endpoints** organized into 9 search tools by outcome, not endpoint
|
|
38
|
+
- **FDA decision documents** — downloads and extracts text from 510(k) summaries, De Novo decisions, PMA approvals, SSEDs, and supplements
|
|
39
|
+
- **OCR fallback** for scanned PDF documents (older FDA submissions)
|
|
40
|
+
- **Context-efficient responses** — summarized output, field discovery on demand, pagination guidance
|
|
41
|
+
- **Optional API key** support for higher rate limits
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
### Search Tools (9)
|
|
46
|
+
|
|
47
|
+
| Tool | Endpoints | Discriminator |
|
|
48
|
+
|------|-----------|---------------|
|
|
49
|
+
| `search_adverse_events` | drug/event, device/event, food/event | `product_type` |
|
|
50
|
+
| `search_recalls` | drug/enforcement, device/enforcement, food/enforcement, device/recall | `product_type`, `source` |
|
|
51
|
+
| `search_drug_labels` | drug/label | — |
|
|
52
|
+
| `search_drugs` | drug/drugsfda, drug/ndc | `source` |
|
|
53
|
+
| `search_drug_shortages` | drug/shortage | — |
|
|
54
|
+
| `search_device_submissions` | device/510k, device/pma, device/classification, device/registrationlisting | `submission_type` |
|
|
55
|
+
| `search_device_udi` | device/udi | — |
|
|
56
|
+
| `search_substances` | other/substance, other/unii, other/nsde | `source` |
|
|
57
|
+
| `search_other` | other/historicaldocument, device/covid19serology | `dataset` |
|
|
58
|
+
|
|
59
|
+
All search tools accept `search` (OpenFDA query string), `limit`, `skip`, and `sort` parameters.
|
|
60
|
+
|
|
61
|
+
### Cross-Cutting Tools (3)
|
|
62
|
+
|
|
63
|
+
| Tool | Purpose |
|
|
64
|
+
|------|---------|
|
|
65
|
+
| `count_records` | Aggregation queries on any endpoint. Returns counts with percentages and narrative summary. |
|
|
66
|
+
| `list_searchable_fields` | Returns searchable field names for any endpoint. Keeps field docs out of tool descriptions. |
|
|
67
|
+
| `get_decision_document` | Fetches FDA regulatory decision PDFs and extracts text. Supports 510(k), De Novo, PMA, SSED, and supplement documents. |
|
|
68
|
+
|
|
69
|
+
### Resources (3)
|
|
70
|
+
|
|
71
|
+
| URI | Content |
|
|
72
|
+
|-----|---------|
|
|
73
|
+
| `fda://reference/query-syntax` | OpenFDA query syntax: AND/OR/NOT, wildcards, date ranges, exact matching |
|
|
74
|
+
| `fda://reference/endpoints` | All 21 endpoints with descriptions |
|
|
75
|
+
| `fda://reference/fields/{endpoint}` | Per-endpoint field reference |
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone <repo-url> fda-mcp
|
|
83
|
+
cd fda-mcp
|
|
84
|
+
uv sync
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Optional: OCR support for scanned PDFs
|
|
88
|
+
|
|
89
|
+
Many older FDA documents (pre-2010) are scanned images. To extract text from these:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# macOS
|
|
93
|
+
brew install tesseract poppler
|
|
94
|
+
|
|
95
|
+
# Linux (Debian/Ubuntu)
|
|
96
|
+
apt install tesseract-ocr poppler-utils
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Without these, the server will still work — it returns a helpful message when it encounters a scanned document it can't read.
|
|
100
|
+
|
|
101
|
+
## Usage with Claude Desktop
|
|
102
|
+
|
|
103
|
+
Add to your `claude_desktop_config.json`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"fda": {
|
|
109
|
+
"command": "uv",
|
|
110
|
+
"args": ["--directory", "/absolute/path/to/fda-mcp", "run", "fda-mcp"],
|
|
111
|
+
"env": {
|
|
112
|
+
"OPENFDA_API_KEY": "your-key-here"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The `OPENFDA_API_KEY` is optional. Without it, you get 40 requests/minute. With a key (free from [open.fda.gov](https://open.fda.gov/apis/authentication/)), you get 240 requests/minute.
|
|
120
|
+
|
|
121
|
+
### Config file location
|
|
122
|
+
|
|
123
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
124
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
125
|
+
|
|
126
|
+
## Usage with Claude Code
|
|
127
|
+
|
|
128
|
+
Add the server to your project's `.claude/settings.json` or global settings:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"mcpServers": {
|
|
133
|
+
"fda": {
|
|
134
|
+
"command": "uv",
|
|
135
|
+
"args": ["--directory", "/absolute/path/to/fda-mcp", "run", "fda-mcp"],
|
|
136
|
+
"env": {
|
|
137
|
+
"OPENFDA_API_KEY": "your-key-here"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or add it interactively with `/mcp` in Claude Code.
|
|
145
|
+
|
|
146
|
+
## Example Queries
|
|
147
|
+
|
|
148
|
+
Once connected, you can ask Claude things like:
|
|
149
|
+
|
|
150
|
+
- "Search for adverse events related to OZEMPIC"
|
|
151
|
+
- "Find all Class I device recalls from 2024"
|
|
152
|
+
- "What are the most common adverse reactions reported for LIPITOR?"
|
|
153
|
+
- "Get the 510(k) summary for K213456"
|
|
154
|
+
- "Search for PMA approvals for cardiovascular devices"
|
|
155
|
+
- "How many drug recalls has Pfizer had? Break down by classification."
|
|
156
|
+
- "Find the drug label for metformin and summarize the warnings"
|
|
157
|
+
- "What COVID-19 serology tests has Abbott submitted?"
|
|
158
|
+
|
|
159
|
+
## Configuration
|
|
160
|
+
|
|
161
|
+
All configuration is via environment variables:
|
|
162
|
+
|
|
163
|
+
| Variable | Default | Description |
|
|
164
|
+
|----------|---------|-------------|
|
|
165
|
+
| `OPENFDA_API_KEY` | *(none)* | API key for higher rate limits (240 vs 40 req/min) |
|
|
166
|
+
| `OPENFDA_TIMEOUT` | `30` | HTTP request timeout in seconds |
|
|
167
|
+
| `OPENFDA_MAX_CONCURRENT` | `4` | Max concurrent API requests |
|
|
168
|
+
| `FDA_PDF_TIMEOUT` | `60` | PDF download timeout in seconds |
|
|
169
|
+
| `FDA_PDF_MAX_LENGTH` | `8000` | Default max text characters extracted from PDFs |
|
|
170
|
+
|
|
171
|
+
## OpenFDA Query Syntax
|
|
172
|
+
|
|
173
|
+
The `search` parameter on all tools uses OpenFDA query syntax:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
# AND
|
|
177
|
+
patient.drug.openfda.brand_name:"ASPIRIN"+AND+serious:1
|
|
178
|
+
|
|
179
|
+
# OR (space = OR)
|
|
180
|
+
brand_name:"ASPIRIN" brand_name:"IBUPROFEN"
|
|
181
|
+
|
|
182
|
+
# NOT
|
|
183
|
+
NOT+classification:"Class III"
|
|
184
|
+
|
|
185
|
+
# Date ranges
|
|
186
|
+
decision_date:[20230101+TO+20231231]
|
|
187
|
+
|
|
188
|
+
# Wildcards (trailing only, min 2 chars)
|
|
189
|
+
device_name:pulse*
|
|
190
|
+
|
|
191
|
+
# Exact matching (required for count queries)
|
|
192
|
+
patient.reaction.reactionmeddrapt.exact:"Nausea"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Use `list_searchable_fields` or the `fda://reference/query-syntax` resource for the full reference.
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Install with dev dependencies
|
|
201
|
+
uv sync --all-extras
|
|
202
|
+
|
|
203
|
+
# Run unit tests (157 tests, no network)
|
|
204
|
+
uv run pytest
|
|
205
|
+
|
|
206
|
+
# Run integration tests (hits real FDA API)
|
|
207
|
+
OPENFDA_TIMEOUT=60 uv run pytest -m integration
|
|
208
|
+
|
|
209
|
+
# Run a specific test file
|
|
210
|
+
uv run pytest tests/test_endpoints.py -v
|
|
211
|
+
|
|
212
|
+
# Start the server directly
|
|
213
|
+
uv run fda-mcp
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Project Structure
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
src/fda_mcp/
|
|
220
|
+
├── server.py # FastMCP server entry point
|
|
221
|
+
├── config.py # Environment-based configuration
|
|
222
|
+
├── errors.py # Custom error types
|
|
223
|
+
├── openfda/
|
|
224
|
+
│ ├── endpoints.py # Enum of all 21 endpoints
|
|
225
|
+
│ ├── client.py # Async HTTP client with rate limiting
|
|
226
|
+
│ └── summarizer.py # Response summarization per endpoint
|
|
227
|
+
├── documents/
|
|
228
|
+
│ ├── urls.py # FDA document URL construction
|
|
229
|
+
│ └── fetcher.py # PDF download + text extraction + OCR
|
|
230
|
+
├── tools/
|
|
231
|
+
│ ├── search.py # 9 search tool handlers
|
|
232
|
+
│ ├── count.py # count_records tool
|
|
233
|
+
│ ├── fields.py # list_searchable_fields tool
|
|
234
|
+
│ └── decision_documents.py
|
|
235
|
+
└── resources/
|
|
236
|
+
├── query_syntax.py # Query syntax reference
|
|
237
|
+
├── endpoints_resource.py
|
|
238
|
+
└── field_definitions.py
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Test Structure
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
tests/
|
|
245
|
+
├── test_endpoints.py # Endpoint enum
|
|
246
|
+
├── test_openfda_client.py # HTTP client
|
|
247
|
+
├── test_summarizer.py # Response summarizers (21 endpoints)
|
|
248
|
+
├── test_document_urls.py # URL construction
|
|
249
|
+
├── test_document_fetcher.py # PDF extraction + OCR
|
|
250
|
+
├── test_tool_search_*.py # 9 search tool test files
|
|
251
|
+
├── test_tool_count_records.py # Count tool
|
|
252
|
+
├── test_tool_list_fields.py # Fields tool
|
|
253
|
+
├── test_tool_decision_documents.py # Document tool
|
|
254
|
+
├── test_resources.py # MCP resources
|
|
255
|
+
├── test_error_handling.py # Error cases
|
|
256
|
+
└── integration/
|
|
257
|
+
├── test_live_openfda.py # Live API (21 endpoints)
|
|
258
|
+
└── test_live_documents.py # Live PDF fetch
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## How It Works
|
|
262
|
+
|
|
263
|
+
### Context Efficiency
|
|
264
|
+
|
|
265
|
+
The server is designed to minimize context window usage when used by LLMs:
|
|
266
|
+
|
|
267
|
+
1. **Response summarization** — Each endpoint type has a custom summarizer that extracts key fields and flattens nested structures. Drug labels truncate sections to 2,000 chars. PDF text defaults to 8,000 chars.
|
|
268
|
+
|
|
269
|
+
2. **Field discovery via tool** — Instead of listing all searchable fields in tool descriptions (which would cost ~8,000-11,000 tokens of persistent context), the `list_searchable_fields` tool provides them on demand.
|
|
270
|
+
|
|
271
|
+
3. **Smart pagination** — Default page sizes are low (10 records). Responses include `total_results`, `showing`, and `has_more`. When results exceed 100, a tip suggests using `count_records` for aggregation.
|
|
272
|
+
|
|
273
|
+
4. **Count enrichment** — `count_records` returns values with pre-calculated percentages and a one-sentence narrative summary.
|
|
274
|
+
|
|
275
|
+
### FDA Decision Documents
|
|
276
|
+
|
|
277
|
+
These documents are **not** available through the OpenFDA API. The server constructs URLs and fetches directly from `accessdata.fda.gov`:
|
|
278
|
+
|
|
279
|
+
| Document Type | URL Pattern |
|
|
280
|
+
|--------------|-------------|
|
|
281
|
+
| 510(k) summary | `https://www.accessdata.fda.gov/cdrh_docs/reviews/{K_NUMBER}.pdf` |
|
|
282
|
+
| De Novo decision | `https://www.accessdata.fda.gov/cdrh_docs/reviews/{DEN_NUMBER}.pdf` |
|
|
283
|
+
| PMA approval | `https://www.accessdata.fda.gov/cdrh_docs/pdf{YY}/{P_NUMBER}A.pdf` |
|
|
284
|
+
| PMA SSED | `https://www.accessdata.fda.gov/cdrh_docs/pdf{YY}/{P_NUMBER}B.pdf` |
|
|
285
|
+
| PMA supplement | `https://www.accessdata.fda.gov/cdrh_docs/pdf{YY}/{P_NUMBER}S{###}A.pdf` |
|
|
286
|
+
|
|
287
|
+
Text extraction uses `pdfplumber` for machine-generated PDFs, with automatic OCR fallback via `pytesseract` + `pdf2image` for scanned documents.
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT
|
fda_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# FDA MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that provides LLM-optimized access to FDA data through the [OpenFDA API](https://open.fda.gov/) and direct FDA document retrieval. Covers all 21 OpenFDA endpoints plus regulatory decision documents (510(k) summaries, De Novo decisions, PMA approval letters).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **12 MCP tools** covering drug, device, food, and other FDA data
|
|
8
|
+
- **3 MCP resources** for query syntax help, endpoint reference, and field discovery
|
|
9
|
+
- **All 21 OpenFDA endpoints** organized into 9 search tools by outcome, not endpoint
|
|
10
|
+
- **FDA decision documents** — downloads and extracts text from 510(k) summaries, De Novo decisions, PMA approvals, SSEDs, and supplements
|
|
11
|
+
- **OCR fallback** for scanned PDF documents (older FDA submissions)
|
|
12
|
+
- **Context-efficient responses** — summarized output, field discovery on demand, pagination guidance
|
|
13
|
+
- **Optional API key** support for higher rate limits
|
|
14
|
+
|
|
15
|
+
## Tools
|
|
16
|
+
|
|
17
|
+
### Search Tools (9)
|
|
18
|
+
|
|
19
|
+
| Tool | Endpoints | Discriminator |
|
|
20
|
+
|------|-----------|---------------|
|
|
21
|
+
| `search_adverse_events` | drug/event, device/event, food/event | `product_type` |
|
|
22
|
+
| `search_recalls` | drug/enforcement, device/enforcement, food/enforcement, device/recall | `product_type`, `source` |
|
|
23
|
+
| `search_drug_labels` | drug/label | — |
|
|
24
|
+
| `search_drugs` | drug/drugsfda, drug/ndc | `source` |
|
|
25
|
+
| `search_drug_shortages` | drug/shortage | — |
|
|
26
|
+
| `search_device_submissions` | device/510k, device/pma, device/classification, device/registrationlisting | `submission_type` |
|
|
27
|
+
| `search_device_udi` | device/udi | — |
|
|
28
|
+
| `search_substances` | other/substance, other/unii, other/nsde | `source` |
|
|
29
|
+
| `search_other` | other/historicaldocument, device/covid19serology | `dataset` |
|
|
30
|
+
|
|
31
|
+
All search tools accept `search` (OpenFDA query string), `limit`, `skip`, and `sort` parameters.
|
|
32
|
+
|
|
33
|
+
### Cross-Cutting Tools (3)
|
|
34
|
+
|
|
35
|
+
| Tool | Purpose |
|
|
36
|
+
|------|---------|
|
|
37
|
+
| `count_records` | Aggregation queries on any endpoint. Returns counts with percentages and narrative summary. |
|
|
38
|
+
| `list_searchable_fields` | Returns searchable field names for any endpoint. Keeps field docs out of tool descriptions. |
|
|
39
|
+
| `get_decision_document` | Fetches FDA regulatory decision PDFs and extracts text. Supports 510(k), De Novo, PMA, SSED, and supplement documents. |
|
|
40
|
+
|
|
41
|
+
### Resources (3)
|
|
42
|
+
|
|
43
|
+
| URI | Content |
|
|
44
|
+
|-----|---------|
|
|
45
|
+
| `fda://reference/query-syntax` | OpenFDA query syntax: AND/OR/NOT, wildcards, date ranges, exact matching |
|
|
46
|
+
| `fda://reference/endpoints` | All 21 endpoints with descriptions |
|
|
47
|
+
| `fda://reference/fields/{endpoint}` | Per-endpoint field reference |
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone <repo-url> fda-mcp
|
|
55
|
+
cd fda-mcp
|
|
56
|
+
uv sync
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Optional: OCR support for scanned PDFs
|
|
60
|
+
|
|
61
|
+
Many older FDA documents (pre-2010) are scanned images. To extract text from these:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# macOS
|
|
65
|
+
brew install tesseract poppler
|
|
66
|
+
|
|
67
|
+
# Linux (Debian/Ubuntu)
|
|
68
|
+
apt install tesseract-ocr poppler-utils
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Without these, the server will still work — it returns a helpful message when it encounters a scanned document it can't read.
|
|
72
|
+
|
|
73
|
+
## Usage with Claude Desktop
|
|
74
|
+
|
|
75
|
+
Add to your `claude_desktop_config.json`:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"fda": {
|
|
81
|
+
"command": "uv",
|
|
82
|
+
"args": ["--directory", "/absolute/path/to/fda-mcp", "run", "fda-mcp"],
|
|
83
|
+
"env": {
|
|
84
|
+
"OPENFDA_API_KEY": "your-key-here"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The `OPENFDA_API_KEY` is optional. Without it, you get 40 requests/minute. With a key (free from [open.fda.gov](https://open.fda.gov/apis/authentication/)), you get 240 requests/minute.
|
|
92
|
+
|
|
93
|
+
### Config file location
|
|
94
|
+
|
|
95
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
96
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
97
|
+
|
|
98
|
+
## Usage with Claude Code
|
|
99
|
+
|
|
100
|
+
Add the server to your project's `.claude/settings.json` or global settings:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"fda": {
|
|
106
|
+
"command": "uv",
|
|
107
|
+
"args": ["--directory", "/absolute/path/to/fda-mcp", "run", "fda-mcp"],
|
|
108
|
+
"env": {
|
|
109
|
+
"OPENFDA_API_KEY": "your-key-here"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Or add it interactively with `/mcp` in Claude Code.
|
|
117
|
+
|
|
118
|
+
## Example Queries
|
|
119
|
+
|
|
120
|
+
Once connected, you can ask Claude things like:
|
|
121
|
+
|
|
122
|
+
- "Search for adverse events related to OZEMPIC"
|
|
123
|
+
- "Find all Class I device recalls from 2024"
|
|
124
|
+
- "What are the most common adverse reactions reported for LIPITOR?"
|
|
125
|
+
- "Get the 510(k) summary for K213456"
|
|
126
|
+
- "Search for PMA approvals for cardiovascular devices"
|
|
127
|
+
- "How many drug recalls has Pfizer had? Break down by classification."
|
|
128
|
+
- "Find the drug label for metformin and summarize the warnings"
|
|
129
|
+
- "What COVID-19 serology tests has Abbott submitted?"
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
All configuration is via environment variables:
|
|
134
|
+
|
|
135
|
+
| Variable | Default | Description |
|
|
136
|
+
|----------|---------|-------------|
|
|
137
|
+
| `OPENFDA_API_KEY` | *(none)* | API key for higher rate limits (240 vs 40 req/min) |
|
|
138
|
+
| `OPENFDA_TIMEOUT` | `30` | HTTP request timeout in seconds |
|
|
139
|
+
| `OPENFDA_MAX_CONCURRENT` | `4` | Max concurrent API requests |
|
|
140
|
+
| `FDA_PDF_TIMEOUT` | `60` | PDF download timeout in seconds |
|
|
141
|
+
| `FDA_PDF_MAX_LENGTH` | `8000` | Default max text characters extracted from PDFs |
|
|
142
|
+
|
|
143
|
+
## OpenFDA Query Syntax
|
|
144
|
+
|
|
145
|
+
The `search` parameter on all tools uses OpenFDA query syntax:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
# AND
|
|
149
|
+
patient.drug.openfda.brand_name:"ASPIRIN"+AND+serious:1
|
|
150
|
+
|
|
151
|
+
# OR (space = OR)
|
|
152
|
+
brand_name:"ASPIRIN" brand_name:"IBUPROFEN"
|
|
153
|
+
|
|
154
|
+
# NOT
|
|
155
|
+
NOT+classification:"Class III"
|
|
156
|
+
|
|
157
|
+
# Date ranges
|
|
158
|
+
decision_date:[20230101+TO+20231231]
|
|
159
|
+
|
|
160
|
+
# Wildcards (trailing only, min 2 chars)
|
|
161
|
+
device_name:pulse*
|
|
162
|
+
|
|
163
|
+
# Exact matching (required for count queries)
|
|
164
|
+
patient.reaction.reactionmeddrapt.exact:"Nausea"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use `list_searchable_fields` or the `fda://reference/query-syntax` resource for the full reference.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Install with dev dependencies
|
|
173
|
+
uv sync --all-extras
|
|
174
|
+
|
|
175
|
+
# Run unit tests (157 tests, no network)
|
|
176
|
+
uv run pytest
|
|
177
|
+
|
|
178
|
+
# Run integration tests (hits real FDA API)
|
|
179
|
+
OPENFDA_TIMEOUT=60 uv run pytest -m integration
|
|
180
|
+
|
|
181
|
+
# Run a specific test file
|
|
182
|
+
uv run pytest tests/test_endpoints.py -v
|
|
183
|
+
|
|
184
|
+
# Start the server directly
|
|
185
|
+
uv run fda-mcp
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Project Structure
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
src/fda_mcp/
|
|
192
|
+
├── server.py # FastMCP server entry point
|
|
193
|
+
├── config.py # Environment-based configuration
|
|
194
|
+
├── errors.py # Custom error types
|
|
195
|
+
├── openfda/
|
|
196
|
+
│ ├── endpoints.py # Enum of all 21 endpoints
|
|
197
|
+
│ ├── client.py # Async HTTP client with rate limiting
|
|
198
|
+
│ └── summarizer.py # Response summarization per endpoint
|
|
199
|
+
├── documents/
|
|
200
|
+
│ ├── urls.py # FDA document URL construction
|
|
201
|
+
│ └── fetcher.py # PDF download + text extraction + OCR
|
|
202
|
+
├── tools/
|
|
203
|
+
│ ├── search.py # 9 search tool handlers
|
|
204
|
+
│ ├── count.py # count_records tool
|
|
205
|
+
│ ├── fields.py # list_searchable_fields tool
|
|
206
|
+
│ └── decision_documents.py
|
|
207
|
+
└── resources/
|
|
208
|
+
├── query_syntax.py # Query syntax reference
|
|
209
|
+
├── endpoints_resource.py
|
|
210
|
+
└── field_definitions.py
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Test Structure
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
tests/
|
|
217
|
+
├── test_endpoints.py # Endpoint enum
|
|
218
|
+
├── test_openfda_client.py # HTTP client
|
|
219
|
+
├── test_summarizer.py # Response summarizers (21 endpoints)
|
|
220
|
+
├── test_document_urls.py # URL construction
|
|
221
|
+
├── test_document_fetcher.py # PDF extraction + OCR
|
|
222
|
+
├── test_tool_search_*.py # 9 search tool test files
|
|
223
|
+
├── test_tool_count_records.py # Count tool
|
|
224
|
+
├── test_tool_list_fields.py # Fields tool
|
|
225
|
+
├── test_tool_decision_documents.py # Document tool
|
|
226
|
+
├── test_resources.py # MCP resources
|
|
227
|
+
├── test_error_handling.py # Error cases
|
|
228
|
+
└── integration/
|
|
229
|
+
├── test_live_openfda.py # Live API (21 endpoints)
|
|
230
|
+
└── test_live_documents.py # Live PDF fetch
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## How It Works
|
|
234
|
+
|
|
235
|
+
### Context Efficiency
|
|
236
|
+
|
|
237
|
+
The server is designed to minimize context window usage when used by LLMs:
|
|
238
|
+
|
|
239
|
+
1. **Response summarization** — Each endpoint type has a custom summarizer that extracts key fields and flattens nested structures. Drug labels truncate sections to 2,000 chars. PDF text defaults to 8,000 chars.
|
|
240
|
+
|
|
241
|
+
2. **Field discovery via tool** — Instead of listing all searchable fields in tool descriptions (which would cost ~8,000-11,000 tokens of persistent context), the `list_searchable_fields` tool provides them on demand.
|
|
242
|
+
|
|
243
|
+
3. **Smart pagination** — Default page sizes are low (10 records). Responses include `total_results`, `showing`, and `has_more`. When results exceed 100, a tip suggests using `count_records` for aggregation.
|
|
244
|
+
|
|
245
|
+
4. **Count enrichment** — `count_records` returns values with pre-calculated percentages and a one-sentence narrative summary.
|
|
246
|
+
|
|
247
|
+
### FDA Decision Documents
|
|
248
|
+
|
|
249
|
+
These documents are **not** available through the OpenFDA API. The server constructs URLs and fetches directly from `accessdata.fda.gov`:
|
|
250
|
+
|
|
251
|
+
| Document Type | URL Pattern |
|
|
252
|
+
|--------------|-------------|
|
|
253
|
+
| 510(k) summary | `https://www.accessdata.fda.gov/cdrh_docs/reviews/{K_NUMBER}.pdf` |
|
|
254
|
+
| De Novo decision | `https://www.accessdata.fda.gov/cdrh_docs/reviews/{DEN_NUMBER}.pdf` |
|
|
255
|
+
| PMA approval | `https://www.accessdata.fda.gov/cdrh_docs/pdf{YY}/{P_NUMBER}A.pdf` |
|
|
256
|
+
| PMA SSED | `https://www.accessdata.fda.gov/cdrh_docs/pdf{YY}/{P_NUMBER}B.pdf` |
|
|
257
|
+
| PMA supplement | `https://www.accessdata.fda.gov/cdrh_docs/pdf{YY}/{P_NUMBER}S{###}A.pdf` |
|
|
258
|
+
|
|
259
|
+
Text extraction uses `pdfplumber` for machine-generated PDFs, with automatic OCR fallback via `pytesseract` + `pdf2image` for scanned documents.
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|