@ythalorossy/openfda 1.0.18 → 1.1.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 +101 -94
- package/dist/index.js +506 -345
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,14 +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 M = Object.defineProperty;
|
|
3
|
+
var G = (e, t, n) => t in e ? M(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
|
|
4
|
+
var D = (e, t, n) => G(e, typeof t != "symbol" ? t + "" : t, n);
|
|
5
|
+
import { McpServer as K } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport as L } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import d from "zod";
|
|
8
|
+
const j = `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.`, q = "openfda: OPENFDA_ALLOW_KEYLESS=1 — using the unauthenticated tier (40 requests/minute, no rate-limit visibility). Set OPENFDA_API_KEY for 240 requests/minute.", Y = "openfda: OPENFDA_API_KEY is not set; every tool call will return a configuration error. See https://open.fda.gov/apis/authentication/";
|
|
11
|
+
function E(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: j };
|
|
15
|
+
}
|
|
16
|
+
function B(e = process.env) {
|
|
17
|
+
const t = E(e);
|
|
18
|
+
t.ok ? t.apiKey === null && console.error(q) : console.error(Y);
|
|
19
|
+
}
|
|
20
|
+
class J {
|
|
21
|
+
constructor(t) {
|
|
22
|
+
D(this, "registerTool", (t) => this.server.registerTool(
|
|
23
|
+
t.name,
|
|
24
|
+
{
|
|
25
|
+
title: t.name,
|
|
26
|
+
description: t.description,
|
|
27
|
+
inputSchema: t.inputSchema
|
|
28
|
+
},
|
|
29
|
+
async (n) => {
|
|
30
|
+
const a = E();
|
|
31
|
+
return a.ok ? t.handler(n) : {
|
|
32
|
+
content: [{ type: "text", text: a.message }],
|
|
33
|
+
isError: !0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
));
|
|
37
|
+
this.server = t;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const m = (e) => Array.isArray(e) ? e : [], P = (e) => {
|
|
41
|
+
const t = m(e.warnings);
|
|
42
|
+
return t.length > 0 ? t : m(e.warnings_and_cautions);
|
|
43
|
+
};
|
|
44
|
+
function W(e) {
|
|
45
|
+
return {
|
|
46
|
+
boxed_warning: m(e.boxed_warning),
|
|
47
|
+
warnings: P(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 V(e) {
|
|
61
|
+
return {
|
|
62
|
+
indications_and_usage: m(e.indications_and_usage),
|
|
63
|
+
boxed_warning: m(e.boxed_warning),
|
|
64
|
+
warnings: P(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 b {
|
|
9
74
|
constructor() {
|
|
10
|
-
|
|
11
|
-
|
|
75
|
+
D(this, "urlBase", "https://api.fda.gov");
|
|
76
|
+
D(this, "params", /* @__PURE__ */ new Map());
|
|
12
77
|
}
|
|
13
78
|
dataset(t) {
|
|
14
79
|
return this.params.set("dataset", t), this;
|
|
@@ -23,230 +88,179 @@ class g {
|
|
|
23
88
|
return this.params.set("limit", t), this;
|
|
24
89
|
}
|
|
25
90
|
build() {
|
|
26
|
-
const t = this.params.get("dataset"),
|
|
27
|
-
if (!t || !
|
|
91
|
+
const t = this.params.get("dataset"), n = this.params.get("context"), a = this.params.get("search"), i = this.params.get("limit") ?? 1;
|
|
92
|
+
if (!t || !n || !a)
|
|
28
93
|
throw new Error("Missing required parameters: context or search");
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
class T {
|
|
33
|
-
constructor(t) {
|
|
34
|
-
k(this, "registerTool", (t) => this.server.registerTool(
|
|
35
|
-
t.name,
|
|
36
|
-
{
|
|
37
|
-
title: t.name,
|
|
38
|
-
description: t.description,
|
|
39
|
-
inputSchema: t.inputSchema
|
|
40
|
-
},
|
|
41
|
-
t.handler
|
|
42
|
-
));
|
|
43
|
-
this.server = t;
|
|
94
|
+
const o = E(), s = new URLSearchParams();
|
|
95
|
+
return o.ok && o.apiKey && s.set("api_key", o.apiKey), s.set("search", String(a)), s.set("limit", String(i)), `${this.urlBase}/${t}/${n}.json?${s}`;
|
|
44
96
|
}
|
|
45
97
|
}
|
|
46
|
-
const
|
|
98
|
+
const C = (e) => e.replace(/([?&]api_key=)[^&\s]*/gi, "$1<REDACTED>"), z = {
|
|
47
99
|
maxRetries: 3,
|
|
48
100
|
retryDelay: 1e3,
|
|
49
101
|
// 1 second
|
|
50
102
|
timeout: 3e4
|
|
51
103
|
// 30 seconds
|
|
52
104
|
};
|
|
53
|
-
function
|
|
54
|
-
return !!(
|
|
105
|
+
function T(e) {
|
|
106
|
+
return !!(e.name === "TypeError" && e.message.includes("fetch") || e.name === "AbortError" || e.status >= 500 && e.status <= 599 || e.status === 429);
|
|
55
107
|
}
|
|
56
|
-
function
|
|
57
|
-
return new Promise((t) => setTimeout(t,
|
|
108
|
+
function F(e) {
|
|
109
|
+
return new Promise((t) => setTimeout(t, e));
|
|
58
110
|
}
|
|
59
|
-
async function
|
|
60
|
-
const { maxRetries:
|
|
111
|
+
async function k(e, t = {}) {
|
|
112
|
+
const { maxRetries: n, retryDelay: a, timeout: i } = { ...z, ...t }, o = {
|
|
61
113
|
"User-Agent": "@ythalorossy/openfda",
|
|
62
114
|
Accept: "application/json"
|
|
63
115
|
};
|
|
64
|
-
let
|
|
65
|
-
for (let p = 0; p <=
|
|
116
|
+
let s = null;
|
|
117
|
+
for (let p = 0; p <= n; p++)
|
|
66
118
|
try {
|
|
67
|
-
const
|
|
68
|
-
headers:
|
|
69
|
-
signal:
|
|
119
|
+
const c = new AbortController(), f = setTimeout(() => c.abort(), i), r = await fetch(e, {
|
|
120
|
+
headers: o,
|
|
121
|
+
signal: c.signal
|
|
70
122
|
});
|
|
71
|
-
if (clearTimeout(
|
|
72
|
-
const
|
|
123
|
+
if (clearTimeout(f), !r.ok) {
|
|
124
|
+
const u = await r.text().catch(() => "Unable to read error response"), l = {
|
|
73
125
|
type: "http",
|
|
74
|
-
message: `HTTP ${
|
|
75
|
-
status:
|
|
76
|
-
details:
|
|
126
|
+
message: `HTTP ${r.status}: ${r.statusText}`,
|
|
127
|
+
status: r.status,
|
|
128
|
+
details: u
|
|
77
129
|
};
|
|
78
|
-
switch (
|
|
130
|
+
switch (r.status) {
|
|
79
131
|
case 400:
|
|
80
|
-
|
|
132
|
+
l.message = "Bad Request: Invalid search query or parameters";
|
|
81
133
|
break;
|
|
82
134
|
case 401:
|
|
83
|
-
|
|
135
|
+
l.message = "Unauthorized: Invalid or missing API key";
|
|
84
136
|
break;
|
|
85
137
|
case 403:
|
|
86
|
-
|
|
138
|
+
l.message = "Forbidden: API key may be invalid or quota exceeded";
|
|
87
139
|
break;
|
|
88
140
|
case 404:
|
|
89
|
-
|
|
141
|
+
l.message = "Not Found: No results found for the specified query";
|
|
90
142
|
break;
|
|
91
143
|
case 429:
|
|
92
|
-
|
|
144
|
+
l.message = "Rate Limited: Too many requests. Retrying...";
|
|
93
145
|
break;
|
|
94
146
|
case 500:
|
|
95
|
-
|
|
147
|
+
l.message = "Server Error: OpenFDA service is experiencing issues";
|
|
96
148
|
break;
|
|
97
149
|
default:
|
|
98
|
-
|
|
150
|
+
l.message = `HTTP Error ${r.status}: ${r.statusText}`;
|
|
99
151
|
}
|
|
100
|
-
if (
|
|
152
|
+
if (s = l, r.status >= 400 && r.status < 500 && r.status !== 429)
|
|
101
153
|
break;
|
|
102
|
-
if (p <
|
|
103
|
-
const h =
|
|
104
|
-
await
|
|
154
|
+
if (p < n && T({ status: r.status })) {
|
|
155
|
+
const h = a * Math.pow(2, p);
|
|
156
|
+
await F(h);
|
|
105
157
|
continue;
|
|
106
158
|
}
|
|
107
159
|
break;
|
|
108
160
|
}
|
|
109
|
-
let
|
|
161
|
+
let g;
|
|
110
162
|
try {
|
|
111
|
-
|
|
112
|
-
} catch (
|
|
113
|
-
|
|
163
|
+
g = await r.json();
|
|
164
|
+
} catch (u) {
|
|
165
|
+
s = {
|
|
114
166
|
type: "parsing",
|
|
115
|
-
message:
|
|
116
|
-
|
|
167
|
+
message: C(
|
|
168
|
+
`Failed to parse JSON response: ${u instanceof Error ? u.message : "Unknown parsing error"}`
|
|
169
|
+
),
|
|
170
|
+
details: u
|
|
117
171
|
};
|
|
118
172
|
break;
|
|
119
173
|
}
|
|
120
|
-
if (!
|
|
121
|
-
|
|
174
|
+
if (!g) {
|
|
175
|
+
s = {
|
|
122
176
|
type: "empty_response",
|
|
123
177
|
message: "Received empty response from OpenFDA API"
|
|
124
178
|
};
|
|
125
179
|
break;
|
|
126
180
|
}
|
|
127
|
-
return { data:
|
|
128
|
-
} catch (
|
|
129
|
-
let
|
|
130
|
-
if (
|
|
181
|
+
return { data: g, error: null };
|
|
182
|
+
} catch (c) {
|
|
183
|
+
let f;
|
|
184
|
+
if (c.name === "AbortError" ? f = {
|
|
131
185
|
type: "timeout",
|
|
132
|
-
message: `Request timeout after ${
|
|
133
|
-
details:
|
|
134
|
-
} :
|
|
186
|
+
message: `Request timeout after ${i}ms`,
|
|
187
|
+
details: c
|
|
188
|
+
} : c instanceof TypeError && c.message.includes("fetch") ? f = {
|
|
135
189
|
type: "network",
|
|
136
190
|
message: "Network error: Unable to connect to OpenFDA API",
|
|
137
|
-
details:
|
|
138
|
-
} :
|
|
191
|
+
details: c.message
|
|
192
|
+
} : f = {
|
|
139
193
|
type: "unknown",
|
|
140
|
-
message:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
194
|
+
message: C(
|
|
195
|
+
`Unexpected error: ${c.message || "Unknown error occurred"}`
|
|
196
|
+
),
|
|
197
|
+
details: c
|
|
198
|
+
}, s = f, p < n && T(c)) {
|
|
199
|
+
const r = a * Math.pow(2, p);
|
|
200
|
+
await F(r);
|
|
145
201
|
continue;
|
|
146
202
|
}
|
|
147
203
|
break;
|
|
148
204
|
}
|
|
149
|
-
return { data: null, error:
|
|
205
|
+
return { data: null, error: s };
|
|
150
206
|
}
|
|
151
|
-
const X =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
207
|
+
const X = [
|
|
208
|
+
"openfda.brand_name",
|
|
209
|
+
"openfda.generic_name",
|
|
210
|
+
"openfda.substance_name",
|
|
211
|
+
"spl_product_data_elements"
|
|
212
|
+
];
|
|
213
|
+
async function O(e, t = 1) {
|
|
214
|
+
let n;
|
|
215
|
+
for (const a of X) {
|
|
216
|
+
const i = new b().dataset("drug").context("label").search(`${a}:"${e}"`).limit(t).build(), { data: o, error: s } = await k(i);
|
|
217
|
+
if (s) {
|
|
218
|
+
n = s;
|
|
219
|
+
continue;
|
|
161
220
|
}
|
|
221
|
+
if (o != null && o.results && o.results.length > 0)
|
|
222
|
+
return { found: !0, matched_via: a, data: o };
|
|
162
223
|
}
|
|
163
|
-
|
|
164
|
-
function E(n) {
|
|
165
|
-
const t = n.trim().toUpperCase();
|
|
166
|
-
let r, s = null;
|
|
167
|
-
if (t.includes("-")) {
|
|
168
|
-
const i = t.split("-");
|
|
169
|
-
if (i.length === 2)
|
|
170
|
-
r = t, s = null;
|
|
171
|
-
else if (i.length === 3)
|
|
172
|
-
r = `${i[0]}-${i[1]}`, s = t;
|
|
173
|
-
else
|
|
174
|
-
return { productNDC: t, packageNDC: null, isValid: !1 };
|
|
175
|
-
} else if (t.length === 11)
|
|
176
|
-
r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = `${t.substring(0, 5)}-${t.substring(5, 9)}-${t.substring(9, 11)}`;
|
|
177
|
-
else if (t.length === 9)
|
|
178
|
-
r = `${t.substring(0, 5)}-${t.substring(5, 9)}`, s = null;
|
|
179
|
-
else
|
|
180
|
-
return { productNDC: t, packageNDC: null, isValid: !1 };
|
|
181
|
-
const e = /^\d{5}-\d{4}$/.test(r);
|
|
182
|
-
return { productNDC: r, packageNDC: s, isValid: e };
|
|
224
|
+
return { found: !1, error: n };
|
|
183
225
|
}
|
|
184
|
-
|
|
185
|
-
name: "get-drug-by-name",
|
|
186
|
-
description: "Get drug by name. Use this tool to get the drug information by name. The drug name should be the brand name. It returns the brand name, generic name, manufacturer name, product NDC, product type, route, substance name, indications and usage, warnings, do not use, ask doctor, ask doctor or pharmacist, stop use, pregnancy or breast feeding.",
|
|
187
|
-
inputSchema: l.object({
|
|
188
|
-
drugName: l.string().describe("Drug name")
|
|
189
|
-
}),
|
|
190
|
-
handler: async ({ drugName: n }) => {
|
|
191
|
-
const t = new g().dataset("drug").context("label").search(`openfda.brand_name:"${n}"`).limit(1).build(), { data: r, error: s } = await y(t);
|
|
192
|
-
if (s) {
|
|
193
|
-
let a = `Failed to retrieve drug data for "${n}": ${s.message}`;
|
|
194
|
-
switch (s.type) {
|
|
195
|
-
case "http":
|
|
196
|
-
s.status === 404 ? a += `${t}
|
|
197
|
-
|
|
198
|
-
Suggestions:
|
|
199
|
-
- Verify the exact brand name spelling
|
|
200
|
-
- Try searching for the generic name instead
|
|
201
|
-
- Check if the drug is FDA-approved` : (s.status === 401 || s.status === 403) && (a += `
|
|
226
|
+
const U = (e) => `No label found for "${e}".
|
|
202
227
|
|
|
203
|
-
|
|
204
|
-
break;
|
|
205
|
-
case "network":
|
|
206
|
-
a += `
|
|
207
|
-
|
|
208
|
-
Please check your internet connection and try again.`;
|
|
209
|
-
break;
|
|
210
|
-
case "timeout":
|
|
211
|
-
a += `
|
|
228
|
+
Searched, in order: ${X.join(", ")}.
|
|
212
229
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
Suggestions:
|
|
231
|
+
- Check the spelling.
|
|
232
|
+
- Try the generic name instead of the brand (e.g. "amiodarone" rather than "Cordarone").
|
|
233
|
+
- Try the originator brand rather than a repackager's name.
|
|
234
|
+
- Some discontinued brands have no current FDA label.`, H = {
|
|
235
|
+
name: "get-drug-by-name",
|
|
236
|
+
description: "Get drug information by brand name, generic name, or active substance. Returns the brand name, generic name, manufacturer name, product NDC, product type, route, substance name, indications and usage, warnings, do not use, ask doctor, ask doctor or pharmacist, stop use, pregnancy or breast feeding. The response reports which field matched via matched_via.",
|
|
237
|
+
inputSchema: d.object({
|
|
238
|
+
drugName: d.string().describe("Drug name")
|
|
239
|
+
}),
|
|
240
|
+
async handler({ drugName: e }) {
|
|
241
|
+
const t = await O(e, 1);
|
|
242
|
+
if (!t.found)
|
|
243
|
+
return t.error ? {
|
|
217
244
|
content: [
|
|
218
245
|
{
|
|
219
246
|
type: "text",
|
|
220
|
-
text:
|
|
247
|
+
text: `Failed to retrieve drug data for "${e}": ${t.error.message}`
|
|
221
248
|
}
|
|
222
249
|
],
|
|
223
250
|
isError: !0
|
|
251
|
+
} : {
|
|
252
|
+
content: [{ type: "text", text: U(e) }]
|
|
224
253
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const e = r.results[0], i = {
|
|
236
|
-
brand_name: e == null ? void 0 : e.openfda.brand_name,
|
|
237
|
-
generic_name: e == null ? void 0 : e.openfda.generic_name,
|
|
238
|
-
manufacturer_name: e == null ? void 0 : e.openfda.manufacturer_name,
|
|
239
|
-
product_ndc: e == null ? void 0 : e.openfda.product_ndc,
|
|
240
|
-
product_type: e == null ? void 0 : e.openfda.product_type,
|
|
241
|
-
route: e == null ? void 0 : e.openfda.route,
|
|
242
|
-
substance_name: e == null ? void 0 : e.openfda.substance_name,
|
|
243
|
-
indications_and_usage: e == null ? void 0 : e.indications_and_usage,
|
|
244
|
-
warnings: e == null ? void 0 : e.warnings,
|
|
245
|
-
do_not_use: e == null ? void 0 : e.do_not_use,
|
|
246
|
-
ask_doctor: e == null ? void 0 : e.ask_doctor,
|
|
247
|
-
ask_doctor_or_pharmacist: e == null ? void 0 : e.ask_doctor_or_pharmacist,
|
|
248
|
-
stop_use: e == null ? void 0 : e.stop_use,
|
|
249
|
-
pregnancy_or_breast_feeding: e == null ? void 0 : e.pregnancy_or_breast_feeding
|
|
254
|
+
const n = t.data.results[0], a = {
|
|
255
|
+
brand_name: n == null ? void 0 : n.openfda.brand_name,
|
|
256
|
+
generic_name: n == null ? void 0 : n.openfda.generic_name,
|
|
257
|
+
manufacturer_name: n == null ? void 0 : n.openfda.manufacturer_name,
|
|
258
|
+
product_ndc: n == null ? void 0 : n.openfda.product_ndc,
|
|
259
|
+
product_type: n == null ? void 0 : n.openfda.product_type,
|
|
260
|
+
route: n == null ? void 0 : n.openfda.route,
|
|
261
|
+
substance_name: n == null ? void 0 : n.openfda.substance_name,
|
|
262
|
+
matched_via: t.matched_via,
|
|
263
|
+
...V(n)
|
|
250
264
|
};
|
|
251
265
|
return {
|
|
252
266
|
content: [
|
|
@@ -254,238 +268,294 @@ The request took too long. Please try again.`;
|
|
|
254
268
|
type: "text",
|
|
255
269
|
text: `Drug information retrieved successfully:
|
|
256
270
|
|
|
257
|
-
${JSON.stringify(
|
|
271
|
+
${JSON.stringify(a, null, 2)}`
|
|
258
272
|
}
|
|
259
273
|
]
|
|
260
274
|
};
|
|
261
275
|
}
|
|
262
|
-
}
|
|
263
|
-
|
|
276
|
+
};
|
|
277
|
+
function N(e, t, n) {
|
|
278
|
+
return typeof t != "number" ? `Showing ${e} ${n} (total unknown)` : e >= t ? `Showing all ${t} ${n}` : `Showing ${e} of ${t} ${n}`;
|
|
279
|
+
}
|
|
280
|
+
function S(e, t, n) {
|
|
281
|
+
return {
|
|
282
|
+
total: typeof t == "number" ? t : null,
|
|
283
|
+
returned: e.length,
|
|
284
|
+
limit: n,
|
|
285
|
+
results: e
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const Q = {
|
|
264
289
|
name: "get-drug-by-generic-name",
|
|
265
290
|
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.",
|
|
266
|
-
inputSchema:
|
|
267
|
-
genericName:
|
|
268
|
-
limit:
|
|
291
|
+
inputSchema: d.object({
|
|
292
|
+
genericName: d.string().describe("Generic drug name (active ingredient)"),
|
|
293
|
+
limit: d.number().optional().default(5).describe("Maximum number of results to return")
|
|
269
294
|
}),
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
295
|
+
async handler({
|
|
296
|
+
genericName: e,
|
|
297
|
+
limit: t
|
|
298
|
+
}) {
|
|
299
|
+
var s, p, c, f;
|
|
300
|
+
const n = new b().dataset("drug").context("label").search(`openfda.generic_name:"${e}"`).limit(t).build(), { data: a, error: i } = await k(n);
|
|
301
|
+
if (i)
|
|
273
302
|
return {
|
|
274
303
|
content: [
|
|
275
304
|
{
|
|
276
305
|
type: "text",
|
|
277
|
-
text: `Failed to retrieve drug data for generic name "${
|
|
306
|
+
text: `Failed to retrieve drug data for generic name "${e}": ${i.message}`
|
|
278
307
|
}
|
|
279
308
|
],
|
|
280
309
|
isError: !0
|
|
281
310
|
};
|
|
282
|
-
if (!(
|
|
311
|
+
if (!(a != null && a.results) || a.results.length === 0)
|
|
283
312
|
return {
|
|
284
313
|
content: [
|
|
285
314
|
{
|
|
286
315
|
type: "text",
|
|
287
|
-
text: `No drug information found for generic name "${
|
|
316
|
+
text: `No drug information found for generic name "${e}".`
|
|
288
317
|
}
|
|
289
318
|
]
|
|
290
319
|
};
|
|
291
|
-
const
|
|
292
|
-
var
|
|
320
|
+
const o = a.results.map((r) => {
|
|
321
|
+
var g, u, l, h;
|
|
293
322
|
return {
|
|
294
|
-
brand_name: ((
|
|
295
|
-
generic_name: ((
|
|
296
|
-
manufacturer_name: ((
|
|
297
|
-
product_type: ((
|
|
298
|
-
route: (
|
|
323
|
+
brand_name: ((g = r == null ? void 0 : r.openfda.brand_name) == null ? void 0 : g[0]) || "Unknown",
|
|
324
|
+
generic_name: ((u = r == null ? void 0 : r.openfda.generic_name) == null ? void 0 : u[0]) || "Unknown",
|
|
325
|
+
manufacturer_name: ((l = r == null ? void 0 : r.openfda.manufacturer_name) == null ? void 0 : l[0]) || "Unknown",
|
|
326
|
+
product_type: ((h = r == null ? void 0 : r.openfda.product_type) == null ? void 0 : h[0]) || "Unknown",
|
|
327
|
+
route: (r == null ? void 0 : r.openfda.route) || []
|
|
299
328
|
};
|
|
300
329
|
});
|
|
301
330
|
return {
|
|
302
331
|
content: [
|
|
303
332
|
{
|
|
304
333
|
type: "text",
|
|
305
|
-
text:
|
|
334
|
+
text: `${N(o.length, (p = (s = a.meta) == null ? void 0 : s.results) == null ? void 0 : p.total, `labels with generic name "${e}"`)}
|
|
306
335
|
|
|
307
|
-
${JSON.stringify(
|
|
336
|
+
${JSON.stringify(S(o, (f = (c = a.meta) == null ? void 0 : c.results) == null ? void 0 : f.total, t ?? 5), null, 2)}`
|
|
308
337
|
}
|
|
309
338
|
]
|
|
310
339
|
};
|
|
311
340
|
}
|
|
312
|
-
}
|
|
313
|
-
|
|
341
|
+
}, Z = {
|
|
342
|
+
1: "Recovered/resolved",
|
|
343
|
+
2: "Recovering/resolving",
|
|
344
|
+
3: "Not recovered/not resolved",
|
|
345
|
+
4: "Recovered/resolved with sequelae",
|
|
346
|
+
5: "Fatal",
|
|
347
|
+
6: "Unknown"
|
|
348
|
+
};
|
|
349
|
+
function ee(e) {
|
|
350
|
+
if (e == null || e === "") return "Not reported";
|
|
351
|
+
const t = String(e);
|
|
352
|
+
return Z[t] ?? `Unrecognized outcome code "${t}"`;
|
|
353
|
+
}
|
|
354
|
+
function te(e) {
|
|
355
|
+
const t = /* @__PURE__ */ new Set(), n = [];
|
|
356
|
+
for (const a of e) {
|
|
357
|
+
const i = a == null ? void 0 : a.reactionmeddrapt;
|
|
358
|
+
if (!i) continue;
|
|
359
|
+
const o = `${i}\0${String(a == null ? void 0 : a.reactionoutcome)}`;
|
|
360
|
+
t.has(o) || (t.add(o), n.push({ reaction: i, outcome: a == null ? void 0 : a.reactionoutcome }));
|
|
361
|
+
}
|
|
362
|
+
return n.slice(0, 3);
|
|
363
|
+
}
|
|
364
|
+
const ne = {
|
|
314
365
|
name: "get-drug-adverse-events",
|
|
315
366
|
description: "Get adverse event reports for a drug. This provides safety information about reported side effects and reactions. Use brand name or generic name.",
|
|
316
|
-
inputSchema:
|
|
317
|
-
drugName:
|
|
318
|
-
limit:
|
|
319
|
-
seriousness:
|
|
367
|
+
inputSchema: d.object({
|
|
368
|
+
drugName: d.string().describe("Drug name (brand or generic)"),
|
|
369
|
+
limit: d.number().optional().default(10).describe("Maximum number of events to return"),
|
|
370
|
+
seriousness: d.enum(["serious", "non-serious", "all"]).optional().default("all").describe("Filter by event seriousness")
|
|
320
371
|
}),
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
372
|
+
async handler({
|
|
373
|
+
drugName: e,
|
|
374
|
+
limit: t,
|
|
375
|
+
seriousness: n
|
|
376
|
+
}) {
|
|
377
|
+
var c, f, r, g;
|
|
378
|
+
let a = `patient.drug.medicinalproduct:"${e}"`;
|
|
379
|
+
n !== "all" && (a += ` AND serious:${n === "serious" ? "1" : "2"}`);
|
|
380
|
+
const i = new b().dataset("drug").context("event").search(a).limit(t).build(), { data: o, error: s } = await k(i);
|
|
381
|
+
if (s)
|
|
326
382
|
return {
|
|
327
383
|
content: [
|
|
328
384
|
{
|
|
329
385
|
type: "text",
|
|
330
|
-
text: `Failed to retrieve adverse events for "${
|
|
386
|
+
text: `Failed to retrieve adverse events for "${e}": ${s.message}`
|
|
331
387
|
}
|
|
332
388
|
],
|
|
333
389
|
isError: !0
|
|
334
390
|
};
|
|
335
|
-
if (!(
|
|
391
|
+
if (!(o != null && o.results) || o.results.length === 0)
|
|
336
392
|
return {
|
|
337
393
|
content: [
|
|
338
394
|
{
|
|
339
395
|
type: "text",
|
|
340
|
-
text: `No adverse events found for "${
|
|
396
|
+
text: `No adverse events found for "${e}".`
|
|
341
397
|
}
|
|
342
398
|
]
|
|
343
399
|
};
|
|
344
|
-
const p =
|
|
345
|
-
var
|
|
400
|
+
const p = o.results.map((u) => {
|
|
401
|
+
var h, _, $, v;
|
|
402
|
+
const l = te(((h = u.patient) == null ? void 0 : h.reaction) ?? []);
|
|
346
403
|
return {
|
|
347
|
-
report_id:
|
|
348
|
-
serious:
|
|
349
|
-
patient_age: ((
|
|
350
|
-
patient_sex: ((
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
404
|
+
report_id: u.safetyreportid,
|
|
405
|
+
serious: u.serious === "1" ? "Yes" : "No",
|
|
406
|
+
patient_age: ((_ = u.patient) == null ? void 0 : _.patientonsetage) || "Unknown",
|
|
407
|
+
patient_sex: (($ = u.patient) == null ? void 0 : $.patientsex) === "1" ? "Male" : ((v = u.patient) == null ? void 0 : v.patientsex) === "2" ? "Female" : "Unknown",
|
|
408
|
+
// Derived from the same deduped pairs so the two arrays stay the
|
|
409
|
+
// same length and positionally aligned (see dedupeReactionPairs).
|
|
410
|
+
reactions: l.map((w) => w.reaction),
|
|
411
|
+
outcomes: l.map((w) => ee(w.outcome)),
|
|
412
|
+
report_date: u.receiptdate || "Unknown"
|
|
354
413
|
};
|
|
355
414
|
});
|
|
356
415
|
return {
|
|
357
416
|
content: [
|
|
358
417
|
{
|
|
359
418
|
type: "text",
|
|
360
|
-
text:
|
|
419
|
+
text: `${N(p.length, (f = (c = o.meta) == null ? void 0 : c.results) == null ? void 0 : f.total, `adverse event reports for "${e}"`)}
|
|
361
420
|
|
|
362
|
-
${JSON.stringify(p, null, 2)}`
|
|
421
|
+
${JSON.stringify(S(p, (g = (r = o.meta) == null ? void 0 : r.results) == null ? void 0 : g.total, t ?? 10), null, 2)}`
|
|
363
422
|
}
|
|
364
423
|
]
|
|
365
424
|
};
|
|
366
425
|
}
|
|
367
|
-
}
|
|
368
|
-
b.registerTool({
|
|
426
|
+
}, ae = {
|
|
369
427
|
name: "get-drugs-by-manufacturer",
|
|
370
428
|
description: "Get all drugs manufactured by a specific company. Useful for finding alternatives or checking manufacturer portfolios.",
|
|
371
|
-
inputSchema:
|
|
372
|
-
manufacturerName:
|
|
373
|
-
limit:
|
|
429
|
+
inputSchema: d.object({
|
|
430
|
+
manufacturerName: d.string().describe("Manufacturer/company name"),
|
|
431
|
+
limit: d.number().optional().default(20).describe("Maximum number of drugs to return")
|
|
374
432
|
}),
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
433
|
+
async handler({
|
|
434
|
+
manufacturerName: e,
|
|
435
|
+
limit: t
|
|
436
|
+
}) {
|
|
437
|
+
var s, p, c, f;
|
|
438
|
+
const n = new b().dataset("drug").context("label").search(`openfda.manufacturer_name:"${e}"`).limit(t).build(), { data: a, error: i } = await k(n);
|
|
439
|
+
if (i)
|
|
378
440
|
return {
|
|
379
441
|
content: [
|
|
380
442
|
{
|
|
381
443
|
type: "text",
|
|
382
|
-
text:
|
|
383
|
-
Failed to retrieve drugs for manufacturer "${n}": ${e.message}`
|
|
444
|
+
text: `Failed to retrieve drugs for manufacturer "${e}": ${i.message}`
|
|
384
445
|
}
|
|
385
446
|
],
|
|
386
447
|
isError: !0
|
|
387
448
|
};
|
|
388
|
-
if (!(
|
|
449
|
+
if (!(a != null && a.results) || a.results.length === 0)
|
|
389
450
|
return {
|
|
390
451
|
content: [
|
|
391
452
|
{
|
|
392
453
|
type: "text",
|
|
393
|
-
text: `No drugs found for manufacturer "${
|
|
454
|
+
text: `No drugs found for manufacturer "${e}".`
|
|
394
455
|
}
|
|
395
456
|
]
|
|
396
457
|
};
|
|
397
|
-
const
|
|
398
|
-
var
|
|
458
|
+
const o = a.results.map((r) => {
|
|
459
|
+
var g, u, l, h;
|
|
399
460
|
return {
|
|
400
|
-
brand_name: ((
|
|
401
|
-
generic_name: ((
|
|
402
|
-
product_type: ((
|
|
403
|
-
route: (
|
|
404
|
-
ndc: ((
|
|
461
|
+
brand_name: ((g = r == null ? void 0 : r.openfda.brand_name) == null ? void 0 : g[0]) || "Unknown",
|
|
462
|
+
generic_name: ((u = r == null ? void 0 : r.openfda.generic_name) == null ? void 0 : u[0]) || "Unknown",
|
|
463
|
+
product_type: ((l = r == null ? void 0 : r.openfda.product_type) == null ? void 0 : l[0]) || "Unknown",
|
|
464
|
+
route: (r == null ? void 0 : r.openfda.route) || [],
|
|
465
|
+
ndc: ((h = r == null ? void 0 : r.openfda.product_ndc) == null ? void 0 : h[0]) || "Unknown"
|
|
405
466
|
};
|
|
406
467
|
});
|
|
407
468
|
return {
|
|
408
469
|
content: [
|
|
409
470
|
{
|
|
410
471
|
type: "text",
|
|
411
|
-
text:
|
|
472
|
+
text: `${N(o.length, (p = (s = a.meta) == null ? void 0 : s.results) == null ? void 0 : p.total, `labels from manufacturer "${e}"`)}
|
|
412
473
|
|
|
413
|
-
${JSON.stringify(
|
|
474
|
+
${JSON.stringify(S(o, (f = (c = a.meta) == null ? void 0 : c.results) == null ? void 0 : f.total, t ?? 20), null, 2)}`
|
|
414
475
|
}
|
|
415
476
|
]
|
|
416
477
|
};
|
|
417
478
|
}
|
|
418
|
-
}
|
|
419
|
-
b.registerTool({
|
|
479
|
+
}, re = {
|
|
420
480
|
name: "get-drug-safety-info",
|
|
421
|
-
description: "Get comprehensive safety information for a drug including warnings, contraindications, drug interactions, and precautions.
|
|
422
|
-
inputSchema:
|
|
423
|
-
drugName:
|
|
481
|
+
description: "Get comprehensive safety information for a drug including the boxed warning, warnings and cautions, contraindications, drug interactions, and precautions. Accepts a brand name, generic name, or active substance; the response reports which field matched via matched_via.",
|
|
482
|
+
inputSchema: d.object({
|
|
483
|
+
drugName: d.string().describe("Drug brand name")
|
|
424
484
|
}),
|
|
425
|
-
|
|
426
|
-
var
|
|
427
|
-
const t =
|
|
428
|
-
if (
|
|
429
|
-
return {
|
|
485
|
+
async handler({ drugName: e }) {
|
|
486
|
+
var i, o;
|
|
487
|
+
const t = await O(e, 1);
|
|
488
|
+
if (!t.found)
|
|
489
|
+
return t.error ? {
|
|
430
490
|
content: [
|
|
431
491
|
{
|
|
432
492
|
type: "text",
|
|
433
|
-
text: `Failed to retrieve safety information for "${
|
|
493
|
+
text: `Failed to retrieve safety information for "${e}": ${t.error.message}`
|
|
434
494
|
}
|
|
435
495
|
],
|
|
436
496
|
isError: !0
|
|
497
|
+
} : {
|
|
498
|
+
content: [{ type: "text", text: U(e) }]
|
|
437
499
|
};
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
text: `No safety information found for "${n}".`
|
|
444
|
-
}
|
|
445
|
-
]
|
|
446
|
-
};
|
|
447
|
-
const e = r.results[0], i = {
|
|
448
|
-
drug_name: ((a = e == null ? void 0 : e.openfda.brand_name) == null ? void 0 : a[0]) || n,
|
|
449
|
-
generic_name: ((p = e == null ? void 0 : e.openfda.generic_name) == null ? void 0 : p[0]) || "Unknown",
|
|
450
|
-
warnings: (e == null ? void 0 : e.warnings) || [],
|
|
451
|
-
contraindications: (e == null ? void 0 : e.contraindications) || [],
|
|
452
|
-
drug_interactions: (e == null ? void 0 : e.drug_interactions) || [],
|
|
453
|
-
precautions: (e == null ? void 0 : e.precautions) || [],
|
|
454
|
-
adverse_reactions: (e == null ? void 0 : e.adverse_reactions) || [],
|
|
455
|
-
overdosage: (e == null ? void 0 : e.overdosage) || [],
|
|
456
|
-
do_not_use: (e == null ? void 0 : e.do_not_use) || [],
|
|
457
|
-
ask_doctor: (e == null ? void 0 : e.ask_doctor) || [],
|
|
458
|
-
stop_use: (e == null ? void 0 : e.stop_use) || [],
|
|
459
|
-
pregnancy_or_breast_feeding: (e == null ? void 0 : e.pregnancy_or_breast_feeding) || []
|
|
500
|
+
const n = t.data.results[0], a = {
|
|
501
|
+
drug_name: ((i = n == null ? void 0 : n.openfda.brand_name) == null ? void 0 : i[0]) || e,
|
|
502
|
+
generic_name: ((o = n == null ? void 0 : n.openfda.generic_name) == null ? void 0 : o[0]) || "Unknown",
|
|
503
|
+
matched_via: t.matched_via,
|
|
504
|
+
...W(n)
|
|
460
505
|
};
|
|
461
506
|
return {
|
|
462
507
|
content: [
|
|
463
508
|
{
|
|
464
509
|
type: "text",
|
|
465
|
-
text: `Safety information for "${
|
|
510
|
+
text: `Safety information for "${e}":
|
|
466
511
|
|
|
467
|
-
${JSON.stringify(
|
|
512
|
+
${JSON.stringify(a, null, 2)}`
|
|
468
513
|
}
|
|
469
514
|
]
|
|
470
515
|
};
|
|
471
516
|
}
|
|
472
|
-
})
|
|
473
|
-
|
|
517
|
+
}, se = /^(?:\d{4}-\d{4}|\d{5}-\d{3,4})$/, oe = /^\d{1,2}$/;
|
|
518
|
+
function I(e) {
|
|
519
|
+
const t = e.trim().toUpperCase(), n = { productNDC: t, packageNDC: null, isValid: !1 };
|
|
520
|
+
let a, i = null;
|
|
521
|
+
if (t.includes("-")) {
|
|
522
|
+
const o = t.split("-");
|
|
523
|
+
if (o.length === 2)
|
|
524
|
+
a = t;
|
|
525
|
+
else if (o.length === 3) {
|
|
526
|
+
const s = o[2];
|
|
527
|
+
if (!s || !oe.test(s)) return n;
|
|
528
|
+
a = `${o[0]}-${o[1]}`, i = t;
|
|
529
|
+
} else
|
|
530
|
+
return n;
|
|
531
|
+
} else if (/^\d+$/.test(t))
|
|
532
|
+
if (t.length === 11)
|
|
533
|
+
a = `${t.slice(0, 5)}-${t.slice(5, 9)}`, i = `${a}-${t.slice(9, 11)}`;
|
|
534
|
+
else if (t.length === 9)
|
|
535
|
+
a = `${t.slice(0, 5)}-${t.slice(5, 9)}`;
|
|
536
|
+
else
|
|
537
|
+
return n;
|
|
538
|
+
else
|
|
539
|
+
return n;
|
|
540
|
+
return se.test(a) ? { productNDC: a, packageNDC: i, isValid: !0 } : n;
|
|
541
|
+
}
|
|
542
|
+
const ie = {
|
|
474
543
|
name: "get-drug-by-ndc",
|
|
475
544
|
description: "Get drug information by National Drug Code (NDC). Accepts both product NDC (XXXXX-XXXX) and package NDC (XXXXX-XXXX-XX) formats. Also accepts NDC codes without dashes.",
|
|
476
|
-
inputSchema:
|
|
477
|
-
ndcCode:
|
|
545
|
+
inputSchema: d.object({
|
|
546
|
+
ndcCode: d.string().describe(
|
|
478
547
|
"National Drug Code (NDC) - accepts formats: XXXXX-XXXX, XXXXX-XXXX-XX, or without dashes"
|
|
479
548
|
)
|
|
480
549
|
}),
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
550
|
+
async handler({ ndcCode: e }) {
|
|
551
|
+
var g, u, l, h;
|
|
552
|
+
const { productNDC: t, packageNDC: n, isValid: a } = I(e);
|
|
553
|
+
if (!a)
|
|
484
554
|
return {
|
|
485
555
|
content: [
|
|
486
556
|
{
|
|
487
557
|
type: "text",
|
|
488
|
-
text: `Invalid NDC format: "${
|
|
558
|
+
text: `Invalid NDC format: "${e}"
|
|
489
559
|
|
|
490
560
|
✅ Accepted formats:
|
|
491
561
|
• Product NDC: 12345-1234
|
|
@@ -495,25 +565,25 @@ b.registerTool({
|
|
|
495
565
|
],
|
|
496
566
|
isError: !0
|
|
497
567
|
};
|
|
498
|
-
let
|
|
499
|
-
|
|
500
|
-
const
|
|
568
|
+
let i = `openfda.product_ndc:"${t}"`;
|
|
569
|
+
n && (i += ` OR openfda.package_ndc:"${n}"`);
|
|
570
|
+
const o = new b().dataset("drug").context("label").search(i).limit(10).build(), { data: s, error: p } = await k(o);
|
|
501
571
|
if (p)
|
|
502
572
|
return {
|
|
503
573
|
content: [
|
|
504
574
|
{
|
|
505
575
|
type: "text",
|
|
506
|
-
text: `Failed to retrieve drug data for NDC "${
|
|
576
|
+
text: `Failed to retrieve drug data for NDC "${e}": ${p.message}`
|
|
507
577
|
}
|
|
508
578
|
],
|
|
509
579
|
isError: !0
|
|
510
580
|
};
|
|
511
|
-
if (!(
|
|
581
|
+
if (!(s != null && s.results) || s.results.length === 0)
|
|
512
582
|
return {
|
|
513
583
|
content: [
|
|
514
584
|
{
|
|
515
585
|
type: "text",
|
|
516
|
-
text: `No drug found with NDC "${
|
|
586
|
+
text: `No drug found with NDC "${e}" (product: ${t}).
|
|
517
587
|
|
|
518
588
|
💡 Tips:
|
|
519
589
|
• Verify the NDC format
|
|
@@ -522,120 +592,211 @@ b.registerTool({
|
|
|
522
592
|
}
|
|
523
593
|
]
|
|
524
594
|
};
|
|
525
|
-
const
|
|
526
|
-
var
|
|
527
|
-
const
|
|
528
|
-
(
|
|
595
|
+
const c = s.results.map((_) => {
|
|
596
|
+
var w, A;
|
|
597
|
+
const $ = ((w = _.openfda.product_ndc) == null ? void 0 : w.filter((x) => x === t)) || [], v = ((A = _.openfda.package_ndc) == null ? void 0 : A.filter(
|
|
598
|
+
(x) => n ? x === n : x.startsWith(t)
|
|
529
599
|
)) || [];
|
|
530
600
|
return {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
package_label_principal_display_panel: u.package_label_principal_display_panel || [],
|
|
546
|
-
active_ingredient: u.active_ingredient || [],
|
|
547
|
-
purpose: u.purpose || []
|
|
601
|
+
brand_name: _.openfda.brand_name || [],
|
|
602
|
+
generic_name: _.openfda.generic_name || [],
|
|
603
|
+
manufacturer_name: _.openfda.manufacturer_name || [],
|
|
604
|
+
product_type: _.openfda.product_type || [],
|
|
605
|
+
route: _.openfda.route || [],
|
|
606
|
+
substance_name: _.openfda.substance_name || [],
|
|
607
|
+
matching_product_ndc: $,
|
|
608
|
+
matching_package_ndc: v,
|
|
609
|
+
all_product_ndc: _.openfda.product_ndc || [],
|
|
610
|
+
all_package_ndc: _.openfda.package_ndc || [],
|
|
611
|
+
dosage_and_administration: _.dosage_and_administration || [],
|
|
612
|
+
package_label_principal_display_panel: _.package_label_principal_display_panel || [],
|
|
613
|
+
active_ingredient: _.active_ingredient || [],
|
|
614
|
+
purpose: _.purpose || []
|
|
548
615
|
};
|
|
549
|
-
}),
|
|
550
|
-
(
|
|
616
|
+
}), f = c.reduce(
|
|
617
|
+
(_, $) => _ + $.matching_package_ndc.length,
|
|
551
618
|
0
|
|
552
|
-
),
|
|
619
|
+
), r = n ? `Searched for specific package NDC: ${n}` : `Searched for product NDC: ${t} (all packages)`;
|
|
553
620
|
return {
|
|
554
621
|
content: [
|
|
555
622
|
{
|
|
556
623
|
type: "text",
|
|
557
|
-
text:
|
|
624
|
+
text: `${N(c.length, (u = (g = s.meta) == null ? void 0 : g.results) == null ? void 0 : u.total, `labels for NDC "${e}"`)} with ${f} package(s)
|
|
558
625
|
|
|
559
|
-
${
|
|
626
|
+
${r}
|
|
560
627
|
|
|
561
|
-
${JSON.stringify(
|
|
628
|
+
${JSON.stringify(S(c, (h = (l = s.meta) == null ? void 0 : l.results) == null ? void 0 : h.total, 10), null, 2)}`
|
|
562
629
|
}
|
|
563
630
|
]
|
|
564
631
|
};
|
|
565
632
|
}
|
|
566
|
-
}
|
|
567
|
-
b.registerTool({
|
|
633
|
+
}, ce = {
|
|
568
634
|
name: "get-drug-by-product-ndc",
|
|
569
|
-
description: "Get drug information by product NDC
|
|
570
|
-
inputSchema:
|
|
571
|
-
productNDC:
|
|
635
|
+
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.",
|
|
636
|
+
inputSchema: d.object({
|
|
637
|
+
productNDC: d.string().describe(
|
|
638
|
+
"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"
|
|
639
|
+
)
|
|
572
640
|
}),
|
|
573
|
-
|
|
574
|
-
var
|
|
575
|
-
|
|
641
|
+
async handler({ productNDC: e }) {
|
|
642
|
+
var f;
|
|
643
|
+
const { productNDC: t, isValid: n } = I(e);
|
|
644
|
+
if (!n)
|
|
576
645
|
return {
|
|
577
646
|
content: [
|
|
578
647
|
{
|
|
579
648
|
type: "text",
|
|
580
|
-
text: `Invalid product NDC format: "${
|
|
649
|
+
text: `Invalid product NDC format: "${e}"
|
|
581
650
|
|
|
582
|
-
✅
|
|
651
|
+
✅ Accepted formats:
|
|
652
|
+
• 4-4 product NDC: 0456-4020
|
|
653
|
+
• 5-3 product NDC: 58151-155
|
|
654
|
+
• 5-4 product NDC: 12345-1234
|
|
655
|
+
• Undashed 9 digits: 123451234 (read as 5-4)
|
|
656
|
+
• Undashed 11 digits: 12345123401 (read as 5-4-2)
|
|
657
|
+
|
|
658
|
+
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.`
|
|
583
659
|
}
|
|
584
660
|
],
|
|
585
661
|
isError: !0
|
|
586
662
|
};
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
663
|
+
const a = new b().dataset("drug").context("label").search(`openfda.product_ndc:"${t}"`).limit(1).build(), { data: i, error: o } = await k(a);
|
|
664
|
+
if (o)
|
|
589
665
|
return {
|
|
590
666
|
content: [
|
|
591
667
|
{
|
|
592
668
|
type: "text",
|
|
593
|
-
text:
|
|
669
|
+
text: `Failed to retrieve drug data for product NDC "${e}": ${o.message}`
|
|
594
670
|
}
|
|
595
671
|
],
|
|
596
672
|
isError: !0
|
|
597
673
|
};
|
|
598
|
-
if (!(
|
|
674
|
+
if (!(i != null && i.results) || i.results.length === 0)
|
|
599
675
|
return {
|
|
600
676
|
content: [
|
|
601
677
|
{
|
|
602
678
|
type: "text",
|
|
603
|
-
text: `No drug found with product NDC "${
|
|
679
|
+
text: `No drug found with product NDC "${e}".`
|
|
604
680
|
}
|
|
605
681
|
],
|
|
606
682
|
structuredContent: null
|
|
607
683
|
};
|
|
608
|
-
const
|
|
609
|
-
(
|
|
610
|
-
)) || [],
|
|
611
|
-
product_ndc:
|
|
612
|
-
available_packages:
|
|
613
|
-
brand_name:
|
|
614
|
-
generic_name:
|
|
615
|
-
manufacturer_name:
|
|
616
|
-
product_type:
|
|
617
|
-
route:
|
|
618
|
-
substance_name:
|
|
619
|
-
active_ingredient:
|
|
620
|
-
purpose:
|
|
621
|
-
dosage_and_administration:
|
|
684
|
+
const s = i.results[0], p = ((f = s.openfda.package_ndc) == null ? void 0 : f.filter(
|
|
685
|
+
(r) => r.startsWith(t)
|
|
686
|
+
)) || [], c = {
|
|
687
|
+
product_ndc: t,
|
|
688
|
+
available_packages: p,
|
|
689
|
+
brand_name: s.openfda.brand_name || [],
|
|
690
|
+
generic_name: s.openfda.generic_name || [],
|
|
691
|
+
manufacturer_name: s.openfda.manufacturer_name || [],
|
|
692
|
+
product_type: s.openfda.product_type || [],
|
|
693
|
+
route: s.openfda.route || [],
|
|
694
|
+
substance_name: s.openfda.substance_name || [],
|
|
695
|
+
active_ingredient: s.active_ingredient || [],
|
|
696
|
+
purpose: s.purpose || [],
|
|
697
|
+
dosage_and_administration: s.dosage_and_administration || []
|
|
622
698
|
};
|
|
623
699
|
return {
|
|
624
700
|
content: [
|
|
625
701
|
{
|
|
626
702
|
type: "text",
|
|
627
|
-
text: `✅ Product NDC "${
|
|
703
|
+
text: `✅ Product NDC "${t}" found with ${p.length} package variation(s):
|
|
628
704
|
|
|
629
|
-
${JSON.stringify(
|
|
705
|
+
${JSON.stringify(c, null, 2)}`
|
|
630
706
|
}
|
|
631
707
|
]
|
|
632
708
|
};
|
|
633
709
|
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
710
|
+
}, ue = {
|
|
711
|
+
name: "get-drugsfda",
|
|
712
|
+
description: "Get drugsfda data by section and field. Search OpenFDA drugsfda endpoint by specifying a section (application, openfda, products, submissions, application_docs), a field name within that section, and a search value.",
|
|
713
|
+
inputSchema: d.object({
|
|
714
|
+
sectionName: d.string().describe(
|
|
715
|
+
"Section within drugsfda. Valid values: application, openfda, products, submissions, application_docs"
|
|
716
|
+
),
|
|
717
|
+
fieldName: d.string().describe(
|
|
718
|
+
"Field name within the selected section. application: application_number. openfda: application_number, brand_name, generic_name, manufacturer_name, nui, package_ndc, pharm_class_cs, pharm_class_epc, pharm_class_pe, pharm_class_moa, product_ndc, route, rxcui, spl_id, spl_set_id, substance_name, unii. products: active_ingredients.name, active_ingredients.strength, dosage_form, marketing_status, product_number, reference_drug, reference_standard, route, te_code. submissions: application_docs, review_priority, submission_class_code, submission_class_code_description, submission_number, submission_property_type.code, submission_public_notes, submission_status, submission_status_date, submission_type. application_docs: applications_doc_id, applications_doc_date, application_docs_title, applications_doc_type, applications_doc_url"
|
|
719
|
+
),
|
|
720
|
+
searchValue: d.string().describe("Value to search for in the specified field")
|
|
721
|
+
}),
|
|
722
|
+
async handler({
|
|
723
|
+
sectionName: e,
|
|
724
|
+
fieldName: t,
|
|
725
|
+
searchValue: n
|
|
726
|
+
}) {
|
|
727
|
+
const a = new b().dataset("drug").context("drugsfda").search(`${e}.${t}:"${n}"`).limit(1).build(), { data: i, error: o } = await k(a);
|
|
728
|
+
if (o) {
|
|
729
|
+
let s = `Failed to retrieve drugsfda data for "${n}" in ${e}.${t}: ${o.message}`;
|
|
730
|
+
switch (o.type) {
|
|
731
|
+
case "http":
|
|
732
|
+
o.status === 404 ? s += `
|
|
733
|
+
|
|
734
|
+
Suggestions:
|
|
735
|
+
- Verify the field name is correct for the section
|
|
736
|
+
- Check the search value spelling` : (o.status === 401 || o.status === 403) && (s += `
|
|
737
|
+
|
|
738
|
+
Please check the API key configuration.`);
|
|
739
|
+
break;
|
|
740
|
+
case "network":
|
|
741
|
+
s += `
|
|
742
|
+
|
|
743
|
+
Please check your internet connection and try again.`;
|
|
744
|
+
break;
|
|
745
|
+
case "timeout":
|
|
746
|
+
s += `
|
|
747
|
+
|
|
748
|
+
The request took too long. Please try again.`;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
content: [{ type: "text", text: s }],
|
|
753
|
+
isError: !0
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return !(i != null && i.results) || i.results.length === 0 ? {
|
|
757
|
+
content: [
|
|
758
|
+
{
|
|
759
|
+
type: "text",
|
|
760
|
+
text: `No drugsfda data found for "${n}" in ${e}.${t}. Please verify the search parameters.`
|
|
761
|
+
}
|
|
762
|
+
]
|
|
763
|
+
} : {
|
|
764
|
+
content: [
|
|
765
|
+
{
|
|
766
|
+
type: "text",
|
|
767
|
+
text: `drugsfda data retrieved successfully:
|
|
768
|
+
|
|
769
|
+
${JSON.stringify(i.results[0], null, 2)}`
|
|
770
|
+
}
|
|
771
|
+
]
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}, de = "1.1.0", R = new K(
|
|
775
|
+
{
|
|
776
|
+
name: "openfda",
|
|
777
|
+
version: de,
|
|
778
|
+
description: "OpenFDA Model Context Protocol"
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
capabilities: {
|
|
782
|
+
resources: {},
|
|
783
|
+
tools: {}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
), y = new J(R);
|
|
787
|
+
y.registerTool(H);
|
|
788
|
+
y.registerTool(Q);
|
|
789
|
+
y.registerTool(ne);
|
|
790
|
+
y.registerTool(ae);
|
|
791
|
+
y.registerTool(re);
|
|
792
|
+
y.registerTool(ie);
|
|
793
|
+
y.registerTool(ce);
|
|
794
|
+
y.registerTool(ue);
|
|
795
|
+
async function pe() {
|
|
796
|
+
B();
|
|
797
|
+
const e = new L();
|
|
798
|
+
await R.connect(e), console.error("OpenFDA MCP Server running on stdio");
|
|
638
799
|
}
|
|
639
|
-
|
|
640
|
-
console.error("Fatal error in main():",
|
|
800
|
+
pe().catch((e) => {
|
|
801
|
+
console.error("Fatal error in main():", e), process.exit(1);
|
|
641
802
|
});
|