@ythalorossy/openfda 1.0.17 → 1.0.19

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 (3) hide show
  1. package/README.md +23 -12
  2. package/dist/index.js +355 -295
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -34,18 +34,29 @@ A Model Context Protocol (MCP) server for querying drug information from the Ope
34
34
  If you are integrating this server with a larger MCP system, your configuration might look like:
35
35
 
36
36
  ```json
37
- {
38
- "mcpServers": {
39
- "openfda": {
40
- "command": "npx",
41
- "args": ["@ythalorossy/openfda"],
42
- "env": {
43
- "OPENFDA_API_KEY": "************"
44
- },
45
- "timeout": 60000
46
- }
47
- }
48
- }
37
+ {
38
+ "mcpServers": {
39
+ "openfda": {
40
+ "command": "npx",
41
+ "args": [
42
+ "@ythalorossy/openfda"
43
+ ],
44
+ "env": {
45
+ "OPENFDA_API_KEY": "*****************************************"
46
+ },
47
+ "timeout": 60000,
48
+ "autoApprove": [
49
+ "get-drug-by-name",
50
+ "get-drug-by-generic-name",
51
+ "get-drug-adverse-events",
52
+ "get-drugs-by-manufacturer",
53
+ "get-drug-safety-info",
54
+ "get-drug-by-ndc",
55
+ "get-drug-by-product-ndc"
56
+ ]
57
+ }
58
+ }
59
+ }
49
60
  ```
50
61
 
51
62
  Replace the asterisks with your actual API key, or ensure it is loaded from your `.env` file.
package/dist/index.js CHANGED
@@ -1,44 +1,46 @@
1
1
  #!/usr/bin/env node
