@ythalorossy/openfda 1.0.17 → 1.0.18

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 +225 -233
  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 l from "zod";
8
+ class g {
9
9
  constructor() {
10
- k(this, "url", "https://api.fda.gov/drug/");
10
+ k(this, "urlBase", "https://api.fda.gov");
11
11
  k(this, "params", /* @__PURE__ */ new Map());
12
12
  }
13
- context(n) {
14
- return this.params.set("context", n), this;
13
+ dataset(t) {
14
+ return this.params.set("dataset", t), this;
15
15
  }
16
- search(n) {
17
- return this.params.set("search", n), this;
16
+ context(t) {
17
+ return this.params.set("context", t), this;
18
18
  }
19
- limit(n = 1) {
20
- return this.params.set("limit", n), this;
19
+ search(t) {
20
+ return this.params.set("search", t), this;
21
+ }
22
+ limit(t = 1) {
23
+ return this.params.set("limit", t), this;
21
24
  }
22
25
  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)
26
+ const t = this.params.get("dataset"), r = this.params.get("context"), s = this.params.get("search"), e = this.params.get("limit") ?? 1, i = process.env.OPENFDA_API_KEY;
27
+ if (!t || !r || !s)
27
28
  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
+ return `${this.urlBase}/${t}/${r}.json?api_key=${i}&search=${s}&limit=${e}`;
29
30
  }
30
31
  }
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
- );
32
+ class T {
33
+ constructor(t) {
34
+ k(this, "registerTool", (t) => this.server.registerTool(
35
+ t.name,
36
+ {
37
+ title: t.name,
38
+ description: t.description,
39
+ inputSchema: t.inputSchema
40
+ },
41
+ t.handler
42
+ ));
43
+ this.server = t;
42
44
  }
43
45
  }
