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