@ythalorossy/openfda 1.0.19 → 1.2.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.
- package/LICENSE +20 -20
- package/README.md +103 -94
- package/dist/index.js +740 -362
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -1,28 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
import { McpServer as
|
|
6
|
-
import { StdioServerTransport as
|
|
7
|
-
import
|
|
8
|
-
|
|
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/
|
|
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) {
|
|
12
|
+
var n;
|
|
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 };
|
|
15
|
+
}
|
|
16
|
+
function te(e = process.env) {
|
|
17
|
+
const t = O(e);
|
|
18
|
+
t.ok ? t.apiKey === null && console.error(Z) : console.error(ee);
|
|
19
|
+
}
|
|
20
|
+
class ne {
|
|
9
21
|
constructor(t) {
|
|
10
|
-
|
|
22
|
+
A(this, "registerTool", (t) => this.server.registerTool(
|
|
11
23
|
t.name,
|
|
12
24
|
{
|
|
13
25
|
title: t.name,
|
|
14
26
|
description: t.description,
|
|
15
27
|
inputSchema: t.inputSchema
|
|
16
28
|
},
|
|
17
|
-
|
|
29
|
+
async (n) => {
|
|
30
|
+
const r = O();
|
|
31
|
+
return r.ok ? t.handler(n) : {
|
|
32
|
+
content: [{ type: "text", text: r.message }],
|
|
33
|
+
isError: !0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
18
36
|
));
|
|
19
37
|
this.server = t;
|
|
20
38
|
}
|
|
21
39
|
}
|
|
22
|
-
|
|
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);
|
|
43
|
+
};
|
|
44
|
+
function re(e) {
|
|
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)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function ae(e) {
|
|
61
|
+
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)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
class y {
|
|
23
74
|
constructor() {
|
|
24
|
-
|
|
25
|
-
|
|
75
|
+
A(this, "urlBase", "https://api.fda.gov");
|
|
76
|
+
A(this, "params", /* @__PURE__ */ new Map());
|
|
26
77
|
}
|
|
27
78
|
dataset(t) {
|
|
28
79
|
return this.params.set("dataset", t), this;
|
|
@@ -36,229 +87,287 @@ class g {
|
|
|
36
87
|
limit(t = 1) {
|
|
37
88
|
return this.params.set("limit", t), this;
|
|
38
89
|
}
|
|
90
|
+
/** Aggregate by a field instead of returning records. */
|
|
91
|
+
count(t) {
|
|
92
|
+
return this.params.set("count", t), this;
|
|
93
|
+
}
|
|
94
|
+
/** Offset into the result set. openFDA rejects values above 25000. */
|
|
95
|
+
skip(t) {
|
|
96
|
+
return this.params.set("skip", t), this;
|
|
97
|
+
}
|
|
98
|
+
/** e.g. 'receivedate:desc'. */
|
|
99
|
+
sort(t) {
|
|
100
|
+
return this.params.set("sort", t), this;
|
|
101
|
+
}
|
|
39
102
|
build() {
|
|
40
|
-
const t = this.params.get("dataset"),
|
|
41
|
-
if (!t || !
|
|
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)
|
|
42
105
|
throw new Error("Missing required parameters: context or search");
|
|
43
|
-
|
|
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));
|
|
111
|
+
}
|
|
112
|
+
return `${this.urlBase}/${t}/${n}.json?${s}`;
|
|
44
113
|
}
|
|
45
114
|
}
|
|
46
|
-
const
|
|
115
|
+
const U = (e) => e.replace(/([?&]api_key=)[^&\s]*/gi, "$1<REDACTED>"), se = {
|
|
47
116
|
maxRetries: 3,
|
|
48
117
|
retryDelay: 1e3,
|
|
49
118
|
// 1 second
|
|
50
119
|
timeout: 3e4
|
|
51
120
|
// 30 seconds
|
|
52
121
|
};
|
|
53
|
-
function
|
|
54
|
-
return !!(
|
|
122
|
+
function I(e) {
|
|
123
|
+
return !!(e.name === "TypeError" && e.message.includes("fetch") || e.name === "AbortError" || e.status >= 500 && e.status <= 599 || e.status === 429);
|
|
55
124
|
}
|
|
56
|
-
function
|
|
57
|
-
return new Promise((t) => setTimeout(t,
|
|
125
|
+
function P(e) {
|
|
126
|
+
return new Promise((t) => setTimeout(t, e));
|
|
58
127
|
}
|
|
59
|
-
async function
|
|
60
|
-
const { maxRetries:
|
|
128
|
+
async function v(e, t = {}) {
|
|
129
|
+
const { maxRetries: n, retryDelay: r, timeout: i } = { ...se, ...t }, o = {
|
|
61
130
|
"User-Agent": "@ythalorossy/openfda",
|
|
62
131
|
Accept: "application/json"
|
|
63
132
|
};
|
|
64
|
-
let
|
|
65
|
-
for (let
|
|
133
|
+
let s = null;
|
|
134
|
+
for (let d = 0; d <= n; d++)
|
|
66
135
|
try {
|
|
67
|
-
const
|
|
136
|
+
const c = new AbortController(), l = setTimeout(() => c.abort(), i), a = await fetch(e, {
|
|
68
137
|
headers: o,
|
|
69
|
-
signal:
|
|
138
|
+
signal: c.signal
|
|
70
139
|
});
|
|
71
|
-
if (clearTimeout(l), !
|
|
72
|
-
const f = await
|
|
140
|
+
if (clearTimeout(l), !a.ok) {
|
|
141
|
+
const f = await a.text().catch(() => "Unable to read error response"), _ = {
|
|
73
142
|
type: "http",
|
|
74
|
-
message: `HTTP ${
|
|
75
|
-
status:
|
|
143
|
+
message: `HTTP ${a.status}: ${a.statusText}`,
|
|
144
|
+
status: a.status,
|
|
76
145
|
details: f
|
|
77
146
|
};
|
|
78
|
-
switch (
|
|
147
|
+
switch (a.status) {
|
|
79
148
|
case 400:
|
|
80
|
-
|
|
149
|
+
_.message = "Bad Request: Invalid search query or parameters";
|
|
81
150
|
break;
|
|
82
151
|
case 401:
|
|
83
|
-
|
|
152
|
+
_.message = "Unauthorized: Invalid or missing API key";
|
|
84
153
|
break;
|
|
85
154
|
case 403:
|
|
86
|
-
|
|
155
|
+
_.message = "Forbidden: API key may be invalid or quota exceeded";
|
|
87
156
|
break;
|
|
88
157
|
case 404:
|
|
89
|
-
|
|
158
|
+
_.message = "Not Found: No results found for the specified query";
|
|
90
159
|
break;
|
|
91
160
|
case 429:
|
|
92
|
-
|
|
161
|
+
_.message = "Rate Limited: Too many requests. Retrying...";
|
|
93
162
|
break;
|
|
94
163
|
case 500:
|
|
95
|
-
|
|
164
|
+
_.message = "Server Error: OpenFDA service is experiencing issues";
|
|
96
165
|
break;
|
|
97
166
|
default:
|
|
98
|
-
|
|
167
|
+
_.message = `HTTP Error ${a.status}: ${a.statusText}`;
|
|
99
168
|
}
|
|
100
|
-
if (
|
|
169
|
+
if (s = _, a.status >= 400 && a.status < 500 && a.status !== 429)
|
|
101
170
|
break;
|
|
102
|
-
if (
|
|
103
|
-
const
|
|
104
|
-
await
|
|
171
|
+
if (d < n && I({ status: a.status })) {
|
|
172
|
+
const h = r * Math.pow(2, d);
|
|
173
|
+
await P(h);
|
|
105
174
|
continue;
|
|
106
175
|
}
|
|
107
176
|
break;
|
|
108
177
|
}
|
|
109
|
-
let
|
|
178
|
+
let g;
|
|
110
179
|
try {
|
|
111
|
-
|
|
180
|
+
g = await a.json();
|
|
112
181
|
} catch (f) {
|
|
113
|
-
|
|
182
|
+
s = {
|
|
114
183
|
type: "parsing",
|
|
115
|
-
message:
|
|
184
|
+
message: U(
|
|
185
|
+
`Failed to parse JSON response: ${f instanceof Error ? f.message : "Unknown parsing error"}`
|
|
186
|
+
),
|
|
116
187
|
details: f
|
|
117
188
|
};
|
|
118
189
|
break;
|
|
119
190
|
}
|
|
120
|
-
if (!
|
|
121
|
-
|
|
191
|
+
if (!g) {
|
|
192
|
+
s = {
|
|
122
193
|
type: "empty_response",
|
|
123
194
|
message: "Received empty response from OpenFDA API"
|
|
124
195
|
};
|
|
125
196
|
break;
|
|
126
197
|
}
|
|
127
|
-
return { data:
|
|
128
|
-
} catch (
|
|
198
|
+
return { data: g, error: null };
|
|
199
|
+
} catch (c) {
|
|
129
200
|
let l;
|
|
130
|
-
if (
|
|
201
|
+
if (c.name === "AbortError" ? l = {
|
|
131
202
|
type: "timeout",
|
|
132
|
-
message: `Request timeout after ${
|
|
133
|
-
details:
|
|
134
|
-
} :
|
|
203
|
+
message: `Request timeout after ${i}ms`,
|
|
204
|
+
details: c
|
|
205
|
+
} : c instanceof TypeError && c.message.includes("fetch") ? l = {
|
|
135
206
|
type: "network",
|
|
136
207
|
message: "Network error: Unable to connect to OpenFDA API",
|
|
137
|
-
details:
|
|
208
|
+
details: c.message
|
|
138
209
|
} : l = {
|
|
139
210
|
type: "unknown",
|
|
140
|
-
message:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
211
|
+
message: U(
|
|
212
|
+
`Unexpected error: ${c.message || "Unknown error occurred"}`
|
|
213
|
+
),
|
|
214
|
+
details: c
|
|
215
|
+
}, s = l, d < n && I(c)) {
|
|
216
|
+
const a = r * Math.pow(2, d);
|
|
217
|
+
await P(a);
|
|
145
218
|
continue;
|
|
146
219
|
}
|
|
147
220
|
break;
|
|
148
221
|
}
|
|
149
|
-
return { data: null, error:
|
|
222
|
+
return { data: null, error: s };
|
|
150
223
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
224
|
+
const j = [
|
|
225
|
+
"openfda.brand_name",
|
|
226
|
+
"openfda.generic_name",
|
|
227
|
+
"openfda.substance_name",
|
|
228
|
+
"spl_product_data_elements"
|
|
229
|
+
];
|
|
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);
|
|
159
234
|
if (s) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
235
|
+
n = s;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (o != null && o.results && o.results.length > 0)
|
|
239
|
+
return { found: !0, matched_via: r, data: o };
|
|
240
|
+
}
|
|
241
|
+
return { found: !1, error: n };
|
|
242
|
+
}
|
|
243
|
+
const L = (e) => `No label found for "${e}".
|
|
169
244
|
|
|
170
|
-
|
|
171
|
-
break;
|
|
172
|
-
case "network":
|
|
173
|
-
a += `
|
|
245
|
+
Searched, in order: ${j.join(", ")}.
|
|
174
246
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
247
|
+
Suggestions:
|
|
248
|
+
- Check the spelling.
|
|
249
|
+
- Try the generic name instead of the brand (e.g. "amiodarone" rather than "Cordarone").
|
|
250
|
+
- Try the originator brand rather than a repackager's name.
|
|
251
|
+
- Some discontinued brands have no current FDA label.`;
|
|
252
|
+
function oe(e) {
|
|
253
|
+
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";
|
|
255
|
+
}
|
|
256
|
+
function x(e, t, n) {
|
|
257
|
+
return typeof t != "number" ? `Showing ${e} ${n} (total unknown)` : e >= t ? `Showing all ${t} ${n}` : `Showing ${e} of ${t} ${n}`;
|
|
258
|
+
}
|
|
259
|
+
function k(e, t, n) {
|
|
260
|
+
return {
|
|
261
|
+
total: typeof t == "number" ? t : null,
|
|
262
|
+
returned: e.length,
|
|
263
|
+
limit: n,
|
|
264
|
+
results: e
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const ie = {
|
|
268
|
+
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.",
|
|
270
|
+
returnsFields: [
|
|
271
|
+
"brand_name",
|
|
272
|
+
"generic_name",
|
|
273
|
+
"manufacturer_name",
|
|
274
|
+
"product_ndc",
|
|
275
|
+
"substance_name",
|
|
276
|
+
"boxed_warning",
|
|
277
|
+
"warnings",
|
|
278
|
+
"warnings_and_cautions",
|
|
279
|
+
"do_not_use",
|
|
280
|
+
"ask_doctor",
|
|
281
|
+
"ask_doctor_or_pharmacist",
|
|
282
|
+
"stop_use",
|
|
283
|
+
"pregnancy_or_breast_feeding",
|
|
284
|
+
"indications_and_usage",
|
|
285
|
+
"matched_via"
|
|
286
|
+
],
|
|
287
|
+
inputSchema: u.object({
|
|
288
|
+
drugName: u.string().describe("Drug name")
|
|
289
|
+
}),
|
|
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 ? {
|
|
190
295
|
content: [
|
|
191
296
|
{
|
|
192
297
|
type: "text",
|
|
193
|
-
text: `
|
|
298
|
+
text: `Failed to retrieve drug data for "${e}": ${t.error.message}`
|
|
194
299
|
}
|
|
195
|
-
]
|
|
300
|
+
],
|
|
301
|
+
isError: !0
|
|
302
|
+
} : {
|
|
303
|
+
content: [{ type: "text", text: L(e) }]
|
|
196
304
|
};
|
|
197
|
-
const
|
|
198
|
-
brand_name:
|
|
199
|
-
generic_name:
|
|
200
|
-
manufacturer_name:
|
|
201
|
-
product_ndc:
|
|
202
|
-
product_type:
|
|
203
|
-
route:
|
|
204
|
-
substance_name:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
do_not_use: e == null ? void 0 : e.do_not_use,
|
|
208
|
-
ask_doctor: e == null ? void 0 : e.ask_doctor,
|
|
209
|
-
ask_doctor_or_pharmacist: e == null ? void 0 : e.ask_doctor_or_pharmacist,
|
|
210
|
-
stop_use: e == null ? void 0 : e.stop_use,
|
|
211
|
-
pregnancy_or_breast_feeding: e == null ? void 0 : e.pregnancy_or_breast_feeding
|
|
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)
|
|
212
315
|
};
|
|
213
316
|
return {
|
|
214
317
|
content: [
|
|
215
318
|
{
|
|
216
319
|
type: "text",
|
|
217
|
-
text:
|
|
320
|
+
text: `${x(1, (o = (i = t.data.meta) == null ? void 0 : i.results) == null ? void 0 : o.total, `labels matching "${e}"`)}
|
|
218
321
|
|
|
219
|
-
${JSON.stringify(
|
|
322
|
+
${JSON.stringify(k([r], (d = (s = t.data.meta) == null ? void 0 : s.results) == null ? void 0 : d.total, 1), null, 2)}`
|
|
220
323
|
}
|
|
221
324
|
]
|
|
222
325
|
};
|
|
223
326
|
}
|
|
224
|
-
},
|
|
327
|
+
}, ce = {
|
|
225
328
|
name: "get-drug-by-generic-name",
|
|
226
|
-
description: "Get drug information by generic (active ingredient) name. Useful when you know the generic name but not the brand name. Returns all brand versions of the generic drug.",
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
329
|
+
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
|
+
// Raw upstream records wrapped in an envelope: declare only the envelope
|
|
331
|
+
// keys this tool guarantees (total, returned, limit, results), never
|
|
332
|
+
// inner record fields, because those vary per record. This tool does not
|
|
333
|
+
// emit matched_via — it searches a single fixed field.
|
|
334
|
+
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")
|
|
230
338
|
}),
|
|
231
339
|
async handler({
|
|
232
|
-
genericName:
|
|
340
|
+
genericName: e,
|
|
233
341
|
limit: t
|
|
234
342
|
}) {
|
|
235
|
-
|
|
236
|
-
|
|
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);
|
|
345
|
+
if (i)
|
|
237
346
|
return {
|
|
238
347
|
content: [
|
|
239
348
|
{
|
|
240
349
|
type: "text",
|
|
241
|
-
text: `Failed to retrieve drug data for generic name "${
|
|
350
|
+
text: `Failed to retrieve drug data for generic name "${e}": ${i.message}`
|
|
242
351
|
}
|
|
243
352
|
],
|
|
244
353
|
isError: !0
|
|
245
354
|
};
|
|
246
|
-
if (!(
|
|
355
|
+
if (!(r != null && r.results) || r.results.length === 0)
|
|
247
356
|
return {
|
|
248
357
|
content: [
|
|
249
358
|
{
|
|
250
359
|
type: "text",
|
|
251
|
-
text: `No drug information found for generic name "${
|
|
360
|
+
text: `No drug information found for generic name "${e}".`
|
|
252
361
|
}
|
|
253
362
|
]
|
|
254
363
|
};
|
|
255
|
-
const o =
|
|
256
|
-
var
|
|
364
|
+
const o = r.results.map((a) => {
|
|
365
|
+
var g, f, _, h;
|
|
257
366
|
return {
|
|
258
|
-
brand_name: ((
|
|
259
|
-
generic_name: ((
|
|
260
|
-
manufacturer_name: ((
|
|
261
|
-
product_type: ((
|
|
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",
|
|
262
371
|
route: (a == null ? void 0 : a.openfda.route) || []
|
|
263
372
|
};
|
|
264
373
|
});
|
|
@@ -266,242 +375,358 @@ ${JSON.stringify(o, null, 2)}`
|
|
|
266
375
|
content: [
|
|
267
376
|
{
|
|
268
377
|
type: "text",
|
|
269
|
-
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}"`)}
|
|
270
379
|
|
|
271
|
-
${JSON.stringify(o, null, 2)}`
|
|
380
|
+
${JSON.stringify(k(o, (l = (c = r.meta) == null ? void 0 : c.results) == null ? void 0 : l.total, t ?? 5), null, 2)}`
|
|
272
381
|
}
|
|
273
382
|
]
|
|
274
383
|
};
|
|
275
384
|
}
|
|
276
|
-
},
|
|
385
|
+
}, de = {
|
|
386
|
+
1: "Recovered/resolved",
|
|
387
|
+
2: "Recovering/resolving",
|
|
388
|
+
3: "Not recovered/not resolved",
|
|
389
|
+
4: "Recovered/resolved with sequelae",
|
|
390
|
+
5: "Fatal",
|
|
391
|
+
6: "Unknown"
|
|
392
|
+
};
|
|
393
|
+
function ue(e) {
|
|
394
|
+
if (e == null || e === "") return "Not reported";
|
|
395
|
+
const t = String(e);
|
|
396
|
+
return de[t] ?? `Unrecognized outcome code "${t}"`;
|
|
397
|
+
}
|
|
398
|
+
const K = [
|
|
399
|
+
"patient.drug.openfda.generic_name",
|
|
400
|
+
"patient.drug.openfda.substance_name",
|
|
401
|
+
"patient.drug.medicinalproduct"
|
|
402
|
+
], G = `union(${K.join(", ")})`;
|
|
403
|
+
function q(e) {
|
|
404
|
+
return K.map((t) => `${t}:"${e}"`).join(
|
|
405
|
+
" OR "
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
function le(e) {
|
|
409
|
+
const t = /* @__PURE__ */ new Set(), n = [];
|
|
410
|
+
for (const r of e) {
|
|
411
|
+
const i = r == null ? void 0 : r.reactionmeddrapt;
|
|
412
|
+
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 }));
|
|
415
|
+
}
|
|
416
|
+
return n.slice(0, 3);
|
|
417
|
+
}
|
|
418
|
+
const F = 25e3, pe = {
|
|
277
419
|
name: "get-drug-adverse-events",
|
|
278
|
-
description: "Get adverse event reports for a drug. This provides safety information about reported side effects and reactions. Use brand name or generic name.",
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
420
|
+
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
|
+
// Raw upstream records wrapped in an envelope: declare only the envelope
|
|
422
|
+
// keys this tool ALWAYS guarantees (matched_via, total, returned, limit,
|
|
423
|
+
// results), never inner record fields, because those vary per record.
|
|
424
|
+
// `skip` is deliberately NOT declared: it is only present in the payload
|
|
425
|
+
// when the caller supplies `skip`, and a conditional field must not be
|
|
426
|
+
// declared here (an earlier round established that declaring a
|
|
427
|
+
// sometimes-absent field makes this guard assert something false).
|
|
428
|
+
returnsFields: [
|
|
429
|
+
"matched_via",
|
|
430
|
+
"total",
|
|
431
|
+
"returned",
|
|
432
|
+
"limit",
|
|
433
|
+
"results"
|
|
434
|
+
],
|
|
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(
|
|
440
|
+
`Offset into the result set, for paging past the limit. Maximum ${F}.`
|
|
441
|
+
),
|
|
442
|
+
sort: u.enum(["receivedate:desc", "receivedate:asc"]).optional().describe(
|
|
443
|
+
"Order results by report receive date. Without it, results are a deterministic earliest-report_id slice, so a small sample is not representative."
|
|
444
|
+
)
|
|
283
445
|
}),
|
|
284
446
|
async handler({
|
|
285
|
-
drugName:
|
|
447
|
+
drugName: e,
|
|
286
448
|
limit: t,
|
|
287
|
-
seriousness:
|
|
449
|
+
seriousness: n,
|
|
450
|
+
skip: r,
|
|
451
|
+
sort: i
|
|
288
452
|
}) {
|
|
289
|
-
|
|
290
|
-
r !==
|
|
291
|
-
const e = new g().dataset("drug").context("event").search(s).limit(t).build(), { data: o, error: a } = await h(e);
|
|
292
|
-
if (a)
|
|
453
|
+
var h, E;
|
|
454
|
+
if (r !== void 0 && r > F)
|
|
293
455
|
return {
|
|
294
456
|
content: [
|
|
295
457
|
{
|
|
296
458
|
type: "text",
|
|
297
|
-
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.`
|
|
298
460
|
}
|
|
299
461
|
],
|
|
300
462
|
isError: !0
|
|
301
463
|
};
|
|
302
|
-
|
|
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);
|
|
469
|
+
if (l)
|
|
303
470
|
return {
|
|
304
471
|
content: [
|
|
305
472
|
{
|
|
306
473
|
type: "text",
|
|
307
|
-
text: `
|
|
474
|
+
text: `Failed to retrieve adverse events for "${e}": ${l.message}`
|
|
475
|
+
}
|
|
476
|
+
],
|
|
477
|
+
isError: !0
|
|
478
|
+
};
|
|
479
|
+
if (!(c != null && c.results) || c.results.length === 0)
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: "text",
|
|
484
|
+
text: `No adverse events found for "${e}".`
|
|
308
485
|
}
|
|
309
486
|
]
|
|
310
487
|
};
|
|
311
|
-
const
|
|
312
|
-
var
|
|
488
|
+
const a = c.results.map((p) => {
|
|
489
|
+
var N, D, S, $;
|
|
490
|
+
const w = le(((N = p.patient) == null ? void 0 : N.reaction) ?? []);
|
|
313
491
|
return {
|
|
314
|
-
report_id:
|
|
315
|
-
serious:
|
|
316
|
-
patient_age: ((
|
|
317
|
-
patient_sex: ((
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
492
|
+
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",
|
|
496
|
+
// Derived from the same deduped pairs so the two arrays stay the
|
|
497
|
+
// same length and positionally aligned (see dedupeReactionPairs).
|
|
498
|
+
reactions: w.map((T) => T.reaction),
|
|
499
|
+
outcomes: w.map((T) => ue(T.outcome)),
|
|
500
|
+
report_date: p.receiptdate || "Unknown"
|
|
321
501
|
};
|
|
322
|
-
})
|
|
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,
|
|
505
|
+
`adverse event reports for "${e}"`
|
|
506
|
+
), _ = {
|
|
507
|
+
matched_via: G,
|
|
508
|
+
...r !== void 0 ? { skip: r } : {},
|
|
509
|
+
...k(a, g, t ?? 10)
|
|
510
|
+
};
|
|
323
511
|
return {
|
|
324
512
|
content: [
|
|
325
513
|
{
|
|
326
514
|
type: "text",
|
|
327
|
-
text:
|
|
515
|
+
text: `${f}
|
|
328
516
|
|
|
329
|
-
${JSON.stringify(
|
|
517
|
+
${JSON.stringify(_, null, 2)}`
|
|
330
518
|
}
|
|
331
519
|
]
|
|
332
520
|
};
|
|
333
521
|
}
|
|
334
|
-
},
|
|
522
|
+
}, me = {
|
|
335
523
|
name: "get-drugs-by-manufacturer",
|
|
336
|
-
description: "Get all drugs manufactured by a specific company. Useful for finding alternatives or checking manufacturer portfolios.",
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
524
|
+
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
|
+
// Raw upstream records wrapped in an envelope: declare only the envelope
|
|
526
|
+
// keys this tool guarantees (matched_via, total, returned, limit,
|
|
527
|
+
// results), never inner record fields, because those vary per record.
|
|
528
|
+
returnsFields: [
|
|
529
|
+
"matched_via",
|
|
530
|
+
"total",
|
|
531
|
+
"returned",
|
|
532
|
+
"limit",
|
|
533
|
+
"results"
|
|
534
|
+
],
|
|
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")
|
|
340
538
|
}),
|
|
341
539
|
async handler({
|
|
342
|
-
manufacturerName:
|
|
540
|
+
manufacturerName: e,
|
|
343
541
|
limit: t
|
|
344
542
|
}) {
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
545
|
+
if (i)
|
|
347
546
|
return {
|
|
348
547
|
content: [
|
|
349
548
|
{
|
|
350
549
|
type: "text",
|
|
351
|
-
text:
|
|
352
|
-
Failed to retrieve drugs for manufacturer "${n}": ${e.message}`
|
|
550
|
+
text: `Failed to retrieve drugs for manufacturer "${e}": ${i.message}`
|
|
353
551
|
}
|
|
354
552
|
],
|
|
355
553
|
isError: !0
|
|
356
554
|
};
|
|
357
|
-
if (!(
|
|
555
|
+
if (!(r != null && r.results) || r.results.length === 0)
|
|
358
556
|
return {
|
|
359
557
|
content: [
|
|
360
558
|
{
|
|
361
559
|
type: "text",
|
|
362
|
-
text: `No drugs found for manufacturer "${
|
|
560
|
+
text: `No drugs found for manufacturer "${e}".`
|
|
363
561
|
}
|
|
364
562
|
]
|
|
365
563
|
};
|
|
366
|
-
const o =
|
|
367
|
-
var
|
|
564
|
+
const o = r.results.map((a) => {
|
|
565
|
+
var g, f, _, h;
|
|
368
566
|
return {
|
|
369
|
-
brand_name: ((
|
|
370
|
-
generic_name: ((
|
|
371
|
-
product_type: ((
|
|
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",
|
|
372
570
|
route: (a == null ? void 0 : a.openfda.route) || [],
|
|
373
|
-
ndc: ((
|
|
571
|
+
ndc: ((h = a == null ? void 0 : a.openfda.product_ndc) == null ? void 0 : h[0]) || "Unknown"
|
|
374
572
|
};
|
|
375
573
|
});
|
|
376
574
|
return {
|
|
377
575
|
content: [
|
|
378
576
|
{
|
|
379
577
|
type: "text",
|
|
380
|
-
text:
|
|
578
|
+
text: `${x(o.length, (d = (s = r.meta) == null ? void 0 : s.results) == null ? void 0 : d.total, `labels from manufacturer "${e}"`)}
|
|
381
579
|
|
|
382
|
-
${JSON.stringify(o, null, 2)}`
|
|
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)}`
|
|
383
581
|
}
|
|
384
582
|
]
|
|
385
583
|
};
|
|
386
584
|
}
|
|
387
|
-
},
|
|
585
|
+
}, fe = {
|
|
388
586
|
name: "get-drug-safety-info",
|
|
389
|
-
description: "Get
|
|
390
|
-
|
|
391
|
-
|
|
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.",
|
|
588
|
+
returnsFields: [
|
|
589
|
+
"drug_name",
|
|
590
|
+
"generic_name",
|
|
591
|
+
"matched_via",
|
|
592
|
+
"boxed_warning",
|
|
593
|
+
"warnings",
|
|
594
|
+
"warnings_and_cautions",
|
|
595
|
+
"contraindications",
|
|
596
|
+
"drug_interactions",
|
|
597
|
+
"precautions",
|
|
598
|
+
"adverse_reactions",
|
|
599
|
+
"overdosage",
|
|
600
|
+
"do_not_use",
|
|
601
|
+
"ask_doctor",
|
|
602
|
+
"stop_use",
|
|
603
|
+
"pregnancy_or_breast_feeding"
|
|
604
|
+
],
|
|
605
|
+
inputSchema: u.object({
|
|
606
|
+
drugName: u.string().describe("Drug brand name")
|
|
392
607
|
}),
|
|
393
|
-
async handler({ drugName:
|
|
394
|
-
var
|
|
395
|
-
const t =
|
|
396
|
-
if (
|
|
397
|
-
return {
|
|
608
|
+
async handler({ drugName: e }) {
|
|
609
|
+
var i;
|
|
610
|
+
const t = await X(e, 1);
|
|
611
|
+
if (!t.found)
|
|
612
|
+
return t.error ? {
|
|
398
613
|
content: [
|
|
399
614
|
{
|
|
400
615
|
type: "text",
|
|
401
|
-
text: `Failed to retrieve safety information for "${
|
|
616
|
+
text: `Failed to retrieve safety information for "${e}": ${t.error.message}`
|
|
402
617
|
}
|
|
403
618
|
],
|
|
404
619
|
isError: !0
|
|
620
|
+
} : {
|
|
621
|
+
content: [{ type: "text", text: L(e) }]
|
|
405
622
|
};
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
]
|
|
414
|
-
};
|
|
415
|
-
const e = r.results[0], o = {
|
|
416
|
-
drug_name: ((a = e == null ? void 0 : e.openfda.brand_name) == null ? void 0 : a[0]) || n,
|
|
417
|
-
generic_name: ((p = e == null ? void 0 : e.openfda.generic_name) == null ? void 0 : p[0]) || "Unknown",
|
|
418
|
-
warnings: (e == null ? void 0 : e.warnings) || [],
|
|
419
|
-
contraindications: (e == null ? void 0 : e.contraindications) || [],
|
|
420
|
-
drug_interactions: (e == null ? void 0 : e.drug_interactions) || [],
|
|
421
|
-
precautions: (e == null ? void 0 : e.precautions) || [],
|
|
422
|
-
adverse_reactions: (e == null ? void 0 : e.adverse_reactions) || [],
|
|
423
|
-
overdosage: (e == null ? void 0 : e.overdosage) || [],
|
|
424
|
-
do_not_use: (e == null ? void 0 : e.do_not_use) || [],
|
|
425
|
-
ask_doctor: (e == null ? void 0 : e.ask_doctor) || [],
|
|
426
|
-
stop_use: (e == null ? void 0 : e.stop_use) || [],
|
|
427
|
-
pregnancy_or_breast_feeding: (e == null ? void 0 : e.pregnancy_or_breast_feeding) || []
|
|
623
|
+
const n = t.data.results[0], r = {
|
|
624
|
+
drug_name: ((i = n == null ? void 0 : n.openfda.brand_name) == null ? void 0 : i[0]) || e,
|
|
625
|
+
generic_name: oe(
|
|
626
|
+
(n == null ? void 0 : n.openfda) ?? {}
|
|
627
|
+
),
|
|
628
|
+
matched_via: t.matched_via,
|
|
629
|
+
...re(n)
|
|
428
630
|
};
|
|
429
631
|
return {
|
|
430
632
|
content: [
|
|
431
633
|
{
|
|
432
634
|
type: "text",
|
|
433
|
-
text: `Safety information for "${
|
|
635
|
+
text: `Safety information for "${e}":
|
|
434
636
|
|
|
435
|
-
${JSON.stringify(
|
|
637
|
+
${JSON.stringify(r, null, 2)}`
|
|
436
638
|
}
|
|
437
639
|
]
|
|
438
640
|
};
|
|
439
641
|
}
|
|
440
|
-
}
|
|
441
|
-
function
|
|
442
|
-
const t =
|
|
443
|
-
let r,
|
|
642
|
+
}, _e = /^(?:\d{4}-\d{4}|\d{5}-\d{3,4})$/, ge = /^\d{1,2}$/;
|
|
643
|
+
function V(e) {
|
|
644
|
+
const t = e.trim().toUpperCase(), n = { productNDC: t, packageNDC: null, isValid: !1 };
|
|
645
|
+
let r, i = null;
|
|
444
646
|
if (t.includes("-")) {
|
|
445
647
|
const o = t.split("-");
|
|
446
648
|
if (o.length === 2)
|
|
447
|
-
r = t
|
|
448
|
-
else if (o.length === 3)
|
|
449
|
-
|
|
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;
|
|
654
|
+
} else
|
|
655
|
+
return n;
|
|
656
|
+
} else if (/^\d+$/.test(t))
|
|
657
|
+
if (t.length === 11)
|
|
658
|
+
r = `${t.slice(0, 5)}-${t.slice(5, 9)}`, i = `${r}-${t.slice(9, 11)}`;
|
|
659
|
+
else if (t.length === 9)
|
|
660
|
+
r = `${t.slice(0, 5)}-${t.slice(5, 9)}`;
|
|
450
661
|
else
|
|
451
|
-
return
|
|
452
|
-
} else if (t.length === 11)
|
|
453
|
-
r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = `${t.substring(0, 5)}-${t.substring(5, 9)}-${t.substring(9, 11)}`;
|
|
454
|
-
else if (t.length === 9)
|
|
455
|
-
r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = null;
|
|
662
|
+
return n;
|
|
456
663
|
else
|
|
457
|
-
return
|
|
458
|
-
|
|
459
|
-
return { productNDC: r, packageNDC: s, isValid: e };
|
|
664
|
+
return n;
|
|
665
|
+
return _e.test(r) ? { productNDC: r, packageNDC: i, isValid: !0 } : n;
|
|
460
666
|
}
|
|
461
|
-
const
|
|
667
|
+
const he = [
|
|
668
|
+
"✅ Accepted formats:",
|
|
669
|
+
"• 4-4 product NDC: 0456-4020",
|
|
670
|
+
"• 5-3 product NDC: 58151-155",
|
|
671
|
+
"• 5-4 product NDC: 12345-1234",
|
|
672
|
+
"• Package NDC: 12345-1234-01 or 58151-155-01",
|
|
673
|
+
"• Undashed 9 digits: 123451234 (read as 5-4)",
|
|
674
|
+
"• Undashed 11 digits: 12345123401 (read as 5-4-2)",
|
|
675
|
+
"",
|
|
676
|
+
"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
|
+
].join(`
|
|
678
|
+
`), W = (e, t) => `Invalid ${t} format: "${e}"
|
|
679
|
+
|
|
680
|
+
${he}`, be = {
|
|
462
681
|
name: "get-drug-by-ndc",
|
|
463
|
-
description: "Get drug information by National Drug Code (NDC). Accepts
|
|
464
|
-
|
|
465
|
-
|
|
682
|
+
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
|
+
// Raw upstream records wrapped in an envelope: declare only the envelope
|
|
684
|
+
// keys this tool guarantees (matched_via, total, returned, limit,
|
|
685
|
+
// results), never inner record fields, because those vary per record.
|
|
686
|
+
returnsFields: [
|
|
687
|
+
"matched_via",
|
|
688
|
+
"total",
|
|
689
|
+
"returned",
|
|
690
|
+
"limit",
|
|
691
|
+
"results"
|
|
692
|
+
],
|
|
693
|
+
inputSchema: u.object({
|
|
694
|
+
ndcCode: u.string().describe(
|
|
466
695
|
"National Drug Code (NDC) - accepts formats: XXXXX-XXXX, XXXXX-XXXX-XX, or without dashes"
|
|
467
696
|
)
|
|
468
697
|
}),
|
|
469
|
-
async handler({ ndcCode:
|
|
470
|
-
|
|
471
|
-
|
|
698
|
+
async handler({ ndcCode: e }) {
|
|
699
|
+
var f, _, h, E;
|
|
700
|
+
const { productNDC: t, packageNDC: n, isValid: r } = V(e);
|
|
701
|
+
if (!r)
|
|
472
702
|
return {
|
|
473
703
|
content: [
|
|
474
704
|
{
|
|
475
705
|
type: "text",
|
|
476
|
-
text:
|
|
477
|
-
|
|
478
|
-
✅ Accepted formats:
|
|
479
|
-
• Product NDC: 12345-1234
|
|
480
|
-
• Package NDC: 12345-1234-01
|
|
481
|
-
• Without dashes: 123451234 or 12345123401`
|
|
706
|
+
text: W(e, "NDC")
|
|
482
707
|
}
|
|
483
708
|
],
|
|
484
709
|
isError: !0
|
|
485
710
|
};
|
|
486
|
-
let
|
|
487
|
-
|
|
488
|
-
const o = new
|
|
489
|
-
if (
|
|
711
|
+
let i = `openfda.product_ndc:"${t}"`;
|
|
712
|
+
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);
|
|
714
|
+
if (c)
|
|
490
715
|
return {
|
|
491
716
|
content: [
|
|
492
717
|
{
|
|
493
718
|
type: "text",
|
|
494
|
-
text: `Failed to retrieve drug data for NDC "${
|
|
719
|
+
text: `Failed to retrieve drug data for NDC "${e}": ${c.message}`
|
|
495
720
|
}
|
|
496
721
|
],
|
|
497
722
|
isError: !0
|
|
498
723
|
};
|
|
499
|
-
if (!(
|
|
724
|
+
if (!(d != null && d.results) || d.results.length === 0)
|
|
500
725
|
return {
|
|
501
726
|
content: [
|
|
502
727
|
{
|
|
503
728
|
type: "text",
|
|
504
|
-
text: `No drug found with NDC "${
|
|
729
|
+
text: `No drug found with NDC "${e}" (product: ${t}).
|
|
505
730
|
|
|
506
731
|
💡 Tips:
|
|
507
732
|
• Verify the NDC format
|
|
@@ -510,179 +735,330 @@ const I = {
|
|
|
510
735
|
}
|
|
511
736
|
]
|
|
512
737
|
};
|
|
513
|
-
const
|
|
514
|
-
var
|
|
515
|
-
const
|
|
516
|
-
(
|
|
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)
|
|
517
742
|
)) || [];
|
|
518
743
|
return {
|
|
519
|
-
brand_name:
|
|
520
|
-
generic_name:
|
|
521
|
-
manufacturer_name:
|
|
522
|
-
product_type:
|
|
523
|
-
route:
|
|
524
|
-
substance_name:
|
|
525
|
-
matching_product_ndc:
|
|
526
|
-
matching_package_ndc:
|
|
527
|
-
all_product_ndc:
|
|
528
|
-
all_package_ndc:
|
|
529
|
-
dosage_and_administration:
|
|
530
|
-
package_label_principal_display_panel:
|
|
531
|
-
active_ingredient:
|
|
532
|
-
purpose:
|
|
744
|
+
brand_name: p.openfda.brand_name || [],
|
|
745
|
+
generic_name: p.openfda.generic_name || [],
|
|
746
|
+
manufacturer_name: p.openfda.manufacturer_name || [],
|
|
747
|
+
product_type: p.openfda.product_type || [],
|
|
748
|
+
route: p.openfda.route || [],
|
|
749
|
+
substance_name: p.openfda.substance_name || [],
|
|
750
|
+
matching_product_ndc: w,
|
|
751
|
+
matching_package_ndc: N,
|
|
752
|
+
all_product_ndc: p.openfda.product_ndc || [],
|
|
753
|
+
all_package_ndc: p.openfda.package_ndc || [],
|
|
754
|
+
dosage_and_administration: p.dosage_and_administration || [],
|
|
755
|
+
package_label_principal_display_panel: p.package_label_principal_display_panel || [],
|
|
756
|
+
active_ingredient: p.active_ingredient || [],
|
|
757
|
+
purpose: p.purpose || []
|
|
533
758
|
};
|
|
534
|
-
}),
|
|
535
|
-
(
|
|
759
|
+
}), a = l.reduce(
|
|
760
|
+
(p, w) => p + w.matching_package_ndc.length,
|
|
536
761
|
0
|
|
537
|
-
),
|
|
762
|
+
), g = n ? `Searched for specific package NDC: ${n}` : `Searched for product NDC: ${t} (all packages)`;
|
|
538
763
|
return {
|
|
539
764
|
content: [
|
|
540
765
|
{
|
|
541
766
|
type: "text",
|
|
542
|
-
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)
|
|
543
768
|
|
|
544
|
-
${
|
|
769
|
+
${g}
|
|
545
770
|
|
|
546
|
-
${JSON.stringify(
|
|
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)}`
|
|
547
772
|
}
|
|
548
773
|
]
|
|
549
774
|
};
|
|
550
775
|
}
|
|
551
|
-
},
|
|
776
|
+
}, ye = {
|
|
552
777
|
name: "get-drug-by-product-ndc",
|
|
553
|
-
description: "Get drug information by product NDC
|
|
554
|
-
|
|
555
|
-
|
|
778
|
+
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
|
+
// Fixed payload of named fields (one record, not an envelope over many):
|
|
780
|
+
// every key here is always set on the success path, defaulting to an
|
|
781
|
+
// empty array when the upstream label omits it.
|
|
782
|
+
returnsFields: [
|
|
783
|
+
"product_ndc",
|
|
784
|
+
"available_packages",
|
|
785
|
+
"brand_name",
|
|
786
|
+
"generic_name",
|
|
787
|
+
"manufacturer_name",
|
|
788
|
+
"product_type",
|
|
789
|
+
"route",
|
|
790
|
+
"substance_name",
|
|
791
|
+
"active_ingredient",
|
|
792
|
+
"purpose",
|
|
793
|
+
"dosage_and_administration"
|
|
794
|
+
],
|
|
795
|
+
inputSchema: u.object({
|
|
796
|
+
productNDC: u.string().describe(
|
|
797
|
+
"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
|
+
)
|
|
556
799
|
}),
|
|
557
|
-
async handler({ productNDC:
|
|
558
|
-
var
|
|
559
|
-
|
|
800
|
+
async handler({ productNDC: e }) {
|
|
801
|
+
var l;
|
|
802
|
+
const { productNDC: t, isValid: n } = V(e);
|
|
803
|
+
if (!n)
|
|
560
804
|
return {
|
|
561
805
|
content: [
|
|
562
806
|
{
|
|
563
807
|
type: "text",
|
|
564
|
-
text:
|
|
565
|
-
|
|
566
|
-
✅ Required format: XXXXX-XXXX (e.g., 12345-1234)`
|
|
808
|
+
text: W(e, "product NDC")
|
|
567
809
|
}
|
|
568
810
|
],
|
|
569
811
|
isError: !0
|
|
570
812
|
};
|
|
571
|
-
const
|
|
572
|
-
if (
|
|
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)
|
|
573
815
|
return {
|
|
574
816
|
content: [
|
|
575
817
|
{
|
|
576
818
|
type: "text",
|
|
577
|
-
text:
|
|
819
|
+
text: `Failed to retrieve drug data for product NDC "${e}": ${o.message}`
|
|
578
820
|
}
|
|
579
821
|
],
|
|
580
822
|
isError: !0
|
|
581
823
|
};
|
|
582
|
-
if (!(
|
|
824
|
+
if (!(i != null && i.results) || i.results.length === 0)
|
|
583
825
|
return {
|
|
584
826
|
content: [
|
|
585
827
|
{
|
|
586
828
|
type: "text",
|
|
587
|
-
text: `No drug found with product NDC "${
|
|
829
|
+
text: `No drug found with product NDC "${e}".`
|
|
588
830
|
}
|
|
589
831
|
],
|
|
590
832
|
structuredContent: null
|
|
591
833
|
};
|
|
592
|
-
const
|
|
593
|
-
(
|
|
594
|
-
)) || [],
|
|
595
|
-
product_ndc:
|
|
596
|
-
available_packages:
|
|
597
|
-
brand_name:
|
|
598
|
-
generic_name:
|
|
599
|
-
manufacturer_name:
|
|
600
|
-
product_type:
|
|
601
|
-
route:
|
|
602
|
-
substance_name:
|
|
603
|
-
active_ingredient:
|
|
604
|
-
purpose:
|
|
605
|
-
dosage_and_administration:
|
|
834
|
+
const s = i.results[0], d = ((l = s.openfda.package_ndc) == null ? void 0 : l.filter(
|
|
835
|
+
(a) => a.startsWith(t)
|
|
836
|
+
)) || [], c = {
|
|
837
|
+
product_ndc: t,
|
|
838
|
+
available_packages: d,
|
|
839
|
+
brand_name: s.openfda.brand_name || [],
|
|
840
|
+
generic_name: s.openfda.generic_name || [],
|
|
841
|
+
manufacturer_name: s.openfda.manufacturer_name || [],
|
|
842
|
+
product_type: s.openfda.product_type || [],
|
|
843
|
+
route: s.openfda.route || [],
|
|
844
|
+
substance_name: s.openfda.substance_name || [],
|
|
845
|
+
active_ingredient: s.active_ingredient || [],
|
|
846
|
+
purpose: s.purpose || [],
|
|
847
|
+
dosage_and_administration: s.dosage_and_administration || []
|
|
606
848
|
};
|
|
607
849
|
return {
|
|
608
850
|
content: [
|
|
609
851
|
{
|
|
610
852
|
type: "text",
|
|
611
|
-
text: `✅ Product NDC "${
|
|
853
|
+
text: `✅ Product NDC "${t}" found with ${d.length} package variation(s):
|
|
612
854
|
|
|
613
|
-
${JSON.stringify(
|
|
855
|
+
${JSON.stringify(c, null, 2)}`
|
|
614
856
|
}
|
|
615
857
|
]
|
|
616
858
|
};
|
|
617
859
|
}
|
|
618
860
|
}, R = {
|
|
861
|
+
application: {
|
|
862
|
+
prefix: "",
|
|
863
|
+
fields: {
|
|
864
|
+
application_number: {},
|
|
865
|
+
// Case-sensitive, stored uppercase: "UPJOHN" -> 13 results,
|
|
866
|
+
// "Upjohn"/"upjohn" -> NOT_FOUND. Normalised so a caller typing
|
|
867
|
+
// "Pfizer" does not get a silent miss that looks like absent data.
|
|
868
|
+
sponsor_name: { uppercase: !0 }
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
openfda: {
|
|
872
|
+
prefix: "openfda",
|
|
873
|
+
fields: {
|
|
874
|
+
application_number: {},
|
|
875
|
+
brand_name: {},
|
|
876
|
+
generic_name: {},
|
|
877
|
+
manufacturer_name: {},
|
|
878
|
+
route: {},
|
|
879
|
+
substance_name: {},
|
|
880
|
+
product_ndc: {}
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
products: {
|
|
884
|
+
prefix: "products",
|
|
885
|
+
fields: {
|
|
886
|
+
dosage_form: {},
|
|
887
|
+
marketing_status: {},
|
|
888
|
+
product_number: {},
|
|
889
|
+
reference_drug: {},
|
|
890
|
+
route: {},
|
|
891
|
+
te_code: {}
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
submissions: {
|
|
895
|
+
prefix: "submissions",
|
|
896
|
+
fields: {
|
|
897
|
+
review_priority: {},
|
|
898
|
+
submission_class_code: {},
|
|
899
|
+
submission_number: {},
|
|
900
|
+
submission_status: {},
|
|
901
|
+
submission_status_date: {},
|
|
902
|
+
submission_type: {}
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
application_docs: {
|
|
906
|
+
prefix: "submissions.application_docs",
|
|
907
|
+
fields: { id: {}, url: {}, date: {}, type: {} }
|
|
908
|
+
}
|
|
909
|
+
}, C = Object.keys(R);
|
|
910
|
+
function ve(e, t) {
|
|
911
|
+
const n = R[e];
|
|
912
|
+
if (!n)
|
|
913
|
+
return {
|
|
914
|
+
ok: !1,
|
|
915
|
+
message: `Unknown section "${e}". Valid sections: ${C.join(", ")}.`
|
|
916
|
+
};
|
|
917
|
+
const r = n.fields[t];
|
|
918
|
+
return r ? {
|
|
919
|
+
ok: !0,
|
|
920
|
+
path: n.prefix ? `${n.prefix}.${t}` : t,
|
|
921
|
+
uppercase: r.uppercase === !0
|
|
922
|
+
} : {
|
|
923
|
+
ok: !1,
|
|
924
|
+
message: `Unknown field "${t}" for section "${e}". Valid fields: ${Object.keys(n.fields).join(", ")}.`
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
const $e = (e) => `${e}: ${Object.keys(R[e].fields).join(", ")}`, xe = {
|
|
619
928
|
name: "get-drugsfda",
|
|
620
|
-
description: "
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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.",
|
|
930
|
+
// Raw upstream records wrapped in an envelope: declare only the envelope
|
|
931
|
+
// keys this tool guarantees (matched_via, total, returned, limit,
|
|
932
|
+
// results), never inner record fields, because those vary per record.
|
|
933
|
+
returnsFields: [
|
|
934
|
+
"matched_via",
|
|
935
|
+
"total",
|
|
936
|
+
"returned",
|
|
937
|
+
"limit",
|
|
938
|
+
"results"
|
|
939
|
+
],
|
|
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(". ")}`
|
|
624
944
|
),
|
|
625
|
-
|
|
626
|
-
"
|
|
945
|
+
searchValue: u.string().describe(
|
|
946
|
+
"Value to search for. Sponsor names are stored uppercase and are normalised automatically."
|
|
627
947
|
),
|
|
628
|
-
|
|
948
|
+
limit: u.number().int().min(1).max(100).optional().default(5).describe("Maximum number of records to return")
|
|
629
949
|
}),
|
|
630
950
|
async handler({
|
|
631
|
-
sectionName:
|
|
951
|
+
sectionName: e,
|
|
632
952
|
fieldName: t,
|
|
633
|
-
searchValue:
|
|
953
|
+
searchValue: n,
|
|
954
|
+
limit: r
|
|
634
955
|
}) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
switch (o.type) {
|
|
639
|
-
case "http":
|
|
640
|
-
o.status === 404 ? a += `
|
|
641
|
-
|
|
642
|
-
Suggestions:
|
|
643
|
-
- Verify the field name is correct for the section
|
|
644
|
-
- Check the search value spelling` : (o.status === 401 || o.status === 403) && (a += `
|
|
645
|
-
|
|
646
|
-
Please check the API key configuration.`);
|
|
647
|
-
break;
|
|
648
|
-
case "network":
|
|
649
|
-
a += `
|
|
650
|
-
|
|
651
|
-
Please check your internet connection and try again.`;
|
|
652
|
-
break;
|
|
653
|
-
case "timeout":
|
|
654
|
-
a += `
|
|
655
|
-
|
|
656
|
-
The request took too long. Please try again.`;
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
956
|
+
var g, f;
|
|
957
|
+
const i = r ?? 5, o = ve(e, t);
|
|
958
|
+
if (!o.ok)
|
|
659
959
|
return {
|
|
660
|
-
content: [{ type: "text", text:
|
|
960
|
+
content: [{ type: "text", text: o.message }],
|
|
661
961
|
isError: !0
|
|
662
962
|
};
|
|
663
|
-
}
|
|
664
|
-
|
|
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)
|
|
965
|
+
return {
|
|
966
|
+
content: [
|
|
967
|
+
{
|
|
968
|
+
type: "text",
|
|
969
|
+
text: `Failed to retrieve Drugs@FDA data for "${s}" in ${o.path}: ${l.message}`
|
|
970
|
+
}
|
|
971
|
+
],
|
|
972
|
+
isError: !0
|
|
973
|
+
};
|
|
974
|
+
if (!(c != null && c.results) || c.results.length === 0)
|
|
975
|
+
return {
|
|
976
|
+
content: [
|
|
977
|
+
{
|
|
978
|
+
type: "text",
|
|
979
|
+
text: `No Drugs@FDA records found for "${s}" in ${o.path}.`
|
|
980
|
+
}
|
|
981
|
+
]
|
|
982
|
+
};
|
|
983
|
+
const a = (f = (g = c.meta) == null ? void 0 : g.results) == null ? void 0 : f.total;
|
|
984
|
+
return {
|
|
665
985
|
content: [
|
|
666
986
|
{
|
|
667
987
|
type: "text",
|
|
668
|
-
text:
|
|
988
|
+
text: `${x(c.results.length, a, `Drugs@FDA records matching ${o.path}`)}
|
|
989
|
+
|
|
990
|
+
${JSON.stringify({ matched_via: o.path, ...k(c.results, a, i) }, null, 2)}`
|
|
669
991
|
}
|
|
670
992
|
]
|
|
671
|
-
}
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}, we = [
|
|
996
|
+
"patient.reaction.reactionmeddrapt.exact",
|
|
997
|
+
"patient.reaction.reactionoutcome",
|
|
998
|
+
"serious",
|
|
999
|
+
"patient.patientsex",
|
|
1000
|
+
"occurcountry.exact",
|
|
1001
|
+
"patient.drug.openfda.generic_name.exact"
|
|
1002
|
+
], ke = {
|
|
1003
|
+
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.",
|
|
1005
|
+
// Raw upstream records wrapped in an envelope: declare only the envelope
|
|
1006
|
+
// keys this tool guarantees (matched_via, counted_by, returned, results),
|
|
1007
|
+
// never inner record fields, because those vary per record. There is no
|
|
1008
|
+
// `total` or `limit` here — openFDA omits a total on aggregated
|
|
1009
|
+
// responses, so this tool deliberately does not declare one.
|
|
1010
|
+
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")
|
|
1015
|
+
}),
|
|
1016
|
+
async handler({
|
|
1017
|
+
drugName: e,
|
|
1018
|
+
field: t,
|
|
1019
|
+
limit: n
|
|
1020
|
+
}) {
|
|
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)
|
|
1023
|
+
return {
|
|
1024
|
+
content: [
|
|
1025
|
+
{
|
|
1026
|
+
type: "text",
|
|
1027
|
+
text: `Failed to retrieve adverse event counts for "${e}": ${d.message}`
|
|
1028
|
+
}
|
|
1029
|
+
],
|
|
1030
|
+
isError: !0
|
|
1031
|
+
};
|
|
1032
|
+
if (!(s != null && s.results) || s.results.length === 0)
|
|
1033
|
+
return {
|
|
1034
|
+
content: [
|
|
1035
|
+
{
|
|
1036
|
+
type: "text",
|
|
1037
|
+
text: `No adverse event counts found for "${e}".`
|
|
1038
|
+
}
|
|
1039
|
+
]
|
|
1040
|
+
};
|
|
1041
|
+
const c = {
|
|
1042
|
+
matched_via: G,
|
|
1043
|
+
counted_by: r,
|
|
1044
|
+
returned: s.results.length,
|
|
1045
|
+
results: s.results
|
|
1046
|
+
};
|
|
1047
|
+
return {
|
|
672
1048
|
content: [
|
|
673
1049
|
{
|
|
674
1050
|
type: "text",
|
|
675
|
-
text: `
|
|
1051
|
+
text: `Top ${s.results.length} values of ${r} for "${e}" (aggregated responses carry no result total)
|
|
676
1052
|
|
|
677
|
-
${JSON.stringify(
|
|
1053
|
+
${JSON.stringify(c, null, 2)}`
|
|
678
1054
|
}
|
|
679
1055
|
]
|
|
680
1056
|
};
|
|
681
1057
|
}
|
|
682
|
-
},
|
|
1058
|
+
}, Ee = "1.2.0", Y = new z(
|
|
683
1059
|
{
|
|
684
1060
|
name: "openfda",
|
|
685
|
-
version:
|
|
1061
|
+
version: Ee,
|
|
686
1062
|
description: "OpenFDA Model Context Protocol"
|
|
687
1063
|
},
|
|
688
1064
|
{
|
|
@@ -691,19 +1067,21 @@ ${JSON.stringify(e.results[0], null, 2)}`
|
|
|
691
1067
|
tools: {}
|
|
692
1068
|
}
|
|
693
1069
|
}
|
|
694
|
-
),
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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);
|
|
1076
|
+
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");
|
|
706
1084
|
}
|
|
707
|
-
|
|
708
|
-
console.error("Fatal error in main():",
|
|
1085
|
+
De().catch((e) => {
|
|
1086
|
+
console.error("Fatal error in main():", e), process.exit(1);
|
|
709
1087
|
});
|