@ythalorossy/openfda 1.2.0 → 1.3.0

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 +13 -4
  2. package/dist/index.js +389 -316
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
- var B = Object.defineProperty;
3
- var J = (e, t, n) => t in e ? B(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
4
- var A = (e, t, n) => J(e, typeof t != "symbol" ? t + "" : t, n);
5
- import { McpServer as z } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import { StdioServerTransport as H } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import u from "zod";
8
- const Q = `OPENFDA_API_KEY is not set. This server reads the key from its process environment; a .env file is not loaded. Add it to the "env" block of your MCP client configuration. Get a free key at https://open.fda.gov/apis/authentication/
2
+ var Q = Object.defineProperty;
3
+ var Z = (e, t, n) => t in e ? Q(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
4
+ var A = (e, t, n) => Z(e, typeof t != "symbol" ? t + "" : t, n);
5
+ import { McpServer as ee } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport as te } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import d from "zod";
8
+ const ne = `OPENFDA_API_KEY is not set. This server reads the key from its process environment; a .env file is not loaded. Add it to the "env" block of your MCP client configuration. Get a free key at https://open.fda.gov/apis/authentication/
9
9
 
10
- To run without a key on the unauthenticated tier (40 requests/minute, 1,000/day per IP, and no rate-limit headers), set OPENFDA_ALLOW_KEYLESS=1.`, Z = "openfda: OPENFDA_ALLOW_KEYLESS=1 — using the unauthenticated tier (40 requests/minute, no rate-limit visibility). Set OPENFDA_API_KEY for 240 requests/minute.", ee = "openfda: OPENFDA_API_KEY is not set; every tool call will return a configuration error. See https://open.fda.gov/apis/authentication/";
11
- function O(e = process.env) {
10
+ To run without a key on the unauthenticated tier (40 requests/minute, 1,000/day per IP, and no rate-limit headers), set OPENFDA_ALLOW_KEYLESS=1.`, re = "openfda: OPENFDA_ALLOW_KEYLESS=1 — using the unauthenticated tier (40 requests/minute, no rate-limit visibility). Set OPENFDA_API_KEY for 240 requests/minute.", ae = "openfda: OPENFDA_API_KEY is not set; every tool call will return a configuration error. See https://open.fda.gov/apis/authentication/";
11
+ function C(e = process.env) {
12
12
  var n;
13
13
  const t = (n = e.OPENFDA_API_KEY) == null ? void 0 : n.trim();
14
- return t ? { ok: !0, apiKey: t } : e.OPENFDA_ALLOW_KEYLESS === "1" ? { ok: !0, apiKey: null } : { ok: !1, message: Q };
14
+ return t ? { ok: !0, apiKey: t } : e.OPENFDA_ALLOW_KEYLESS === "1" ? { ok: !0, apiKey: null } : { ok: !1, message: ne };
15
15
  }
16
- function te(e = process.env) {
17
- const t = O(e);
18
- t.ok ? t.apiKey === null && console.error(Z) : console.error(ee);
16
+ function se(e = process.env) {
17
+ const t = C(e);
18
+ t.ok ? t.apiKey === null && console.error(re) : console.error(ae);
19
19
  }
20
- class ne {
20
+ class oe {
21
21
  constructor(t) {
22
22
  A(this, "registerTool", (t) => this.server.registerTool(
23
23
  t.name,
@@ -27,9 +27,9 @@ class ne {
27
27
  inputSchema: t.inputSchema
28
28
  },
29
29
  async (n) => {
30
- const r = O();
31
- return r.ok ? t.handler(n) : {
32
- content: [{ type: "text", text: r.message }],
30
+ const a = C();
31
+ return a.ok ? t.handler(n) : {
32
+ content: [{ type: "text", text: a.message }],
33
33
  isError: !0
34
34
  };
35
35
  }
@@ -37,40 +37,41 @@ class ne {
37
37
  this.server = t;
38
38
  }
39
39
  }
40
- const m = (e) => Array.isArray(e) ? e : [], M = (e) => {
41
- const t = m(e.warnings);
42
- return t.length > 0 ? t : m(e.warnings_and_cautions);
40
+ const f = (e) => Array.isArray(e) ? e : [], j = (e) => {
41
+ const t = f(e.warnings);
42
+ return t.length > 0 ? t : f(e.warnings_and_cautions);
43
43
  };
44
- function re(e) {
44
+ function ie(e) {
45
45
  return {
46
- boxed_warning: m(e.boxed_warning),
47
- warnings: M(e),
48
- warnings_and_cautions: m(e.warnings_and_cautions),
49
- contraindications: m(e.contraindications),
50
- drug_interactions: m(e.drug_interactions),
51
- precautions: m(e.precautions),
52
- adverse_reactions: m(e.adverse_reactions),
53
- overdosage: m(e.overdosage),
54
- do_not_use: m(e.do_not_use),
55
- ask_doctor: m(e.ask_doctor),
56
- stop_use: m(e.stop_use),
57
- pregnancy_or_breast_feeding: m(e.pregnancy_or_breast_feeding)
46
+ boxed_warning: f(e.boxed_warning),
47
+ warnings: j(e),
48
+ warnings_and_cautions: f(e.warnings_and_cautions),
49
+ contraindications: f(e.contraindications),
50
+ drug_interactions: f(e.drug_interactions),
51
+ precautions: f(e.precautions),
52
+ adverse_reactions: f(e.adverse_reactions),
53
+ overdosage: f(e.overdosage),
54
+ do_not_use: f(e.do_not_use),
55
+ ask_doctor: f(e.ask_doctor),
56
+ ask_doctor_or_pharmacist: f(e.ask_doctor_or_pharmacist),
57
+ stop_use: f(e.stop_use),
58
+ pregnancy_or_breast_feeding: f(e.pregnancy_or_breast_feeding)
58
59
  };
59
60
  }
60
- function ae(e) {
61
+ function ce(e) {
61
62
  return {
62
- indications_and_usage: m(e.indications_and_usage),
63
- boxed_warning: m(e.boxed_warning),
64
- warnings: M(e),
65
- warnings_and_cautions: m(e.warnings_and_cautions),
66
- do_not_use: m(e.do_not_use),
67
- ask_doctor: m(e.ask_doctor),
68
- ask_doctor_or_pharmacist: m(e.ask_doctor_or_pharmacist),
69
- stop_use: m(e.stop_use),
70
- pregnancy_or_breast_feeding: m(e.pregnancy_or_breast_feeding)
63
+ indications_and_usage: f(e.indications_and_usage),
64
+ boxed_warning: f(e.boxed_warning),
65
+ warnings: j(e),
66
+ warnings_and_cautions: f(e.warnings_and_cautions),
67
+ do_not_use: f(e.do_not_use),
68
+ ask_doctor: f(e.ask_doctor),
69
+ ask_doctor_or_pharmacist: f(e.ask_doctor_or_pharmacist),
70
+ stop_use: f(e.stop_use),
71
+ pregnancy_or_breast_feeding: f(e.pregnancy_or_breast_feeding)
71
72
  };
72
73
  }
73
- class y {
74
+ class x {
74
75
  constructor() {
75
76
  A(this, "urlBase", "https://api.fda.gov");
76
77
  A(this, "params", /* @__PURE__ */ new Map());
@@ -100,102 +101,102 @@ class y {
100
101
  return this.params.set("sort", t), this;
101
102
  }
102
103
  build() {
103
- const t = this.params.get("dataset"), n = this.params.get("context"), r = this.params.get("search"), i = this.params.get("limit") ?? 1;
104
- if (!t || !n || !r)
104
+ const t = this.params.get("dataset"), n = this.params.get("context"), a = this.params.get("search"), i = this.params.get("limit") ?? 1;
105
+ if (!t || !n || !a)
105
106
  throw new Error("Missing required parameters: context or search");
106
- const o = O(), s = new URLSearchParams();
107
- o.ok && o.apiKey && s.set("api_key", o.apiKey), s.set("search", String(r)), s.set("limit", String(i));
108
- for (const d of ["count", "skip", "sort"]) {
109
- const c = this.params.get(d);
110
- c !== void 0 && s.set(d, String(c));
107
+ const u = C(), s = new URLSearchParams();
108
+ u.ok && u.apiKey && s.set("api_key", u.apiKey), s.set("search", String(a)), s.set("limit", String(i));
109
+ for (const o of ["count", "skip", "sort"]) {
110
+ const c = this.params.get(o);
111
+ c !== void 0 && s.set(o, String(c));
111
112
  }
112
113
  return `${this.urlBase}/${t}/${n}.json?${s}`;
113
114
  }
114
115
  }
115
- const U = (e) => e.replace(/([?&]api_key=)[^&\s]*/gi, "$1<REDACTED>"), se = {
116
+ const I = (e) => e.replace(/([?&]api_key=)[^&\s]*/gi, "$1<REDACTED>"), ue = {
116
117
  maxRetries: 3,
117
118
  retryDelay: 1e3,
118
119
  // 1 second
119
120
  timeout: 3e4
120
121
  // 30 seconds
121
122
  };
122
- function I(e) {
123
+ function R(e) {
123
124
  return !!(e.name === "TypeError" && e.message.includes("fetch") || e.name === "AbortError" || e.status >= 500 && e.status <= 599 || e.status === 429);
124
125
  }
125
126
  function P(e) {
126
127
  return new Promise((t) => setTimeout(t, e));
127
128
  }
128
- async function v(e, t = {}) {
129
- const { maxRetries: n, retryDelay: r, timeout: i } = { ...se, ...t }, o = {
129
+ async function $(e, t = {}) {
130
+ const { maxRetries: n, retryDelay: a, timeout: i } = { ...ue, ...t }, u = {
130
131
  "User-Agent": "@ythalorossy/openfda",
131
132
  Accept: "application/json"
132
133
  };
133
134
  let s = null;
134
- for (let d = 0; d <= n; d++)
135
+ for (let o = 0; o <= n; o++)
135
136
  try {
136
- const c = new AbortController(), l = setTimeout(() => c.abort(), i), a = await fetch(e, {
137
- headers: o,
137
+ const c = new AbortController(), l = setTimeout(() => c.abort(), i), r = await fetch(e, {
138
+ headers: u,
138
139
  signal: c.signal
139
140
  });
140
- if (clearTimeout(l), !a.ok) {
141
- const f = await a.text().catch(() => "Unable to read error response"), _ = {
141
+ if (clearTimeout(l), !r.ok) {
142
+ const _ = await r.text().catch(() => "Unable to read error response"), m = {
142
143
  type: "http",
143
- message: `HTTP ${a.status}: ${a.statusText}`,
144
- status: a.status,
145
- details: f
144
+ message: `HTTP ${r.status}: ${r.statusText}`,
145
+ status: r.status,
146
+ details: _
146
147
  };
147
- switch (a.status) {
148
+ switch (r.status) {
148
149
  case 400:
149
- _.message = "Bad Request: Invalid search query or parameters";
150
+ m.message = "Bad Request: Invalid search query or parameters";
150
151
  break;
151
152
  case 401:
152
- _.message = "Unauthorized: Invalid or missing API key";
153
+ m.message = "Unauthorized: Invalid or missing API key";
153
154
  break;
154
155
  case 403:
155
- _.message = "Forbidden: API key may be invalid or quota exceeded";
156
+ m.message = "Forbidden: API key may be invalid or quota exceeded";
156
157
  break;
157
158
  case 404:
158
- _.message = "Not Found: No results found for the specified query";
159
+ m.message = "Not Found: No results found for the specified query";
159
160
  break;
160
161
  case 429:
161
- _.message = "Rate Limited: Too many requests. Retrying...";
162
+ m.message = "Rate Limited: Too many requests. Retrying...";
162
163
  break;
163
164
  case 500:
164
- _.message = "Server Error: OpenFDA service is experiencing issues";
165
+ m.message = "Server Error: OpenFDA service is experiencing issues";
165
166
  break;
166
167
  default:
167
- _.message = `HTTP Error ${a.status}: ${a.statusText}`;
168
+ m.message = `HTTP Error ${r.status}: ${r.statusText}`;
168
169
  }
169
- if (s = _, a.status >= 400 && a.status < 500 && a.status !== 429)
170
+ if (s = m, r.status >= 400 && r.status < 500 && r.status !== 429)
170
171
  break;
171
- if (d < n && I({ status: a.status })) {
172
- const h = r * Math.pow(2, d);
173
- await P(h);
172
+ if (o < n && R({ status: r.status })) {
173
+ const g = a * Math.pow(2, o);
174
+ await P(g);
174
175
  continue;
175
176
  }
176
177
  break;
177
178
  }
178
- let g;
179
+ let h;
179
180
  try {
180
- g = await a.json();
181
- } catch (f) {
181
+ h = await r.json();
182
+ } catch (_) {
182
183
  s = {
183
184
  type: "parsing",
184
- message: U(
185
- `Failed to parse JSON response: ${f instanceof Error ? f.message : "Unknown parsing error"}`
185
+ message: I(
186
+ `Failed to parse JSON response: ${_ instanceof Error ? _.message : "Unknown parsing error"}`
186
187
  ),
187
- details: f
188
+ details: _
188
189
  };
189
190
  break;
190
191
  }
191
- if (!g) {
192
+ if (!h) {
192
193
  s = {
193
194
  type: "empty_response",
194
195
  message: "Received empty response from OpenFDA API"
195
196
  };
196
197
  break;
197
198
  }
198
- return { data: g, error: null };
199
+ return { data: h, error: null };
199
200
  } catch (c) {
200
201
  let l;
201
202
  if (c.name === "AbortError" ? l = {
@@ -208,55 +209,57 @@ async function v(e, t = {}) {
208
209
  details: c.message
209
210
  } : l = {
210
211
  type: "unknown",
211
- message: U(
212
+ message: I(
212
213
  `Unexpected error: ${c.message || "Unknown error occurred"}`
213
214
  ),
214
215
  details: c
215
- }, s = l, d < n && I(c)) {
216
- const a = r * Math.pow(2, d);
217
- await P(a);
216
+ }, s = l, o < n && R(c)) {
217
+ const r = a * Math.pow(2, o);
218
+ await P(r);
218
219
  continue;
219
220
  }
220
221
  break;
221
222
  }
222
223
  return { data: null, error: s };
223
224
  }
224
- const j = [
225
+ const L = [
225
226
  "openfda.brand_name",
226
227
  "openfda.generic_name",
227
228
  "openfda.substance_name",
228
229
  "spl_product_data_elements"
229
230
  ];
230
- async function X(e, t = 1) {
231
- let n;
232
- for (const r of j) {
233
- const i = new y().dataset("drug").context("label").search(`${r}:"${e}"`).limit(t).build(), { data: o, error: s } = await v(i);
234
- if (s) {
235
- n = s;
231
+ async function X(e, t = 1, n) {
232
+ let a;
233
+ for (const i of L) {
234
+ const u = new x().dataset("drug").context("label").search(`${i}:"${e}"`).limit(t);
235
+ n !== void 0 && u.skip(n);
236
+ const s = u.build(), { data: o, error: c } = await $(s);
237
+ if (c) {
238
+ a = c;
236
239
  continue;
237
240
  }
238
241
  if (o != null && o.results && o.results.length > 0)
239
- return { found: !0, matched_via: r, data: o };
242
+ return { found: !0, matched_via: i, data: o };
240
243
  }
241
- return { found: !1, error: n };
244
+ return { found: !1, error: a };
242
245
  }
243
- const L = (e) => `No label found for "${e}".
246
+ const K = (e) => `No label found for "${e}".
244
247
 
245
- Searched, in order: ${j.join(", ")}.
248
+ Searched, in order: ${L.join(", ")}.
246
249
 
247
250
  Suggestions:
248
251
  - Check the spelling.
249
252
  - Try the generic name instead of the brand (e.g. "amiodarone" rather than "Cordarone").
250
253
  - Try the originator brand rather than a repackager's name.
251
254
  - Some discontinued brands have no current FDA label.`;
252
- function oe(e) {
255
+ function O(e) {
253
256
  const t = (n) => Array.isArray(n) && typeof n[0] == "string" && n[0] ? n[0] : void 0;
254
- return t(e == null ? void 0 : e.generic_name) ?? t(e == null ? void 0 : e.substance_name) ?? "Unknown";
257
+ return t(e == null ? void 0 : e.generic_name) ?? t(e == null ? void 0 : e.substance_name) ?? null;
255
258
  }
256
- function x(e, t, n) {
259
+ function w(e, t, n) {
257
260
  return typeof t != "number" ? `Showing ${e} ${n} (total unknown)` : e >= t ? `Showing all ${t} ${n}` : `Showing ${e} of ${t} ${n}`;
258
261
  }
259
- function k(e, t, n) {
262
+ function S(e, t, n) {
260
263
  return {
261
264
  total: typeof t == "number" ? t : null,
262
265
  returned: e.length,
@@ -264,9 +267,9 @@ function k(e, t, n) {
264
267
  results: e
265
268
  };
266
269
  }
267
- const ie = {
270
+ const de = {
268
271
  name: "get-drug-by-name",
269
- description: "Look up a drug by brand, generic or substance name. Returns brand_name, generic_name, manufacturer_name, product_ndc, substance_name, indications_and_usage, and the safety narrative: boxed_warning, warnings, warnings_and_cautions, do_not_use, ask_doctor, ask_doctor_or_pharmacist, stop_use and pregnancy_or_breast_feeding. Every field is always present, empty when the label has none. Reports matched_via to say which field matched, and a total for how many labels matched.",
272
+ description: `Look up a drug by brand, generic or substance name. Returns an array of labels (default 1, up to limit), each with substance_name, brand_name, generic_name, manufacturer_name, product_ndc, indications_and_usage, and the safety narrative: boxed_warning, warnings, warnings_and_cautions, do_not_use, ask_doctor, ask_doctor_or_pharmacist, stop_use and pregnancy_or_breast_feeding. Every field is always present, empty when the label has none. Reports matched_via to say which field matched, and a total for how many labels matched. The first label is not necessarily the most canonical one — a popular brand can have dozens of labels, and the top match is often a combination product (e.g. "Advil" can return Advil Dual Action with Acetaminophen before plain ibuprofen). substance_name is listed first in each label specifically so a combination product is obvious at a glance. Use limit and skip to page through the rest when the total is high or the first result looks like a combination. route comes from openfda.route, the SPL route of administration. get-drugsfda's products[].route is the Drugs@FDA product route and uses a different controlled vocabulary — the same product can be SUBCUTANEOUS here and INJECTION there — so joining on route across tools will silently miss.`,
270
273
  returnsFields: [
271
274
  "brand_name",
272
275
  "generic_name",
@@ -284,47 +287,62 @@ const ie = {
284
287
  "indications_and_usage",
285
288
  "matched_via"
286
289
  ],
287
- inputSchema: u.object({
288
- drugName: u.string().describe("Drug name")
290
+ inputSchema: d.object({
291
+ drugName: d.string().describe("Drug name (brand, generic or substance)"),
292
+ limit: d.number().int().min(1).max(25).optional().default(1).describe("Maximum number of labels to return"),
293
+ skip: d.number().int().min(0).optional().describe(
294
+ "Offset into the matching labels. The first label is not necessarily the most canonical one — for a brand with many labels, skip reaches the others."
295
+ )
289
296
  }),
290
- async handler({ drugName: e }) {
291
- var i, o, s, d;
292
- const t = await X(e, 1);
293
- if (!t.found)
294
- return t.error ? {
297
+ async handler({
298
+ drugName: e,
299
+ limit: t,
300
+ skip: n
301
+ }) {
302
+ var s, o, c, l;
303
+ const a = await X(e, t ?? 1, n);
304
+ if (!a.found)
305
+ return a.error ? {
295
306
  content: [
296
307
  {
297
308
  type: "text",
298
- text: `Failed to retrieve drug data for "${e}": ${t.error.message}`
309
+ text: `Failed to retrieve drug data for "${e}": ${a.error.message}`
299
310
  }
300
311
  ],
301
312
  isError: !0
302
313
  } : {
303
- content: [{ type: "text", text: L(e) }]
314
+ content: [{ type: "text", text: K(e) }]
304
315
  };
305
- const n = t.data.results[0], r = {
306
- brand_name: n == null ? void 0 : n.openfda.brand_name,
307
- generic_name: n == null ? void 0 : n.openfda.generic_name,
308
- manufacturer_name: n == null ? void 0 : n.openfda.manufacturer_name,
309
- product_ndc: n == null ? void 0 : n.openfda.product_ndc,
310
- product_type: n == null ? void 0 : n.openfda.product_type,
311
- route: n == null ? void 0 : n.openfda.route,
312
- substance_name: n == null ? void 0 : n.openfda.substance_name,
313
- matched_via: t.matched_via,
314
- ...ae(n)
316
+ const i = a.data.results.map((r) => ({
317
+ // substance_name first: a combination product must be obvious at a
318
+ // glance, because the top match for a brand is often a combination.
319
+ substance_name: (r == null ? void 0 : r.openfda.substance_name) ?? [],
320
+ brand_name: (r == null ? void 0 : r.openfda.brand_name) ?? [],
321
+ generic_name: (r == null ? void 0 : r.openfda.generic_name) ?? [],
322
+ manufacturer_name: (r == null ? void 0 : r.openfda.manufacturer_name) ?? [],
323
+ product_ndc: (r == null ? void 0 : r.openfda.product_ndc) ?? [],
324
+ product_type: (r == null ? void 0 : r.openfda.product_type) ?? [],
325
+ route: (r == null ? void 0 : r.openfda.route) ?? [],
326
+ matched_via: a.matched_via,
327
+ ...ce(r)
328
+ })), u = {
329
+ // Only present when the caller supplied it, mirroring
330
+ // get-drug-adverse-events so paged responses stay distinguishable.
331
+ ...n !== void 0 ? { skip: n } : {},
332
+ ...S(i, (o = (s = a.data.meta) == null ? void 0 : s.results) == null ? void 0 : o.total, t ?? 1)
315
333
  };
316
334
  return {
317
335
  content: [
318
336
  {
319
337
  type: "text",
320
- text: `${x(1, (o = (i = t.data.meta) == null ? void 0 : i.results) == null ? void 0 : o.total, `labels matching "${e}"`)}
338
+ text: `${w(i.length, (l = (c = a.data.meta) == null ? void 0 : c.results) == null ? void 0 : l.total, `labels matching "${e}"`)}
321
339
 
322
- ${JSON.stringify(k([r], (d = (s = t.data.meta) == null ? void 0 : s.results) == null ? void 0 : d.total, 1), null, 2)}`
340
+ ${JSON.stringify(u, null, 2)}`
323
341
  }
324
342
  ]
325
343
  };
326
344
  }
327
- }, ce = {
345
+ }, le = {
328
346
  name: "get-drug-by-generic-name",
329
347
  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 as results, up to limit, along with total matched and returned counts.",
330
348
  // Raw upstream records wrapped in an envelope: declare only the envelope
@@ -332,16 +350,16 @@ ${JSON.stringify(k([r], (d = (s = t.data.meta) == null ? void 0 : s.results) ==
332
350
  // inner record fields, because those vary per record. This tool does not
333
351
  // emit matched_via — it searches a single fixed field.
334
352
  returnsFields: ["total", "returned", "limit", "results"],
335
- inputSchema: u.object({
336
- genericName: u.string().describe("Generic drug name (active ingredient)"),
337
- limit: u.number().optional().default(5).describe("Maximum number of results to return")
353
+ inputSchema: d.object({
354
+ genericName: d.string().describe("Generic drug name (active ingredient)"),
355
+ limit: d.number().optional().default(5).describe("Maximum number of results to return")
338
356
  }),
339
357
  async handler({
340
358
  genericName: e,
341
359
  limit: t
342
360
  }) {
343
- var s, d, c, l;
344
- const n = new y().dataset("drug").context("label").search(`openfda.generic_name:"${e}"`).limit(t).build(), { data: r, error: i } = await v(n);
361
+ var s, o, c, l;
362
+ const n = new x().dataset("drug").context("label").search(`openfda.generic_name:"${e}"`).limit(t).build(), { data: a, error: i } = await $(n);
345
363
  if (i)
346
364
  return {
347
365
  content: [
@@ -352,7 +370,7 @@ ${JSON.stringify(k([r], (d = (s = t.data.meta) == null ? void 0 : s.results) ==
352
370
  ],
353
371
  isError: !0
354
372
  };
355
- if (!(r != null && r.results) || r.results.length === 0)
373
+ if (!(a != null && a.results) || a.results.length === 0)
356
374
  return {
357
375
  content: [
358
376
  {
@@ -361,28 +379,30 @@ ${JSON.stringify(k([r], (d = (s = t.data.meta) == null ? void 0 : s.results) ==
361
379
  }
362
380
  ]
363
381
  };
364
- const o = r.results.map((a) => {
365
- var g, f, _, h;
382
+ const u = a.results.map((r) => {
383
+ var h, _, m;
366
384
  return {
367
- brand_name: ((g = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : g[0]) || "Unknown",
368
- generic_name: ((f = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : f[0]) || "Unknown",
369
- manufacturer_name: ((_ = a == null ? void 0 : a.openfda.manufacturer_name) == null ? void 0 : _[0]) || "Unknown",
370
- product_type: ((h = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : h[0]) || "Unknown",
371
- route: (a == null ? void 0 : a.openfda.route) || []
385
+ brand_name: ((h = r == null ? void 0 : r.openfda.brand_name) == null ? void 0 : h[0]) || "Unknown",
386
+ generic_name: O(
387
+ (r == null ? void 0 : r.openfda) ?? {}
388
+ ),
389
+ manufacturer_name: ((_ = r == null ? void 0 : r.openfda.manufacturer_name) == null ? void 0 : _[0]) || "Unknown",
390
+ product_type: ((m = r == null ? void 0 : r.openfda.product_type) == null ? void 0 : m[0]) || "Unknown",
391
+ route: (r == null ? void 0 : r.openfda.route) || []
372
392
  };
373
393
  });
374
394
  return {
375
395
  content: [
376
396
  {
377
397
  type: "text",
378
- text: `${x(o.length, (d = (s = r.meta) == null ? void 0 : s.results) == null ? void 0 : d.total, `labels with generic name "${e}"`)}
398
+ text: `${w(u.length, (o = (s = a.meta) == null ? void 0 : s.results) == null ? void 0 : o.total, `labels with generic name "${e}"`)}
379
399
 
380
- ${JSON.stringify(k(o, (l = (c = r.meta) == null ? void 0 : c.results) == null ? void 0 : l.total, t ?? 5), null, 2)}`
400
+ ${JSON.stringify(S(u, (l = (c = a.meta) == null ? void 0 : c.results) == null ? void 0 : l.total, t ?? 5), null, 2)}`
381
401
  }
382
402
  ]
383
403
  };
384
404
  }
385
- }, de = {
405
+ }, G = {
386
406
  1: "Recovered/resolved",
387
407
  2: "Recovering/resolving",
388
408
  3: "Not recovered/not resolved",
@@ -390,32 +410,51 @@ ${JSON.stringify(k(o, (l = (c = r.meta) == null ? void 0 : c.results) == null ?
390
410
  5: "Fatal",
391
411
  6: "Unknown"
392
412
  };
393
- function ue(e) {
413
+ function pe(e) {
394
414
  if (e == null || e === "") return "Not reported";
395
415
  const t = String(e);
396
- return de[t] ?? `Unrecognized outcome code "${t}"`;
416
+ return G[t] ?? `Unrecognized outcome code "${t}"`;
397
417
  }
398
- const K = [
418
+ const q = {
419
+ 1: "Serious",
420
+ 2: "Not serious"
421
+ }, B = {
422
+ 0: "Unknown",
423
+ 1: "Male",
424
+ 2: "Female"
425
+ }, me = {
426
+ serious: q,
427
+ "patient.patientsex": B,
428
+ "patient.reaction.reactionoutcome": G
429
+ };
430
+ function fe(e, t) {
431
+ if (t == null || t === "") return "Not reported";
432
+ const n = me[e];
433
+ if (!n) return String(t);
434
+ const a = String(t);
435
+ return n[a] ?? `Unrecognized ${e} code "${a}"`;
436
+ }
437
+ const V = [
399
438
  "patient.drug.openfda.generic_name",
400
439
  "patient.drug.openfda.substance_name",
401
440
  "patient.drug.medicinalproduct"
402
- ], G = `union(${K.join(", ")})`;
403
- function q(e) {
404
- return K.map((t) => `${t}:"${e}"`).join(
441
+ ], J = `union(${V.join(", ")})`;
442
+ function W(e) {
443
+ return V.map((t) => `${t}:"${e}"`).join(
405
444
  " OR "
406
445
  );
407
446
  }
408
- function le(e) {
447
+ function _e(e) {
409
448
  const t = /* @__PURE__ */ new Set(), n = [];
410
- for (const r of e) {
411
- const i = r == null ? void 0 : r.reactionmeddrapt;
449
+ for (const a of e) {
450
+ const i = a == null ? void 0 : a.reactionmeddrapt;
412
451
  if (!i) continue;
413
- const o = `${i}\0${String(r == null ? void 0 : r.reactionoutcome)}`;
414
- t.has(o) || (t.add(o), n.push({ reaction: i, outcome: r == null ? void 0 : r.reactionoutcome }));
452
+ const u = `${i}\0${String(a == null ? void 0 : a.reactionoutcome)}`;
453
+ t.has(u) || (t.add(u), n.push({ reaction: i, outcome: a == null ? void 0 : a.reactionoutcome }));
415
454
  }
416
455
  return n.slice(0, 3);
417
456
  }
418
- const F = 25e3, pe = {
457
+ const F = 25e3, he = {
419
458
  name: "get-drug-adverse-events",
420
459
  description: "Get adverse event reports for a drug. This provides safety information about reported side effects and reactions. Use brand name or generic name. Without the sort parameter, results are a deterministic earliest-report_id slice, so a small sample is not representative. Returns results, up to limit, reporting matched_via, the total matched, and returned, the number actually sent back. When skip is set, the response also reports the offset (not declared here, since it is only present when skip is supplied).",
421
460
  // Raw upstream records wrapped in an envelope: declare only the envelope
@@ -432,14 +471,14 @@ const F = 25e3, pe = {
432
471
  "limit",
433
472
  "results"
434
473
  ],
435
- inputSchema: u.object({
436
- drugName: u.string().describe("Drug name (brand or generic)"),
437
- limit: u.number().optional().default(10).describe("Maximum number of events to return"),
438
- seriousness: u.enum(["serious", "non-serious", "all"]).optional().default("all").describe("Filter by event seriousness"),
439
- skip: u.number().int().min(0).max(F).optional().describe(
474
+ inputSchema: d.object({
475
+ drugName: d.string().describe("Drug name (brand or generic)"),
476
+ limit: d.number().optional().default(10).describe("Maximum number of events to return"),
477
+ seriousness: d.enum(["serious", "non-serious", "all"]).optional().default("all").describe("Filter by event seriousness"),
478
+ skip: d.number().int().min(0).max(F).optional().describe(
440
479
  `Offset into the result set, for paging past the limit. Maximum ${F}.`
441
480
  ),
442
- sort: u.enum(["receivedate:desc", "receivedate:asc"]).optional().describe(
481
+ sort: d.enum(["receivedate:desc", "receivedate:asc"]).optional().describe(
443
482
  "Order results by report receive date. Without it, results are a deterministic earliest-report_id slice, so a small sample is not representative."
444
483
  )
445
484
  }),
@@ -447,25 +486,25 @@ const F = 25e3, pe = {
447
486
  drugName: e,
448
487
  limit: t,
449
488
  seriousness: n,
450
- skip: r,
489
+ skip: a,
451
490
  sort: i
452
491
  }) {
453
- var h, E;
454
- if (r !== void 0 && r > F)
492
+ var g, y;
493
+ if (a !== void 0 && a > F)
455
494
  return {
456
495
  content: [
457
496
  {
458
497
  type: "text",
459
- text: `skip must be ${F} or less (openFDA's ceiling); received ${r}. To reach records beyond that, narrow the search or use sort to bring the records you want into range.`
498
+ text: `skip must be ${F} or less (openFDA's ceiling); received ${a}. To reach records beyond that, narrow the search or use sort to bring the records you want into range.`
460
499
  }
461
500
  ],
462
501
  isError: !0
463
502
  };
464
- let o = q(e);
465
- n !== "all" && (o = `(${o}) AND serious:${n === "serious" ? "1" : "2"}`);
466
- const s = new y().dataset("drug").context("event").search(o).limit(t);
467
- r !== void 0 && s.skip(r), i !== void 0 && s.sort(i);
468
- const d = s.build(), { data: c, error: l } = await v(d);
503
+ let u = W(e);
504
+ n !== "all" && (u = `(${u}) AND serious:${n === "serious" ? "1" : "2"}`);
505
+ const s = new x().dataset("drug").context("event").search(u).limit(t);
506
+ a !== void 0 && s.skip(a), i !== void 0 && s.sort(i);
507
+ const o = s.build(), { data: c, error: l } = await $(o);
469
508
  if (l)
470
509
  return {
471
510
  content: [
@@ -485,41 +524,42 @@ const F = 25e3, pe = {
485
524
  }
486
525
  ]
487
526
  };
488
- const a = c.results.map((p) => {
489
- var N, D, S, $;
490
- const w = le(((N = p.patient) == null ? void 0 : N.reaction) ?? []);
527
+ const r = c.results.map((p) => {
528
+ var D, E, N;
529
+ const k = _e(((D = p.patient) == null ? void 0 : D.reaction) ?? []);
491
530
  return {
492
531
  report_id: p.safetyreportid,
493
- serious: p.serious === "1" ? "Yes" : "No",
494
- patient_age: ((D = p.patient) == null ? void 0 : D.patientonsetage) || "Unknown",
495
- patient_sex: ((S = p.patient) == null ? void 0 : S.patientsex) === "1" ? "Male" : (($ = p.patient) == null ? void 0 : $.patientsex) === "2" ? "Female" : "Unknown",
532
+ // Same map the counts tool uses, so the two tools cannot disagree.
533
+ serious: q[String(p.serious)] ?? "Not reported",
534
+ patient_age: ((E = p.patient) == null ? void 0 : E.patientonsetage) || "Unknown",
535
+ patient_sex: B[String((N = p.patient) == null ? void 0 : N.patientsex)] ?? "Not reported",
496
536
  // Derived from the same deduped pairs so the two arrays stay the
497
537
  // same length and positionally aligned (see dedupeReactionPairs).
498
- reactions: w.map((T) => T.reaction),
499
- outcomes: w.map((T) => ue(T.outcome)),
538
+ reactions: k.map((v) => v.reaction),
539
+ outcomes: k.map((v) => pe(v.outcome)),
500
540
  report_date: p.receiptdate || "Unknown"
501
541
  };
502
- }), g = (E = (h = c.meta) == null ? void 0 : h.results) == null ? void 0 : E.total, f = r !== void 0 ? `${x(a.length, g, `adverse event reports for "${e}"`)}, starting at offset ${r}` : x(
503
- a.length,
504
- g,
542
+ }), h = (y = (g = c.meta) == null ? void 0 : g.results) == null ? void 0 : y.total, _ = a !== void 0 ? `${w(r.length, h, `adverse event reports for "${e}"`)}, starting at offset ${a}` : w(
543
+ r.length,
544
+ h,
505
545
  `adverse event reports for "${e}"`
506
- ), _ = {
507
- matched_via: G,
508
- ...r !== void 0 ? { skip: r } : {},
509
- ...k(a, g, t ?? 10)
546
+ ), m = {
547
+ matched_via: J,
548
+ ...a !== void 0 ? { skip: a } : {},
549
+ ...S(r, h, t ?? 10)
510
550
  };
511
551
  return {
512
552
  content: [
513
553
  {
514
554
  type: "text",
515
- text: `${f}
555
+ text: `${_}
516
556
 
517
- ${JSON.stringify(_, null, 2)}`
557
+ ${JSON.stringify(m, null, 2)}`
518
558
  }
519
559
  ]
520
560
  };
521
561
  }
522
- }, me = {
562
+ }, ge = {
523
563
  name: "get-drugs-by-manufacturer",
524
564
  description: "Get all drugs manufactured by a specific company. Useful for finding alternatives or checking manufacturer portfolios. Returns results, up to limit, reporting matched_via, the total matched, and returned, the number actually sent back.",
525
565
  // Raw upstream records wrapped in an envelope: declare only the envelope
@@ -532,16 +572,16 @@ ${JSON.stringify(_, null, 2)}`
532
572
  "limit",
533
573
  "results"
534
574
  ],
535
- inputSchema: u.object({
536
- manufacturerName: u.string().describe("Manufacturer/company name"),
537
- limit: u.number().optional().default(20).describe("Maximum number of drugs to return")
575
+ inputSchema: d.object({
576
+ manufacturerName: d.string().describe("Manufacturer/company name"),
577
+ limit: d.number().optional().default(20).describe("Maximum number of drugs to return")
538
578
  }),
539
579
  async handler({
540
580
  manufacturerName: e,
541
581
  limit: t
542
582
  }) {
543
- var s, d, c, l;
544
- const n = new y().dataset("drug").context("label").search(`openfda.manufacturer_name:"${e}"`).limit(t).build(), { data: r, error: i } = await v(n);
583
+ var s, o, c, l;
584
+ const n = new x().dataset("drug").context("label").search(`openfda.manufacturer_name:"${e}"`).limit(t).build(), { data: a, error: i } = await $(n);
545
585
  if (i)
546
586
  return {
547
587
  content: [
@@ -552,7 +592,7 @@ ${JSON.stringify(_, null, 2)}`
552
592
  ],
553
593
  isError: !0
554
594
  };
555
- if (!(r != null && r.results) || r.results.length === 0)
595
+ if (!(a != null && a.results) || a.results.length === 0)
556
596
  return {
557
597
  content: [
558
598
  {
@@ -561,30 +601,32 @@ ${JSON.stringify(_, null, 2)}`
561
601
  }
562
602
  ]
563
603
  };
564
- const o = r.results.map((a) => {
565
- var g, f, _, h;
604
+ const u = a.results.map((r) => {
605
+ var h, _, m;
566
606
  return {
567
- brand_name: ((g = a == null ? void 0 : a.openfda.brand_name) == null ? void 0 : g[0]) || "Unknown",
568
- generic_name: ((f = a == null ? void 0 : a.openfda.generic_name) == null ? void 0 : f[0]) || "Unknown",
569
- product_type: ((_ = a == null ? void 0 : a.openfda.product_type) == null ? void 0 : _[0]) || "Unknown",
570
- route: (a == null ? void 0 : a.openfda.route) || [],
571
- ndc: ((h = a == null ? void 0 : a.openfda.product_ndc) == null ? void 0 : h[0]) || "Unknown"
607
+ brand_name: ((h = r == null ? void 0 : r.openfda.brand_name) == null ? void 0 : h[0]) || "Unknown",
608
+ generic_name: O(
609
+ (r == null ? void 0 : r.openfda) ?? {}
610
+ ),
611
+ product_type: ((_ = r == null ? void 0 : r.openfda.product_type) == null ? void 0 : _[0]) || "Unknown",
612
+ route: (r == null ? void 0 : r.openfda.route) || [],
613
+ ndc: ((m = r == null ? void 0 : r.openfda.product_ndc) == null ? void 0 : m[0]) || "Unknown"
572
614
  };
573
615
  });
574
616
  return {
575
617
  content: [
576
618
  {
577
619
  type: "text",
578
- text: `${x(o.length, (d = (s = r.meta) == null ? void 0 : s.results) == null ? void 0 : d.total, `labels from manufacturer "${e}"`)}
620
+ text: `${w(u.length, (o = (s = a.meta) == null ? void 0 : s.results) == null ? void 0 : o.total, `labels from manufacturer "${e}"`)}
579
621
 
580
- ${JSON.stringify({ matched_via: "openfda.manufacturer_name", ...k(o, (l = (c = r.meta) == null ? void 0 : c.results) == null ? void 0 : l.total, t ?? 20) }, null, 2)}`
622
+ ${JSON.stringify({ matched_via: "openfda.manufacturer_name", ...S(u, (l = (c = a.meta) == null ? void 0 : c.results) == null ? void 0 : l.total, t ?? 20) }, null, 2)}`
581
623
  }
582
624
  ]
583
625
  };
584
626
  }
585
- }, fe = {
627
+ }, be = {
586
628
  name: "get-drug-safety-info",
587
- description: "Get safety information for a drug. Returns drug_name, generic_name, boxed_warning, warnings, warnings_and_cautions, contraindications, drug_interactions, precautions, adverse_reactions, overdosage, do_not_use, ask_doctor, stop_use and pregnancy_or_breast_feeding. Every field is always present, empty when the label has none, so an empty boxed_warning means the drug has none rather than that it was not checked. Accepts a brand, generic or substance name and reports matched_via.",
629
+ description: "Get safety information for a drug. Returns drug_name, generic_name, boxed_warning, warnings, warnings_and_cautions, contraindications, drug_interactions, precautions, adverse_reactions, overdosage, do_not_use, ask_doctor, ask_doctor_or_pharmacist, stop_use and pregnancy_or_breast_feeding. Every field is always present, empty when the label has none, so an empty boxed_warning means the drug has none rather than that it was not checked. generic_name is null when the label carries no structured generic or substance name. Accepts a brand, generic or substance name and reports matched_via.",
588
630
  returnsFields: [
589
631
  "drug_name",
590
632
  "generic_name",
@@ -599,11 +641,12 @@ ${JSON.stringify({ matched_via: "openfda.manufacturer_name", ...k(o, (l = (c = r
599
641
  "overdosage",
600
642
  "do_not_use",
601
643
  "ask_doctor",
644
+ "ask_doctor_or_pharmacist",
602
645
  "stop_use",
603
646
  "pregnancy_or_breast_feeding"
604
647
  ],
605
- inputSchema: u.object({
606
- drugName: u.string().describe("Drug brand name")
648
+ inputSchema: d.object({
649
+ drugName: d.string().describe("Drug brand name")
607
650
  }),
608
651
  async handler({ drugName: e }) {
609
652
  var i;
@@ -618,15 +661,15 @@ ${JSON.stringify({ matched_via: "openfda.manufacturer_name", ...k(o, (l = (c = r
618
661
  ],
619
662
  isError: !0
620
663
  } : {
621
- content: [{ type: "text", text: L(e) }]
664
+ content: [{ type: "text", text: K(e) }]
622
665
  };
623
- const n = t.data.results[0], r = {
666
+ const n = t.data.results[0], a = {
624
667
  drug_name: ((i = n == null ? void 0 : n.openfda.brand_name) == null ? void 0 : i[0]) || e,
625
- generic_name: oe(
668
+ generic_name: O(
626
669
  (n == null ? void 0 : n.openfda) ?? {}
627
670
  ),
628
671
  matched_via: t.matched_via,
629
- ...re(n)
672
+ ...ie(n)
630
673
  };
631
674
  return {
632
675
  content: [
@@ -634,37 +677,37 @@ ${JSON.stringify({ matched_via: "openfda.manufacturer_name", ...k(o, (l = (c = r
634
677
  type: "text",
635
678
  text: `Safety information for "${e}":
636
679
 
637
- ${JSON.stringify(r, null, 2)}`
680
+ ${JSON.stringify(a, null, 2)}`
638
681
  }
639
682
  ]
640
683
  };
641
684
  }
642
- }, _e = /^(?:\d{4}-\d{4}|\d{5}-\d{3,4})$/, ge = /^\d{1,2}$/;
643
- function V(e) {
685
+ }, ye = /^(?:\d{4}-\d{4}|\d{5}-\d{3,4})$/, ve = /^\d{1,2}$/;
686
+ function Y(e) {
644
687
  const t = e.trim().toUpperCase(), n = { productNDC: t, packageNDC: null, isValid: !1 };
645
- let r, i = null;
688
+ let a, i = null;
646
689
  if (t.includes("-")) {
647
- const o = t.split("-");
648
- if (o.length === 2)
649
- r = t;
650
- else if (o.length === 3) {
651
- const s = o[2];
652
- if (!s || !ge.test(s)) return n;
653
- r = `${o[0]}-${o[1]}`, i = t;
690
+ const u = t.split("-");
691
+ if (u.length === 2)
692
+ a = t;
693
+ else if (u.length === 3) {
694
+ const s = u[2];
695
+ if (!s || !ve.test(s)) return n;
696
+ a = `${u[0]}-${u[1]}`, i = t;
654
697
  } else
655
698
  return n;
656
699
  } else if (/^\d+$/.test(t))
657
700
  if (t.length === 11)
658
- r = `${t.slice(0, 5)}-${t.slice(5, 9)}`, i = `${r}-${t.slice(9, 11)}`;
701
+ a = `${t.slice(0, 5)}-${t.slice(5, 9)}`, i = `${a}-${t.slice(9, 11)}`;
659
702
  else if (t.length === 9)
660
- r = `${t.slice(0, 5)}-${t.slice(5, 9)}`;
703
+ a = `${t.slice(0, 5)}-${t.slice(5, 9)}`;
661
704
  else
662
705
  return n;
663
706
  else
664
707
  return n;
665
- return _e.test(r) ? { productNDC: r, packageNDC: i, isValid: !0 } : n;
708
+ return ye.test(a) ? { productNDC: a, packageNDC: i, isValid: !0 } : n;
666
709
  }
667
- const he = [
710
+ const xe = [
668
711
  "✅ Accepted formats:",
669
712
  "• 4-4 product NDC: 0456-4020",
670
713
  "• 5-3 product NDC: 58151-155",
@@ -675,9 +718,9 @@ const he = [
675
718
  "",
676
719
  "Undashed 8- and 10-digit input is rejected because the split is ambiguous: 8 digits could be 5-3 or 4-4, and 10 could be 4-4-2, 5-3-2 or 5-4-1. Guessing could return a different drug, so add the dashes instead."
677
720
  ].join(`
678
- `), W = (e, t) => `Invalid ${t} format: "${e}"
721
+ `), z = (e, t) => `Invalid ${t} format: "${e}"
679
722
 
680
- ${he}`, be = {
723
+ ${xe}`, $e = {
681
724
  name: "get-drug-by-ndc",
682
725
  description: "Get drug information by National Drug Code (NDC). Accepts dashed formats: 4-4 (0456-4020), 5-3 (58151-155), 5-4 (12345-1234), and package NDC. Also accepts undashed 9-digit and 11-digit input. Undashed 8- and 10-digit input is rejected as ambiguous. Returns results, up to limit, reporting matched_via, the total matched, and returned, the number actually sent back.",
683
726
  // Raw upstream records wrapped in an envelope: declare only the envelope
@@ -690,27 +733,27 @@ ${he}`, be = {
690
733
  "limit",
691
734
  "results"
692
735
  ],
693
- inputSchema: u.object({
694
- ndcCode: u.string().describe(
736
+ inputSchema: d.object({
737
+ ndcCode: d.string().describe(
695
738
  "National Drug Code (NDC) - accepts formats: XXXXX-XXXX, XXXXX-XXXX-XX, or without dashes"
696
739
  )
697
740
  }),
698
741
  async handler({ ndcCode: e }) {
699
- var f, _, h, E;
700
- const { productNDC: t, packageNDC: n, isValid: r } = V(e);
701
- if (!r)
742
+ var _, m, g, y;
743
+ const { productNDC: t, packageNDC: n, isValid: a } = Y(e);
744
+ if (!a)
702
745
  return {
703
746
  content: [
704
747
  {
705
748
  type: "text",
706
- text: W(e, "NDC")
749
+ text: z(e, "NDC")
707
750
  }
708
751
  ],
709
752
  isError: !0
710
753
  };
711
754
  let i = `openfda.product_ndc:"${t}"`;
712
755
  n && (i += ` OR openfda.package_ndc:"${n}"`);
713
- const o = n ? "openfda.product_ndc OR openfda.package_ndc" : "openfda.product_ndc", s = new y().dataset("drug").context("label").search(i).limit(10).build(), { data: d, error: c } = await v(s);
756
+ const u = n ? "openfda.product_ndc OR openfda.package_ndc" : "openfda.product_ndc", s = new x().dataset("drug").context("label").search(i).limit(10).build(), { data: o, error: c } = await $(s);
714
757
  if (c)
715
758
  return {
716
759
  content: [
@@ -721,7 +764,7 @@ ${he}`, be = {
721
764
  ],
722
765
  isError: !0
723
766
  };
724
- if (!(d != null && d.results) || d.results.length === 0)
767
+ if (!(o != null && o.results) || o.results.length === 0)
725
768
  return {
726
769
  content: [
727
770
  {
@@ -735,10 +778,10 @@ ${he}`, be = {
735
778
  }
736
779
  ]
737
780
  };
738
- const l = d.results.map((p) => {
739
- var D, S;
740
- const w = ((D = p.openfda.product_ndc) == null ? void 0 : D.filter(($) => $ === t)) || [], N = ((S = p.openfda.package_ndc) == null ? void 0 : S.filter(
741
- ($) => n ? $ === n : $.startsWith(t)
781
+ const l = o.results.map((p) => {
782
+ var E, N;
783
+ const k = ((E = p.openfda.product_ndc) == null ? void 0 : E.filter((v) => v === t)) || [], D = ((N = p.openfda.package_ndc) == null ? void 0 : N.filter(
784
+ (v) => n ? v === n : v.startsWith(t)
742
785
  )) || [];
743
786
  return {
744
787
  brand_name: p.openfda.brand_name || [],
@@ -747,8 +790,8 @@ ${he}`, be = {
747
790
  product_type: p.openfda.product_type || [],
748
791
  route: p.openfda.route || [],
749
792
  substance_name: p.openfda.substance_name || [],
750
- matching_product_ndc: w,
751
- matching_package_ndc: N,
793
+ matching_product_ndc: k,
794
+ matching_package_ndc: D,
752
795
  all_product_ndc: p.openfda.product_ndc || [],
753
796
  all_package_ndc: p.openfda.package_ndc || [],
754
797
  dosage_and_administration: p.dosage_and_administration || [],
@@ -756,24 +799,24 @@ ${he}`, be = {
756
799
  active_ingredient: p.active_ingredient || [],
757
800
  purpose: p.purpose || []
758
801
  };
759
- }), a = l.reduce(
760
- (p, w) => p + w.matching_package_ndc.length,
802
+ }), r = l.reduce(
803
+ (p, k) => p + k.matching_package_ndc.length,
761
804
  0
762
- ), g = n ? `Searched for specific package NDC: ${n}` : `Searched for product NDC: ${t} (all packages)`;
805
+ ), h = n ? `Searched for specific package NDC: ${n}` : `Searched for product NDC: ${t} (all packages)`;
763
806
  return {
764
807
  content: [
765
808
  {
766
809
  type: "text",
767
- text: `${x(l.length, (_ = (f = d.meta) == null ? void 0 : f.results) == null ? void 0 : _.total, `labels for NDC "${e}"`)} with ${a} package(s)
810
+ text: `${w(l.length, (m = (_ = o.meta) == null ? void 0 : _.results) == null ? void 0 : m.total, `labels for NDC "${e}"`)} with ${r} package(s)
768
811
 
769
- ${g}
812
+ ${h}
770
813
 
771
- ${JSON.stringify({ matched_via: o, ...k(l, (E = (h = d.meta) == null ? void 0 : h.results) == null ? void 0 : E.total, 10) }, null, 2)}`
814
+ ${JSON.stringify({ matched_via: u, ...S(l, (y = (g = o.meta) == null ? void 0 : g.results) == null ? void 0 : y.total, 10) }, null, 2)}`
772
815
  }
773
816
  ]
774
817
  };
775
818
  }
776
- }, ye = {
819
+ }, we = {
777
820
  name: "get-drug-by-product-ndc",
778
821
  description: "Get drug information by product NDC. Accepts the dashed forms 4-4 (0456-4020), 5-3 (58151-155) and 5-4 (12345-1234), plus undashed 9-digit (5-4) and 11-digit (5-4-2) input. Undashed 8- and 10-digit input is rejected as ambiguous — dash it. This ignores package variations and finds all packages for a product. Returns product_ndc, available_packages, brand_name, generic_name, manufacturer_name, product_type, route, substance_name, active_ingredient, purpose and dosage_and_administration; every field is always present, empty when the label has none.",
779
822
  // Fixed payload of named fields (one record, not an envelope over many):
@@ -792,31 +835,31 @@ ${JSON.stringify({ matched_via: o, ...k(l, (E = (h = d.meta) == null ? void 0 :
792
835
  "purpose",
793
836
  "dosage_and_administration"
794
837
  ],
795
- inputSchema: u.object({
796
- productNDC: u.string().describe(
838
+ inputSchema: d.object({
839
+ productNDC: d.string().describe(
797
840
  "Product NDC: dashed 4-4 (0456-4020), 5-3 (58151-155) or 5-4 (12345-1234); undashed 9-digit (123451234) or 11-digit (12345123401) also work"
798
841
  )
799
842
  }),
800
843
  async handler({ productNDC: e }) {
801
844
  var l;
802
- const { productNDC: t, isValid: n } = V(e);
845
+ const { productNDC: t, isValid: n } = Y(e);
803
846
  if (!n)
804
847
  return {
805
848
  content: [
806
849
  {
807
850
  type: "text",
808
- text: W(e, "product NDC")
851
+ text: z(e, "product NDC")
809
852
  }
810
853
  ],
811
854
  isError: !0
812
855
  };
813
- const r = new y().dataset("drug").context("label").search(`openfda.product_ndc:"${t}"`).limit(1).build(), { data: i, error: o } = await v(r);
814
- if (o)
856
+ const a = new x().dataset("drug").context("label").search(`openfda.product_ndc:"${t}"`).limit(1).build(), { data: i, error: u } = await $(a);
857
+ if (u)
815
858
  return {
816
859
  content: [
817
860
  {
818
861
  type: "text",
819
- text: `Failed to retrieve drug data for product NDC "${e}": ${o.message}`
862
+ text: `Failed to retrieve drug data for product NDC "${e}": ${u.message}`
820
863
  }
821
864
  ],
822
865
  isError: !0
@@ -831,11 +874,11 @@ ${JSON.stringify({ matched_via: o, ...k(l, (E = (h = d.meta) == null ? void 0 :
831
874
  ],
832
875
  structuredContent: null
833
876
  };
834
- const s = i.results[0], d = ((l = s.openfda.package_ndc) == null ? void 0 : l.filter(
835
- (a) => a.startsWith(t)
877
+ const s = i.results[0], o = ((l = s.openfda.package_ndc) == null ? void 0 : l.filter(
878
+ (r) => r.startsWith(t)
836
879
  )) || [], c = {
837
880
  product_ndc: t,
838
- available_packages: d,
881
+ available_packages: o,
839
882
  brand_name: s.openfda.brand_name || [],
840
883
  generic_name: s.openfda.generic_name || [],
841
884
  manufacturer_name: s.openfda.manufacturer_name || [],
@@ -850,14 +893,14 @@ ${JSON.stringify({ matched_via: o, ...k(l, (E = (h = d.meta) == null ? void 0 :
850
893
  content: [
851
894
  {
852
895
  type: "text",
853
- text: `✅ Product NDC "${t}" found with ${d.length} package variation(s):
896
+ text: `✅ Product NDC "${t}" found with ${o.length} package variation(s):
854
897
 
855
898
  ${JSON.stringify(c, null, 2)}`
856
899
  }
857
900
  ]
858
901
  };
859
902
  }
860
- }, R = {
903
+ }, U = {
861
904
  application: {
862
905
  prefix: "",
863
906
  fields: {
@@ -906,27 +949,45 @@ ${JSON.stringify(c, null, 2)}`
906
949
  prefix: "submissions.application_docs",
907
950
  fields: { id: {}, url: {}, date: {}, type: {} }
908
951
  }
909
- }, C = Object.keys(R);
910
- function ve(e, t) {
911
- const n = R[e];
952
+ }, T = Object.keys(U);
953
+ function ke(e, t) {
954
+ const n = U[e];
912
955
  if (!n)
913
956
  return {
914
957
  ok: !1,
915
- message: `Unknown section "${e}". Valid sections: ${C.join(", ")}.`
958
+ message: `Unknown section "${e}". Valid sections: ${T.join(", ")}.`
916
959
  };
917
- const r = n.fields[t];
918
- return r ? {
960
+ const a = n.fields[t];
961
+ return a ? {
919
962
  ok: !0,
920
963
  path: n.prefix ? `${n.prefix}.${t}` : t,
921
- uppercase: r.uppercase === !0
964
+ uppercase: a.uppercase === !0
922
965
  } : {
923
966
  ok: !1,
924
967
  message: `Unknown field "${t}" for section "${e}". Valid fields: ${Object.keys(n.fields).join(", ")}.`
925
968
  };
926
969
  }
927
- const $e = (e) => `${e}: ${Object.keys(R[e].fields).join(", ")}`, xe = {
970
+ const Se = (e) => `${e}: ${Object.keys(U[e].fields).join(", ")}`, M = 10, Ee = (e) => ({
971
+ ...e,
972
+ te_code: e.te_code ?? null
973
+ }), Ne = (e, t) => {
974
+ const n = Array.isArray(e.submissions) ? e.submissions : [], a = Array.isArray(e.products) ? e.products : [], i = {
975
+ application_number: e.application_number,
976
+ sponsor_name: e.sponsor_name,
977
+ // openFDA omits openfda entirely on most records (43/50 for a LILLY
978
+ // sponsor_name search); emit it either way, matching te_code above.
979
+ openfda: e.openfda ?? {},
980
+ products: a.map(Ee),
981
+ submission_count: n.length
982
+ };
983
+ return t === "summary" ? i : {
984
+ ...i,
985
+ submissions: n.slice(0, M),
986
+ submissions_truncated: n.length > M
987
+ };
988
+ }, De = {
928
989
  name: "get-drugsfda",
929
- description: "Search Drugs@FDA application data by section and field. Returns application, sponsor, product and submission records as results, up to limit. Reports matched_via (the resolved field path), the total number of records matched, and returned, the number actually sent back.",
990
+ description: "Search Drugs@FDA application data by section and field. Returns application, sponsor, product and submission records as results, up to limit. Reports matched_via (the resolved field path), the total number of records matched, and returned, the number actually sent back. detail controls record shape: summary (default) returns application_number, sponsor_name, openfda, products and a submission_count; full adds the submissions array, capped at 10 per record, plus submissions_truncated when more were omitted. This default changed in 1.3.0: submissions is no longer returned unless detail is set to full. Note: openfda.route is the SPL route of administration and products[].route is the Drugs@FDA product route. They use different controlled vocabularies — the same product can be SUBCUTANEOUS in one and INJECTION in the other — so joining on route across tools will silently miss.",
930
991
  // Raw upstream records wrapped in an envelope: declare only the envelope
931
992
  // keys this tool guarantees (matched_via, total, returned, limit,
932
993
  // results), never inner record fields, because those vary per record.
@@ -937,94 +998,100 @@ const $e = (e) => `${e}: ${Object.keys(R[e].fields).join(", ")}`, xe = {
937
998
  "limit",
938
999
  "results"
939
1000
  ],
940
- inputSchema: u.object({
941
- sectionName: u.enum([...C]).describe(`Section to search. One of: ${C.join(", ")}`),
942
- fieldName: u.string().describe(
943
- `Field within the section. ${C.map($e).join(". ")}`
1001
+ inputSchema: d.object({
1002
+ sectionName: d.enum([...T]).describe(`Section to search. One of: ${T.join(", ")}`),
1003
+ fieldName: d.string().describe(
1004
+ `Field within the section. ${T.map(Se).join(". ")}`
944
1005
  ),
945
- searchValue: u.string().describe(
1006
+ searchValue: d.string().describe(
946
1007
  "Value to search for. Sponsor names are stored uppercase and are normalised automatically."
947
1008
  ),
948
- limit: u.number().int().min(1).max(100).optional().default(5).describe("Maximum number of records to return")
1009
+ limit: d.number().int().min(1).max(100).optional().default(5).describe("Maximum number of records to return"),
1010
+ detail: d.enum(["summary", "full"]).optional().default("summary").describe(
1011
+ "summary (default) returns application number, sponsor, products and a submission count. full adds the submissions array, capped per record."
1012
+ )
949
1013
  }),
950
1014
  async handler({
951
1015
  sectionName: e,
952
1016
  fieldName: t,
953
1017
  searchValue: n,
954
- limit: r
1018
+ limit: a,
1019
+ detail: i
955
1020
  }) {
956
- var g, f;
957
- const i = r ?? 5, o = ve(e, t);
1021
+ var g, y;
1022
+ const u = a ?? 5, s = i ?? "summary", o = ke(e, t);
958
1023
  if (!o.ok)
959
1024
  return {
960
1025
  content: [{ type: "text", text: o.message }],
961
1026
  isError: !0
962
1027
  };
963
- const s = o.uppercase ? n.toUpperCase() : n, d = new y().dataset("drug").context("drugsfda").search(`${o.path}:"${s}"`).limit(i).build(), { data: c, error: l } = await v(d);
964
- if (l)
1028
+ const c = o.uppercase ? n.toUpperCase() : n, l = new x().dataset("drug").context("drugsfda").search(`${o.path}:"${c}"`).limit(u).build(), { data: r, error: h } = await $(l);
1029
+ if (h)
965
1030
  return {
966
1031
  content: [
967
1032
  {
968
1033
  type: "text",
969
- text: `Failed to retrieve Drugs@FDA data for "${s}" in ${o.path}: ${l.message}`
1034
+ text: `Failed to retrieve Drugs@FDA data for "${c}" in ${o.path}: ${h.message}`
970
1035
  }
971
1036
  ],
972
1037
  isError: !0
973
1038
  };
974
- if (!(c != null && c.results) || c.results.length === 0)
1039
+ if (!(r != null && r.results) || r.results.length === 0)
975
1040
  return {
976
1041
  content: [
977
1042
  {
978
1043
  type: "text",
979
- text: `No Drugs@FDA records found for "${s}" in ${o.path}.`
1044
+ text: `No Drugs@FDA records found for "${c}" in ${o.path}.`
980
1045
  }
981
1046
  ]
982
1047
  };
983
- const a = (f = (g = c.meta) == null ? void 0 : g.results) == null ? void 0 : f.total;
1048
+ const _ = (y = (g = r.meta) == null ? void 0 : g.results) == null ? void 0 : y.total, m = r.results.map(
1049
+ (p) => Ne(p, s)
1050
+ );
984
1051
  return {
985
1052
  content: [
986
1053
  {
987
1054
  type: "text",
988
- text: `${x(c.results.length, a, `Drugs@FDA records matching ${o.path}`)}
1055
+ text: `${w(r.results.length, _, `Drugs@FDA records matching ${o.path}`)}
989
1056
 
990
- ${JSON.stringify({ matched_via: o.path, ...k(c.results, a, i) }, null, 2)}`
1057
+ ${JSON.stringify({ matched_via: o.path, ...S(m, _, u) }, null, 2)}`
991
1058
  }
992
1059
  ]
993
1060
  };
994
1061
  }
995
- }, we = [
1062
+ }, Ae = [
996
1063
  "patient.reaction.reactionmeddrapt.exact",
997
1064
  "patient.reaction.reactionoutcome",
998
1065
  "serious",
999
1066
  "patient.patientsex",
1000
1067
  "occurcountry.exact",
1001
1068
  "patient.drug.openfda.generic_name.exact"
1002
- ], ke = {
1069
+ ], Fe = {
1003
1070
  name: "get-drug-adverse-event-counts",
1004
- description: "Rank adverse-event values for a drug by frequency — for example the most commonly reported reactions. Returns aggregated {term, count} pairs as results, not individual reports, along with matched_via, counted_by (the field that was aggregated) and returned (how many ranked terms came back). Note that openFDA omits a result total on aggregated responses, so this tool reports no total; use get-drug-adverse-events for individual reports and their total.",
1071
+ description: 'Rank adverse-event values for a drug by frequency — for example the most commonly reported reactions. Returns aggregated {term, term_code, count} pairs as results, not individual reports, along with matched_via, counted_by (the field that was aggregated) and returned (how many ranked terms came back). For coded fields (serious, patient.patientsex, patient.reaction.reactionoutcome), term is a decoded human-readable label (e.g. "Serious", "Female") and term_code is the raw upstream value; for text fields term and term_code are identical. Note that openFDA omits a result total on aggregated responses, so this tool reports no total; use get-drug-adverse-events for individual reports and their total.',
1005
1072
  // Raw upstream records wrapped in an envelope: declare only the envelope
1006
1073
  // keys this tool guarantees (matched_via, counted_by, returned, results),
1007
1074
  // never inner record fields, because those vary per record. There is no
1008
1075
  // `total` or `limit` here — openFDA omits a total on aggregated
1009
1076
  // responses, so this tool deliberately does not declare one.
1010
1077
  returnsFields: ["matched_via", "counted_by", "returned", "results"],
1011
- inputSchema: u.object({
1012
- drugName: u.string().describe("Drug name (brand, generic or substance)"),
1013
- field: u.enum(we).optional().default("patient.reaction.reactionmeddrapt.exact").describe("Field to aggregate by"),
1014
- limit: u.number().int().min(1).max(100).optional().default(10).describe("Maximum number of ranked terms to return")
1078
+ inputSchema: d.object({
1079
+ drugName: d.string().describe("Drug name (brand, generic or substance)"),
1080
+ field: d.enum(Ae).optional().default("patient.reaction.reactionmeddrapt.exact").describe("Field to aggregate by"),
1081
+ limit: d.number().int().min(1).max(100).optional().default(10).describe("Maximum number of ranked terms to return")
1015
1082
  }),
1016
1083
  async handler({
1017
1084
  drugName: e,
1018
1085
  field: t,
1019
1086
  limit: n
1020
1087
  }) {
1021
- const r = t ?? "patient.reaction.reactionmeddrapt.exact", i = n ?? 10, o = new y().dataset("drug").context("event").search(q(e)).count(r).limit(i).build(), { data: s, error: d } = await v(o);
1022
- if (d)
1088
+ const a = t ?? "patient.reaction.reactionmeddrapt.exact", i = n ?? 10, u = new x().dataset("drug").context("event").search(W(e)).count(a).limit(i).build(), { data: s, error: o } = await $(u);
1089
+ if (o)
1023
1090
  return {
1024
1091
  content: [
1025
1092
  {
1026
1093
  type: "text",
1027
- text: `Failed to retrieve adverse event counts for "${e}": ${d.message}`
1094
+ text: `Failed to retrieve adverse event counts for "${e}": ${o.message}`
1028
1095
  }
1029
1096
  ],
1030
1097
  isError: !0
@@ -1039,26 +1106,32 @@ ${JSON.stringify({ matched_via: o.path, ...k(c.results, a, i) }, null, 2)}`
1039
1106
  ]
1040
1107
  };
1041
1108
  const c = {
1042
- matched_via: G,
1043
- counted_by: r,
1109
+ matched_via: J,
1110
+ counted_by: a,
1044
1111
  returned: s.results.length,
1045
- results: s.results
1112
+ // Decode coded enums; text fields pass through. term_code keeps the
1113
+ // raw value so callers aggregating by code are unaffected.
1114
+ results: s.results.map((l) => ({
1115
+ term: fe(a, l.term),
1116
+ term_code: l.term,
1117
+ count: l.count
1118
+ }))
1046
1119
  };
1047
1120
  return {
1048
1121
  content: [
1049
1122
  {
1050
1123
  type: "text",
1051
- text: `Top ${s.results.length} values of ${r} for "${e}" (aggregated responses carry no result total)
1124
+ text: `Top ${s.results.length} values of ${a} for "${e}" (aggregated responses carry no result total)
1052
1125
 
1053
1126
  ${JSON.stringify(c, null, 2)}`
1054
1127
  }
1055
1128
  ]
1056
1129
  };
1057
1130
  }
1058
- }, Ee = "1.2.0", Y = new z(
1131
+ }, Te = "1.3.0", H = new ee(
1059
1132
  {
1060
1133
  name: "openfda",
1061
- version: Ee,
1134
+ version: Te,
1062
1135
  description: "OpenFDA Model Context Protocol"
1063
1136
  },
1064
1137
  {
@@ -1067,21 +1140,21 @@ ${JSON.stringify(c, null, 2)}`
1067
1140
  tools: {}
1068
1141
  }
1069
1142
  }
1070
- ), b = new ne(Y);
1071
- b.registerTool(ie);
1072
- b.registerTool(ce);
1073
- b.registerTool(pe);
1074
- b.registerTool(me);
1075
- b.registerTool(fe);
1143
+ ), b = new oe(H);
1144
+ b.registerTool(de);
1145
+ b.registerTool(le);
1146
+ b.registerTool(he);
1147
+ b.registerTool(ge);
1076
1148
  b.registerTool(be);
1077
- b.registerTool(ye);
1078
- b.registerTool(xe);
1079
- b.registerTool(ke);
1080
- async function De() {
1081
- te();
1082
- const e = new H();
1083
- await Y.connect(e), console.error("OpenFDA MCP Server running on stdio");
1149
+ b.registerTool($e);
1150
+ b.registerTool(we);
1151
+ b.registerTool(De);
1152
+ b.registerTool(Fe);
1153
+ async function Ce() {
1154
+ se();
1155
+ const e = new te();
1156
+ await H.connect(e), console.error("OpenFDA MCP Server running on stdio");
1084
1157
  }
1085
- De().catch((e) => {
1158
+ Ce().catch((e) => {
1086
1159
  console.error("Fatal error in main():", e), process.exit(1);
1087
1160
  });