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.
Files changed (53) hide show
  1. fda_mcp-0.1.0/.claude/settings.json +5 -0
  2. fda_mcp-0.1.0/.claude/settings.local.json +6 -0
  3. fda_mcp-0.1.0/.gitignore +10 -0
  4. fda_mcp-0.1.0/CLAUDE.md +62 -0
  5. fda_mcp-0.1.0/LICENSE +21 -0
  6. fda_mcp-0.1.0/PKG-INFO +291 -0
  7. fda_mcp-0.1.0/README.md +263 -0
  8. fda_mcp-0.1.0/pyproject.toml +47 -0
  9. fda_mcp-0.1.0/src/fda_mcp/__init__.py +1 -0
  10. fda_mcp-0.1.0/src/fda_mcp/config.py +25 -0
  11. fda_mcp-0.1.0/src/fda_mcp/documents/__init__.py +1 -0
  12. fda_mcp-0.1.0/src/fda_mcp/documents/fetcher.py +94 -0
  13. fda_mcp-0.1.0/src/fda_mcp/documents/urls.py +82 -0
  14. fda_mcp-0.1.0/src/fda_mcp/errors.py +58 -0
  15. fda_mcp-0.1.0/src/fda_mcp/openfda/__init__.py +1 -0
  16. fda_mcp-0.1.0/src/fda_mcp/openfda/client.py +108 -0
  17. fda_mcp-0.1.0/src/fda_mcp/openfda/endpoints.py +90 -0
  18. fda_mcp-0.1.0/src/fda_mcp/openfda/summarizer.py +500 -0
  19. fda_mcp-0.1.0/src/fda_mcp/resources/__init__.py +1 -0
  20. fda_mcp-0.1.0/src/fda_mcp/resources/endpoints_resource.py +19 -0
  21. fda_mcp-0.1.0/src/fda_mcp/resources/field_definitions.py +813 -0
  22. fda_mcp-0.1.0/src/fda_mcp/resources/query_syntax.py +44 -0
  23. fda_mcp-0.1.0/src/fda_mcp/server.py +21 -0
  24. fda_mcp-0.1.0/src/fda_mcp/tools/__init__.py +1 -0
  25. fda_mcp-0.1.0/src/fda_mcp/tools/count.py +47 -0
  26. fda_mcp-0.1.0/src/fda_mcp/tools/decision_documents.py +42 -0
  27. fda_mcp-0.1.0/src/fda_mcp/tools/fields.py +44 -0
  28. fda_mcp-0.1.0/src/fda_mcp/tools/search.py +273 -0
  29. fda_mcp-0.1.0/tests/__init__.py +0 -0
  30. fda_mcp-0.1.0/tests/conftest.py +379 -0
  31. fda_mcp-0.1.0/tests/integration/__init__.py +0 -0
  32. fda_mcp-0.1.0/tests/integration/test_live_documents.py +41 -0
  33. fda_mcp-0.1.0/tests/integration/test_live_openfda.py +165 -0
  34. fda_mcp-0.1.0/tests/test_document_fetcher.py +142 -0
  35. fda_mcp-0.1.0/tests/test_document_urls.py +111 -0
  36. fda_mcp-0.1.0/tests/test_endpoints.py +62 -0
  37. fda_mcp-0.1.0/tests/test_error_handling.py +153 -0
  38. fda_mcp-0.1.0/tests/test_openfda_client.py +142 -0
  39. fda_mcp-0.1.0/tests/test_resources.py +117 -0
  40. fda_mcp-0.1.0/tests/test_summarizer.py +232 -0
  41. fda_mcp-0.1.0/tests/test_tool_count_records.py +86 -0
  42. fda_mcp-0.1.0/tests/test_tool_decision_documents.py +108 -0
  43. fda_mcp-0.1.0/tests/test_tool_list_fields.py +94 -0
  44. fda_mcp-0.1.0/tests/test_tool_search_adverse_events.py +64 -0
  45. fda_mcp-0.1.0/tests/test_tool_search_device_submissions.py +76 -0
  46. fda_mcp-0.1.0/tests/test_tool_search_device_udi.py +39 -0
  47. fda_mcp-0.1.0/tests/test_tool_search_drug_labels.py +49 -0
  48. fda_mcp-0.1.0/tests/test_tool_search_drug_shortages.py +38 -0
  49. fda_mcp-0.1.0/tests/test_tool_search_drugs.py +50 -0
  50. fda_mcp-0.1.0/tests/test_tool_search_other.py +56 -0
  51. fda_mcp-0.1.0/tests/test_tool_search_recalls.py +82 -0
  52. fda_mcp-0.1.0/tests/test_tool_search_substances.py +63 -0
  53. fda_mcp-0.1.0/uv.lock +1093 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "env": {
3
+ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sandbox": {
3
+ "enabled": true,
4
+ "autoAllowBashIfSandboxed": true
5
+ }
6
+ }
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .env
8
+ *.pdf
9
+ .pytest_cache/
10
+ .ruff_cache/
@@ -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
@@ -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