openfda-mcp-server 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/defineProperty-BEGP2H1W.js +1 -0
- package/dist/handlers/bulk-data-handlers.d.ts +108 -0
- package/dist/handlers/bulk-data-handlers.js +2 -0
- package/dist/handlers/bulk-data-handlers.js.map +1 -0
- package/dist/handlers/device-handlers.d.ts +0 -1
- package/dist/handlers/device-handlers.js +2 -1
- package/dist/handlers/device-handlers.js.map +1 -0
- package/dist/handlers/drug-handlers.d.ts +0 -1
- package/dist/handlers/drug-handlers.js +2 -1
- package/dist/handlers/drug-handlers.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +4 -2
- package/dist/lib.js +1 -1
- package/dist/server.d.ts +1 -2
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/index.d.ts +205 -382
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -0
- package/dist/types/fda.d.ts +58 -1
- package/dist/utils/api-client.d.ts +0 -1
- package/dist/utils/api-client.js +2 -1
- package/dist/utils/api-client.js.map +1 -0
- package/dist/utils/bulk-data-client.d.ts +24 -0
- package/dist/utils/bulk-data-client.js +4 -0
- package/dist/utils/bulk-data-client.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +27 -23
- package/dist/api-client-BGnUXMwX.js +0 -2
- package/dist/api-client-BGnUXMwX.js.map +0 -1
- package/dist/device-handlers-D8AD5oIk.js +0 -2
- package/dist/device-handlers-D8AD5oIk.js.map +0 -1
- package/dist/drug-handlers-D5VxbBCL.js +0 -2
- package/dist/drug-handlers-D5VxbBCL.js.map +0 -1
- package/dist/tools-3M3lSxQv.js +0 -2
- package/dist/tools-3M3lSxQv.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(t){"@babel/helpers - typeof";return e=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},e(t)}function t(t,n){if(e(t)!=`object`||!t)return t;var r=t[Symbol.toPrimitive];if(r!==void 0){var i=r.call(t,n||`default`);if(e(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(n===`string`?String:Number)(t)}function n(n){var r=t(n,`string`);return e(r)==`symbol`?r:r+``}function r(e,t,r){return(t=n(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}export{r as t};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { FDAToolResponse } from "../types/fda.js";
|
|
2
|
+
|
|
3
|
+
//#region src/handlers/bulk-data-handlers.d.ts
|
|
4
|
+
type OrangeBookSearchParams = {
|
|
5
|
+
drugName?: string;
|
|
6
|
+
applicant?: string;
|
|
7
|
+
applNo?: string;
|
|
8
|
+
teCode?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
skip?: number;
|
|
11
|
+
};
|
|
12
|
+
type FormattedOrangeBookProduct = {
|
|
13
|
+
tradeName: string;
|
|
14
|
+
ingredient: string;
|
|
15
|
+
applicant: string;
|
|
16
|
+
applicantFullName: string;
|
|
17
|
+
applType: string;
|
|
18
|
+
applNo: string;
|
|
19
|
+
productNo: string;
|
|
20
|
+
dfRoute: string;
|
|
21
|
+
teCode: string;
|
|
22
|
+
approvalDate: string;
|
|
23
|
+
rld: string;
|
|
24
|
+
type: string;
|
|
25
|
+
patentCount: number;
|
|
26
|
+
exclusivityCount: number;
|
|
27
|
+
};
|
|
28
|
+
declare function handleSearchOrangeBook(params: OrangeBookSearchParams): Promise<FDAToolResponse<FormattedOrangeBookProduct[]>>;
|
|
29
|
+
type OrangeBookPatentsParams = {
|
|
30
|
+
drugName?: string;
|
|
31
|
+
applNo?: string;
|
|
32
|
+
patentNo?: string;
|
|
33
|
+
limit?: number;
|
|
34
|
+
skip?: number;
|
|
35
|
+
};
|
|
36
|
+
type FormattedOrangeBookPatent = {
|
|
37
|
+
patentNo: string;
|
|
38
|
+
patentExpireDate: string;
|
|
39
|
+
drugSubstanceFlag: string;
|
|
40
|
+
drugProductFlag: string;
|
|
41
|
+
patentUseCode: string;
|
|
42
|
+
applType: string;
|
|
43
|
+
applNo: string;
|
|
44
|
+
productNo: string;
|
|
45
|
+
tradeName: string;
|
|
46
|
+
ingredient: string;
|
|
47
|
+
submissionDate: string;
|
|
48
|
+
exclusivities: Array<{
|
|
49
|
+
code: string;
|
|
50
|
+
date: string;
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
declare function handleSearchOrangeBookPatents(params: OrangeBookPatentsParams): Promise<FDAToolResponse<FormattedOrangeBookPatent[]>>;
|
|
54
|
+
type PurpleBookSearchParams = {
|
|
55
|
+
productName?: string;
|
|
56
|
+
applicant?: string;
|
|
57
|
+
blaNumber?: string;
|
|
58
|
+
licenseType?: "351(a)" | "351(k)";
|
|
59
|
+
biosimilar?: boolean;
|
|
60
|
+
interchangeable?: boolean;
|
|
61
|
+
limit?: number;
|
|
62
|
+
skip?: number;
|
|
63
|
+
};
|
|
64
|
+
type FormattedPurpleBookEntry = {
|
|
65
|
+
blaNumber: string;
|
|
66
|
+
proprietaryName: string;
|
|
67
|
+
properName: string;
|
|
68
|
+
applicant: string;
|
|
69
|
+
blaType: string;
|
|
70
|
+
strength: string;
|
|
71
|
+
dosageForm: string;
|
|
72
|
+
route: string;
|
|
73
|
+
status: string;
|
|
74
|
+
licensingStatus: string;
|
|
75
|
+
biosimilar: boolean;
|
|
76
|
+
interchangeable: boolean;
|
|
77
|
+
referenceProductBla: string;
|
|
78
|
+
referenceProductName: string;
|
|
79
|
+
approvalDate: string;
|
|
80
|
+
};
|
|
81
|
+
declare function handleSearchPurpleBook(params: PurpleBookSearchParams): Promise<FDAToolResponse<FormattedPurpleBookEntry[]>>;
|
|
82
|
+
type DrugPatentExpiryParams = {
|
|
83
|
+
drugName?: string;
|
|
84
|
+
applNo?: string;
|
|
85
|
+
includeExclusivity?: boolean;
|
|
86
|
+
includePurpleBook?: boolean;
|
|
87
|
+
limit?: number;
|
|
88
|
+
skip?: number;
|
|
89
|
+
};
|
|
90
|
+
type PatentExpiryEntry = {
|
|
91
|
+
source: string;
|
|
92
|
+
tradeName: string;
|
|
93
|
+
ingredient: string;
|
|
94
|
+
applNo: string;
|
|
95
|
+
patentNo: string;
|
|
96
|
+
patentExpireDate: string;
|
|
97
|
+
drugSubstanceFlag: string;
|
|
98
|
+
drugProductFlag: string;
|
|
99
|
+
patentUseCode: string;
|
|
100
|
+
exclusivities: Array<{
|
|
101
|
+
code: string;
|
|
102
|
+
date: string;
|
|
103
|
+
}>;
|
|
104
|
+
};
|
|
105
|
+
declare function handleSearchDrugPatentExpiry(params: DrugPatentExpiryParams): Promise<FDAToolResponse<PatentExpiryEntry[]>>;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { DrugPatentExpiryParams, OrangeBookPatentsParams, OrangeBookSearchParams, PurpleBookSearchParams, handleSearchDrugPatentExpiry, handleSearchOrangeBook, handleSearchOrangeBookPatents, handleSearchPurpleBook };
|
|
108
|
+
//# sourceMappingURL=bulk-data-handlers.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{loggers as e}from"../utils/logger.js";import{bulkDataClient as t}from"../utils/bulk-data-client.js";function n(e,t){return t?e.toLowerCase().includes(t.toLowerCase()):!0}function r(e){return`${e.applType}-${e.applNo}-${e.productNo}`}function i(e,t,n){let r=e.length,i=n??0,a=Math.min(t??10,100);return{items:e.slice(i,i+a),total:r}}const a={name:`FDA Orange Book`,lastUpdated:`Monthly`,url:`https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book`},o={name:`FDA Purple Book`,lastUpdated:`Monthly`,url:`https://purplebooksearch.fda.gov/`};async function s(o){e.tools(`searchOrangeBook`,o);try{let{products:e,patents:s,exclusivities:c}=await t.getOrangeBookData(),l=new Map;for(let e of s){let t=r(e);l.set(t,(l.get(t)??0)+1)}let u=new Map;for(let e of c){let t=r(e);u.set(t,(u.get(t)??0)+1)}let{items:d,total:f}=i(e.filter(e=>!(!n(e.tradeName,o.drugName)&&!n(e.ingredient,o.drugName)||!n(e.applicant,o.applicant)&&!n(e.applicantFullName,o.applicant)||o.applNo&&e.applNo!==o.applNo||!n(e.teCode,o.teCode))),o.limit,o.skip);return{success:!0,data:d.map(e=>{let t=r(e);return{tradeName:e.tradeName,ingredient:e.ingredient,applicant:e.applicant,applicantFullName:e.applicantFullName,applType:e.applType,applNo:e.applNo,productNo:e.productNo,dfRoute:e.dfRoute,teCode:e.teCode,approvalDate:e.approvalDate,rld:e.rld,type:e.type,patentCount:l.get(t)??0,exclusivityCount:u.get(t)??0}}),totalResults:f,displayedResults:d.length,dataSource:a}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}async function c(o){e.tools(`searchOrangeBookPatents`,o);try{let{products:e,patents:s,exclusivities:c}=await t.getOrangeBookData(),l=new Map;for(let t of e)l.set(r(t),t);let u=new Map;for(let e of c){let t=r(e),n=u.get(t)??[];n.push(e),u.set(t,n)}let{items:d,total:f}=i(s.filter(e=>{if(o.patentNo&&e.patentNo!==o.patentNo||o.applNo&&e.applNo!==o.applNo)return!1;if(o.drugName){let t=l.get(r(e));if(!t||!n(t.tradeName,o.drugName)&&!n(t.ingredient,o.drugName))return!1}return!0}),o.limit,o.skip);return{success:!0,data:d.map(e=>{let t=r(e),n=l.get(t),i=u.get(t)??[];return{patentNo:e.patentNo,patentExpireDate:e.patentExpireDate,drugSubstanceFlag:e.drugSubstanceFlag,drugProductFlag:e.drugProductFlag,patentUseCode:e.patentUseCode,applType:e.applType,applNo:e.applNo,productNo:e.productNo,tradeName:n?.tradeName??``,ingredient:n?.ingredient??``,submissionDate:e.submissionDate,exclusivities:i.map(e=>({code:e.exclusivityCode,date:e.exclusivityDate}))}}),totalResults:f,displayedResults:d.length,dataSource:a}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}async function l(r){e.tools(`searchPurpleBook`,r);try{let{items:e,total:a}=i((await t.getPurpleBookData()).filter(e=>!(!n(e.proprietaryName,r.productName)&&!n(e.properName,r.productName)||!n(e.applicant,r.applicant)||r.blaNumber&&e.blaNumber!==r.blaNumber||r.licenseType&&e.blaType!==r.licenseType||r.biosimilar!==void 0&&e.biosimilar!==r.biosimilar||r.interchangeable!==void 0&&e.interchangeable!==r.interchangeable)),r.limit,r.skip);return{success:!0,data:e.map(e=>({blaNumber:e.blaNumber,proprietaryName:e.proprietaryName,properName:e.properName,applicant:e.applicant,blaType:e.blaType,strength:e.strength,dosageForm:e.dosageForm,route:e.route,status:e.status,licensingStatus:e.licensingStatus,biosimilar:e.biosimilar,interchangeable:e.interchangeable,referenceProductBla:e.referenceProductBla,referenceProductName:e.referenceProductProprietaryName,approvalDate:e.approvalDate})),totalResults:a,displayedResults:e.length,dataSource:o}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}async function u(o){e.tools(`searchDrugPatentExpiry`,o);try{let s=o.includeExclusivity!==!1,c=o.includePurpleBook===!0,{products:l,patents:u,exclusivities:d}=await t.getOrangeBookData(),f=new Map;for(let e of l)f.set(r(e),e);let p=new Map;if(s)for(let e of d){let t=r(e),n=p.get(t)??[];n.push(e),p.set(t,n)}let m=u.filter(e=>{if(o.applNo&&e.applNo!==o.applNo)return!1;if(o.drugName){let t=f.get(r(e));if(!t||!n(t.tradeName,o.drugName)&&!n(t.ingredient,o.drugName))return!1}return!0}).map(e=>{let t=r(e),n=f.get(t),i=s?p.get(t)??[]:[];return{source:`Orange Book`,tradeName:n?.tradeName??``,ingredient:n?.ingredient??``,applNo:e.applNo,patentNo:e.patentNo,patentExpireDate:e.patentExpireDate,drugSubstanceFlag:e.drugSubstanceFlag,drugProductFlag:e.drugProductFlag,patentUseCode:e.patentUseCode,exclusivities:i.map(e=>({code:e.exclusivityCode,date:e.exclusivityDate}))}});if(c)try{let e=(await t.getPurpleBookData()).filter(e=>!(o.drugName&&!n(e.proprietaryName,o.drugName)&&!n(e.properName,o.drugName)||o.applNo&&e.blaNumber!==o.applNo));for(let t of e)m.push({source:`Purple Book`,tradeName:t.proprietaryName,ingredient:t.properName,applNo:t.blaNumber,patentNo:``,patentExpireDate:``,drugSubstanceFlag:``,drugProductFlag:``,patentUseCode:``,exclusivities:[]})}catch(t){e.bulk(`Purple Book data unavailable: ${t instanceof Error?t.message:String(t)}`)}m.sort((e,t)=>!e.patentExpireDate&&!t.patentExpireDate?0:e.patentExpireDate?t.patentExpireDate?e.patentExpireDate.localeCompare(t.patentExpireDate):-1:1);let{items:h,total:g}=i(m,o.limit,o.skip);return{success:!0,data:h,totalResults:g,displayedResults:h.length,dataSource:a}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}export{u as handleSearchDrugPatentExpiry,s as handleSearchOrangeBook,c as handleSearchOrangeBookPatents,l as handleSearchPurpleBook};
|
|
2
|
+
//# sourceMappingURL=bulk-data-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-data-handlers.js","names":[],"sources":["../../src/handlers/bulk-data-handlers.ts"],"sourcesContent":["/**\n * Bulk Data Handlers\n * Query handlers for FDA Orange Book and Purple Book data\n */\n\nimport type { FDAToolResponse, OrangeBookExclusivity, OrangeBookPatent, OrangeBookProduct } from \"../types/fda.js\"\nimport { bulkDataClient } from \"../utils/bulk-data-client.js\"\nimport { loggers } from \"../utils/logger.js\"\n\n// Helper for case-insensitive substring matching\nfunction matchesFilter(value: string, filter: string | undefined): boolean {\n if (!filter) return true\n return value.toLowerCase().includes(filter.toLowerCase())\n}\n\n// Join key for Orange Book records\nfunction obJoinKey(record: { applType: string; applNo: string; productNo: string }): string {\n return `${record.applType}-${record.applNo}-${record.productNo}`\n}\n\n// Default pagination values\nconst DEFAULT_LIMIT = 10\nconst MAX_LIMIT = 100\n\nfunction applyPagination<T>(items: T[], limit?: number, skip?: number): { items: T[]; total: number } {\n const total = items.length\n const effectiveSkip = skip ?? 0\n const effectiveLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)\n return {\n items: items.slice(effectiveSkip, effectiveSkip + effectiveLimit),\n total,\n }\n}\n\nconst ORANGE_BOOK_SOURCE = {\n name: \"FDA Orange Book\",\n lastUpdated: \"Monthly\",\n url: \"https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book\",\n}\n\nconst PURPLE_BOOK_SOURCE = {\n name: \"FDA Purple Book\",\n lastUpdated: \"Monthly\",\n url: \"https://purplebooksearch.fda.gov/\",\n}\n\n// ============================================================\n// search_fda_orange_book\n// ============================================================\n\nexport type OrangeBookSearchParams = {\n drugName?: string\n applicant?: string\n applNo?: string\n teCode?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedOrangeBookProduct = {\n tradeName: string\n ingredient: string\n applicant: string\n applicantFullName: string\n applType: string\n applNo: string\n productNo: string\n dfRoute: string\n teCode: string\n approvalDate: string\n rld: string\n type: string\n patentCount: number\n exclusivityCount: number\n}\n\nexport async function handleSearchOrangeBook(\n params: OrangeBookSearchParams,\n): Promise<FDAToolResponse<FormattedOrangeBookProduct[]>> {\n loggers.tools(\"searchOrangeBook\", params)\n\n try {\n const { products, patents, exclusivities } = await bulkDataClient.getOrangeBookData()\n\n // Build patent/exclusivity count maps\n const patentCounts = new Map<string, number>()\n for (const p of patents) {\n const key = obJoinKey(p)\n patentCounts.set(key, (patentCounts.get(key) ?? 0) + 1)\n }\n\n const exclusivityCounts = new Map<string, number>()\n for (const e of exclusivities) {\n const key = obJoinKey(e)\n exclusivityCounts.set(key, (exclusivityCounts.get(key) ?? 0) + 1)\n }\n\n // Filter products\n const filtered = products.filter((p) => {\n if (!matchesFilter(p.tradeName, params.drugName) && !matchesFilter(p.ingredient, params.drugName)) return false\n if (!matchesFilter(p.applicant, params.applicant) && !matchesFilter(p.applicantFullName, params.applicant))\n return false\n if (params.applNo && p.applNo !== params.applNo) return false\n if (!matchesFilter(p.teCode, params.teCode)) return false\n return true\n })\n\n const { items, total } = applyPagination(filtered, params.limit, params.skip)\n\n const formatted: FormattedOrangeBookProduct[] = items.map((p) => {\n const key = obJoinKey(p)\n return {\n tradeName: p.tradeName,\n ingredient: p.ingredient,\n applicant: p.applicant,\n applicantFullName: p.applicantFullName,\n applType: p.applType,\n applNo: p.applNo,\n productNo: p.productNo,\n dfRoute: p.dfRoute,\n teCode: p.teCode,\n approvalDate: p.approvalDate,\n rld: p.rld,\n type: p.type,\n patentCount: patentCounts.get(key) ?? 0,\n exclusivityCount: exclusivityCounts.get(key) ?? 0,\n }\n })\n\n return {\n success: true,\n data: formatted,\n totalResults: total,\n displayedResults: items.length,\n dataSource: ORANGE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// ============================================================\n// search_fda_orange_book_patents\n// ============================================================\n\nexport type OrangeBookPatentsParams = {\n drugName?: string\n applNo?: string\n patentNo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedOrangeBookPatent = {\n patentNo: string\n patentExpireDate: string\n drugSubstanceFlag: string\n drugProductFlag: string\n patentUseCode: string\n applType: string\n applNo: string\n productNo: string\n tradeName: string\n ingredient: string\n submissionDate: string\n exclusivities: Array<{ code: string; date: string }>\n}\n\nexport async function handleSearchOrangeBookPatents(\n params: OrangeBookPatentsParams,\n): Promise<FDAToolResponse<FormattedOrangeBookPatent[]>> {\n loggers.tools(\"searchOrangeBookPatents\", params)\n\n try {\n const { products, patents, exclusivities } = await bulkDataClient.getOrangeBookData()\n\n // Build product lookup by join key\n const productMap = new Map<string, OrangeBookProduct>()\n for (const p of products) {\n productMap.set(obJoinKey(p), p)\n }\n\n // Build exclusivity lookup by join key\n const exclusivityMap = new Map<string, OrangeBookExclusivity[]>()\n for (const e of exclusivities) {\n const key = obJoinKey(e)\n const existing = exclusivityMap.get(key) ?? []\n existing.push(e)\n exclusivityMap.set(key, existing)\n }\n\n // Filter patents\n const filtered = patents.filter((pat) => {\n if (params.patentNo && pat.patentNo !== params.patentNo) return false\n if (params.applNo && pat.applNo !== params.applNo) return false\n\n if (params.drugName) {\n const product = productMap.get(obJoinKey(pat))\n if (!product) return false\n if (!matchesFilter(product.tradeName, params.drugName) && !matchesFilter(product.ingredient, params.drugName))\n return false\n }\n\n return true\n })\n\n const { items, total } = applyPagination(filtered, params.limit, params.skip)\n\n const formatted: FormattedOrangeBookPatent[] = items.map((pat) => {\n const key = obJoinKey(pat)\n const product = productMap.get(key)\n const excl = exclusivityMap.get(key) ?? []\n\n return {\n patentNo: pat.patentNo,\n patentExpireDate: pat.patentExpireDate,\n drugSubstanceFlag: pat.drugSubstanceFlag,\n drugProductFlag: pat.drugProductFlag,\n patentUseCode: pat.patentUseCode,\n applType: pat.applType,\n applNo: pat.applNo,\n productNo: pat.productNo,\n tradeName: product?.tradeName ?? \"\",\n ingredient: product?.ingredient ?? \"\",\n submissionDate: pat.submissionDate,\n exclusivities: excl.map((e) => ({ code: e.exclusivityCode, date: e.exclusivityDate })),\n }\n })\n\n return {\n success: true,\n data: formatted,\n totalResults: total,\n displayedResults: items.length,\n dataSource: ORANGE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// ============================================================\n// search_fda_purple_book\n// ============================================================\n\nexport type PurpleBookSearchParams = {\n productName?: string\n applicant?: string\n blaNumber?: string\n licenseType?: \"351(a)\" | \"351(k)\"\n biosimilar?: boolean\n interchangeable?: boolean\n limit?: number\n skip?: number\n}\n\ntype FormattedPurpleBookEntry = {\n blaNumber: string\n proprietaryName: string\n properName: string\n applicant: string\n blaType: string\n strength: string\n dosageForm: string\n route: string\n status: string\n licensingStatus: string\n biosimilar: boolean\n interchangeable: boolean\n referenceProductBla: string\n referenceProductName: string\n approvalDate: string\n}\n\nexport async function handleSearchPurpleBook(\n params: PurpleBookSearchParams,\n): Promise<FDAToolResponse<FormattedPurpleBookEntry[]>> {\n loggers.tools(\"searchPurpleBook\", params)\n\n try {\n const entries = await bulkDataClient.getPurpleBookData()\n\n const filtered = entries.filter((e) => {\n if (!matchesFilter(e.proprietaryName, params.productName) && !matchesFilter(e.properName, params.productName))\n return false\n if (!matchesFilter(e.applicant, params.applicant)) return false\n if (params.blaNumber && e.blaNumber !== params.blaNumber) return false\n if (params.licenseType && e.blaType !== params.licenseType) return false\n if (params.biosimilar !== undefined && e.biosimilar !== params.biosimilar) return false\n if (params.interchangeable !== undefined && e.interchangeable !== params.interchangeable) return false\n return true\n })\n\n const { items, total } = applyPagination(filtered, params.limit, params.skip)\n\n const formatted: FormattedPurpleBookEntry[] = items.map((e) => ({\n blaNumber: e.blaNumber,\n proprietaryName: e.proprietaryName,\n properName: e.properName,\n applicant: e.applicant,\n blaType: e.blaType,\n strength: e.strength,\n dosageForm: e.dosageForm,\n route: e.route,\n status: e.status,\n licensingStatus: e.licensingStatus,\n biosimilar: e.biosimilar,\n interchangeable: e.interchangeable,\n referenceProductBla: e.referenceProductBla,\n referenceProductName: e.referenceProductProprietaryName,\n approvalDate: e.approvalDate,\n }))\n\n return {\n success: true,\n data: formatted,\n totalResults: total,\n displayedResults: items.length,\n dataSource: PURPLE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// ============================================================\n// search_fda_drug_patent_expiry\n// ============================================================\n\nexport type DrugPatentExpiryParams = {\n drugName?: string\n applNo?: string\n includeExclusivity?: boolean\n includePurpleBook?: boolean\n limit?: number\n skip?: number\n}\n\ntype PatentExpiryEntry = {\n source: string\n tradeName: string\n ingredient: string\n applNo: string\n patentNo: string\n patentExpireDate: string\n drugSubstanceFlag: string\n drugProductFlag: string\n patentUseCode: string\n exclusivities: Array<{ code: string; date: string }>\n}\n\nexport async function handleSearchDrugPatentExpiry(\n params: DrugPatentExpiryParams,\n): Promise<FDAToolResponse<PatentExpiryEntry[]>> {\n loggers.tools(\"searchDrugPatentExpiry\", params)\n\n try {\n const includeExclusivity = params.includeExclusivity !== false\n const includePurpleBook = params.includePurpleBook === true\n\n const { products, patents, exclusivities } = await bulkDataClient.getOrangeBookData()\n\n // Build product lookup\n const productMap = new Map<string, OrangeBookProduct>()\n for (const p of products) {\n productMap.set(obJoinKey(p), p)\n }\n\n // Build exclusivity lookup\n const exclusivityMap = new Map<string, OrangeBookExclusivity[]>()\n if (includeExclusivity) {\n for (const e of exclusivities) {\n const key = obJoinKey(e)\n const existing = exclusivityMap.get(key) ?? []\n existing.push(e)\n exclusivityMap.set(key, existing)\n }\n }\n\n // Filter patents by drug name / applNo\n const filteredPatents = patents.filter((pat) => {\n if (params.applNo && pat.applNo !== params.applNo) return false\n if (params.drugName) {\n const product = productMap.get(obJoinKey(pat))\n if (!product) return false\n if (!matchesFilter(product.tradeName, params.drugName) && !matchesFilter(product.ingredient, params.drugName))\n return false\n }\n return true\n })\n\n const entries: PatentExpiryEntry[] = filteredPatents.map((pat) => {\n const key = obJoinKey(pat)\n const product = productMap.get(key)\n const excl = includeExclusivity ? (exclusivityMap.get(key) ?? []) : []\n\n return {\n source: \"Orange Book\",\n tradeName: product?.tradeName ?? \"\",\n ingredient: product?.ingredient ?? \"\",\n applNo: pat.applNo,\n patentNo: pat.patentNo,\n patentExpireDate: pat.patentExpireDate,\n drugSubstanceFlag: pat.drugSubstanceFlag,\n drugProductFlag: pat.drugProductFlag,\n patentUseCode: pat.patentUseCode,\n exclusivities: excl.map((e) => ({ code: e.exclusivityCode, date: e.exclusivityDate })),\n }\n })\n\n // Optionally add Purple Book entries that match\n if (includePurpleBook) {\n try {\n const purpleEntries = await bulkDataClient.getPurpleBookData()\n const filteredPurple = purpleEntries.filter((e) => {\n if (params.drugName) {\n if (!matchesFilter(e.proprietaryName, params.drugName) && !matchesFilter(e.properName, params.drugName))\n return false\n }\n if (params.applNo && e.blaNumber !== params.applNo) return false\n return true\n })\n\n for (const pe of filteredPurple) {\n entries.push({\n source: \"Purple Book\",\n tradeName: pe.proprietaryName,\n ingredient: pe.properName,\n applNo: pe.blaNumber,\n patentNo: \"\",\n patentExpireDate: \"\",\n drugSubstanceFlag: \"\",\n drugProductFlag: \"\",\n patentUseCode: \"\",\n exclusivities: [],\n })\n }\n } catch (error) {\n loggers.bulk(`Purple Book data unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n\n // Sort by earliest expiry date (non-empty dates first)\n entries.sort((a, b) => {\n if (!a.patentExpireDate && !b.patentExpireDate) return 0\n if (!a.patentExpireDate) return 1\n if (!b.patentExpireDate) return -1\n return a.patentExpireDate.localeCompare(b.patentExpireDate)\n })\n\n const { items, total } = applyPagination(entries, params.limit, params.skip)\n\n return {\n success: true,\n data: items,\n totalResults: total,\n displayedResults: items.length,\n dataSource: ORANGE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import"../utils/logger.js";import"../api-client
|
|
1
|
+
import{loggers as e}from"../utils/logger.js";import{fdaAPIClient as t}from"../utils/api-client.js";function n(e,t,n){if(!(!t&&!n))return`${e}:[${t?.replace(/-/g,``)??`*`}+TO+${n?.replace(/-/g,``)??`*`}]`}function r(e){return e.replace(/[+\-&|!(){}[\]^"~*?:\\]/g,`\\$&`).replace(/\s+/g,`+`)}function i(e){let t=e.filter(e=>e.value!==void 0&&e.value.trim()!==``);return t.length===0?``:t.map(e=>`${e.field}:"${r(e.value)}"`).join(`+AND+`)}function a(e,t=200){if(e)return e.length>t?e.substring(0,t)+`...`:e}function o(e){return{kNumber:e.k_number,deviceName:e.device_name??e.openfda?.device_name,applicant:e.applicant,productCode:e.product_code,clearanceType:e.clearance_type,decisionCode:e.decision_code,decisionDescription:e.decision_description,decisionDate:e.decision_date,dateReceived:e.date_received,city:e.city,state:e.state,country:e.country_code}}async function s(r){e.tools(`searchDevice510K`,r);try{let e={search:[i([{field:`device_name`,value:r.deviceName},{field:`applicant`,value:r.applicant},{field:`product_code`,value:r.productCode},{field:`clearance_type`,value:r.clearanceType}]),n(`decision_date`,r.decisionDateFrom,r.decisionDateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:r.limit,skip:r.skip},a=await t.searchDevice510K(e),s=a.results??[];return{success:!0,data:s.map(o),totalResults:a.meta?.results?.total,displayedResults:s.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function c(e){return{deviceName:e.device_name,deviceClass:e.device_class,definition:a(e.definition),medicalSpecialty:e.medical_specialty,medicalSpecialtyDescription:e.medical_specialty_description,productCode:e.product_code,regulationNumber:e.regulation_number,gmpExempt:e.gmp_exempt_flag===`Y`,implant:e.implant_flag===`Y`,lifeSustaining:e.life_sustain_support_flag===`Y`}}async function l(n){e.tools(`searchDeviceClassifications`,n);try{let e={search:i([{field:`device_name`,value:n.deviceName},{field:`device_class`,value:n.deviceClass},{field:`medical_specialty`,value:n.medicalSpecialty},{field:`product_code`,value:n.productCode},{field:`regulation_number`,value:n.regulationNumber}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDeviceClassifications(e),a=r.results??[];return{success:!0,data:a.map(c),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function u(e){return{reportNumber:e.report_number,dateOfEvent:e.date_of_event,dateReceived:e.date_received,eventType:e.event_type,adverseEventFlag:e.adverse_event_flag===`Y`,productProblemFlag:e.product_problem_flag===`Y`,devices:e.device?.slice(0,3).map(e=>({brandName:e.brand_name,genericName:e.generic_name,manufacturerName:e.manufacturer_d_name,modelNumber:e.model_number,productCode:e.device_report_product_code,deviceClass:e.openfda?.device_class}))??[],mdrText:e.mdr_text?.slice(0,2).map(e=>({textType:e.text_type_code,text:a(e.text,300)}))??[]}}async function d(a){e.tools(`searchDeviceAdverseEvents`,a);try{if(a.reportNumber){let e={search:`report_number:"${r(a.reportNumber)}"`,limit:1},n=await t.searchDeviceAdverseEvents(e),i=n.results??[];return{success:!0,data:i.map(u),totalResults:n.meta?.results?.total,displayedResults:i.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}let e={search:[i([{field:`device.generic_name`,value:a.deviceName},{field:`device.brand_name`,value:a.brandName},{field:`device.manufacturer_d_name`,value:a.manufacturerName},{field:`event_type`,value:a.eventType}]),n(`date_received`,a.dateFrom,a.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:a.limit,skip:a.skip},o=await t.searchDeviceAdverseEvents(e),s=o.results??[];return{success:!0,data:s.map(u),totalResults:o.meta?.results?.total,displayedResults:s.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function f(e){return{recallNumber:e.recall_number,recallingFirm:e.recalling_firm,classification:e.classification,status:e.status,productDescription:a(e.product_description),reasonForRecall:a(e.reason_for_recall),recallInitiationDate:e.recall_initiation_date,distributionPattern:a(e.distribution_pattern),city:e.city,state:e.state,deviceName:e.openfda?.device_name,deviceClass:e.openfda?.device_class}}async function p(r){e.tools(`searchDeviceEnforcement`,r);try{let e={search:[i([{field:`recalling_firm`,value:r.recallingFirm},{field:`product_description`,value:r.productDescription},{field:`classification`,value:r.classification},{field:`status`,value:r.status}]),n(`recall_initiation_date`,r.dateFrom,r.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:r.limit,skip:r.skip},a=await t.searchDeviceEnforcement(e),o=a.results??[];return{success:!0,data:o.map(f),totalResults:a.meta?.results?.total,displayedResults:o.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}export{s as handleSearchDevice510K,d as handleSearchDeviceAdverseEvents,l as handleSearchDeviceClassifications,p as handleSearchDeviceEnforcement};
|
|
2
|
+
//# sourceMappingURL=device-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-handlers.js","names":[],"sources":["../../src/handlers/device-handlers.ts"],"sourcesContent":["/**\n * Device Handlers\n * Query handlers for FDA device-related endpoints\n */\n\nimport type {\n Device510K,\n DeviceAdverseEvent,\n DeviceClassification,\n DeviceEnforcement,\n FDAToolResponse,\n SearchParams,\n} from \"../types/fda.js\"\nimport { fdaAPIClient } from \"../utils/api-client.js\"\nimport { loggers } from \"../utils/logger.js\"\n\n// Helper to build date range query\nfunction buildDateQuery(field: string, dateFrom?: string, dateTo?: string): string | undefined {\n if (!dateFrom && !dateTo) return undefined\n\n // FDA date format is YYYYMMDD\n const from = dateFrom?.replace(/-/g, \"\") ?? \"*\"\n const to = dateTo?.replace(/-/g, \"\") ?? \"*\"\n\n return `${field}:[${from}+TO+${to}]`\n}\n\n// Helper to escape special characters in search terms\nfunction escapeSearchTerm(term: string): string {\n return term.replace(/[+\\-&|!(){}[\\]^\"~*?:\\\\]/g, \"\\\\$&\").replace(/\\s+/g, \"+\")\n}\n\n// Helper to build a search query from multiple terms\nfunction buildSearchQuery(terms: Array<{ field: string; value?: string }>): string {\n const validTerms = terms.filter((t) => t.value !== undefined && t.value.trim() !== \"\")\n if (validTerms.length === 0) return \"\"\n\n return validTerms.map((t) => `${t.field}:\"${escapeSearchTerm(t.value!)}\"`).join(\"+AND+\")\n}\n\n// Format truncated text\nfunction truncateText(text: string | undefined, maxLength = 200): string | undefined {\n if (!text) return undefined\n return text.length > maxLength ? text.substring(0, maxLength) + \"...\" : text\n}\n\n// Device 510(k) Handler\nexport type Device510KParams = {\n deviceName?: string\n applicant?: string\n productCode?: string\n clearanceType?: string\n decisionDateFrom?: string\n decisionDateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDevice510K = {\n kNumber: string | undefined\n deviceName: string | undefined\n applicant: string | undefined\n productCode: string | undefined\n clearanceType: string | undefined\n decisionCode: string | undefined\n decisionDescription: string | undefined\n decisionDate: string | undefined\n dateReceived: string | undefined\n city: string | undefined\n state: string | undefined\n country: string | undefined\n}\n\nfunction formatDevice510K(device: Device510K): FormattedDevice510K {\n return {\n kNumber: device.k_number,\n deviceName: device.device_name ?? device.openfda?.device_name,\n applicant: device.applicant,\n productCode: device.product_code,\n clearanceType: device.clearance_type,\n decisionCode: device.decision_code,\n decisionDescription: device.decision_description,\n decisionDate: device.decision_date,\n dateReceived: device.date_received,\n city: device.city,\n state: device.state,\n country: device.country_code,\n }\n}\n\nexport async function handleSearchDevice510K(\n params: Device510KParams,\n): Promise<FDAToolResponse<FormattedDevice510K[]>> {\n loggers.tools(\"searchDevice510K\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"device_name\", value: params.deviceName },\n { field: \"applicant\", value: params.applicant },\n { field: \"product_code\", value: params.productCode },\n { field: \"clearance_type\", value: params.clearanceType },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"decision_date\", params.decisionDateFrom, params.decisionDateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDevice510K(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDevice510K),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Device Classification Handler\nexport type DeviceClassificationParams = {\n deviceName?: string\n deviceClass?: \"1\" | \"2\" | \"3\"\n medicalSpecialty?: string\n productCode?: string\n regulationNumber?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDeviceClassification = {\n deviceName: string | undefined\n deviceClass: string | undefined\n definition: string | undefined\n medicalSpecialty: string | undefined\n medicalSpecialtyDescription: string | undefined\n productCode: string | undefined\n regulationNumber: string | undefined\n gmpExempt: boolean\n implant: boolean\n lifeSustaining: boolean\n}\n\nfunction formatDeviceClassification(classification: DeviceClassification): FormattedDeviceClassification {\n return {\n deviceName: classification.device_name,\n deviceClass: classification.device_class,\n definition: truncateText(classification.definition),\n medicalSpecialty: classification.medical_specialty,\n medicalSpecialtyDescription: classification.medical_specialty_description,\n productCode: classification.product_code,\n regulationNumber: classification.regulation_number,\n gmpExempt: classification.gmp_exempt_flag === \"Y\",\n implant: classification.implant_flag === \"Y\",\n lifeSustaining: classification.life_sustain_support_flag === \"Y\",\n }\n}\n\nexport async function handleSearchDeviceClassifications(\n params: DeviceClassificationParams,\n): Promise<FDAToolResponse<FormattedDeviceClassification[]>> {\n loggers.tools(\"searchDeviceClassifications\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"device_name\", value: params.deviceName },\n { field: \"device_class\", value: params.deviceClass },\n { field: \"medical_specialty\", value: params.medicalSpecialty },\n { field: \"product_code\", value: params.productCode },\n { field: \"regulation_number\", value: params.regulationNumber },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDeviceClassifications(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceClassification),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Device Adverse Events (MDR) Handler\nexport type DeviceAdverseEventsParams = {\n reportNumber?: string\n deviceName?: string\n brandName?: string\n manufacturerName?: string\n eventType?: \"Injury\" | \"Malfunction\" | \"Death\" | \"Other\"\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDeviceAdverseEvent = {\n reportNumber: string | undefined\n dateOfEvent: string | undefined\n dateReceived: string | undefined\n eventType: string | undefined\n adverseEventFlag: boolean\n productProblemFlag: boolean\n devices: Array<{\n brandName: string | undefined\n genericName: string | undefined\n manufacturerName: string | undefined\n modelNumber: string | undefined\n productCode: string | undefined\n deviceClass: string | undefined\n }>\n mdrText: Array<{\n textType: string | undefined\n text: string | undefined\n }>\n}\n\nfunction formatDeviceAdverseEvent(event: DeviceAdverseEvent): FormattedDeviceAdverseEvent {\n return {\n reportNumber: event.report_number,\n dateOfEvent: event.date_of_event,\n dateReceived: event.date_received,\n eventType: event.event_type,\n adverseEventFlag: event.adverse_event_flag === \"Y\",\n productProblemFlag: event.product_problem_flag === \"Y\",\n devices:\n event.device?.slice(0, 3).map((d) => ({\n brandName: d.brand_name,\n genericName: d.generic_name,\n manufacturerName: d.manufacturer_d_name,\n modelNumber: d.model_number,\n productCode: d.device_report_product_code,\n deviceClass: d.openfda?.device_class,\n })) ?? [],\n mdrText:\n event.mdr_text?.slice(0, 2).map((t) => ({\n textType: t.text_type_code,\n text: truncateText(t.text, 300),\n })) ?? [],\n }\n}\n\nexport async function handleSearchDeviceAdverseEvents(\n params: DeviceAdverseEventsParams,\n): Promise<FDAToolResponse<FormattedDeviceAdverseEvent[]>> {\n loggers.tools(\"searchDeviceAdverseEvents\", params)\n\n try {\n // If reportNumber is provided, do a direct lookup\n if (params.reportNumber) {\n const searchParams: SearchParams = {\n search: `report_number:\"${escapeSearchTerm(params.reportNumber)}\"`,\n limit: 1,\n }\n\n const response = await fdaAPIClient.searchDeviceAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n }\n\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"device.generic_name\", value: params.deviceName },\n { field: \"device.brand_name\", value: params.brandName },\n { field: \"device.manufacturer_d_name\", value: params.manufacturerName },\n { field: \"event_type\", value: params.eventType },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"date_received\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDeviceAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Device Enforcement (Recalls) Handler\nexport type DeviceEnforcementParams = {\n recallingFirm?: string\n productDescription?: string\n classification?: \"Class I\" | \"Class II\" | \"Class III\"\n status?: \"Ongoing\" | \"Completed\" | \"Terminated\" | \"Pending\"\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDeviceEnforcement = {\n recallNumber: string | undefined\n recallingFirm: string | undefined\n classification: string | undefined\n status: string | undefined\n productDescription: string | undefined\n reasonForRecall: string | undefined\n recallInitiationDate: string | undefined\n distributionPattern: string | undefined\n city: string | undefined\n state: string | undefined\n deviceName: string | undefined\n deviceClass: string | undefined\n}\n\nfunction formatDeviceEnforcement(recall: DeviceEnforcement): FormattedDeviceEnforcement {\n return {\n recallNumber: recall.recall_number,\n recallingFirm: recall.recalling_firm,\n classification: recall.classification,\n status: recall.status,\n productDescription: truncateText(recall.product_description),\n reasonForRecall: truncateText(recall.reason_for_recall),\n recallInitiationDate: recall.recall_initiation_date,\n distributionPattern: truncateText(recall.distribution_pattern),\n city: recall.city,\n state: recall.state,\n deviceName: recall.openfda?.device_name,\n deviceClass: recall.openfda?.device_class,\n }\n}\n\nexport async function handleSearchDeviceEnforcement(\n params: DeviceEnforcementParams,\n): Promise<FDAToolResponse<FormattedDeviceEnforcement[]>> {\n loggers.tools(\"searchDeviceEnforcement\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"recalling_firm\", value: params.recallingFirm },\n { field: \"product_description\", value: params.productDescription },\n { field: \"classification\", value: params.classification },\n { field: \"status\", value: params.status },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"recall_initiation_date\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDeviceEnforcement(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceEnforcement),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import"../utils/logger.js";import"../api-client
|
|
1
|
+
import{loggers as e}from"../utils/logger.js";import{fdaAPIClient as t}from"../utils/api-client.js";function n(e,t,n){if(!(!t&&!n))return`${e}:[${t?.replace(/-/g,``)??`*`}+TO+${n?.replace(/-/g,``)??`*`}]`}function r(e){return e.replace(/[+\-&|!(){}[\]^"~*?:\\]/g,`\\$&`).replace(/\s+/g,`+`)}function i(e){let t=e.filter(e=>e.value!==void 0&&e.value.trim()!==``);return t.length===0?``:t.map(e=>`${e.field}:"${r(e.value)}"`).join(`+AND+`)}function a(e,t=200){if(e)return e.length>t?e.substring(0,t)+`...`:e}function o(e){return{reportId:e.safetyreportid,receiveDate:e.receivedate,serious:e.serious===`1`,drugs:e.patient?.drug?.slice(0,3).map(e=>({name:e.medicinalproduct??e.activesubstance?.activesubstancename,indication:e.drugindication,route:e.drugadministrationroute}))??[],reactions:e.patient?.reaction?.slice(0,3).map(e=>({reaction:e.reactionmeddrapt,outcome:e.reactionoutcome}))??[],patient:{age:e.patient?.patientonsetage?`${e.patient.patientonsetage} ${e.patient.patientonsetageunit??``}`:void 0,sex:e.patient?.patientsex===`1`?`Male`:e.patient?.patientsex===`2`?`Female`:void 0,weight:e.patient?.patientweight?`${e.patient.patientweight} kg`:void 0}}}async function s(a){e.tools(`searchDrugAdverseEvents`,a);try{if(a.safetyReportId){let e={search:`safetyreportid:"${r(a.safetyReportId)}"`,limit:1},n=await t.searchDrugAdverseEvents(e),i=n.results??[];return{success:!0,data:i.map(o),totalResults:n.meta?.results?.total,displayedResults:i.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}let e=[{field:`patient.drug.medicinalproduct`,value:a.drugName},{field:`patient.reaction.reactionmeddrapt`,value:a.reaction},{field:`patient.drug.openfda.manufacturer_name`,value:a.manufacturer}];a.serious!==void 0&&e.push({field:`serious`,value:a.serious?`1`:`2`});let s={search:[i(e),n(`receivedate`,a.dateFrom,a.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:a.limit,skip:a.skip},c=await t.searchDrugAdverseEvents(s),l=c.results??[];return{success:!0,data:l.map(o),totalResults:c.meta?.results?.total,displayedResults:l.length,searchParams:s,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function c(e,t){let n=e=>!t||t.includes(e);return{setId:e.set_id,brandName:e.openfda?.brand_name?.[0],genericName:e.openfda?.generic_name?.[0],manufacturer:e.openfda?.manufacturer_name?.[0],activeIngredients:e.openfda?.substance_name?.slice(0,5)??[],boxedWarning:n(`boxed_warning`)?a(e.boxed_warning?.[0],500):void 0,indications:n(`indications_and_usage`)?a(e.indications_and_usage?.[0]):void 0,warnings:n(`warnings`)?a(e.warnings?.[0]):void 0,contraindications:n(`contraindications`)?a(e.contraindications?.[0]):void 0,adverseReactions:n(`adverse_reactions`)?a(e.adverse_reactions?.[0]):void 0,drugInteractions:n(`drug_interactions`)?a(e.drug_interactions?.[0]):void 0,dosageAndAdministration:n(`dosage_and_administration`)?a(e.dosage_and_administration?.[0]):void 0,clinicalPharmacology:n(`clinical_pharmacology`)?a(e.clinical_pharmacology?.[0]):void 0,mechanismOfAction:n(`mechanism_of_action`)?a(e.mechanism_of_action?.[0]):void 0,pharmacokinetics:n(`pharmacokinetics`)?a(e.pharmacokinetics?.[0]):void 0,overdosage:n(`overdosage`)?a(e.overdosage?.[0]):void 0,description:n(`description`)?a(e.description?.[0]):void 0,howSupplied:n(`how_supplied`)?a(e.how_supplied?.[0]):void 0,storageAndHandling:n(`storage_and_handling`)?a(e.storage_and_handling?.[0]):void 0,route:e.openfda?.route?.slice(0,3)??[]}}async function l(n){e.tools(`searchDrugLabels`,n);try{if(n.setId){let e={search:`set_id:"${r(n.setId)}"`,limit:1},i=await t.searchDrugLabels(e),a=i.results??[];return{success:!0,data:a.map(e=>c(e,n.sections)),totalResults:i.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}let e={search:[i([{field:`openfda.brand_name`,value:n.drugName},{field:`indications_and_usage`,value:n.indication},{field:`openfda.substance_name`,value:n.activeIngredient},{field:`openfda.route`,value:n.route}]),n.hasBoxedWarning?`_exists_:boxed_warning`:void 0].filter(Boolean).join(`+AND+`)||void 0,limit:n.limit,skip:n.skip},a=await t.searchDrugLabels(e),o=a.results??[];return{success:!0,data:o.map(e=>c(e,n.sections)),totalResults:a.meta?.results?.total,displayedResults:o.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function u(e){return{productNdc:e.product_ndc,brandName:e.brand_name,genericName:e.generic_name,labelerName:e.labeler_name,dosageForm:e.dosage_form,route:e.route??[],activeIngredients:e.active_ingredients?.slice(0,3).map(e=>({name:e.name,strength:e.strength}))??[],marketingStartDate:e.marketing_start_date,productType:e.product_type}}async function d(n){e.tools(`searchDrugNDC`,n);try{let e={search:i([{field:`product_ndc`,value:n.productNdc},{field:`brand_name`,value:n.brandName},{field:`generic_name`,value:n.genericName},{field:`labeler_name`,value:n.labelerName},{field:`dosage_form`,value:n.dosageForm},{field:`route`,value:n.route}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDrugNDC(e),a=r.results??[];return{success:!0,data:a.map(u),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function f(e){return{recallNumber:e.recall_number,recallingFirm:e.recalling_firm,classification:e.classification,status:e.status,productDescription:a(e.product_description),reasonForRecall:a(e.reason_for_recall),recallInitiationDate:e.recall_initiation_date,distributionPattern:a(e.distribution_pattern),city:e.city,state:e.state}}async function p(r){e.tools(`searchDrugEnforcement`,r);try{let e={search:[i([{field:`recalling_firm`,value:r.recallingFirm},{field:`classification`,value:r.classification},{field:`status`,value:r.status},{field:`state`,value:r.state}]),n(`recall_initiation_date`,r.dateFrom,r.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:r.limit,skip:r.skip},a=await t.searchDrugEnforcement(e),o=a.results??[];return{success:!0,data:o.map(f),totalResults:a.meta?.results?.total,displayedResults:o.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function m(e){let t=e.submissions?.[0];return{applicationNumber:e.application_number,sponsorName:e.sponsor_name,products:e.products?.slice(0,3).map(e=>({brandName:e.brand_name,dosageForm:e.dosage_form,route:e.route,marketingStatus:e.marketing_status,activeIngredients:e.active_ingredients?.slice(0,3).map(e=>({name:e.name,strength:e.strength}))??[]}))??[],latestSubmission:t?{type:t.submission_type,status:t.submission_status,statusDate:t.submission_status_date}:void 0}}async function h(n){e.tools(`searchDrugsFDA`,n);try{let e={search:i([{field:`sponsor_name`,value:n.sponsorName},{field:`application_number`,value:n.applicationNumber},{field:`products.brand_name`,value:n.brandName},{field:`products.marketing_status`,value:n.marketingStatus}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDrugsFDA(e),a=r.results??[];return{success:!0,data:a.map(m),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function g(e){return{genericName:e.generic_name,proprietaryName:e.proprietary_name,status:e.status,description:a(e.description),initialPostingDate:e.initial_posting_date,resolvedShortageDate:e.resolved_shortage_date}}async function _(n){e.tools(`searchDrugShortages`,n);try{let e={search:i([{field:`generic_name`,value:n.genericName},{field:`status`,value:n.status}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDrugShortages(e),a=r.results??[];return{success:!0,data:a.map(g),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}export{s as handleSearchDrugAdverseEvents,p as handleSearchDrugEnforcement,l as handleSearchDrugLabels,d as handleSearchDrugNDC,_ as handleSearchDrugShortages,h as handleSearchDrugsFDA};
|
|
2
|
+
//# sourceMappingURL=drug-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drug-handlers.js","names":[],"sources":["../../src/handlers/drug-handlers.ts"],"sourcesContent":["/**\n * Drug Handlers\n * Query handlers for FDA drug-related endpoints\n */\n\nimport type {\n DrugAdverseEvent,\n DrugEnforcement,\n DrugLabel,\n DrugNDC,\n DrugsFDA,\n DrugShortage,\n FDAToolResponse,\n SearchParams,\n} from \"../types/fda.js\"\nimport { fdaAPIClient } from \"../utils/api-client.js\"\nimport { loggers } from \"../utils/logger.js\"\n\n// Helper to build date range query\nfunction buildDateQuery(field: string, dateFrom?: string, dateTo?: string): string | undefined {\n if (!dateFrom && !dateTo) return undefined\n\n // FDA date format is YYYYMMDD\n const from = dateFrom?.replace(/-/g, \"\") ?? \"*\"\n const to = dateTo?.replace(/-/g, \"\") ?? \"*\"\n\n return `${field}:[${from}+TO+${to}]`\n}\n\n// Helper to escape special characters in search terms\nfunction escapeSearchTerm(term: string): string {\n // Escape special characters that have meaning in FDA's search syntax\n return term.replace(/[+\\-&|!(){}[\\]^\"~*?:\\\\]/g, \"\\\\$&\").replace(/\\s+/g, \"+\")\n}\n\n// Helper to build a search query from multiple terms\nfunction buildSearchQuery(terms: Array<{ field: string; value?: string }>): string {\n const validTerms = terms.filter((t) => t.value !== undefined && t.value.trim() !== \"\")\n if (validTerms.length === 0) return \"\"\n\n return validTerms.map((t) => `${t.field}:\"${escapeSearchTerm(t.value!)}\"`).join(\"+AND+\")\n}\n\n// Format truncated text\nfunction truncateText(text: string | undefined, maxLength = 200): string | undefined {\n if (!text) return undefined\n return text.length > maxLength ? text.substring(0, maxLength) + \"...\" : text\n}\n\n// Drug Adverse Events Handler\nexport type DrugAdverseEventsParams = {\n safetyReportId?: string\n drugName?: string\n reaction?: string\n manufacturer?: string\n serious?: boolean\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugAdverseEvent = {\n reportId: string | undefined\n receiveDate: string | undefined\n serious: boolean\n drugs: Array<{\n name: string | undefined\n indication: string | undefined\n route: string | undefined\n }>\n reactions: Array<{\n reaction: string | undefined\n outcome: string | undefined\n }>\n patient: {\n age: string | undefined\n sex: string | undefined\n weight: string | undefined\n }\n}\n\nfunction formatDrugAdverseEvent(event: DrugAdverseEvent): FormattedDrugAdverseEvent {\n return {\n reportId: event.safetyreportid,\n receiveDate: event.receivedate,\n serious: event.serious === \"1\",\n drugs:\n event.patient?.drug?.slice(0, 3).map((d) => ({\n name: d.medicinalproduct ?? d.activesubstance?.activesubstancename,\n indication: d.drugindication,\n route: d.drugadministrationroute,\n })) ?? [],\n reactions:\n event.patient?.reaction?.slice(0, 3).map((r) => ({\n reaction: r.reactionmeddrapt,\n outcome: r.reactionoutcome,\n })) ?? [],\n patient: {\n age: event.patient?.patientonsetage\n ? `${event.patient.patientonsetage} ${event.patient.patientonsetageunit ?? \"\"}`\n : undefined,\n sex: event.patient?.patientsex === \"1\" ? \"Male\" : event.patient?.patientsex === \"2\" ? \"Female\" : undefined,\n weight: event.patient?.patientweight ? `${event.patient.patientweight} kg` : undefined,\n },\n }\n}\n\nexport async function handleSearchDrugAdverseEvents(\n params: DrugAdverseEventsParams,\n): Promise<FDAToolResponse<FormattedDrugAdverseEvent[]>> {\n loggers.tools(\"searchDrugAdverseEvents\", params)\n\n try {\n // If safetyReportId is provided, do a direct lookup\n if (params.safetyReportId) {\n const searchParams: SearchParams = {\n search: `safetyreportid:\"${escapeSearchTerm(params.safetyReportId)}\"`,\n limit: 1,\n }\n\n const response = await fdaAPIClient.searchDrugAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n }\n\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"patient.drug.medicinalproduct\", value: params.drugName },\n { field: \"patient.reaction.reactionmeddrapt\", value: params.reaction },\n { field: \"patient.drug.openfda.manufacturer_name\", value: params.manufacturer },\n ]\n\n if (params.serious !== undefined) {\n searchTerms.push({ field: \"serious\", value: params.serious ? \"1\" : \"2\" })\n }\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"receivedate\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug Labels Handler\nexport type DrugLabelsParams = {\n setId?: string\n drugName?: string\n indication?: string\n activeIngredient?: string\n route?: string\n hasBoxedWarning?: boolean\n sections?: string[]\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugLabel = {\n setId: string | undefined\n brandName: string | undefined\n genericName: string | undefined\n manufacturer: string | undefined\n activeIngredients: string[]\n boxedWarning: string | undefined\n indications: string | undefined\n warnings: string | undefined\n contraindications: string | undefined\n adverseReactions: string | undefined\n drugInteractions: string | undefined\n dosageAndAdministration: string | undefined\n clinicalPharmacology: string | undefined\n mechanismOfAction: string | undefined\n pharmacokinetics: string | undefined\n overdosage: string | undefined\n description: string | undefined\n howSupplied: string | undefined\n storageAndHandling: string | undefined\n route: string[]\n}\n\nfunction formatDrugLabel(label: DrugLabel, sections?: string[]): FormattedDrugLabel {\n // Helper to conditionally include section based on sections filter\n const includeSection = (sectionName: string) => !sections || sections.includes(sectionName)\n\n return {\n setId: label.set_id,\n brandName: label.openfda?.brand_name?.[0],\n genericName: label.openfda?.generic_name?.[0],\n manufacturer: label.openfda?.manufacturer_name?.[0],\n activeIngredients: label.openfda?.substance_name?.slice(0, 5) ?? [],\n boxedWarning: includeSection(\"boxed_warning\") ? truncateText(label.boxed_warning?.[0], 500) : undefined,\n indications: includeSection(\"indications_and_usage\") ? truncateText(label.indications_and_usage?.[0]) : undefined,\n warnings: includeSection(\"warnings\") ? truncateText(label.warnings?.[0]) : undefined,\n contraindications: includeSection(\"contraindications\") ? truncateText(label.contraindications?.[0]) : undefined,\n adverseReactions: includeSection(\"adverse_reactions\") ? truncateText(label.adverse_reactions?.[0]) : undefined,\n drugInteractions: includeSection(\"drug_interactions\") ? truncateText(label.drug_interactions?.[0]) : undefined,\n dosageAndAdministration: includeSection(\"dosage_and_administration\")\n ? truncateText(label.dosage_and_administration?.[0])\n : undefined,\n clinicalPharmacology: includeSection(\"clinical_pharmacology\")\n ? truncateText(label.clinical_pharmacology?.[0])\n : undefined,\n mechanismOfAction: includeSection(\"mechanism_of_action\") ? truncateText(label.mechanism_of_action?.[0]) : undefined,\n pharmacokinetics: includeSection(\"pharmacokinetics\") ? truncateText(label.pharmacokinetics?.[0]) : undefined,\n overdosage: includeSection(\"overdosage\") ? truncateText(label.overdosage?.[0]) : undefined,\n description: includeSection(\"description\") ? truncateText(label.description?.[0]) : undefined,\n howSupplied: includeSection(\"how_supplied\") ? truncateText(label.how_supplied?.[0]) : undefined,\n storageAndHandling: includeSection(\"storage_and_handling\")\n ? truncateText(label.storage_and_handling?.[0])\n : undefined,\n route: label.openfda?.route?.slice(0, 3) ?? [],\n }\n}\n\nexport async function handleSearchDrugLabels(params: DrugLabelsParams): Promise<FDAToolResponse<FormattedDrugLabel[]>> {\n loggers.tools(\"searchDrugLabels\", params)\n\n try {\n // If setId is provided, do a direct lookup\n if (params.setId) {\n const searchParams: SearchParams = {\n search: `set_id:\"${escapeSearchTerm(params.setId)}\"`,\n limit: 1,\n }\n\n const response = await fdaAPIClient.searchDrugLabels(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map((label) => formatDrugLabel(label, params.sections)),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n }\n\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"openfda.brand_name\", value: params.drugName },\n { field: \"indications_and_usage\", value: params.indication },\n { field: \"openfda.substance_name\", value: params.activeIngredient },\n { field: \"openfda.route\", value: params.route },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n // Build boxed warning existence query\n const boxedWarningQuery = params.hasBoxedWarning ? \"_exists_:boxed_warning\" : undefined\n\n const fullQuery = [searchQuery, boxedWarningQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugLabels(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map((label) => formatDrugLabel(label, params.sections)),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug NDC Handler\nexport type DrugNDCParams = {\n productNdc?: string\n brandName?: string\n genericName?: string\n labelerName?: string\n dosageForm?: string\n route?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugNDC = {\n productNdc: string | undefined\n brandName: string | undefined\n genericName: string | undefined\n labelerName: string | undefined\n dosageForm: string | undefined\n route: string[]\n activeIngredients: Array<{ name: string | undefined; strength: string | undefined }>\n marketingStartDate: string | undefined\n productType: string | undefined\n}\n\nfunction formatDrugNDC(ndc: DrugNDC): FormattedDrugNDC {\n return {\n productNdc: ndc.product_ndc,\n brandName: ndc.brand_name,\n genericName: ndc.generic_name,\n labelerName: ndc.labeler_name,\n dosageForm: ndc.dosage_form,\n route: ndc.route ?? [],\n activeIngredients:\n ndc.active_ingredients?.slice(0, 3).map((ai) => ({\n name: ai.name,\n strength: ai.strength,\n })) ?? [],\n marketingStartDate: ndc.marketing_start_date,\n productType: ndc.product_type,\n }\n}\n\nexport async function handleSearchDrugNDC(params: DrugNDCParams): Promise<FDAToolResponse<FormattedDrugNDC[]>> {\n loggers.tools(\"searchDrugNDC\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"product_ndc\", value: params.productNdc },\n { field: \"brand_name\", value: params.brandName },\n { field: \"generic_name\", value: params.genericName },\n { field: \"labeler_name\", value: params.labelerName },\n { field: \"dosage_form\", value: params.dosageForm },\n { field: \"route\", value: params.route },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugNDC(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugNDC),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug Enforcement (Recalls) Handler\nexport type DrugEnforcementParams = {\n recallingFirm?: string\n classification?: \"Class I\" | \"Class II\" | \"Class III\"\n status?: \"Ongoing\" | \"Completed\" | \"Terminated\" | \"Pending\"\n state?: string\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugEnforcement = {\n recallNumber: string | undefined\n recallingFirm: string | undefined\n classification: string | undefined\n status: string | undefined\n productDescription: string | undefined\n reasonForRecall: string | undefined\n recallInitiationDate: string | undefined\n distributionPattern: string | undefined\n city: string | undefined\n state: string | undefined\n}\n\nfunction formatDrugEnforcement(recall: DrugEnforcement): FormattedDrugEnforcement {\n return {\n recallNumber: recall.recall_number,\n recallingFirm: recall.recalling_firm,\n classification: recall.classification,\n status: recall.status,\n productDescription: truncateText(recall.product_description),\n reasonForRecall: truncateText(recall.reason_for_recall),\n recallInitiationDate: recall.recall_initiation_date,\n distributionPattern: truncateText(recall.distribution_pattern),\n city: recall.city,\n state: recall.state,\n }\n}\n\nexport async function handleSearchDrugEnforcement(\n params: DrugEnforcementParams,\n): Promise<FDAToolResponse<FormattedDrugEnforcement[]>> {\n loggers.tools(\"searchDrugEnforcement\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"recalling_firm\", value: params.recallingFirm },\n { field: \"classification\", value: params.classification },\n { field: \"status\", value: params.status },\n { field: \"state\", value: params.state },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"recall_initiation_date\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugEnforcement(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugEnforcement),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drugs@FDA Handler\nexport type DrugsFDAParams = {\n sponsorName?: string\n applicationNumber?: string\n brandName?: string\n marketingStatus?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugsFDA = {\n applicationNumber: string | undefined\n sponsorName: string | undefined\n products: Array<{\n brandName: string | undefined\n dosageForm: string | undefined\n route: string | undefined\n marketingStatus: string | undefined\n activeIngredients: Array<{ name: string | undefined; strength: string | undefined }>\n }>\n latestSubmission:\n | {\n type: string | undefined\n status: string | undefined\n statusDate: string | undefined\n }\n | undefined\n}\n\nfunction formatDrugsFDA(drug: DrugsFDA): FormattedDrugsFDA {\n const latestSubmission = drug.submissions?.[0]\n\n return {\n applicationNumber: drug.application_number,\n sponsorName: drug.sponsor_name,\n products:\n drug.products?.slice(0, 3).map((p) => ({\n brandName: p.brand_name,\n dosageForm: p.dosage_form,\n route: p.route,\n marketingStatus: p.marketing_status,\n activeIngredients:\n p.active_ingredients?.slice(0, 3).map((ai) => ({\n name: ai.name,\n strength: ai.strength,\n })) ?? [],\n })) ?? [],\n latestSubmission: latestSubmission\n ? {\n type: latestSubmission.submission_type,\n status: latestSubmission.submission_status,\n statusDate: latestSubmission.submission_status_date,\n }\n : undefined,\n }\n}\n\nexport async function handleSearchDrugsFDA(params: DrugsFDAParams): Promise<FDAToolResponse<FormattedDrugsFDA[]>> {\n loggers.tools(\"searchDrugsFDA\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"sponsor_name\", value: params.sponsorName },\n { field: \"application_number\", value: params.applicationNumber },\n { field: \"products.brand_name\", value: params.brandName },\n { field: \"products.marketing_status\", value: params.marketingStatus },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugsFDA(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugsFDA),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug Shortages Handler\nexport type DrugShortagesParams = {\n genericName?: string\n status?: \"Current\" | \"Resolved\"\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugShortage = {\n genericName: string | undefined\n proprietaryName: string | undefined\n status: string | undefined\n description: string | undefined\n initialPostingDate: string | undefined\n resolvedShortageDate: string | undefined\n}\n\nfunction formatDrugShortage(shortage: DrugShortage): FormattedDrugShortage {\n return {\n genericName: shortage.generic_name,\n proprietaryName: shortage.proprietary_name,\n status: shortage.status,\n description: truncateText(shortage.description),\n initialPostingDate: shortage.initial_posting_date,\n resolvedShortageDate: shortage.resolved_shortage_date,\n }\n}\n\nexport async function handleSearchDrugShortages(\n params: DrugShortagesParams,\n): Promise<FDAToolResponse<FormattedDrugShortage[]>> {\n loggers.tools(\"searchDrugShortages\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"generic_name\", value: params.genericName },\n { field: \"status\", value: params.status },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugShortages(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugShortage),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{loggers as e}from"./utils/logger.js";import
|
|
2
|
+
import{loggers as e}from"./utils/logger.js";import{createOpenFDAServer as t}from"./server.js";import{Command as n}from"commander";const r=new n;r.name(`openfda-mcp-server`).description(`MCP server for querying U.S. FDA public datasets`).version(`0.1.0`).option(`-t, --transport <type>`,`Transport type (stdio or http)`,`stdio`).option(`-p, --port <number>`,`Port for HTTP transport`,`3000`).option(`--host <host>`,`Host for HTTP transport`,`localhost`).action(async n=>{let{transport:r,port:i,host:a}=n;e.main(`Starting OpenFDA MCP server with transport: ${r}`);let o=t({name:`openfda-mcp-server`,version:`0.1.0`});try{if(r===`http`){let t=parseInt(i,10);e.main(`Starting HTTP server on ${a}:${t}`),await o.start({transportType:`httpStream`,httpStream:{endpoint:`/mcp`,port:t}}),console.error(`OpenFDA MCP server running at http://${a}:${t}/mcp`)}else e.main(`Starting stdio server`),await o.start({transportType:`stdio`})}catch(e){console.error(`Failed to start server:`,e),process.exit(1)}}),r.parse();export{};
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * OpenFDA MCP Server CLI\n * Command-line interface for starting the MCP server\n */\n\nimport { Command } from \"commander\"\n\nimport { createOpenFDAServer } from \"./server.js\"\nimport { loggers } from \"./utils/logger.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"openfda-mcp-server\")\n .description(\"MCP server for querying U.S. FDA public datasets\")\n .version(\"0.1.0\")\n .option(\"-t, --transport <type>\", \"Transport type (stdio or http)\", \"stdio\")\n .option(\"-p, --port <number>\", \"Port for HTTP transport\", \"3000\")\n .option(\"--host <host>\", \"Host for HTTP transport\", \"localhost\")\n .action(async (options) => {\n const { transport, port, host } = options\n\n loggers.main(`Starting OpenFDA MCP server with transport: ${transport}`)\n\n const server = createOpenFDAServer({\n name: \"openfda-mcp-server\",\n version: \"0.1.0\" as `${number}.${number}.${number}`,\n })\n\n try {\n if (transport === \"http\") {\n const portNum = parseInt(port, 10)\n loggers.main(`Starting HTTP server on ${host}:${portNum}`)\n\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n endpoint: \"/mcp\" as `/${string}`,\n port: portNum,\n },\n })\n\n console.error(`OpenFDA MCP server running at http://${host}:${portNum}/mcp`)\n } else {\n loggers.main(\"Starting stdio server\")\n\n await server.start({\n transportType: \"stdio\",\n })\n }\n } catch (error) {\n console.error(\"Failed to start server:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * OpenFDA MCP Server CLI\n * Command-line interface for starting the MCP server\n */\n\nimport { Command } from \"commander\"\n\nimport { createOpenFDAServer } from \"./server.js\"\nimport { loggers } from \"./utils/logger.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"openfda-mcp-server\")\n .description(\"MCP server for querying U.S. FDA public datasets\")\n .version(\"0.1.0\")\n .option(\"-t, --transport <type>\", \"Transport type (stdio or http)\", \"stdio\")\n .option(\"-p, --port <number>\", \"Port for HTTP transport\", \"3000\")\n .option(\"--host <host>\", \"Host for HTTP transport\", \"localhost\")\n .action(async (options) => {\n const { transport, port, host } = options\n\n loggers.main(`Starting OpenFDA MCP server with transport: ${transport}`)\n\n const server = createOpenFDAServer({\n name: \"openfda-mcp-server\",\n version: \"0.1.0\" as `${number}.${number}.${number}`,\n })\n\n try {\n if (transport === \"http\") {\n const portNum = parseInt(port, 10)\n loggers.main(`Starting HTTP server on ${host}:${portNum}`)\n\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n endpoint: \"/mcp\" as `/${string}`,\n port: portNum,\n },\n })\n\n console.error(`OpenFDA MCP server running at http://${host}:${portNum}/mcp`)\n } else {\n loggers.main(\"Starting stdio server\")\n\n await server.start({\n transportType: \"stdio\",\n })\n }\n } catch (error) {\n console.error(\"Failed to start server:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":";kIAWA,MAAM,EAAU,IAAI,EAEpB,EACG,KAAK,qBAAqB,CAC1B,YAAY,mDAAmD,CAC/D,QAAQ,QAAQ,CAChB,OAAO,yBAA0B,iCAAkC,QAAQ,CAC3E,OAAO,sBAAuB,0BAA2B,OAAO,CAChE,OAAO,gBAAiB,0BAA2B,YAAY,CAC/D,OAAO,KAAO,IAAY,CACzB,GAAM,CAAE,YAAW,OAAM,QAAS,EAElC,EAAQ,KAAK,+CAA+C,IAAY,CAExE,IAAM,EAAS,EAAoB,CACjC,KAAM,qBACN,QAAS,QACV,CAAC,CAEF,GAAI,CACF,GAAI,IAAc,OAAQ,CACxB,IAAM,EAAU,SAAS,EAAM,GAAG,CAClC,EAAQ,KAAK,2BAA2B,EAAK,GAAG,IAAU,CAE1D,MAAM,EAAO,MAAM,CACjB,cAAe,aACf,WAAY,CACV,SAAU,OACV,KAAM,EACP,CACF,CAAC,CAEF,QAAQ,MAAM,wCAAwC,EAAK,GAAG,EAAQ,MAAM,MAE5E,EAAQ,KAAK,wBAAwB,CAErC,MAAM,EAAO,MAAM,CACjB,cAAe,QAChB,CAAC,OAEG,EAAO,CACd,QAAQ,MAAM,0BAA2B,EAAM,CAC/C,QAAQ,KAAK,EAAE,GAEjB,CAEJ,EAAQ,OAAO"}
|
package/dist/lib.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Device510K, DeviceAdverseEvent, DeviceClassification, DeviceEnforcement, DrugAdverseEvent, DrugEnforcement, DrugLabel, DrugNDC, DrugShortage, DrugsFDA, FDAResponse, FDAToolResponse, OpenFDAFields, SearchParams } from "./types/fda.js";
|
|
1
|
+
import { Device510K, DeviceAdverseEvent, DeviceClassification, DeviceEnforcement, DrugAdverseEvent, DrugEnforcement, DrugLabel, DrugNDC, DrugShortage, DrugsFDA, FDAResponse, FDAToolResponse, OpenFDAFields, OrangeBookExclusivity, OrangeBookPatent, OrangeBookProduct, PurpleBookEntry, SearchParams } from "./types/fda.js";
|
|
2
|
+
import { DrugPatentExpiryParams, OrangeBookPatentsParams, OrangeBookSearchParams, PurpleBookSearchParams, handleSearchDrugPatentExpiry, handleSearchOrangeBook, handleSearchOrangeBookPatents, handleSearchPurpleBook } from "./handlers/bulk-data-handlers.js";
|
|
2
3
|
import { Device510KParams, DeviceAdverseEventsParams, DeviceClassificationParams, DeviceEnforcementParams, handleSearchDevice510K, handleSearchDeviceAdverseEvents, handleSearchDeviceClassifications, handleSearchDeviceEnforcement } from "./handlers/device-handlers.js";
|
|
3
4
|
import { DrugAdverseEventsParams, DrugEnforcementParams, DrugLabelsParams, DrugNDCParams, DrugShortagesParams, DrugsFDAParams, handleSearchDrugAdverseEvents, handleSearchDrugEnforcement, handleSearchDrugLabels, handleSearchDrugNDC, handleSearchDrugShortages, handleSearchDrugsFDA } from "./handlers/drug-handlers.js";
|
|
4
5
|
import { ServerOptions, createOpenFDAServer } from "./server.js";
|
|
5
6
|
import { toolSchemas } from "./tools/index.js";
|
|
6
7
|
import { FDAAPIClient, fdaAPIClient } from "./utils/api-client.js";
|
|
8
|
+
import { BulkDataClient, bulkDataClient } from "./utils/bulk-data-client.js";
|
|
7
9
|
import { loggers } from "./utils/logger.js";
|
|
8
|
-
export { type Device510K, type Device510KParams, type DeviceAdverseEvent, type DeviceAdverseEventsParams, type DeviceClassification, type DeviceClassificationParams, type DeviceEnforcement, type DeviceEnforcementParams, type DrugAdverseEvent, type DrugAdverseEventsParams, type DrugEnforcement, type DrugEnforcementParams, type DrugLabel, type DrugLabelsParams, type DrugNDC, type DrugNDCParams, type DrugShortage, type DrugShortagesParams, type DrugsFDA, type DrugsFDAParams, FDAAPIClient, type FDAResponse, type FDAToolResponse, type OpenFDAFields, type SearchParams, type ServerOptions, createOpenFDAServer, fdaAPIClient, handleSearchDevice510K, handleSearchDeviceAdverseEvents, handleSearchDeviceClassifications, handleSearchDeviceEnforcement, handleSearchDrugAdverseEvents, handleSearchDrugEnforcement, handleSearchDrugLabels, handleSearchDrugNDC, handleSearchDrugShortages, handleSearchDrugsFDA, loggers, toolSchemas };
|
|
10
|
+
export { BulkDataClient, type Device510K, type Device510KParams, type DeviceAdverseEvent, type DeviceAdverseEventsParams, type DeviceClassification, type DeviceClassificationParams, type DeviceEnforcement, type DeviceEnforcementParams, type DrugAdverseEvent, type DrugAdverseEventsParams, type DrugEnforcement, type DrugEnforcementParams, type DrugLabel, type DrugLabelsParams, type DrugNDC, type DrugNDCParams, type DrugPatentExpiryParams, type DrugShortage, type DrugShortagesParams, type DrugsFDA, type DrugsFDAParams, FDAAPIClient, type FDAResponse, type FDAToolResponse, type OpenFDAFields, type OrangeBookExclusivity, type OrangeBookPatent, type OrangeBookPatentsParams, type OrangeBookProduct, type OrangeBookSearchParams, type PurpleBookEntry, type PurpleBookSearchParams, type SearchParams, type ServerOptions, bulkDataClient, createOpenFDAServer, fdaAPIClient, handleSearchDevice510K, handleSearchDeviceAdverseEvents, handleSearchDeviceClassifications, handleSearchDeviceEnforcement, handleSearchDrugAdverseEvents, handleSearchDrugEnforcement, handleSearchDrugLabels, handleSearchDrugNDC, handleSearchDrugPatentExpiry, handleSearchDrugShortages, handleSearchDrugsFDA, handleSearchOrangeBook, handleSearchOrangeBookPatents, handleSearchPurpleBook, loggers, toolSchemas };
|
package/dist/lib.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{loggers as e}from"./utils/logger.js";import{
|
|
1
|
+
import{loggers as e}from"./utils/logger.js";import{BulkDataClient as t,bulkDataClient as n}from"./utils/bulk-data-client.js";import{handleSearchDrugPatentExpiry as r,handleSearchOrangeBook as i,handleSearchOrangeBookPatents as a,handleSearchPurpleBook as o}from"./handlers/bulk-data-handlers.js";import{FDAAPIClient as s,fdaAPIClient as c}from"./utils/api-client.js";import{handleSearchDevice510K as l,handleSearchDeviceAdverseEvents as u,handleSearchDeviceClassifications as d,handleSearchDeviceEnforcement as f}from"./handlers/device-handlers.js";import{handleSearchDrugAdverseEvents as p,handleSearchDrugEnforcement as m,handleSearchDrugLabels as h,handleSearchDrugNDC as g,handleSearchDrugShortages as _,handleSearchDrugsFDA as v}from"./handlers/drug-handlers.js";import{toolSchemas as y}from"./tools/index.js";import{createOpenFDAServer as b}from"./server.js";export{t as BulkDataClient,s as FDAAPIClient,n as bulkDataClient,b as createOpenFDAServer,c as fdaAPIClient,l as handleSearchDevice510K,u as handleSearchDeviceAdverseEvents,d as handleSearchDeviceClassifications,f as handleSearchDeviceEnforcement,p as handleSearchDrugAdverseEvents,m as handleSearchDrugEnforcement,h as handleSearchDrugLabels,g as handleSearchDrugNDC,r as handleSearchDrugPatentExpiry,_ as handleSearchDrugShortages,v as handleSearchDrugsFDA,i as handleSearchOrangeBook,a as handleSearchOrangeBookPatents,o as handleSearchPurpleBook,e as loggers,y as toolSchemas};
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{loggers as e}from"./utils/logger.js";import"./
|
|
1
|
+
import{loggers as e}from"./utils/logger.js";import{handleSearchDrugPatentExpiry as t,handleSearchOrangeBook as n,handleSearchOrangeBookPatents as r,handleSearchPurpleBook as i}from"./handlers/bulk-data-handlers.js";import{handleSearchDevice510K as a,handleSearchDeviceAdverseEvents as o,handleSearchDeviceClassifications as s,handleSearchDeviceEnforcement as c}from"./handlers/device-handlers.js";import{handleSearchDrugAdverseEvents as l,handleSearchDrugEnforcement as u,handleSearchDrugLabels as d,handleSearchDrugNDC as f,handleSearchDrugShortages as p,handleSearchDrugsFDA as m}from"./handlers/drug-handlers.js";import{searchDevice510KSchema as h,searchDeviceAdverseEventsSchema as g,searchDeviceClassificationsSchema as _,searchDeviceEnforcementSchema as v,searchDrugAdverseEventsSchema as y,searchDrugEnforcementSchema as b,searchDrugLabelsSchema as x,searchDrugNDCSchema as S,searchDrugPatentExpirySchema as C,searchDrugShortagesSchema as w,searchDrugsFDASchema as T,searchOrangeBookPatentsSchema as E,searchOrangeBookSchema as D,searchPurpleBookSchema as O}from"./tools/index.js";import{FastMCP as k}from"fastmcp";function A(A={}){let{name:j=`openfda-mcp-server`,version:M=`0.1.0`}=A;e.core(`Creating OpenFDA MCP server: ${j} v${M}`);let N=new k({name:j,version:M});return N.addTool({name:`search_drug_adverse_events`,description:`Search FDA Adverse Event Reporting System (FAERS) for drug safety reports. Find adverse events by drug name, reaction type, manufacturer, date range, and seriousness. Retrieve a specific report by safetyReportId. Returns patient demographics, drug details, and reported reactions.`,parameters:y,execute:async t=>{e.tools(`Executing search_drug_adverse_events`,t);let n=await l(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_drug_labels`,description:`Search FDA drug labeling (SPL) information including prescribing information, indications, warnings, boxed warnings, dosage, and active ingredients. Retrieve a specific label by setId. Filter for drugs with boxed warnings using hasBoxedWarning. Request specific sections (e.g., indications_and_usage, adverse_reactions, boxed_warning) to limit response.`,parameters:x,execute:async t=>{e.tools(`Executing search_drug_labels`,t);let n=await d(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_drug_ndc`,description:`Search the National Drug Code (NDC) Directory for drug product identification. Find drugs by NDC, brand name, generic name, labeler, dosage form, or route. Returns product details, active ingredients, and packaging information.`,parameters:S,execute:async t=>{e.tools(`Executing search_drug_ndc`,t);let n=await f(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_drug_recalls`,description:`Search FDA drug recall and enforcement reports. Find recalls by company, classification (I-III), status, state, and date range. Class I is most serious (may cause death), Class III is least serious.`,parameters:b,execute:async t=>{e.tools(`Executing search_drug_recalls`,t);let n=await u(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_drugs_at_fda`,description:`Search the Drugs@FDA database for approved drug applications. Find approved drugs by sponsor, application number, brand name, or marketing status. Returns application details, products, and submission history.`,parameters:T,execute:async t=>{e.tools(`Executing search_drugs_at_fda`,t);let n=await m(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_drug_shortages`,description:`Search the FDA Drug Shortage Database for current and resolved drug shortages. Find shortages by generic drug name or status (Current/Resolved). Returns shortage details, posting dates, and resolution information.`,parameters:w,execute:async t=>{e.tools(`Executing search_drug_shortages`,t);let n=await p(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_device_510k`,description:`Search FDA 510(k) premarket notification database for medical device clearances. Find device clearances by device name, applicant, product code, clearance type, or decision date. Returns clearance decisions and device classification details.`,parameters:h,execute:async t=>{e.tools(`Executing search_device_510k`,t);let n=await a(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_device_classifications`,description:`Search FDA medical device classification database. Find devices by name, class (1-3), medical specialty, product code, or regulation number. Class 1 is lowest risk, Class 3 is highest risk (implants, life-sustaining).`,parameters:_,execute:async t=>{e.tools(`Executing search_device_classifications`,t);let n=await s(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_device_adverse_events`,description:`Search FDA Medical Device Report (MDR) database for device adverse events. Find adverse events by device name, brand, manufacturer, event type (Injury/Death/Malfunction), and date. Retrieve a specific MDR report by reportNumber. Returns device details, event descriptions, and patient outcomes.`,parameters:g,execute:async t=>{e.tools(`Executing search_device_adverse_events`,t);let n=await o(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_device_recalls`,description:`Search FDA medical device recall and enforcement reports. Find recalls by company, product description, classification (I-III), status, and date. Class I is most serious (may cause death), Class III is least serious.`,parameters:v,execute:async t=>{e.tools(`Executing search_device_recalls`,t);let n=await c(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_fda_orange_book`,description:`Search FDA Orange Book for approved drug products with therapeutic equivalence evaluations. Find drugs by trade name, ingredient, applicant, application number, or TE code. Returns product details, patent counts, and exclusivity counts. Data is downloaded from FDA bulk files and cached for 24 hours.`,parameters:D,execute:async t=>{e.tools(`Executing search_fda_orange_book`,t);let r=await n(t);return JSON.stringify(r,null,2)}}),N.addTool({name:`search_fda_orange_book_patents`,description:`Search FDA Orange Book patent data for drug products. Find patents by drug name, application number, or patent number. Returns patent details including expiry dates, substance/product/use flags, and associated exclusivities.`,parameters:E,execute:async t=>{e.tools(`Executing search_fda_orange_book_patents`,t);let n=await r(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_fda_purple_book`,description:`Search FDA Purple Book for licensed biological products. Find biologics by product name, applicant, BLA number, license type (351(a)/351(k)), biosimilar status, or interchangeability. Returns product details, licensing status, and reference product information.`,parameters:O,execute:async t=>{e.tools(`Executing search_fda_purple_book`,t);let n=await i(t);return JSON.stringify(n,null,2)}}),N.addTool({name:`search_fda_drug_patent_expiry`,description:`Unified patent expiry and exclusivity view across FDA Orange Book and optionally Purple Book. Search by drug name or application number. Returns patents sorted by earliest expiry date with associated exclusivity data. Set includePurpleBook=true to also include biologics data.`,parameters:C,execute:async n=>{e.tools(`Executing search_fda_drug_patent_expiry`,n);let r=await t(n);return JSON.stringify(r,null,2)}}),e.core(`OpenFDA MCP server configured with 14 tools`),N}export{A as createOpenFDAServer};
|
|
2
2
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["/**\n * OpenFDA MCP Server\n * FastMCP server implementation with FDA tools\n */\n\nimport { FastMCP } from \"@jordanburke/fastmcp\"\n\nimport {\n handleSearchDevice510K,\n handleSearchDeviceAdverseEvents,\n handleSearchDeviceClassifications,\n handleSearchDeviceEnforcement,\n} from \"./handlers/device-handlers.js\"\nimport {\n handleSearchDrugAdverseEvents,\n handleSearchDrugEnforcement,\n handleSearchDrugLabels,\n handleSearchDrugNDC,\n handleSearchDrugsFDA,\n handleSearchDrugShortages,\n} from \"./handlers/drug-handlers.js\"\nimport {\n searchDevice510KSchema,\n searchDeviceAdverseEventsSchema,\n searchDeviceClassificationsSchema,\n searchDeviceEnforcementSchema,\n searchDrugAdverseEventsSchema,\n searchDrugEnforcementSchema,\n searchDrugLabelsSchema,\n searchDrugNDCSchema,\n searchDrugsFDASchema,\n searchDrugShortagesSchema,\n} from \"./tools/index.js\"\nimport { loggers } from \"./utils/logger.js\"\n\nexport type ServerOptions = {\n name?: string\n version?: `${number}.${number}.${number}`\n}\n\n/**\n * Create and configure the OpenFDA MCP server\n */\nexport function createOpenFDAServer(options: ServerOptions = {}): FastMCP {\n const { name = \"openfda-mcp-server\", version = \"0.1.0\" as `${number}.${number}.${number}` } = options\n\n loggers.core(`Creating OpenFDA MCP server: ${name} v${version}`)\n\n const server = new FastMCP({\n name,\n version,\n })\n\n // Register Drug Tools\n\n server.addTool({\n name: \"search_drug_adverse_events\",\n description:\n \"Search FDA Adverse Event Reporting System (FAERS) for drug safety reports. \" +\n \"Find adverse events by drug name, reaction type, manufacturer, date range, and seriousness. \" +\n \"Retrieve a specific report by safetyReportId. \" +\n \"Returns patient demographics, drug details, and reported reactions.\",\n parameters: searchDrugAdverseEventsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_adverse_events\", args)\n const result = await handleSearchDrugAdverseEvents(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_labels\",\n description:\n \"Search FDA drug labeling (SPL) information including prescribing information, \" +\n \"indications, warnings, boxed warnings, dosage, and active ingredients. \" +\n \"Retrieve a specific label by setId. Filter for drugs with boxed warnings using hasBoxedWarning. \" +\n \"Request specific sections (e.g., indications_and_usage, adverse_reactions, boxed_warning) to limit response.\",\n parameters: searchDrugLabelsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_labels\", args)\n const result = await handleSearchDrugLabels(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_ndc\",\n description:\n \"Search the National Drug Code (NDC) Directory for drug product identification. \" +\n \"Find drugs by NDC, brand name, generic name, labeler, dosage form, or route. \" +\n \"Returns product details, active ingredients, and packaging information.\",\n parameters: searchDrugNDCSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_ndc\", args)\n const result = await handleSearchDrugNDC(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_recalls\",\n description:\n \"Search FDA drug recall and enforcement reports. \" +\n \"Find recalls by company, classification (I-III), status, state, and date range. \" +\n \"Class I is most serious (may cause death), Class III is least serious.\",\n parameters: searchDrugEnforcementSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_recalls\", args)\n const result = await handleSearchDrugEnforcement(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drugs_at_fda\",\n description:\n \"Search the Drugs@FDA database for approved drug applications. \" +\n \"Find approved drugs by sponsor, application number, brand name, or marketing status. \" +\n \"Returns application details, products, and submission history.\",\n parameters: searchDrugsFDASchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drugs_at_fda\", args)\n const result = await handleSearchDrugsFDA(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_shortages\",\n description:\n \"Search the FDA Drug Shortage Database for current and resolved drug shortages. \" +\n \"Find shortages by generic drug name or status (Current/Resolved). \" +\n \"Returns shortage details, posting dates, and resolution information.\",\n parameters: searchDrugShortagesSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_shortages\", args)\n const result = await handleSearchDrugShortages(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n // Register Device Tools\n\n server.addTool({\n name: \"search_device_510k\",\n description:\n \"Search FDA 510(k) premarket notification database for medical device clearances. \" +\n \"Find device clearances by device name, applicant, product code, clearance type, or decision date. \" +\n \"Returns clearance decisions and device classification details.\",\n parameters: searchDevice510KSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_510k\", args)\n const result = await handleSearchDevice510K(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_device_classifications\",\n description:\n \"Search FDA medical device classification database. \" +\n \"Find devices by name, class (1-3), medical specialty, product code, or regulation number. \" +\n \"Class 1 is lowest risk, Class 3 is highest risk (implants, life-sustaining).\",\n parameters: searchDeviceClassificationsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_classifications\", args)\n const result = await handleSearchDeviceClassifications(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_device_adverse_events\",\n description:\n \"Search FDA Medical Device Report (MDR) database for device adverse events. \" +\n \"Find adverse events by device name, brand, manufacturer, event type (Injury/Death/Malfunction), and date. \" +\n \"Retrieve a specific MDR report by reportNumber. \" +\n \"Returns device details, event descriptions, and patient outcomes.\",\n parameters: searchDeviceAdverseEventsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_adverse_events\", args)\n const result = await handleSearchDeviceAdverseEvents(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_device_recalls\",\n description:\n \"Search FDA medical device recall and enforcement reports. \" +\n \"Find recalls by company, product description, classification (I-III), status, and date. \" +\n \"Class I is most serious (may cause death), Class III is least serious.\",\n parameters: searchDeviceEnforcementSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_recalls\", args)\n const result = await handleSearchDeviceEnforcement(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n loggers.core(\"OpenFDA MCP server configured with 10 tools\")\n\n return server\n}\n"],"mappings":"8WA2CA,SAAgB,EAAoB,EAAyB,EAAE,CAAW,CACxE,GAAM,CAAE,OAAO,qBAAsB,UAAU,SAA+C,EAE9F,EAAQ,KAAK,gCAAgC,EAAK,IAAI,IAAU,CAEhE,IAAM,EAAS,IAAI,EAAQ,CACzB,OACA,UACD,CAAC,CAuJF,OAnJA,EAAO,QAAQ,CACb,KAAM,6BACN,YACE,2RAIF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,uCAAwC,EAAK,CAC3D,IAAM,EAAS,MAAM,EAA8B,EAAK,CACxD,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,qBACN,YACE,oWAIF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,+BAAgC,EAAK,CACnD,IAAM,EAAS,MAAM,EAAuB,EAAK,CACjD,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,kBACN,YACE,sOAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,4BAA6B,EAAK,CAChD,IAAM,EAAS,MAAM,EAAoB,EAAK,CAC9C,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,sBACN,YACE,yMAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,gCAAiC,EAAK,CACpD,IAAM,EAAS,MAAM,EAA4B,EAAK,CACtD,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,sBACN,YACE,oNAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,gCAAiC,EAAK,CACpD,IAAM,EAAS,MAAM,EAAqB,EAAK,CAC/C,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,wBACN,YACE,wNAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,kCAAmC,EAAK,CACtD,IAAM,EAAS,MAAM,EAA0B,EAAK,CACpD,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAIF,EAAO,QAAQ,CACb,KAAM,qBACN,YACE,oPAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,+BAAgC,EAAK,CACnD,IAAM,EAAS,MAAM,EAAuB,EAAK,CACjD,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,gCACN,YACE,4NAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,0CAA2C,EAAK,CAC9D,IAAM,EAAS,MAAM,EAAkC,EAAK,CAC5D,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,+BACN,YACE,ySAIF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,yCAA0C,EAAK,CAC7D,IAAM,EAAS,MAAM,EAAgC,EAAK,CAC1D,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAO,QAAQ,CACb,KAAM,wBACN,YACE,2NAGF,WAAY,EACZ,QAAS,KAAO,IAAS,CACvB,EAAQ,MAAM,kCAAmC,EAAK,CACtD,IAAM,EAAS,MAAM,EAA8B,EAAK,CACxD,OAAO,KAAK,UAAU,EAAQ,KAAM,EAAE,EAEzC,CAAC,CAEF,EAAQ,KAAK,8CAA8C,CAEpD"}
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["/**\n * OpenFDA MCP Server\n * FastMCP server implementation with FDA tools\n */\n\nimport { FastMCP } from \"fastmcp\"\n\nimport {\n handleSearchDrugPatentExpiry,\n handleSearchOrangeBook,\n handleSearchOrangeBookPatents,\n handleSearchPurpleBook,\n} from \"./handlers/bulk-data-handlers.js\"\nimport {\n handleSearchDevice510K,\n handleSearchDeviceAdverseEvents,\n handleSearchDeviceClassifications,\n handleSearchDeviceEnforcement,\n} from \"./handlers/device-handlers.js\"\nimport {\n handleSearchDrugAdverseEvents,\n handleSearchDrugEnforcement,\n handleSearchDrugLabels,\n handleSearchDrugNDC,\n handleSearchDrugsFDA,\n handleSearchDrugShortages,\n} from \"./handlers/drug-handlers.js\"\nimport {\n searchDevice510KSchema,\n searchDeviceAdverseEventsSchema,\n searchDeviceClassificationsSchema,\n searchDeviceEnforcementSchema,\n searchDrugAdverseEventsSchema,\n searchDrugEnforcementSchema,\n searchDrugLabelsSchema,\n searchDrugNDCSchema,\n searchDrugPatentExpirySchema,\n searchDrugsFDASchema,\n searchDrugShortagesSchema,\n searchOrangeBookPatentsSchema,\n searchOrangeBookSchema,\n searchPurpleBookSchema,\n} from \"./tools/index.js\"\nimport { loggers } from \"./utils/logger.js\"\n\nexport type ServerOptions = {\n name?: string\n version?: `${number}.${number}.${number}`\n}\n\n/**\n * Create and configure the OpenFDA MCP server\n */\nexport function createOpenFDAServer(options: ServerOptions = {}): FastMCP {\n const { name = \"openfda-mcp-server\", version = \"0.1.0\" as `${number}.${number}.${number}` } = options\n\n loggers.core(`Creating OpenFDA MCP server: ${name} v${version}`)\n\n const server = new FastMCP({\n name,\n version,\n })\n\n // Register Drug Tools\n\n server.addTool({\n name: \"search_drug_adverse_events\",\n description:\n \"Search FDA Adverse Event Reporting System (FAERS) for drug safety reports. \" +\n \"Find adverse events by drug name, reaction type, manufacturer, date range, and seriousness. \" +\n \"Retrieve a specific report by safetyReportId. \" +\n \"Returns patient demographics, drug details, and reported reactions.\",\n parameters: searchDrugAdverseEventsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_adverse_events\", args)\n const result = await handleSearchDrugAdverseEvents(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_labels\",\n description:\n \"Search FDA drug labeling (SPL) information including prescribing information, \" +\n \"indications, warnings, boxed warnings, dosage, and active ingredients. \" +\n \"Retrieve a specific label by setId. Filter for drugs with boxed warnings using hasBoxedWarning. \" +\n \"Request specific sections (e.g., indications_and_usage, adverse_reactions, boxed_warning) to limit response.\",\n parameters: searchDrugLabelsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_labels\", args)\n const result = await handleSearchDrugLabels(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_ndc\",\n description:\n \"Search the National Drug Code (NDC) Directory for drug product identification. \" +\n \"Find drugs by NDC, brand name, generic name, labeler, dosage form, or route. \" +\n \"Returns product details, active ingredients, and packaging information.\",\n parameters: searchDrugNDCSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_ndc\", args)\n const result = await handleSearchDrugNDC(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_recalls\",\n description:\n \"Search FDA drug recall and enforcement reports. \" +\n \"Find recalls by company, classification (I-III), status, state, and date range. \" +\n \"Class I is most serious (may cause death), Class III is least serious.\",\n parameters: searchDrugEnforcementSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_recalls\", args)\n const result = await handleSearchDrugEnforcement(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drugs_at_fda\",\n description:\n \"Search the Drugs@FDA database for approved drug applications. \" +\n \"Find approved drugs by sponsor, application number, brand name, or marketing status. \" +\n \"Returns application details, products, and submission history.\",\n parameters: searchDrugsFDASchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drugs_at_fda\", args)\n const result = await handleSearchDrugsFDA(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_drug_shortages\",\n description:\n \"Search the FDA Drug Shortage Database for current and resolved drug shortages. \" +\n \"Find shortages by generic drug name or status (Current/Resolved). \" +\n \"Returns shortage details, posting dates, and resolution information.\",\n parameters: searchDrugShortagesSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_drug_shortages\", args)\n const result = await handleSearchDrugShortages(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n // Register Device Tools\n\n server.addTool({\n name: \"search_device_510k\",\n description:\n \"Search FDA 510(k) premarket notification database for medical device clearances. \" +\n \"Find device clearances by device name, applicant, product code, clearance type, or decision date. \" +\n \"Returns clearance decisions and device classification details.\",\n parameters: searchDevice510KSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_510k\", args)\n const result = await handleSearchDevice510K(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_device_classifications\",\n description:\n \"Search FDA medical device classification database. \" +\n \"Find devices by name, class (1-3), medical specialty, product code, or regulation number. \" +\n \"Class 1 is lowest risk, Class 3 is highest risk (implants, life-sustaining).\",\n parameters: searchDeviceClassificationsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_classifications\", args)\n const result = await handleSearchDeviceClassifications(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_device_adverse_events\",\n description:\n \"Search FDA Medical Device Report (MDR) database for device adverse events. \" +\n \"Find adverse events by device name, brand, manufacturer, event type (Injury/Death/Malfunction), and date. \" +\n \"Retrieve a specific MDR report by reportNumber. \" +\n \"Returns device details, event descriptions, and patient outcomes.\",\n parameters: searchDeviceAdverseEventsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_adverse_events\", args)\n const result = await handleSearchDeviceAdverseEvents(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_device_recalls\",\n description:\n \"Search FDA medical device recall and enforcement reports. \" +\n \"Find recalls by company, product description, classification (I-III), status, and date. \" +\n \"Class I is most serious (may cause death), Class III is least serious.\",\n parameters: searchDeviceEnforcementSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_device_recalls\", args)\n const result = await handleSearchDeviceEnforcement(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n // Register Bulk Data Tools (Orange Book & Purple Book)\n\n server.addTool({\n name: \"search_fda_orange_book\",\n description:\n \"Search FDA Orange Book for approved drug products with therapeutic equivalence evaluations. \" +\n \"Find drugs by trade name, ingredient, applicant, application number, or TE code. \" +\n \"Returns product details, patent counts, and exclusivity counts. \" +\n \"Data is downloaded from FDA bulk files and cached for 24 hours.\",\n parameters: searchOrangeBookSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_fda_orange_book\", args)\n const result = await handleSearchOrangeBook(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_fda_orange_book_patents\",\n description:\n \"Search FDA Orange Book patent data for drug products. \" +\n \"Find patents by drug name, application number, or patent number. \" +\n \"Returns patent details including expiry dates, substance/product/use flags, and associated exclusivities.\",\n parameters: searchOrangeBookPatentsSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_fda_orange_book_patents\", args)\n const result = await handleSearchOrangeBookPatents(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_fda_purple_book\",\n description:\n \"Search FDA Purple Book for licensed biological products. \" +\n \"Find biologics by product name, applicant, BLA number, license type (351(a)/351(k)), \" +\n \"biosimilar status, or interchangeability. \" +\n \"Returns product details, licensing status, and reference product information.\",\n parameters: searchPurpleBookSchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_fda_purple_book\", args)\n const result = await handleSearchPurpleBook(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n server.addTool({\n name: \"search_fda_drug_patent_expiry\",\n description:\n \"Unified patent expiry and exclusivity view across FDA Orange Book and optionally Purple Book. \" +\n \"Search by drug name or application number. \" +\n \"Returns patents sorted by earliest expiry date with associated exclusivity data. \" +\n \"Set includePurpleBook=true to also include biologics data.\",\n parameters: searchDrugPatentExpirySchema,\n execute: async (args) => {\n loggers.tools(\"Executing search_fda_drug_patent_expiry\", args)\n const result = await handleSearchDrugPatentExpiry(args)\n return JSON.stringify(result, null, 2)\n },\n })\n\n loggers.core(\"OpenFDA MCP server configured with 14 tools\")\n\n return server\n}\n"],"mappings":"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"}
|