44
46
  const F = {
@@ -48,95 +50,81 @@ 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 y(n, t = {}) {
60
+ const { maxRetries: r, retryDelay: s, timeout: e } = { ...F, ...t }, i = {
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,
67
+ const o = new AbortController(), d = setTimeout(() => o.abort(), e), c = await fetch(n, {
68
+ headers: i,
71
69
  signal: o.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(d), !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 h = s * Math.pow(2, p);
104
+ await w(h);
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 };
127
+ return { data: u, error: null };
140
128
  } catch (o) {
141
129
  let d;
142
130
  if (o.name === "AbortError" ? d = {
@@ -151,13 +139,9 @@ async function y(t, n = {}) {
151
139
  type: "unknown",
152
140
  message: `Unexpected error: ${o.message || "Unknown error occurred"}`,
153
141
  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);
142
+ }, a = d, p < r && x(o)) {
143
+ const c = s * Math.pow(2, p);
144
+ await w(c);
161
145
  continue;
162
146
  }
163
147
  break;
@@ -176,40 +160,40 @@ const X = new N(
176
160
  tools: {}
177
161
  }
178
162
  }
179
- ), b = new C(X);
180
- function A(t) {
181
- const n = t.trim().toUpperCase();
163
+ ), b = new T(X);
164
+ function E(n) {
165
+ const t = n.trim().toUpperCase();
182
166
  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;
167
+ if (t.includes("-")) {
168
+ const i = t.split("-");
169
+ if (i.length === 2)
170
+ r = t, s = null;
171
+ else if (i.length === 3)
172
+ r = `${i[0]}-${i[1]}`, s = t;
189
173
  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;
174
+ return { productNDC: t, packageNDC: null, isValid: !1 };
175
+ } else if (t.length === 11)
176
+ r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = `${t.substring(0, 5)}-${t.substring(5, 9)}-${t.substring(9, 11)}`;
177
+ else if (t.length === 9)
178
+ r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = null;
195
179
  else
196
- return { productNDC: n, packageNDC: null, isValid: !1 };
180
+ return { productNDC: t, packageNDC: null, isValid: !1 };
197
181
  const e = /^\d{5}-\d{4}$/.test(r);
198
182
  return { productNDC: r, packageNDC: s, isValid: e };
199
183
  }
200
184
  b.registerTool({
201
185
  name: "get-drug-by-name",
202
186
  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")
187
+ inputSchema: l.object({
188
+ drugName: l.string().describe("Drug name")
205
189
  }),
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);
190
+ handler: async ({ drugName: n }) => {
191
+ const t = new g().dataset("drug").context("label").search(`openfda.brand_name:"${n}"`).limit(1).build(), { data: r, error: s } = await y(t);
208
192
  if (s) {
209
- let a = `Failed to retrieve drug data for "${t}": ${s.message}`;
193
+ let a = `Failed to retrieve drug data for "${n}": ${s.message}`;
210
194
  switch (s.type) {
211
195
  case "http":
212
- s.status === 404 ? a += `
196
+ s.status === 404 ? a += `${t}
213
197
 
214
198
  Suggestions:
215
199
  - Verify the exact brand name spelling
@@ -235,19 +219,20 @@ The request took too long. Please try again.`;
235
219
  type: "text",
236
220
  text: a
237
221
  }
238
- ]
222
+ ],
223
+ isError: !0
239
224
  };
240
225
  }
241
- if (!r || !r.results || r.results.length === 0)
226
+ if (!(r != null && r.results) || r.results.length === 0)
242
227
  return {
243
228
  content: [
244
229
  {
245
230
  type: "text",
246
- text: `No drug information found for "${t}". Please verify the brand name spelling or try searching for the generic name.`
231
+ text: `No drug information found for "${n}". Please verify the brand name spelling or try searching for the generic name.`
247
232
  }
248
233
  ]
249
234
  };
250
- const e = r.results[0], p = {
235
+ const e = r.results[0], i = {
251
236
  brand_name: e == null ? void 0 : e.openfda.brand_name,
252
237
  generic_name: e == null ? void 0 : e.openfda.generic_name,
253
238
  manufacturer_name: e == null ? void 0 : e.openfda.manufacturer_name,
@@ -269,7 +254,7 @@ The request took too long. Please try again.`;
269
254
  type: "text",
270
255
  text: `Drug information retrieved successfully:
271
256
 
272
- ${JSON.stringify(p, null, 2)}`
257
+ ${JSON.stringify(i, null, 2)}`
273
258
  }
274
259
  ]
275
260
  };
@@ -278,37 +263,38 @@ ${JSON.stringify(p, null, 2)}`
278
263
  b.registerTool({
279
264
  name: "get-drug-by-generic-name",
280
265
  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")
266
+ inputSchema: l.object({
267
+ genericName: l.string().describe("Generic drug name (active ingredient)"),
268
+ limit: l.number().optional().default(5).describe("Maximum number of results to return")
284
269
  }),
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);
270
+ handler: async ({ genericName: n, limit: t }) => {
271
+ const r = new g().dataset("drug").context("label").search(`openfda.generic_name:"${n}"`).limit(t).build(), { data: s, error: e } = await y(r);
287
272
  if (e)
288
273
  return {
289
274
  content: [
290
275
  {
291
276
  type: "text",
292
- text: `Failed to retrieve drug data for generic name "${t}": ${e.message}`
277
+ text: `Failed to retrieve drug data for generic name "${n}": ${e.message}`
293
278
  }
294
- ]
279
+ ],
280
+ isError: !0
295
281
  };
296
- if (!s || !s.results || s.results.length === 0)
282
+ if (!(s != null && s.results) || s.results.length === 0)
297
283
  return {
298
284
  content: [
299
285
  {
300
286
  type: "text",
301
- text: `No drug information found for generic name "${t}".`
287
+ text: `No drug information found for generic name "${n}".`
302
288
  }
303
289
  ]
304
290
  };
305
- const p = s.results.map((a) => {
306
- var c, o, d, i;
291
+ const i = s.results.map((a) => {
292
+ var p, o, d, c;
307
293
  return {
308
- brand_name: ((c = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : c[0]) || "Unknown",
294
+ brand_name: ((p = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : p[0]) || "Unknown",
309
295
  generic_name: ((o = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : o[0]) || "Unknown",
310
296
  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",
297
+ product_type: ((c = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : c[0]) || "Unknown",
312
298
  route: (a == null ? void 0 : a.openfda.route) || []
313
299
  };
314
300
  });
@@ -316,9 +302,9 @@ b.registerTool({
316
302
  content: [
317
303
  {
318
304
  type: "text",
319
- text: `Found ${p.length} drug(s) with generic name "${t}":
305
+ text: `Found ${i.length} drug(s) with generic name "${n}":
320
306
 
321
- ${JSON.stringify(p, null, 2)}`
307
+ ${JSON.stringify(i, null, 2)}`
322
308
  }
323
309
  ]
324
310
  };
@@ -327,42 +313,43 @@ ${JSON.stringify(p, null, 2)}`
327
313
  b.registerTool({
328
314
  name: "get-drug-adverse-events",
329
315
  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")
316
+ inputSchema: l.object({
317
+ drugName: l.string().describe("Drug name (brand or generic)"),
318
+ limit: l.number().optional().default(10).describe("Maximum number of events to return"),
319
+ seriousness: l.enum(["serious", "non-serious", "all"]).optional().default("all").describe("Filter by event seriousness")
334
320
  }),
335
- handler: async ({ drugName: t, limit: n, seriousness: r }) => {
336
- let s = `patient.drug.medicinalproduct:"${t}"`;
321
+ handler: async ({ drugName: n, limit: t, seriousness: r }) => {
322
+ let s = `patient.drug.medicinalproduct:"${n}"`;
337
323
  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);
324
+ const e = new g().dataset("drug").context("event").search(s).limit(t).build(), { data: i, error: a } = await y(e);
339
325
  if (a)
340
326
  return {
341
327
  content: [
342
328
  {
343
329
  type: "text",
344
- text: `Failed to retrieve adverse events for "${t}": ${a.message}`
330
+ text: `Failed to retrieve adverse events for "${n}": ${a.message}`
345
331
  }
346
- ]
332
+ ],
333
+ isError: !0
347
334
  };
348
- if (!p || !p.results || p.results.length === 0)
335
+ if (!(i != null && i.results) || i.results.length === 0)
349
336
  return {
350
337
  content: [
351
338
  {
352
339
  type: "text",
353
- text: `No adverse events found for "${t}".`
340
+ text: `No adverse events found for "${n}".`
354
341
  }
355
342
  ]
356
343
  };
357
- const c = p.results.map((o) => {
358
- var d, i, l, m, f, g, $;
344
+ const p = i.results.map((o) => {
345
+ var d, c, u, f, m, h, $;
359
346
  return {
360
347
  report_id: o.safetyreportid,
361
348
  serious: o.serious === "1" ? "Yes" : "No",
362
349
  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)) || [],
350
+ patient_sex: ((c = o.patient) == null ? void 0 : c.patientsex) === "1" ? "Male" : ((u = o.patient) == null ? void 0 : u.patientsex) === "2" ? "Female" : "Unknown",
351
+ reactions: ((m = (f = o.patient) == null ? void 0 : f.reaction) == null ? void 0 : m.map((_) => _.reactionmeddrapt).slice(0, 3)) || [],
352
+ outcomes: (($ = (h = o.patient) == null ? void 0 : h.reaction) == null ? void 0 : $.map((_) => _.reactionoutcome).slice(0, 3)) || [],
366
353
  report_date: o.receiptdate || "Unknown"
367
354
  };
368
355
  });
@@ -370,9 +357,9 @@ b.registerTool({
370
357
  content: [
371
358
  {
372
359
  type: "text",
373
- text: `Found ${c.length} adverse event report(s) for "${t}":
360
+ text: `Found ${p.length} adverse event report(s) for "${n}":
374
361
 
375
- ${JSON.stringify(c, null, 2)}`
362
+ ${JSON.stringify(p, null, 2)}`
376
363
  }
377
364
  ]
378
365
  };
@@ -381,47 +368,49 @@ ${JSON.stringify(c, null, 2)}`
381
368
  b.registerTool({
382
369
  name: "get-drugs-by-manufacturer",
383
370
  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")
371
+ inputSchema: l.object({
372
+ manufacturerName: l.string().describe("Manufacturer/company name"),
373
+ limit: l.number().optional().default(20).describe("Maximum number of drugs to return")
387
374
  }),
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);
375
+ handler: async ({ manufacturerName: n, limit: t }) => {
376
+ const r = new g().dataset("drug").context("label").search(`openfda.manufacturer_name:"${n}"`).limit(t).build(), { data: s, error: e } = await y(r);
390
377
  if (e)
391
378
  return {
392
379
  content: [
393
380
  {
394
381
  type: "text",
395
- text: `Failed to retrieve drugs for manufacturer "${t}": ${e.message}`
382
+ text: `${r}
383
+ Failed to retrieve drugs for manufacturer "${n}": ${e.message}`
396
384
  }
397
- ]
385
+ ],
386
+ isError: !0
398
387
  };
399
- if (!s || !s.results || s.results.length === 0)
388
+ if (!(s != null && s.results) || s.results.length === 0)
400
389
  return {
401
390
  content: [
402
391
  {
403
392
  type: "text",
404
- text: `No drugs found for manufacturer "${t}".`
393
+ text: `No drugs found for manufacturer "${n}".`
405
394
  }
406
395
  ]
407
396
  };
408
- const p = s.results.map((a) => {
409
- var c, o, d, i;
397
+ const i = s.results.map((a) => {
398
+ var p, o, d, c;
410
399
  return {
411
- brand_name: ((c = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : c[0]) || "Unknown",
400
+ brand_name: ((p = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : p[0]) || "Unknown",
412
401
  generic_name: ((o = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : o[0]) || "Unknown",
413
402
  product_type: ((d = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : d[0]) || "Unknown",
414
403
  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"
404
+ ndc: ((c = a == null ? void 0 : a.openfda.product_ndc) == null ? void 0 : c[0]) || "Unknown"
416
405
  };
417
406
  });
418
407
  return {
419
408
  content: [
420
409
  {
421
410
  type: "text",
422
- text: `Found ${p.length} drug(s) from manufacturer "${t}":
411
+ text: `Found ${i.length} drug(s) from manufacturer "${n}":
423
412
 
424
- ${JSON.stringify(p, null, 2)}`
413
+ ${JSON.stringify(i, null, 2)}`
425
414
  }
426
415
  ]
427
416
  };
@@ -430,33 +419,34 @@ ${JSON.stringify(p, null, 2)}`
430
419
  b.registerTool({
431
420
  name: "get-drug-safety-info",
432
421
  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")
422
+ inputSchema: l.object({
423
+ drugName: l.string().describe("Drug brand name")
435
424
  }),
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);
425
+ handler: async ({ drugName: n }) => {
426
+ var a, p;
427
+ const t = new g().dataset("drug").context("label").search(`openfda.brand_name:"${n}"`).limit(1).build(), { data: r, error: s } = await y(t);
439
428
  if (s)
440
429
  return {
441
430
  content: [
442
431
  {
443
432
  type: "text",
444
- text: `Failed to retrieve safety information for "${t}": ${s.message}`
433
+ text: `Failed to retrieve safety information for "${n}": ${s.message}`
445
434
  }
446
- ]
435
+ ],
436
+ isError: !0
447
437
  };
448
- if (!r || !r.results || r.results.length === 0)
438
+ if (!(r != null && r.results) || r.results.length === 0)
449
439
  return {
450
440
  content: [
451
441
  {
452
442
  type: "text",
453
- text: `No safety information found for "${t}".`
443
+ text: `No safety information found for "${n}".`
454
444
  }
455
445
  ]
456
446
  };
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",
447
+ const e = r.results[0], i = {
448
+ drug_name: ((a = e == null ? void 0 : e.openfda.brand_name) == null ? void 0 : a[0]) || n,
449
+ generic_name: ((p = e == null ? void 0 : e.openfda.generic_name) == null ? void 0 : p[0]) || "Unknown",
460
450
  warnings: (e == null ? void 0 : e.warnings) || [],
461
451
  contraindications: (e == null ? void 0 : e.contraindications) || [],
462
452
  drug_interactions: (e == null ? void 0 : e.drug_interactions) || [],
@@ -472,9 +462,9 @@ b.registerTool({
472
462
  content: [
473
463
  {
474
464
  type: "text",
475
- text: `Safety information for "${t}":
465
+ text: `Safety information for "${n}":
476
466
 
477
- ${JSON.stringify(p, null, 2)}`
467
+ ${JSON.stringify(i, null, 2)}`
478
468
  }
479
469
  ]
480
470
  };
@@ -483,48 +473,47 @@ ${JSON.stringify(p, null, 2)}`
483
473
  b.registerTool({
484
474
  name: "get-drug-by-ndc",
485
475
  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(
476
+ inputSchema: l.object({
477
+ ndcCode: l.string().describe(
488
478
  "National Drug Code (NDC) - accepts formats: XXXXX-XXXX, XXXXX-XXXX-XX, or without dashes"
489
479
  )
490
480
  }),
491
- handler: async ({ ndcCode: t }) => {
492
- const { productNDC: n, packageNDC: r, isValid: s } = A(t);
481
+ handler: async ({ ndcCode: n }) => {
482
+ const { productNDC: t, packageNDC: r, isValid: s } = E(n);
493
483
  if (!s)
494
484
  return {
495
485
  content: [
496
486
  {
497
487
  type: "text",
498
- text: `Invalid NDC format: "${t}"
488
+ text: `Invalid NDC format: "${n}"
499
489
 
500
490
  ✅ Accepted formats:
501
491
  • Product NDC: 12345-1234
502
492
  • Package NDC: 12345-1234-01
503
493
  • Without dashes: 123451234 or 12345123401`
504
494
  }
505
- ]
495
+ ],
496
+ isError: !0
506
497
  };
507
- console.log(
508
- `Searching for NDC: input="${t}", productNDC="${n}", packageNDC="${r}"`
509
- );
510
- let e = `openfda.product_ndc:"${n}"`;
498
+ let e = `openfda.product_ndc:"${t}"`;
511
499
  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)
500
+ const i = new g().dataset("drug").context("label").search(e).limit(10).build(), { data: a, error: p } = await y(i);
501
+ if (p)
514
502
  return {
515
503
  content: [
516
504
  {
517
505
  type: "text",
518
- text: `Failed to retrieve drug data for NDC "${t}": ${c.message}`
506
+ text: `Failed to retrieve drug data for NDC "${n}": ${p.message}`
519
507
  }
520
- ]
508
+ ],
509
+ isError: !0
521
510
  };
522
- if (!a || !a.results || a.results.length === 0)
511
+ if (!(a != null && a.results) || a.results.length === 0)
523
512
  return {
524
513
  content: [
525
514
  {
526
515
  type: "text",
527
- text: `No drug found with NDC "${t}" (product: ${n}).
516
+ text: `No drug found with NDC "${n}" (product: ${t}).
528
517
 
529
518
  💡 Tips:
530
519
  • Verify the NDC format
@@ -533,41 +522,41 @@ b.registerTool({
533
522
  }
534
523
  ]
535
524
  };
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)
525
+ const o = a.results.map((u) => {
526
+ var h, $;
527
+ const f = ((h = u.openfda.product_ndc) == null ? void 0 : h.filter((_) => _ === t)) || [], m = (($ = u.openfda.package_ndc) == null ? void 0 : $.filter(
528
+ (_) => r ? _ === r : _.startsWith(t)
540
529
  )) || [];
541
530
  return {
542
531
  // 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 || [],
532
+ brand_name: u.openfda.brand_name || [],
533
+ generic_name: u.openfda.generic_name || [],
534
+ manufacturer_name: u.openfda.manufacturer_name || [],
535
+ product_type: u.openfda.product_type || [],
536
+ route: u.openfda.route || [],
537
+ substance_name: u.openfda.substance_name || [],
549
538
  // 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 || [],
539
+ matching_product_ndc: f,
540
+ matching_package_ndc: m,
541
+ all_product_ndc: u.openfda.product_ndc || [],
542
+ all_package_ndc: u.openfda.package_ndc || [],
554
543
  // 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 || []
544
+ dosage_and_administration: u.dosage_and_administration || [],
545
+ package_label_principal_display_panel: u.package_label_principal_display_panel || [],
546
+ active_ingredient: u.active_ingredient || [],
547
+ purpose: u.purpose || []
559
548
  };
560
549
  }), d = o.reduce(
561
- (l, m) => l + m.matching_package_ndc.length,
550
+ (u, f) => u + f.matching_package_ndc.length,
562
551
  0
563
- ), i = r ? `Searched for specific package NDC: ${r}` : `Searched for product NDC: ${n} (all packages)`;
552
+ ), c = r ? `Searched for specific package NDC: ${r}` : `Searched for product NDC: ${t} (all packages)`;
564
553
  return {
565
554
  content: [
566
555
  {
567
556
  type: "text",
568
- text: `✅ Found ${o.length} drug(s) with ${d} package(s) for NDC "${t}"
557
+ text: `✅ Found ${o.length} drug(s) with ${d} package(s) for NDC "${n}"
569
558
 
570
- ${i}
559
+ ${c}
571
560
 
572
561
  ${JSON.stringify(o, null, 2)}`
573
562
  }
@@ -578,46 +567,49 @@ ${JSON.stringify(o, null, 2)}`
578
567
  b.registerTool({
579
568
  name: "get-drug-by-product-ndc",
580
569
  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")
570
+ inputSchema: l.object({
571
+ productNDC: l.string().describe("Product NDC in format XXXXX-XXXX")
583
572
  }),
584
- handler: async ({ productNDC: t }) => {
585
- var c;
586
- if (!/^\d{5}-\d{4}$/.test(t.trim()))
573
+ handler: async ({ productNDC: n }) => {
574
+ var p;
575
+ if (!/^\d{5}-\d{4}$/.test(n.trim()))
587
576
  return {
588
577
  content: [
589
578
  {
590
579
  type: "text",
591
- text: `Invalid product NDC format: "${t}"
580
+ text: `Invalid product NDC format: "${n}"
592
581
 
593
582
  ✅ Required format: XXXXX-XXXX (e.g., 12345-1234)`
594
583
  }
595
- ]
584
+ ],
585
+ isError: !0
596
586
  };
597
- const n = new h().context("label").search(`openfda.product_ndc:"${t.trim()}"`).limit(1).build(), { data: r, error: s } = await y(n);
587
+ const t = new g().dataset("drug").context("label").search(`openfda.product_ndc:"${n.trim()}"`).limit(1).build(), { data: r, error: s } = await y(t);
598
588
  if (s)
599
589
  return {
600
590
  content: [
601
591
  {
602
592
  type: "text",
603
- text: `Failed to retrieve drug data for product NDC "${t}": ${s.message}`
593
+ text: `${t}Failed to retrieve drug data for product NDC "${n}": ${s.message}`
604
594
  }
605
- ]
595
+ ],
596
+ isError: !0
606
597
  };
607
- if (!r || !r.results || r.results.length === 0)
598
+ if (!(r != null && r.results) || r.results.length === 0)
608
599
  return {
609
600
  content: [
610
601
  {
611
602
  type: "text",
612
- text: `No drug found with product NDC "${t}".`
603
+ text: `No drug found with product NDC "${n}".`
613
604
  }
614
- ]
605
+ ],
606
+ structuredContent: null
615
607
  };
616
- const e = r.results[0], p = ((c = e.openfda.package_ndc) == null ? void 0 : c.filter(
617
- (o) => o.startsWith(t.trim())
608
+ const e = r.results[0], i = ((p = e.openfda.package_ndc) == null ? void 0 : p.filter(
609
+ (o) => o.startsWith(n.trim())
618
610
  )) || [], a = {
619
- product_ndc: t,
620
- available_packages: p,
611
+ product_ndc: n,
612
+ available_packages: i,
621
613
  brand_name: e.openfda.brand_name || [],
622
614
  generic_name: e.openfda.generic_name || [],
623
615
  manufacturer_name: e.openfda.manufacturer_name || [],
@@ -632,7 +624,7 @@ b.registerTool({
632
624
  content: [
633
625
  {
634
626
  type: "text",
635
- text: `✅ Product NDC "${t}" found with ${p.length} package variation(s):
627
+ text: `✅ Product NDC "${n}" found with ${i.length} package variation(s):
636
628
 
637
629
  ${JSON.stringify(a, null, 2)}`
638
630
  }
@@ -640,10 +632,10 @@ ${JSON.stringify(a, null, 2)}`
640
632
  };
641
633
  }
642
634
  });
643
- async function P() {
644
- const t = new T();
645
- await X.connect(t), console.error("OpenFDA MCP Server running on stdio");
635
+ async function S() {
636
+ const n = new C();
637
+ await X.connect(n), console.error("OpenFDA MCP Server running on stdio");
646
638
  }
647
- P().catch((t) => {
648
- console.error("Fatal error in main():", t), process.exit(1);
639
+ S().catch((n) => {
640
+ console.error("Fatal error in main():", n), process.exit(1);
649
641
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ythalorossy/openfda",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
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",