@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.
- package/README.md +13 -4
- package/dist/index.js +389 -316
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
var A = (e, t, n) =>
|
|
5
|
-
import { McpServer as
|
|
6
|
-
import { StdioServerTransport as
|
|
7
|
-
import
|
|
8
|
-
const
|
|
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.`,
|
|
11
|
-
function
|
|
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:
|
|
14
|
+
return t ? { ok: !0, apiKey: t } : e.OPENFDA_ALLOW_KEYLESS === "1" ? { ok: !0, apiKey: null } : { ok: !1, message: ne };
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const t =
|
|
18
|
-
t.ok ? t.apiKey === null && console.error(
|
|
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
|
|
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
|
|
31
|
-
return
|
|
32
|
-
content: [{ type: "text", text:
|
|
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
|
|
41
|
-
const t =
|
|
42
|
-
return t.length > 0 ? t :
|
|
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
|
|
44
|
+
function ie(e) {
|
|
45
45
|
return {
|
|
46
|
-
boxed_warning:
|
|
47
|
-
warnings:
|
|
48
|
-
warnings_and_cautions:
|
|
49
|
-
contraindications:
|
|
50
|
-
drug_interactions:
|
|
51
|
-
precautions:
|
|
52
|
-
adverse_reactions:
|
|
53
|
-
overdosage:
|
|
54
|
-
do_not_use:
|
|
55
|
-
ask_doctor:
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
61
|
+
function ce(e) {
|
|
61
62
|
return {
|
|
62
|
-
indications_and_usage:
|
|
63
|
-
boxed_warning:
|
|
64
|
-
warnings:
|
|
65
|
-
warnings_and_cautions:
|
|
66
|
-
do_not_use:
|
|
67
|
-
ask_doctor:
|
|
68
|
-
ask_doctor_or_pharmacist:
|
|
69
|
-
stop_use:
|
|
70
|
-
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
|
|
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"),
|
|
104
|
-
if (!t || !n || !
|
|
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
|
|
107
|
-
|
|
108
|
-
for (const
|
|
109
|
-
const c = this.params.get(
|
|
110
|
-
c !== void 0 && s.set(
|
|
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
|
|
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
|
|
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
|
|
129
|
-
const { maxRetries: n, retryDelay:
|
|
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
|
|
135
|
+
for (let o = 0; o <= n; o++)
|
|
135
136
|
try {
|
|
136
|
-
const c = new AbortController(), l = setTimeout(() => c.abort(), i),
|
|
137
|
-
headers:
|
|
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), !
|
|
141
|
-
const
|
|
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 ${
|
|
144
|
-
status:
|
|
145
|
-
details:
|
|
144
|
+
message: `HTTP ${r.status}: ${r.statusText}`,
|
|
145
|
+
status: r.status,
|
|
146
|
+
details: _
|
|
146
147
|
};
|
|
147
|
-
switch (
|
|
148
|
+
switch (r.status) {
|
|
148
149
|
case 400:
|
|
149
|
-
|
|
150
|
+
m.message = "Bad Request: Invalid search query or parameters";
|
|
150
151
|
break;
|
|
151
152
|
case 401:
|
|
152
|
-
|
|
153
|
+
m.message = "Unauthorized: Invalid or missing API key";
|
|
153
154
|
break;
|
|
154
155
|
case 403:
|
|
155
|
-
|
|
156
|
+
m.message = "Forbidden: API key may be invalid or quota exceeded";
|
|
156
157
|
break;
|
|
157
158
|
case 404:
|
|
158
|
-
|
|
159
|
+
m.message = "Not Found: No results found for the specified query";
|
|
159
160
|
break;
|
|
160
161
|
case 429:
|
|
161
|
-
|
|
162
|
+
m.message = "Rate Limited: Too many requests. Retrying...";
|
|
162
163
|
break;
|
|
163
164
|
case 500:
|
|
164
|
-
|
|
165
|
+
m.message = "Server Error: OpenFDA service is experiencing issues";
|
|
165
166
|
break;
|
|
166
167
|
default:
|
|
167
|
-
|
|
168
|
+
m.message = `HTTP Error ${r.status}: ${r.statusText}`;
|
|
168
169
|
}
|
|
169
|
-
if (s =
|
|
170
|
+
if (s = m, r.status >= 400 && r.status < 500 && r.status !== 429)
|
|
170
171
|
break;
|
|
171
|
-
if (
|
|
172
|
-
const
|
|
173
|
-
await P(
|
|
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
|
|
179
|
+
let h;
|
|
179
180
|
try {
|
|
180
|
-
|
|
181
|
-
} catch (
|
|
181
|
+
h = await r.json();
|
|
182
|
+
} catch (_) {
|
|
182
183
|
s = {
|
|
183
184
|
type: "parsing",
|
|
184
|
-
message:
|
|
185
|
-
`Failed to parse JSON response: ${
|
|
185
|
+
message: I(
|
|
186
|
+
`Failed to parse JSON response: ${_ instanceof Error ? _.message : "Unknown parsing error"}`
|
|
186
187
|
),
|
|
187
|
-
details:
|
|
188
|
+
details: _
|
|
188
189
|
};
|
|
189
190
|
break;
|
|
190
191
|
}
|
|
191
|
-
if (!
|
|
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:
|
|
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:
|
|
212
|
+
message: I(
|
|
212
213
|
`Unexpected error: ${c.message || "Unknown error occurred"}`
|
|
213
214
|
),
|
|
214
215
|
details: c
|
|
215
|
-
}, s = l,
|
|
216
|
-
const
|
|
217
|
-
await P(
|
|
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
|
|
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
|
|
232
|
-
for (const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
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:
|
|
242
|
+
return { found: !0, matched_via: i, data: o };
|
|
240
243
|
}
|
|
241
|
-
return { found: !1, error:
|
|
244
|
+
return { found: !1, error: a };
|
|
242
245
|
}
|
|
243
|
-
const
|
|
246
|
+
const K = (e) => `No label found for "${e}".
|
|
244
247
|
|
|
245
|
-
Searched, in order: ${
|
|
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
|
|
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) ??
|
|
257
|
+
return t(e == null ? void 0 : e.generic_name) ?? t(e == null ? void 0 : e.substance_name) ?? null;
|
|
255
258
|
}
|
|
256
|
-
function
|
|
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
|
|
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
|
|
270
|
+
const de = {
|
|
268
271
|
name: "get-drug-by-name",
|
|
269
|
-
description:
|
|
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:
|
|
288
|
-
drugName:
|
|
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({
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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}": ${
|
|
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:
|
|
314
|
+
content: [{ type: "text", text: K(e) }]
|
|
304
315
|
};
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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: `${
|
|
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(
|
|
340
|
+
${JSON.stringify(u, null, 2)}`
|
|
323
341
|
}
|
|
324
342
|
]
|
|
325
343
|
};
|
|
326
344
|
}
|
|
327
|
-
},
|
|
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:
|
|
336
|
-
genericName:
|
|
337
|
-
limit:
|
|
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,
|
|
344
|
-
const n = new
|
|
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 (!(
|
|
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
|
|
365
|
-
var
|
|
382
|
+
const u = a.results.map((r) => {
|
|
383
|
+
var h, _, m;
|
|
366
384
|
return {
|
|
367
|
-
brand_name: ((
|
|
368
|
-
generic_name: (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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: `${
|
|
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(
|
|
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
|
-
},
|
|
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
|
|
413
|
+
function pe(e) {
|
|
394
414
|
if (e == null || e === "") return "Not reported";
|
|
395
415
|
const t = String(e);
|
|
396
|
-
return
|
|
416
|
+
return G[t] ?? `Unrecognized outcome code "${t}"`;
|
|
397
417
|
}
|
|
398
|
-
const
|
|
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
|
-
],
|
|
403
|
-
function
|
|
404
|
-
return
|
|
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
|
|
447
|
+
function _e(e) {
|
|
409
448
|
const t = /* @__PURE__ */ new Set(), n = [];
|
|
410
|
-
for (const
|
|
411
|
-
const i =
|
|
449
|
+
for (const a of e) {
|
|
450
|
+
const i = a == null ? void 0 : a.reactionmeddrapt;
|
|
412
451
|
if (!i) continue;
|
|
413
|
-
const
|
|
414
|
-
t.has(
|
|
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,
|
|
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:
|
|
436
|
-
drugName:
|
|
437
|
-
limit:
|
|
438
|
-
seriousness:
|
|
439
|
-
skip:
|
|
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:
|
|
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:
|
|
489
|
+
skip: a,
|
|
451
490
|
sort: i
|
|
452
491
|
}) {
|
|
453
|
-
var
|
|
454
|
-
if (
|
|
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 ${
|
|
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
|
|
465
|
-
n !== "all" && (
|
|
466
|
-
const s = new
|
|
467
|
-
|
|
468
|
-
const
|
|
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
|
|
489
|
-
var
|
|
490
|
-
const
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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:
|
|
499
|
-
outcomes:
|
|
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
|
-
}),
|
|
503
|
-
|
|
504
|
-
|
|
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:
|
|
508
|
-
...
|
|
509
|
-
...
|
|
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: `${
|
|
555
|
+
text: `${_}
|
|
516
556
|
|
|
517
|
-
${JSON.stringify(
|
|
557
|
+
${JSON.stringify(m, null, 2)}`
|
|
518
558
|
}
|
|
519
559
|
]
|
|
520
560
|
};
|
|
521
561
|
}
|
|
522
|
-
},
|
|
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:
|
|
536
|
-
manufacturerName:
|
|
537
|
-
limit:
|
|
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,
|
|
544
|
-
const n = new
|
|
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 (!(
|
|
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
|
|
565
|
-
var
|
|
604
|
+
const u = a.results.map((r) => {
|
|
605
|
+
var h, _, m;
|
|
566
606
|
return {
|
|
567
|
-
brand_name: ((
|
|
568
|
-
generic_name: (
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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: `${
|
|
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", ...
|
|
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
|
-
},
|
|
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:
|
|
606
|
-
drugName:
|
|
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:
|
|
664
|
+
content: [{ type: "text", text: K(e) }]
|
|
622
665
|
};
|
|
623
|
-
const n = t.data.results[0],
|
|
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:
|
|
668
|
+
generic_name: O(
|
|
626
669
|
(n == null ? void 0 : n.openfda) ?? {}
|
|
627
670
|
),
|
|
628
671
|
matched_via: t.matched_via,
|
|
629
|
-
...
|
|
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(
|
|
680
|
+
${JSON.stringify(a, null, 2)}`
|
|
638
681
|
}
|
|
639
682
|
]
|
|
640
683
|
};
|
|
641
684
|
}
|
|
642
|
-
},
|
|
643
|
-
function
|
|
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
|
|
688
|
+
let a, i = null;
|
|
646
689
|
if (t.includes("-")) {
|
|
647
|
-
const
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
else if (
|
|
651
|
-
const s =
|
|
652
|
-
if (!s || !
|
|
653
|
-
|
|
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
|
-
|
|
701
|
+
a = `${t.slice(0, 5)}-${t.slice(5, 9)}`, i = `${a}-${t.slice(9, 11)}`;
|
|
659
702
|
else if (t.length === 9)
|
|
660
|
-
|
|
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
|
|
708
|
+
return ye.test(a) ? { productNDC: a, packageNDC: i, isValid: !0 } : n;
|
|
666
709
|
}
|
|
667
|
-
const
|
|
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
|
-
`),
|
|
721
|
+
`), z = (e, t) => `Invalid ${t} format: "${e}"
|
|
679
722
|
|
|
680
|
-
${
|
|
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:
|
|
694
|
-
ndcCode:
|
|
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
|
|
700
|
-
const { productNDC: t, packageNDC: n, isValid:
|
|
701
|
-
if (!
|
|
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:
|
|
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
|
|
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 (!(
|
|
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 =
|
|
739
|
-
var
|
|
740
|
-
const
|
|
741
|
-
(
|
|
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:
|
|
751
|
-
matching_package_ndc:
|
|
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
|
-
}),
|
|
760
|
-
(p,
|
|
802
|
+
}), r = l.reduce(
|
|
803
|
+
(p, k) => p + k.matching_package_ndc.length,
|
|
761
804
|
0
|
|
762
|
-
),
|
|
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: `${
|
|
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
|
-
${
|
|
812
|
+
${h}
|
|
770
813
|
|
|
771
|
-
${JSON.stringify({ matched_via:
|
|
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
|
-
},
|
|
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:
|
|
796
|
-
productNDC:
|
|
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 } =
|
|
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:
|
|
851
|
+
text: z(e, "product NDC")
|
|
809
852
|
}
|
|
810
853
|
],
|
|
811
854
|
isError: !0
|
|
812
855
|
};
|
|
813
|
-
const
|
|
814
|
-
if (
|
|
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}": ${
|
|
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],
|
|
835
|
-
(
|
|
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:
|
|
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 ${
|
|
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
|
-
},
|
|
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
|
-
},
|
|
910
|
-
function
|
|
911
|
-
const n =
|
|
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: ${
|
|
958
|
+
message: `Unknown section "${e}". Valid sections: ${T.join(", ")}.`
|
|
916
959
|
};
|
|
917
|
-
const
|
|
918
|
-
return
|
|
960
|
+
const a = n.fields[t];
|
|
961
|
+
return a ? {
|
|
919
962
|
ok: !0,
|
|
920
963
|
path: n.prefix ? `${n.prefix}.${t}` : t,
|
|
921
|
-
uppercase:
|
|
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
|
|
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:
|
|
941
|
-
sectionName:
|
|
942
|
-
fieldName:
|
|
943
|
-
`Field within the section. ${
|
|
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:
|
|
1006
|
+
searchValue: d.string().describe(
|
|
946
1007
|
"Value to search for. Sponsor names are stored uppercase and are normalised automatically."
|
|
947
1008
|
),
|
|
948
|
-
limit:
|
|
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:
|
|
1018
|
+
limit: a,
|
|
1019
|
+
detail: i
|
|
955
1020
|
}) {
|
|
956
|
-
var g,
|
|
957
|
-
const
|
|
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
|
|
964
|
-
if (
|
|
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 "${
|
|
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 (!(
|
|
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 "${
|
|
1044
|
+
text: `No Drugs@FDA records found for "${c}" in ${o.path}.`
|
|
980
1045
|
}
|
|
981
1046
|
]
|
|
982
1047
|
};
|
|
983
|
-
const
|
|
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: `${
|
|
1055
|
+
text: `${w(r.results.length, _, `Drugs@FDA records matching ${o.path}`)}
|
|
989
1056
|
|
|
990
|
-
${JSON.stringify({ matched_via: o.path, ...
|
|
1057
|
+
${JSON.stringify({ matched_via: o.path, ...S(m, _, u) }, null, 2)}`
|
|
991
1058
|
}
|
|
992
1059
|
]
|
|
993
1060
|
};
|
|
994
1061
|
}
|
|
995
|
-
},
|
|
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
|
-
],
|
|
1069
|
+
], Fe = {
|
|
1003
1070
|
name: "get-drug-adverse-event-counts",
|
|
1004
|
-
description:
|
|
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:
|
|
1012
|
-
drugName:
|
|
1013
|
-
field:
|
|
1014
|
-
limit:
|
|
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
|
|
1022
|
-
if (
|
|
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}": ${
|
|
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:
|
|
1043
|
-
counted_by:
|
|
1109
|
+
matched_via: J,
|
|
1110
|
+
counted_by: a,
|
|
1044
1111
|
returned: s.results.length,
|
|
1045
|
-
|
|
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 ${
|
|
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
|
-
},
|
|
1131
|
+
}, Te = "1.3.0", H = new ee(
|
|
1059
1132
|
{
|
|
1060
1133
|
name: "openfda",
|
|
1061
|
-
version:
|
|
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
|
|
1071
|
-
b.registerTool(
|
|
1072
|
-
b.registerTool(
|
|
1073
|
-
b.registerTool(
|
|
1074
|
-
b.registerTool(
|
|
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(
|
|
1078
|
-
b.registerTool(
|
|
1079
|
-
b.registerTool(
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
-
|
|
1158
|
+
Ce().catch((e) => {
|
|
1086
1159
|
console.error("Fatal error in main():", e), process.exit(1);
|
|
1087
1160
|
});
|