2
- var D = Object.defineProperty;
3
- var v = (t, n, r) => n in t ? D(t, n, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[n] = r;
4
- var k = (t, n, r) => v(t, typeof n != "symbol" ? n + "" : n, r);
2
+ var v = Object.defineProperty;
3
+ var D = (n, t, r) => t in n ? v(n, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[t] = r;
4
+ var k = (n, t, r) => D(n, typeof t != "symbol" ? t + "" : t, r);
5
5
  import { McpServer as N } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import { StdioServerTransport as T } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import u from "zod";
8
- class h {
6
+ import { StdioServerTransport as C } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import d from "zod";
8
+ class T {
9
+ constructor(t) {
10
+ k(this, "registerTool", (t) => this.server.registerTool(
11
+ t.name,
12
+ {
13
+ title: t.name,
14
+ description: t.description,
15
+ inputSchema: t.inputSchema
16
+ },
17
+ t.handler
18
+ ));
19
+ this.server = t;
20
+ }
21
+ }
22
+ class g {
9
23
  constructor() {
10
- k(this, "url", "https://api.fda.gov/drug/");
24
+ k(this, "urlBase", "https://api.fda.gov");
11
25
  k(this, "params", /* @__PURE__ */ new Map());
12
26
  }
13
- context(n) {
14
- return this.params.set("context", n), this;
27
+ dataset(t) {
28
+ return this.params.set("dataset", t), this;
15
29
  }
16
- search(n) {
17
- return this.params.set("search", n), this;
30
+ context(t) {
31
+ return this.params.set("context", t), this;
18
32
  }
19
- limit(n = 1) {
20
- return this.params.set("limit", n), this;
33
+ search(t) {
34
+ return this.params.set("search", t), this;
35
+ }
36
+ limit(t = 1) {
37
+ return this.params.set("limit", t), this;
21
38
  }
22
39
  build() {
23
- const n = this.params.get("context"), r = this.params.get("search");
24
- let s = this.params.get("limit");
25
- const e = process.env.OPENFDA_API_KEY;
26
- if (!n || !r)
40
+ const t = this.params.get("dataset"), r = this.params.get("context"), s = this.params.get("search"), e = this.params.get("limit") ?? 1, o = process.env.OPENFDA_API_KEY;
41
+ if (!t || !r || !s)
27
42
  throw new Error("Missing required parameters: context or search");
28
- return s === void 0 && (s = 1), `${this.url}${n}.json?api_key=${e}&search=${r}&limit=${s}`;
29
- }
30
- }
31
- class C {
32
- constructor(n) {
33
- this.server = n;
34
- }
35
- registerTool(n) {
36
- this.server.tool(
37
- n.name,
38
- n.description,
39
- n.schema.shape,
40
- n.handler
41
- );
43
+ return `${this.urlBase}/${t}/${r}.json?api_key=${o}&search=${s}&limit=${e}`;
42
44
  }
43
45
  }
44
46
  const F = {
@@ -48,168 +50,117 @@ const F = {
48
50
  timeout: 3e4
49
51
  // 30 seconds
50
52
  };
51
- function x(t) {
52
- return !!(t.name === "TypeError" && t.message.includes("fetch") || t.name === "AbortError" || t.status >= 500 && t.status <= 599 || t.status === 429);
53
+ function x(n) {
54
+ return !!(n.name === "TypeError" && n.message.includes("fetch") || n.name === "AbortError" || n.status >= 500 && n.status <= 599 || n.status === 429);
53
55
  }
54
- function w(t) {
55
- return new Promise((n) => setTimeout(n, t));
56
+ function w(n) {
57
+ return new Promise((t) => setTimeout(t, n));
56
58
  }
57
- async function y(t, n = {}) {
58
- const { maxRetries: r, retryDelay: s, timeout: e } = { ...F, ...n }, p = {
59
+ async function h(n, t = {}) {
60
+ const { maxRetries: r, retryDelay: s, timeout: e } = { ...F, ...t }, o = {
59
61
  "User-Agent": "@ythalorossy/openfda",
60
62
  Accept: "application/json"
61
63
  };
62
64
  let a = null;
63
- for (let c = 0; c <= r; c++)
65
+ for (let p = 0; p <= r; p++)
64
66
  try {
65
- const o = new AbortController(), d = setTimeout(() => o.abort(), e);
66
- console.log(
67
- `Making OpenFDA request (attempt ${c + 1}/${r + 1}): ${t}`
68
- );
69
- const i = await fetch(t, {
70
- headers: p,
71
- signal: o.signal
67
+ const i = new AbortController(), l = setTimeout(() => i.abort(), e), c = await fetch(n, {
68
+ headers: o,
69
+ signal: i.signal
72
70
  });
73
- if (clearTimeout(d), !i.ok) {
74
- const m = await i.text().catch(() => "Unable to read error response"), f = {
71
+ if (clearTimeout(l), !c.ok) {
72
+ const f = await c.text().catch(() => "Unable to read error response"), m = {
75
73
  type: "http",
76
- message: `HTTP ${i.status}: ${i.statusText}`,
77
- status: i.status,
78
- details: m
74
+ message: `HTTP ${c.status}: ${c.statusText}`,
75
+ status: c.status,
76
+ details: f
79
77
  };
80
- switch (console.error(`OpenFDA HTTP Error (${i.status}):`, {
81
- url: t,
82
- status: i.status,
83
- statusText: i.statusText,
84
- errorText: m.substring(0, 200)
85
- // Truncate long error messages
86
- }), i.status) {
78
+ switch (c.status) {
87
79
  case 400:
88
- f.message = "Bad Request: Invalid search query or parameters";
80
+ m.message = "Bad Request: Invalid search query or parameters";
89
81
  break;
90
82
  case 401:
91
- f.message = "Unauthorized: Invalid or missing API key";
83
+ m.message = "Unauthorized: Invalid or missing API key";
92
84
  break;
93
85
  case 403:
94
- f.message = "Forbidden: API key may be invalid or quota exceeded";
86
+ m.message = "Forbidden: API key may be invalid or quota exceeded";
95
87
  break;
96
88
  case 404:
97
- f.message = "Not Found: No results found for the specified query";
89
+ m.message = "Not Found: No results found for the specified query";
98
90
  break;
99
91
  case 429:
100
- f.message = "Rate Limited: Too many requests. Retrying...";
92
+ m.message = "Rate Limited: Too many requests. Retrying...";
101
93
  break;
102
94
  case 500:
103
- f.message = "Server Error: OpenFDA service is experiencing issues";
95
+ m.message = "Server Error: OpenFDA service is experiencing issues";
104
96
  break;
105
97
  default:
106
- f.message = `HTTP Error ${i.status}: ${i.statusText}`;
98
+ m.message = `HTTP Error ${c.status}: ${c.statusText}`;
107
99
  }
108
- if (a = f, i.status >= 400 && i.status < 500 && i.status !== 429)
100
+ if (a = m, c.status >= 400 && c.status < 500 && c.status !== 429)
109
101
  break;
110
- if (c < r && x({ status: i.status })) {
111
- const g = s * Math.pow(2, c);
112
- console.log(`Retrying in ${g}ms...`), await w(g);
102
+ if (p < r && x({ status: c.status })) {
103
+ const b = s * Math.pow(2, p);
104
+ await w(b);
113
105
  continue;
114
106
  }
115
107
  break;
116
108
  }
117
- let l;
109
+ let u;
118
110
  try {
119
- l = await i.json();
120
- } catch (m) {
121
- const f = {
111
+ u = await c.json();
112
+ } catch (f) {
113
+ a = {
122
114
  type: "parsing",
123
- message: `Failed to parse JSON response: ${m instanceof Error ? m.message : "Unknown parsing error"}`,
124
- details: m
115
+ message: `Failed to parse JSON response: ${f instanceof Error ? f.message : "Unknown parsing error"}`,
116
+ details: f
125
117
  };
126
- console.error("OpenFDA JSON Parsing Error:", {
127
- url: t,
128
- parseError: m instanceof Error ? m.message : m
129
- }), a = f;
130
118
  break;
131
119
  }
132
- if (!l) {
120
+ if (!u) {
133
121
  a = {
134
122
  type: "empty_response",
135
123
  message: "Received empty response from OpenFDA API"
136
124
  };
137
125
  break;
138
126
  }
139
- return console.log(`OpenFDA request successful on attempt ${c + 1}`), { data: l, error: null };
140
- } catch (o) {
141
- let d;
142
- if (o.name === "AbortError" ? d = {
127
+ return { data: u, error: null };
128
+ } catch (i) {
129
+ let l;
130
+ if (i.name === "AbortError" ? l = {
143
131
  type: "timeout",
144
132
  message: `Request timeout after ${e}ms`,
145
- details: o
146
- } : o instanceof TypeError && o.message.includes("fetch") ? d = {
133
+ details: i
134
+ } : i instanceof TypeError && i.message.includes("fetch") ? l = {
147
135
  type: "network",
148
136
  message: "Network error: Unable to connect to OpenFDA API",
149
- details: o.message
150
- } : d = {
137
+ details: i.message
138
+ } : l = {
151
139
  type: "unknown",
152
- message: `Unexpected error: ${o.message || "Unknown error occurred"}`,
153
- details: o
154
- }, console.error(`OpenFDA Request Error (attempt ${c + 1}):`, {
155
- url: t,
156
- error: o.message,
157
- type: o.name
158
- }), a = d, c < r && x(o)) {
159
- const i = s * Math.pow(2, c);
160
- console.log(`Network error, retrying in ${i}ms...`), await w(i);
140
+ message: `Unexpected error: ${i.message || "Unknown error occurred"}`,
141
+ details: i
142
+ }, a = l, p < r && x(i)) {
143
+ const c = s * Math.pow(2, p);
144
+ await w(c);
161
145
  continue;
162
146
  }
163
147
  break;
164
148
  }
165
149
  return { data: null, error: a };
166
150
  }
167
- const X = new N(
168
- {
169
- name: "openfda",
170
- version: "1.0.0",
171
- description: "OpenFDA Model Context Protocol"
172
- },
173
- {
174
- capabilities: {
175
- resources: {},
176
- tools: {}
177
- }
178
- }
179
- ), b = new C(X);
180
- function A(t) {
181
- const n = t.trim().toUpperCase();
182
- let r, s = null;
183
- if (n.includes("-")) {
184
- const p = n.split("-");
185
- if (p.length === 2)
186
- r = n, s = null;
187
- else if (p.length === 3)
188
- r = `${p[0]}-${p[1]}`, s = n;
189
- else
190
- return { productNDC: n, packageNDC: null, isValid: !1 };
191
- } else if (n.length === 11)
192
- r = `${n.substring(0, 5)}-${n.substring(5, 9)}`, s = `${n.substring(0, 5)}-${n.substring(5, 9)}-${n.substring(9, 11)}`;
193
- else if (n.length === 9)
194
- r = `${n.substring(0, 5)}-${n.substring(5, 9)}`, s = null;
195
- else
196
- return { productNDC: n, packageNDC: null, isValid: !1 };
197
- const e = /^\d{5}-\d{4}$/.test(r);
198
- return { productNDC: r, packageNDC: s, isValid: e };
199
- }
200
- b.registerTool({
151
+ const S = {
201
152
  name: "get-drug-by-name",
202
153
  description: "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.",
203
- schema: u.object({
204
- drugName: u.string().describe("Drug name")
154
+ inputSchema: d.object({
155
+ drugName: d.string().describe("Drug name")
205
156
  }),
206
- handler: async ({ drugName: t }) => {
207
- const n = new h().context("label").search(`openfda.brand_name:"${t}"`).limit(1).build(), { data: r, error: s } = await y(n);
157
+ async handler({ drugName: n }) {
158
+ const t = new g().dataset("drug").context("label").search(`openfda.brand_name:"${n}"`).limit(1).build(), { data: r, error: s } = await h(t);
208
159
  if (s) {
209
- let a = `Failed to retrieve drug data for "${t}": ${s.message}`;
160
+ let a = `Failed to retrieve drug data for "${n}": ${s.message}`;
210
161
  switch (s.type) {
211
162
  case "http":
212
- s.status === 404 ? a += `
163
+ s.status === 404 ? a += `${t}
213
164
 
214
165
  Suggestions:
215
166
  - Verify the exact brand name spelling
@@ -230,24 +181,20 @@ The request took too long. Please try again.`;
230
181
  break;
231
182
  }
232
183
  return {
233
- content: [
234
- {
235
- type: "text",
236
- text: a
237
- }
238
- ]
184
+ content: [{ type: "text", text: a }],
185
+ isError: !0
239
186
  };
240
187
  }
241
- if (!r || !r.results || r.results.length === 0)
188
+ if (!(r != null && r.results) || r.results.length === 0)
242
189
  return {
243
190
  content: [
244
191
  {
245
192
  type: "text",
246
- text: `No drug information found for "${t}". Please verify the brand name spelling or try searching for the generic name.`
193
+ text: `No drug information found for "${n}". Please verify the brand name spelling or try searching for the generic name.`
247
194
  }
248
195
  ]
249
196
  };
250
- const e = r.results[0], p = {
197
+ const e = r.results[0], o = {
251
198
  brand_name: e == null ? void 0 : e.openfda.brand_name,
252
199
  generic_name: e == null ? void 0 : e.openfda.generic_name,
253
200
  manufacturer_name: e == null ? void 0 : e.openfda.manufacturer_name,
@@ -269,46 +216,49 @@ The request took too long. Please try again.`;
269
216
  type: "text",
270
217
  text: `Drug information retrieved successfully:
271
218
 
272
- ${JSON.stringify(p, null, 2)}`
219
+ ${JSON.stringify(o, null, 2)}`
273
220
  }
274
221
  ]
275
222
  };
276
223
  }
277
- });
278
- b.registerTool({
224
+ }, E = {
279
225
  name: "get-drug-by-generic-name",
280
226
  description: "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.",
281
- schema: u.object({
282
- genericName: u.string().describe("Generic drug name (active ingredient)"),
283
- limit: u.number().optional().default(5).describe("Maximum number of results to return")
227
+ inputSchema: d.object({
228
+ genericName: d.string().describe("Generic drug name (active ingredient)"),
229
+ limit: d.number().optional().default(5).describe("Maximum number of results to return")
284
230
  }),
285
- handler: async ({ genericName: t, limit: n }) => {
286
- const r = new h().context("label").search(`openfda.generic_name:"${t}"`).limit(n).build(), { data: s, error: e } = await y(r);
231
+ async handler({
232
+ genericName: n,
233
+ limit: t
234
+ }) {
235
+ const r = new g().dataset("drug").context("label").search(`openfda.generic_name:"${n}"`).limit(t).build(), { data: s, error: e } = await h(r);
287
236
  if (e)
288
237
  return {
289
238
  content: [
290
239
  {
291
240
  type: "text",
292
- text: `Failed to retrieve drug data for generic name "${t}": ${e.message}`
241
+ text: `Failed to retrieve drug data for generic name "${n}": ${e.message}`
293
242
  }
294
- ]
243
+ ],
244
+ isError: !0
295
245
  };
296
- if (!s || !s.results || s.results.length === 0)
246
+ if (!(s != null && s.results) || s.results.length === 0)
297
247
  return {
298
248
  content: [
299
249
  {
300
250
  type: "text",
301
- text: `No drug information found for generic name "${t}".`
251
+ text: `No drug information found for generic name "${n}".`
302
252
  }
303
253
  ]
304
254
  };
305
- const p = s.results.map((a) => {
306
- var c, o, d, i;
255
+ const o = s.results.map((a) => {
256
+ var p, i, l, c;
307
257
  return {
308
- brand_name: ((c = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : c[0]) || "Unknown",
309
- generic_name: ((o = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : o[0]) || "Unknown",
310
- manufacturer_name: ((d = a == null ? void 0 : a.openfda.manufacturer_name) == null ? void 0 : d[0]) || "Unknown",
311
- product_type: ((i = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : i[0]) || "Unknown",
258
+ brand_name: ((p = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : p[0]) || "Unknown",
259
+ generic_name: ((i = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : i[0]) || "Unknown",
260
+ manufacturer_name: ((l = a == null ? void 0 : a.openfda.manufacturer_name) == null ? void 0 : l[0]) || "Unknown",
261
+ product_type: ((c = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : c[0]) || "Unknown",
312
262
  route: (a == null ? void 0 : a.openfda.route) || []
313
263
  };
314
264
  });
@@ -316,147 +266,155 @@ b.registerTool({
316
266
  content: [
317
267
  {
318
268
  type: "text",
319
- text: `Found ${p.length} drug(s) with generic name "${t}":
269
+ text: `Found ${o.length} drug(s) with generic name "${n}":
320
270
 
321
- ${JSON.stringify(p, null, 2)}`
271
+ ${JSON.stringify(o, null, 2)}`
322
272
  }
323
273
  ]
324
274
  };
325
275
  }
326
- });
327
- b.registerTool({
276
+ }, P = {
328
277
  name: "get-drug-adverse-events",
329
278
  description: "Get adverse event reports for a drug. This provides safety information about reported side effects and reactions. Use brand name or generic name.",
330
- schema: u.object({
331
- drugName: u.string().describe("Drug name (brand or generic)"),
332
- limit: u.number().optional().default(10).describe("Maximum number of events to return"),
333
- seriousness: u.enum(["serious", "non-serious", "all"]).optional().default("all").describe("Filter by event seriousness")
279
+ inputSchema: d.object({
280
+ drugName: d.string().describe("Drug name (brand or generic)"),
281
+ limit: d.number().optional().default(10).describe("Maximum number of events to return"),
282
+ seriousness: d.enum(["serious", "non-serious", "all"]).optional().default("all").describe("Filter by event seriousness")
334
283
  }),
335
- handler: async ({ drugName: t, limit: n, seriousness: r }) => {
336
- let s = `patient.drug.medicinalproduct:"${t}"`;
284
+ async handler({
285
+ drugName: n,
286
+ limit: t,
287
+ seriousness: r
288
+ }) {
289
+ let s = `patient.drug.medicinalproduct:"${n}"`;
337
290
  r !== "all" && (s += `+AND+serious:${r === "serious" ? "1" : "2"}`);
338
- const e = new h().context("event").search(s).limit(n).build(), { data: p, error: a } = await y(e);
291
+ const e = new g().dataset("drug").context("event").search(s).limit(t).build(), { data: o, error: a } = await h(e);
339
292
  if (a)
340
293
  return {
341
294
  content: [
342
295
  {
343
296
  type: "text",
344
- text: `Failed to retrieve adverse events for "${t}": ${a.message}`
297
+ text: `Failed to retrieve adverse events for "${n}": ${a.message}`
345
298
  }
346
- ]
299
+ ],
300
+ isError: !0
347
301
  };
348
- if (!p || !p.results || p.results.length === 0)
302
+ if (!(o != null && o.results) || o.results.length === 0)
349
303
  return {
350
304
  content: [
351
305
  {
352
306
  type: "text",
353
- text: `No adverse events found for "${t}".`
307
+ text: `No adverse events found for "${n}".`
354
308
  }
355
309
  ]
356
310
  };
357
- const c = p.results.map((o) => {
358
- var d, i, l, m, f, g, $;
311
+ const p = o.results.map((i) => {
312
+ var l, c, u, f, m, b, $;
359
313
  return {
360
- report_id: o.safetyreportid,
361
- serious: o.serious === "1" ? "Yes" : "No",
362
- patient_age: ((d = o.patient) == null ? void 0 : d.patientonsetage) || "Unknown",
363
- patient_sex: ((i = o.patient) == null ? void 0 : i.patientsex) === "1" ? "Male" : ((l = o.patient) == null ? void 0 : l.patientsex) === "2" ? "Female" : "Unknown",
364
- reactions: ((f = (m = o.patient) == null ? void 0 : m.reaction) == null ? void 0 : f.map((_) => _.reactionmeddrapt).slice(0, 3)) || [],
365
- outcomes: (($ = (g = o.patient) == null ? void 0 : g.reaction) == null ? void 0 : $.map((_) => _.reactionoutcome).slice(0, 3)) || [],
366
- report_date: o.receiptdate || "Unknown"
314
+ report_id: i.safetyreportid,
315
+ serious: i.serious === "1" ? "Yes" : "No",
316
+ patient_age: ((l = i.patient) == null ? void 0 : l.patientonsetage) || "Unknown",
317
+ patient_sex: ((c = i.patient) == null ? void 0 : c.patientsex) === "1" ? "Male" : ((u = i.patient) == null ? void 0 : u.patientsex) === "2" ? "Female" : "Unknown",
318
+ reactions: ((m = (f = i.patient) == null ? void 0 : f.reaction) == null ? void 0 : m.map((_) => _.reactionmeddrapt).slice(0, 3)) || [],
319
+ outcomes: (($ = (b = i.patient) == null ? void 0 : b.reaction) == null ? void 0 : $.map((_) => _.reactionoutcome).slice(0, 3)) || [],
320
+ report_date: i.receiptdate || "Unknown"
367
321
  };
368
322
  });
369
323
  return {
370
324
  content: [
371
325
  {
372
326
  type: "text",
373
- text: `Found ${c.length} adverse event report(s) for "${t}":
327
+ text: `Found ${p.length} adverse event report(s) for "${n}":
374
328
 
375
- ${JSON.stringify(c, null, 2)}`
329
+ ${JSON.stringify(p, null, 2)}`
376
330
  }
377
331
  ]
378
332
  };
379
333
  }
380
- });
381
- b.registerTool({
334
+ }, A = {
382
335
  name: "get-drugs-by-manufacturer",
383
336
  description: "Get all drugs manufactured by a specific company. Useful for finding alternatives or checking manufacturer portfolios.",
384
- schema: u.object({
385
- manufacturerName: u.string().describe("Manufacturer/company name"),
386
- limit: u.number().optional().default(20).describe("Maximum number of drugs to return")
337
+ inputSchema: d.object({
338
+ manufacturerName: d.string().describe("Manufacturer/company name"),
339
+ limit: d.number().optional().default(20).describe("Maximum number of drugs to return")
387
340
  }),
388
- handler: async ({ manufacturerName: t, limit: n }) => {
389
- const r = new h().context("label").search(`openfda.manufacturer_name:"${t}"`).limit(n).build(), { data: s, error: e } = await y(r);
341
+ async handler({
342
+ manufacturerName: n,
343
+ limit: t
344
+ }) {
345
+ const r = new g().dataset("drug").context("label").search(`openfda.manufacturer_name:"${n}"`).limit(t).build(), { data: s, error: e } = await h(r);
390
346
  if (e)
391
347
  return {
392
348
  content: [
393
349
  {
394
350
  type: "text",
395
- text: `Failed to retrieve drugs for manufacturer "${t}": ${e.message}`
351
+ text: `${r}
352
+ Failed to retrieve drugs for manufacturer "${n}": ${e.message}`
396
353
  }
397
- ]
354
+ ],
355
+ isError: !0
398
356
  };
399
- if (!s || !s.results || s.results.length === 0)
357
+ if (!(s != null && s.results) || s.results.length === 0)
400
358
  return {
401
359
  content: [
402
360
  {
403
361
  type: "text",
404
- text: `No drugs found for manufacturer "${t}".`
362
+ text: `No drugs found for manufacturer "${n}".`
405
363
  }
406
364
  ]
407
365
  };
408
- const p = s.results.map((a) => {
409
- var c, o, d, i;
366
+ const o = s.results.map((a) => {
367
+ var p, i, l, c;
410
368
  return {
411
- brand_name: ((c = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : c[0]) || "Unknown",
412
- generic_name: ((o = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : o[0]) || "Unknown",
413
- product_type: ((d = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : d[0]) || "Unknown",
369
+ brand_name: ((p = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : p[0]) || "Unknown",
370
+ generic_name: ((i = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : i[0]) || "Unknown",
371
+ product_type: ((l = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : l[0]) || "Unknown",
414
372
  route: (a == null ? void 0 : a.openfda.route) || [],
415
- ndc: ((i = a == null ? void 0 : a.openfda.product_ndc) == null ? void 0 : i[0]) || "Unknown"
373
+ ndc: ((c = a == null ? void 0 : a.openfda.product_ndc) == null ? void 0 : c[0]) || "Unknown"
416
374
  };
417
375
  });
418
376
  return {
419
377
  content: [
420
378
  {
421
379
  type: "text",
422
- text: `Found ${p.length} drug(s) from manufacturer "${t}":
380
+ text: `Found ${o.length} drug(s) from manufacturer "${n}":
423
381
 
424
- ${JSON.stringify(p, null, 2)}`
382
+ ${JSON.stringify(o, null, 2)}`
425
383
  }
426
384
  ]
427
385
  };
428
386
  }
429
- });
430
- b.registerTool({
387
+ }, U = {
431
388
  name: "get-drug-safety-info",
432
389
  description: "Get comprehensive safety information for a drug including warnings, contraindications, drug interactions, and precautions. Use brand name.",
433
- schema: u.object({
434
- drugName: u.string().describe("Drug brand name")
390
+ inputSchema: d.object({
391
+ drugName: d.string().describe("Drug brand name")
435
392
  }),
436
- handler: async ({ drugName: t }) => {
437
- var a, c;
438
- const n = new h().context("label").search(`openfda.brand_name:"${t}"`).limit(1).build(), { data: r, error: s } = await y(n);
393
+ async handler({ drugName: n }) {
394
+ var a, p;
395
+ const t = new g().dataset("drug").context("label").search(`openfda.brand_name:"${n}"`).limit(1).build(), { data: r, error: s } = await h(t);
439
396
  if (s)
440
397
  return {
441
398
  content: [
442
399
  {
443
400
  type: "text",
444
- text: `Failed to retrieve safety information for "${t}": ${s.message}`
401
+ text: `Failed to retrieve safety information for "${n}": ${s.message}`
445
402
  }
446
- ]
403
+ ],
404
+ isError: !0
447
405
  };
448
- if (!r || !r.results || r.results.length === 0)
406
+ if (!(r != null && r.results) || r.results.length === 0)
449
407
  return {
450
408
  content: [
451
409
  {
452
410
  type: "text",
453
- text: `No safety information found for "${t}".`
411
+ text: `No safety information found for "${n}".`
454
412
  }
455
413
  ]
456
414
  };
457
- const e = r.results[0], p = {
458
- drug_name: ((a = e == null ? void 0 : e.openfda.brand_name) == null ? void 0 : a[0]) || t,
459
- generic_name: ((c = e == null ? void 0 : e.openfda.generic_name) == null ? void 0 : c[0]) || "Unknown",
415
+ const e = r.results[0], o = {
416
+ drug_name: ((a = e == null ? void 0 : e.openfda.brand_name) == null ? void 0 : a[0]) || n,
417
+ generic_name: ((p = e == null ? void 0 : e.openfda.generic_name) == null ? void 0 : p[0]) || "Unknown",
460
418
  warnings: (e == null ? void 0 : e.warnings) || [],
461
419
  contraindications: (e == null ? void 0 : e.contraindications) || [],
462
420
  drug_interactions: (e == null ? void 0 : e.drug_interactions) || [],
@@ -472,59 +430,78 @@ b.registerTool({
472
430
  content: [
473
431
  {
474
432
  type: "text",
475
- text: `Safety information for "${t}":
433
+ text: `Safety information for "${n}":
476
434
 
477
- ${JSON.stringify(p, null, 2)}`
435
+ ${JSON.stringify(o, null, 2)}`
478
436
  }
479
437
  ]
480
438
  };
481
439
  }
482
- });
483
- b.registerTool({
440
+ };
441
+ function O(n) {
442
+ const t = n.trim().toUpperCase();
443
+ let r, s = null;
444
+ if (t.includes("-")) {
445
+ const o = t.split("-");
446
+ if (o.length === 2)
447
+ r = t, s = null;
448
+ else if (o.length === 3)
449
+ r = `${o[0]}-${o[1]}`, s = t;
450
+ else
451
+ return { productNDC: t, packageNDC: null, isValid: !1 };
452
+ } else if (t.length === 11)
453
+ r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = `${t.substring(0, 5)}-${t.substring(5, 9)}-${t.substring(9, 11)}`;
454
+ else if (t.length === 9)
455
+ r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = null;
456
+ else
457
+ return { productNDC: t, packageNDC: null, isValid: !1 };
458
+ const e = /^\d{5}-\d{4}$/.test(r);
459
+ return { productNDC: r, packageNDC: s, isValid: e };
460
+ }
461
+ const I = {
484
462
  name: "get-drug-by-ndc",
485
463
  description: "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.",
486
- schema: u.object({
487
- ndcCode: u.string().describe(
464
+ inputSchema: d.object({
465
+ ndcCode: d.string().describe(
488
466
  "National Drug Code (NDC) - accepts formats: XXXXX-XXXX, XXXXX-XXXX-XX, or without dashes"
489
467
  )
490
468
  }),
491
- handler: async ({ ndcCode: t }) => {
492
- const { productNDC: n, packageNDC: r, isValid: s } = A(t);
469
+ async handler({ ndcCode: n }) {
470
+ const { productNDC: t, packageNDC: r, isValid: s } = O(n);
493
471
  if (!s)
494
472
  return {
495
473
  content: [
496
474
  {
497
475
  type: "text",
498
- text: `Invalid NDC format: "${t}"
476
+ text: `Invalid NDC format: "${n}"
499
477
 
500
478
  ✅ Accepted formats:
501
479
  • Product NDC: 12345-1234
502
480
  • Package NDC: 12345-1234-01
503
481
  • Without dashes: 123451234 or 12345123401`
504
482
  }
505
- ]
483
+ ],
484
+ isError: !0
506
485
  };
507
- console.log(
508
- `Searching for NDC: input="${t}", productNDC="${n}", packageNDC="${r}"`
509
- );
510
- let e = `openfda.product_ndc:"${n}"`;
486
+ let e = `openfda.product_ndc:"${t}"`;
511
487
  r && (e += `+OR+openfda.package_ndc:"${r}"`);
512
- const p = new h().context("label").search(e).limit(10).build(), { data: a, error: c } = await y(p);
513
- if (c)
488
+ const o = new g().dataset("drug").context("label").search(e).limit(10).build(), { data: a, error: p } = await h(o);
489
+ if (p)
514
490
  return {
515
491
  content: [
516
492
  {
517
493
  type: "text",
518
- text: `Failed to retrieve drug data for NDC "${t}": ${c.message}`
494
+ text: `Failed to retrieve drug data for NDC "${n}": ${p.message}`
519
495
  }
520
- ]
496
+ ],
497
+ isError: !0
521
498
  };
522
- if (!a || !a.results || a.results.length === 0)
499
+ if (!(a != null && a.results) || a.results.length === 0)
523
500
  return {
524
501
  content: [
525
502
  {
526
503
  type: "text",
527
- text: `No drug found with NDC "${t}" (product: ${n}).
504
+ text: `No drug found with NDC "${n}" (product: ${t}).
528
505
 
529
506
  💡 Tips:
530
507
  • Verify the NDC format
@@ -533,91 +510,90 @@ b.registerTool({
533
510
  }
534
511
  ]
535
512
  };
536
- const o = a.results.map((l) => {
537
- var g, $;
538
- const m = ((g = l.openfda.product_ndc) == null ? void 0 : g.filter((_) => _ === n)) || [], f = (($ = l.openfda.package_ndc) == null ? void 0 : $.filter(
539
- (_) => r ? _ === r : _.startsWith(n)
513
+ const i = a.results.map((u) => {
514
+ var b, $;
515
+ const f = ((b = u.openfda.product_ndc) == null ? void 0 : b.filter((_) => _ === t)) || [], m = (($ = u.openfda.package_ndc) == null ? void 0 : $.filter(
516
+ (_) => r ? _ === r : _.startsWith(t)
540
517
  )) || [];
541
518
  return {
542
- // Basic drug information
543
- brand_name: l.openfda.brand_name || [],
544
- generic_name: l.openfda.generic_name || [],
545
- manufacturer_name: l.openfda.manufacturer_name || [],
546
- product_type: l.openfda.product_type || [],
547
- route: l.openfda.route || [],
548
- substance_name: l.openfda.substance_name || [],
549
- // NDC information
550
- matching_product_ndc: m,
551
- matching_package_ndc: f,
552
- all_product_ndc: l.openfda.product_ndc || [],
553
- all_package_ndc: l.openfda.package_ndc || [],
554
- // Additional product details
555
- dosage_and_administration: l.dosage_and_administration || [],
556
- package_label_principal_display_panel: l.package_label_principal_display_panel || [],
557
- active_ingredient: l.active_ingredient || [],
558
- purpose: l.purpose || []
519
+ brand_name: u.openfda.brand_name || [],
520
+ generic_name: u.openfda.generic_name || [],
521
+ manufacturer_name: u.openfda.manufacturer_name || [],
522
+ product_type: u.openfda.product_type || [],
523
+ route: u.openfda.route || [],
524
+ substance_name: u.openfda.substance_name || [],
525
+ matching_product_ndc: f,
526
+ matching_package_ndc: m,
527
+ all_product_ndc: u.openfda.product_ndc || [],
528
+ all_package_ndc: u.openfda.package_ndc || [],
529
+ dosage_and_administration: u.dosage_and_administration || [],
530
+ package_label_principal_display_panel: u.package_label_principal_display_panel || [],
531
+ active_ingredient: u.active_ingredient || [],
532
+ purpose: u.purpose || []
559
533
  };
560
- }), d = o.reduce(
561
- (l, m) => l + m.matching_package_ndc.length,
534
+ }), l = i.reduce(
535
+ (u, f) => u + f.matching_package_ndc.length,
562
536
  0
563
- ), i = r ? `Searched for specific package NDC: ${r}` : `Searched for product NDC: ${n} (all packages)`;
537
+ ), c = r ? `Searched for specific package NDC: ${r}` : `Searched for product NDC: ${t} (all packages)`;
564
538
  return {
565
539
  content: [
566
540
  {
567
541
  type: "text",
568
- text: `✅ Found ${o.length} drug(s) with ${d} package(s) for NDC "${t}"
542
+ text: `✅ Found ${i.length} drug(s) with ${l} package(s) for NDC "${n}"
569
543
 
570
- ${i}
544
+ ${c}
571
545
 
572
- ${JSON.stringify(o, null, 2)}`
546
+ ${JSON.stringify(i, null, 2)}`
573
547
  }
574
548
  ]
575
549
  };
576
550
  }
577
- });
578
- b.registerTool({
551
+ }, M = {
579
552
  name: "get-drug-by-product-ndc",
580
553
  description: "Get drug information by product NDC only (XXXXX-XXXX format). This ignores package variations and finds all packages for a product.",
581
- schema: u.object({
582
- productNDC: u.string().describe("Product NDC in format XXXXX-XXXX")
554
+ inputSchema: d.object({
555
+ productNDC: d.string().describe("Product NDC in format XXXXX-XXXX")
583
556
  }),
584
- handler: async ({ productNDC: t }) => {
585
- var c;
586
- if (!/^\d{5}-\d{4}$/.test(t.trim()))
557
+ async handler({ productNDC: n }) {
558
+ var p;
559
+ if (!/^\d{5}-\d{4}$/.test(n.trim()))
587
560
  return {
588
561
  content: [
589
562
  {
590
563
  type: "text",
591
- text: `Invalid product NDC format: "${t}"
564
+ text: `Invalid product NDC format: "${n}"
592
565
 
593
566
  ✅ Required format: XXXXX-XXXX (e.g., 12345-1234)`
594
567
  }
595
- ]
568
+ ],
569
+ isError: !0
596
570
  };
597
- const n = new h().context("label").search(`openfda.product_ndc:"${t.trim()}"`).limit(1).build(), { data: r, error: s } = await y(n);
571
+ const t = new g().dataset("drug").context("label").search(`openfda.product_ndc:"${n.trim()}"`).limit(1).build(), { data: r, error: s } = await h(t);
598
572
  if (s)
599
573
  return {
600
574
  content: [
601
575
  {
602
576
  type: "text",
603
- text: `Failed to retrieve drug data for product NDC "${t}": ${s.message}`
577
+ text: `${t}Failed to retrieve drug data for product NDC "${n}": ${s.message}`
604
578
  }
605
- ]
579
+ ],
580
+ isError: !0
606
581
  };
607
- if (!r || !r.results || r.results.length === 0)
582
+ if (!(r != null && r.results) || r.results.length === 0)
608
583
  return {
609
584
  content: [
610
585
  {
611
586
  type: "text",
612
- text: `No drug found with product NDC "${t}".`
587
+ text: `No drug found with product NDC "${n}".`
613
588
  }
614
- ]
589
+ ],
590
+ structuredContent: null
615
591
  };
616
- const e = r.results[0], p = ((c = e.openfda.package_ndc) == null ? void 0 : c.filter(
617
- (o) => o.startsWith(t.trim())
592
+ const e = r.results[0], o = ((p = e.openfda.package_ndc) == null ? void 0 : p.filter(
593
+ (i) => i.startsWith(n.trim())
618
594
  )) || [], a = {
619
- product_ndc: t,
620
- available_packages: p,
595
+ product_ndc: n,
596
+ available_packages: o,
621
597
  brand_name: e.openfda.brand_name || [],
622
598
  generic_name: e.openfda.generic_name || [],
623
599
  manufacturer_name: e.openfda.manufacturer_name || [],
@@ -632,18 +608,102 @@ b.registerTool({
632
608
  content: [
633
609
  {
634
610
  type: "text",
635
- text: `✅ Product NDC "${t}" found with ${p.length} package variation(s):
611
+ text: `✅ Product NDC "${n}" found with ${o.length} package variation(s):
636
612
 
637
613
  ${JSON.stringify(a, null, 2)}`
638
614
  }
639
615
  ]
640
616
  };
641
617
  }
642
- });
643
- async function P() {
644
- const t = new T();
645
- await X.connect(t), console.error("OpenFDA MCP Server running on stdio");
618
+ }, R = {
619
+ name: "get-drugsfda",
620
+ description: "Get drugsfda data by section and field. Search OpenFDA drugsfda endpoint by specifying a section (application, openfda, products, submissions, application_docs), a field name within that section, and a search value.",
621
+ inputSchema: d.object({
622
+ sectionName: d.string().describe(
623
+ "Section within drugsfda. Valid values: application, openfda, products, submissions, application_docs"
624
+ ),
625
+ fieldName: d.string().describe(
626
+ "Field name within the selected section. application: application_number. openfda: application_number, brand_name, generic_name, manufacturer_name, nui, package_ndc, pharm_class_cs, pharm_class_epc, pharm_class_pe, pharm_class_moa, product_ndc, route, rxcui, spl_id, spl_set_id, substance_name, unii. products: active_ingredients.name, active_ingredients.strength, dosage_form, marketing_status, product_number, reference_drug, reference_standard, route, te_code. submissions: application_docs, review_priority, submission_class_code, submission_class_code_description, submission_number, submission_property_type.code, submission_public_notes, submission_status, submission_status_date, submission_type. application_docs: applications_doc_id, applications_doc_date, application_docs_title, applications_doc_type, applications_doc_url"
627
+ ),
628
+ searchValue: d.string().describe("Value to search for in the specified field")
629
+ }),
630
+ async handler({
631
+ sectionName: n,
632
+ fieldName: t,
633
+ searchValue: r
634
+ }) {
635
+ const s = new g().dataset("drug").context("drugsfda").search(`${n}.${t}:"${r}"`).limit(1).build(), { data: e, error: o } = await h(s);
636
+ if (o) {
637
+ let a = `${s} Failed to retrieve drugsfda data for "${r}" in ${n}.${t}: ${o.message}`;
638
+ switch (o.type) {
639
+ case "http":
640
+ o.status === 404 ? a += `
641
+
642
+ Suggestions:
643
+ - Verify the field name is correct for the section
644
+ - Check the search value spelling` : (o.status === 401 || o.status === 403) && (a += `
645
+
646
+ Please check the API key configuration.`);
647
+ break;
648
+ case "network":
649
+ a += `
650
+
651
+ Please check your internet connection and try again.`;
652
+ break;
653
+ case "timeout":
654
+ a += `
655
+
656
+ The request took too long. Please try again.`;
657
+ break;
658
+ }
659
+ return {
660
+ content: [{ type: "text", text: a }],
661
+ isError: !0
662
+ };
663
+ }
664
+ return !(e != null && e.results) || e.results.length === 0 ? {
665
+ content: [
666
+ {
667
+ type: "text",
668
+ text: `No drugsfda data found for "${r}" in ${n}.${t}. Please verify the search parameters.`
669
+ }
670
+ ]
671
+ } : {
672
+ content: [
673
+ {
674
+ type: "text",
675
+ text: `drugsfda data retrieved successfully:
676
+
677
+ ${JSON.stringify(e.results[0], null, 2)}`
678
+ }
679
+ ]
680
+ };
681
+ }
682
+ }, X = new N(
683
+ {
684
+ name: "openfda",
685
+ version: "1.0.0",
686
+ description: "OpenFDA Model Context Protocol"
687
+ },
688
+ {
689
+ capabilities: {
690
+ resources: {},
691
+ tools: {}
692
+ }
693
+ }
694
+ ), y = new T(X);
695
+ y.registerTool(S);
696
+ y.registerTool(E);
697
+ y.registerTool(P);
698
+ y.registerTool(A);
699
+ y.registerTool(U);
700
+ y.registerTool(I);
701
+ y.registerTool(M);
702
+ y.registerTool(R);
703
+ async function j() {
704
+ const n = new C();
705
+ await X.connect(n), console.error("OpenFDA MCP Server running on stdio");
646
706
  }
647
- P().catch((t) => {
648
- console.error("Fatal error in main():", t), process.exit(1);
707
+ j().catch((n) => {
708
+ console.error("Fatal error in main():", n), process.exit(1);
649
709
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ythalorossy/openfda",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "OpenFDA Model Context Protocol",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,6 +36,7 @@
36
36
  "zod": "^3.25.0"
37
37
  },
38
38
  "devDependencies": {
39
+ "@types/node": "^25.5.2",
39
40
  "@typescript-eslint/eslint-plugin": "^8.0.0",
40
41
  "@typescript-eslint/parser": "^8.0.0",
41
42
  "eslint": "^9.0.0",