@cyanheads/pubmed-mcp-server 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +174 -0
- package/dist/config/index.d.ts +91 -0
- package/dist/config/index.js +324 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +190 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +79 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +82 -0
- package/dist/mcp-server/resources/echoResource/index.d.ts +13 -0
- package/dist/mcp-server/resources/echoResource/index.js +13 -0
- package/dist/mcp-server/resources/echoResource/registration.d.ts +30 -0
- package/dist/mcp-server/resources/echoResource/registration.js +168 -0
- package/dist/mcp-server/server.d.ts +28 -0
- package/dist/mcp-server/server.js +159 -0
- package/dist/mcp-server/tools/fetchPubMedContent/index.d.ts +6 -0
- package/dist/mcp-server/tools/fetchPubMedContent/index.js +6 -0
- package/dist/mcp-server/tools/fetchPubMedContent/logic.d.ts +63 -0
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +518 -0
- package/dist/mcp-server/tools/fetchPubMedContent/registration.d.ts +10 -0
- package/dist/mcp-server/tools/fetchPubMedContent/registration.js +37 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/index.d.ts +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/index.js +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +9 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +288 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.d.ts +9 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +208 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.d.ts +15 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +82 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +27 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.js +5 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.d.ts +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.js +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +36 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +76 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/index.d.ts +6 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/index.js +6 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/index.d.ts +7 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/index.js +9 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.d.ts +111 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.js +149 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/outputTypes.d.ts +106 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/outputTypes.js +7 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.d.ts +10 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +251 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +12 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +55 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.d.ts +10 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +36 -0
- package/dist/mcp-server/tools/searchPubMedArticles/index.d.ts +6 -0
- package/dist/mcp-server/tools/searchPubMedArticles/index.js +6 -0
- package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +61 -0
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +229 -0
- package/dist/mcp-server/tools/searchPubMedArticles/registration.d.ts +10 -0
- package/dist/mcp-server/tools/searchPubMedArticles/registration.js +38 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +29 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.js +174 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +23 -0
- package/dist/mcp-server/transports/httpTransport.js +463 -0
- package/dist/mcp-server/transports/stdioTransport.d.ts +42 -0
- package/dist/mcp-server/transports/stdioTransport.js +63 -0
- package/dist/services/NCBI/ncbiConstants.d.ts +22 -0
- package/dist/services/NCBI/ncbiConstants.js +5 -0
- package/dist/services/NCBI/ncbiCoreApiClient.d.ts +24 -0
- package/dist/services/NCBI/ncbiCoreApiClient.js +118 -0
- package/dist/services/NCBI/ncbiRequestQueueManager.d.ts +36 -0
- package/dist/services/NCBI/ncbiRequestQueueManager.js +96 -0
- package/dist/services/NCBI/ncbiResponseHandler.d.ts +22 -0
- package/dist/services/NCBI/ncbiResponseHandler.js +192 -0
- package/dist/services/NCBI/ncbiService.d.ts +24 -0
- package/dist/services/NCBI/ncbiService.js +57 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.js +7 -0
- package/dist/services/llm-providers/index.d.ts +7 -0
- package/dist/services/llm-providers/index.js +7 -0
- package/dist/services/llm-providers/llmFactory.d.ts +69 -0
- package/dist/services/llm-providers/llmFactory.js +132 -0
- package/dist/services/llm-providers/openRouter/index.d.ts +6 -0
- package/dist/services/llm-providers/openRouter/index.js +7 -0
- package/dist/services/llm-providers/openRouter/openRouterProvider.d.ts +99 -0
- package/dist/services/llm-providers/openRouter/openRouterProvider.js +329 -0
- package/dist/types-global/errors.d.ts +119 -0
- package/dist/types-global/errors.js +119 -0
- package/dist/types-global/pubmedXml.d.ts +350 -0
- package/dist/types-global/pubmedXml.js +7 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/internal/errorHandler.d.ts +176 -0
- package/dist/utils/internal/errorHandler.js +335 -0
- package/dist/utils/internal/index.d.ts +9 -0
- package/dist/utils/internal/index.js +9 -0
- package/dist/utils/internal/logger.d.ts +141 -0
- package/dist/utils/internal/logger.js +426 -0
- package/dist/utils/internal/requestContext.d.ts +83 -0
- package/dist/utils/internal/requestContext.js +72 -0
- package/dist/utils/metrics/index.d.ts +7 -0
- package/dist/utils/metrics/index.js +7 -0
- package/dist/utils/metrics/tokenCounter.d.ts +35 -0
- package/dist/utils/metrics/tokenCounter.js +110 -0
- package/dist/utils/parsing/dateParser.d.ts +73 -0
- package/dist/utils/parsing/dateParser.js +107 -0
- package/dist/utils/parsing/index.d.ts +8 -0
- package/dist/utils/parsing/index.js +8 -0
- package/dist/utils/parsing/jsonParser.d.ts +82 -0
- package/dist/utils/parsing/jsonParser.js +126 -0
- package/dist/utils/parsing/ncbi-parsing/eSummaryResultParser.d.ts +32 -0
- package/dist/utils/parsing/ncbi-parsing/eSummaryResultParser.js +295 -0
- package/dist/utils/parsing/ncbi-parsing/index.d.ts +8 -0
- package/dist/utils/parsing/ncbi-parsing/index.js +8 -0
- package/dist/utils/parsing/ncbi-parsing/pubmedArticleStructureParser.d.ts +70 -0
- package/dist/utils/parsing/ncbi-parsing/pubmedArticleStructureParser.js +229 -0
- package/dist/utils/parsing/ncbi-parsing/xmlGenericHelpers.d.ts +31 -0
- package/dist/utils/parsing/ncbi-parsing/xmlGenericHelpers.js +76 -0
- package/dist/utils/security/idGenerator.d.ts +134 -0
- package/dist/utils/security/idGenerator.js +198 -0
- package/dist/utils/security/index.d.ts +9 -0
- package/dist/utils/security/index.js +9 -0
- package/dist/utils/security/rateLimiter.d.ts +108 -0
- package/dist/utils/security/rateLimiter.js +179 -0
- package/dist/utils/security/sanitization.d.ts +173 -0
- package/dist/utils/security/sanitization.js +439 -0
- package/package.json +97 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handles citation formatting for the getPubMedArticleConnections tool.
|
|
3
|
+
* Fetches article details using EFetch and formats them into various citation styles.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter
|
|
5
|
+
*/
|
|
6
|
+
import { ncbiService } from "../../../../services/NCBI/ncbiService.js";
|
|
7
|
+
import { logger, requestContextService, } from "../../../../utils/index.js";
|
|
8
|
+
import { extractAuthors, extractDoi, extractJournalInfo, extractPmid, getText, } from "../../../../utils/parsing/ncbi-parsing/index.js";
|
|
9
|
+
// Main handler for citation formats
|
|
10
|
+
export async function handleCitationFormats(input, outputData, context) {
|
|
11
|
+
const eFetchParams = {
|
|
12
|
+
db: "pubmed",
|
|
13
|
+
id: input.sourcePmid,
|
|
14
|
+
retmode: "xml",
|
|
15
|
+
// Omitting rettype to hopefully get the fullest XML record by default
|
|
16
|
+
};
|
|
17
|
+
const eFetchBaseUrl = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi";
|
|
18
|
+
const searchParamsString = new URLSearchParams(eFetchParams).toString();
|
|
19
|
+
outputData.eUtilityUrl = `${eFetchBaseUrl}?${searchParamsString}`;
|
|
20
|
+
const eFetchResult = await ncbiService.eFetch(eFetchParams, context);
|
|
21
|
+
if (!eFetchResult?.PubmedArticleSet?.PubmedArticle?.[0]) {
|
|
22
|
+
outputData.message =
|
|
23
|
+
"Could not retrieve article details for citation formatting.";
|
|
24
|
+
logger.warning(outputData.message, context);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const article = eFetchResult.PubmedArticleSet.PubmedArticle[0];
|
|
28
|
+
if (input.citationStyles?.includes("ris")) {
|
|
29
|
+
outputData.citations.ris = formatAsRIS(article, context);
|
|
30
|
+
}
|
|
31
|
+
if (input.citationStyles?.includes("bibtex")) {
|
|
32
|
+
outputData.citations.bibtex = formatAsBibTeX(article, context);
|
|
33
|
+
}
|
|
34
|
+
if (input.citationStyles?.includes("apa_string")) {
|
|
35
|
+
outputData.citations.apa_string = formatAsAPA(article, context);
|
|
36
|
+
}
|
|
37
|
+
if (input.citationStyles?.includes("mla_string")) {
|
|
38
|
+
outputData.citations.mla_string = formatAsMLA(article, context);
|
|
39
|
+
}
|
|
40
|
+
outputData.retrievedCount = 1;
|
|
41
|
+
}
|
|
42
|
+
// --- Citation Formatting Helper Functions ---
|
|
43
|
+
function formatAsRIS(article, context) {
|
|
44
|
+
const medlineCitation = article.MedlineCitation;
|
|
45
|
+
const articleDetails = medlineCitation?.Article;
|
|
46
|
+
const pmid = extractPmid(medlineCitation);
|
|
47
|
+
logger.debug("Formatting RIS for article", requestContextService.createRequestContext({ ...context, pmid }));
|
|
48
|
+
if (!articleDetails)
|
|
49
|
+
return "TY - ERROR\nTI - Article details not found\nER - \n";
|
|
50
|
+
const authors = extractAuthors(articleDetails.AuthorList);
|
|
51
|
+
const journalInfo = extractJournalInfo(articleDetails.Journal, medlineCitation);
|
|
52
|
+
const title = getText(articleDetails.ArticleTitle);
|
|
53
|
+
const doi = extractDoi(articleDetails);
|
|
54
|
+
let risString = "TY - JOUR\n";
|
|
55
|
+
authors.forEach((author) => {
|
|
56
|
+
if (author.lastName && author.firstName) {
|
|
57
|
+
risString += `AU - ${author.lastName}, ${author.firstName}\n`;
|
|
58
|
+
}
|
|
59
|
+
else if (author.lastName) {
|
|
60
|
+
// Handle collective name or incomplete author
|
|
61
|
+
risString += `AU - ${author.lastName}\n`;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
risString += `TI - ${title || "N/A"}\n`;
|
|
65
|
+
risString += `JO - ${journalInfo?.title || "N/A"}\n`;
|
|
66
|
+
risString += `VL - ${journalInfo?.volume || ""}\n`;
|
|
67
|
+
risString += `IS - ${journalInfo?.issue || ""}\n`;
|
|
68
|
+
if (journalInfo?.pages) {
|
|
69
|
+
const pages = journalInfo.pages.split("-");
|
|
70
|
+
risString += `SP - ${pages[0] || ""}\n`;
|
|
71
|
+
if (pages.length > 1)
|
|
72
|
+
risString += `EP - ${pages[1] || ""}\n`;
|
|
73
|
+
}
|
|
74
|
+
risString += `PY - ${journalInfo?.publicationDate?.year || ""}\n`;
|
|
75
|
+
if (doi)
|
|
76
|
+
risString += `DO - ${doi}\n`;
|
|
77
|
+
if (pmid)
|
|
78
|
+
risString += `UR - https://pubmed.ncbi.nlm.nih.gov/${pmid}\n`;
|
|
79
|
+
risString += "ER - \n";
|
|
80
|
+
return risString;
|
|
81
|
+
}
|
|
82
|
+
function formatAsBibTeX(article, context) {
|
|
83
|
+
const medlineCitation = article.MedlineCitation;
|
|
84
|
+
const articleDetails = medlineCitation?.Article;
|
|
85
|
+
const pmid = extractPmid(medlineCitation);
|
|
86
|
+
logger.debug("Formatting BibTeX for article", requestContextService.createRequestContext({ ...context, pmid }));
|
|
87
|
+
if (!articleDetails)
|
|
88
|
+
return `@article{ERROR_ArticleNotFound${pmid || ""},\n title = {Article details not found}\n}\n`;
|
|
89
|
+
const parsedAuthors = extractAuthors(articleDetails.AuthorList);
|
|
90
|
+
const journalInfo = extractJournalInfo(articleDetails.Journal, medlineCitation);
|
|
91
|
+
const title = getText(articleDetails.ArticleTitle) || "N/A";
|
|
92
|
+
const doi = extractDoi(articleDetails);
|
|
93
|
+
const authorsBibtex = parsedAuthors
|
|
94
|
+
.map((author) => {
|
|
95
|
+
if (author.lastName && author.firstName)
|
|
96
|
+
return `${author.lastName}, ${author.firstName}`;
|
|
97
|
+
if (author.lastName)
|
|
98
|
+
return `{${author.lastName}}`; // For collective names or single names
|
|
99
|
+
return "";
|
|
100
|
+
})
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.join(" and ");
|
|
103
|
+
const year = journalInfo?.publicationDate?.year || "ND";
|
|
104
|
+
const firstAuthorLastName = parsedAuthors[0]?.lastName?.replace(/\s+/g, "") || "Unknown";
|
|
105
|
+
const bibKey = `${firstAuthorLastName}${year}`;
|
|
106
|
+
let bibtexString = `@article{${bibKey},\n`;
|
|
107
|
+
if (authorsBibtex)
|
|
108
|
+
bibtexString += ` author = {${authorsBibtex}},\n`;
|
|
109
|
+
bibtexString += ` title = {${title}},\n`;
|
|
110
|
+
bibtexString += ` journal = {${journalInfo?.title || "N/A"}},\n`;
|
|
111
|
+
if (journalInfo?.publicationDate?.year)
|
|
112
|
+
bibtexString += ` year = {${journalInfo.publicationDate.year}},\n`;
|
|
113
|
+
if (journalInfo?.volume)
|
|
114
|
+
bibtexString += ` volume = {${journalInfo.volume}},\n`;
|
|
115
|
+
if (journalInfo?.issue)
|
|
116
|
+
bibtexString += ` number = {${journalInfo.issue}},\n`;
|
|
117
|
+
if (journalInfo?.pages)
|
|
118
|
+
bibtexString += ` pages = {${journalInfo.pages.replace("-", "--")}},\n`;
|
|
119
|
+
if (journalInfo?.publicationDate?.month)
|
|
120
|
+
bibtexString += ` month = {${journalInfo.publicationDate.month.toLowerCase()}},\n`;
|
|
121
|
+
if (doi)
|
|
122
|
+
bibtexString += ` doi = {${doi}},\n`;
|
|
123
|
+
if (pmid)
|
|
124
|
+
bibtexString += ` pmid = {${pmid}}\n`;
|
|
125
|
+
bibtexString += `}\n`;
|
|
126
|
+
return bibtexString;
|
|
127
|
+
}
|
|
128
|
+
function formatAsAPA(article, context) {
|
|
129
|
+
const medlineCitation = article.MedlineCitation;
|
|
130
|
+
const articleDetails = medlineCitation?.Article;
|
|
131
|
+
const pmid = extractPmid(medlineCitation);
|
|
132
|
+
logger.debug("Formatting APA for article", requestContextService.createRequestContext({ ...context, pmid }));
|
|
133
|
+
if (!articleDetails)
|
|
134
|
+
return "Article details not found.";
|
|
135
|
+
const parsedAuthors = extractAuthors(articleDetails.AuthorList);
|
|
136
|
+
const journalInfo = extractJournalInfo(articleDetails.Journal, medlineCitation);
|
|
137
|
+
const titleText = getText(articleDetails.ArticleTitle) || "N/A";
|
|
138
|
+
const doi = extractDoi(articleDetails);
|
|
139
|
+
let authorsString = "N/A";
|
|
140
|
+
if (parsedAuthors.length > 0) {
|
|
141
|
+
if (parsedAuthors.length <= 20) {
|
|
142
|
+
authorsString = parsedAuthors
|
|
143
|
+
.map((author) => {
|
|
144
|
+
if (author.lastName && author.firstName) {
|
|
145
|
+
const initials = author.firstName
|
|
146
|
+
.split(/\s+|-/)
|
|
147
|
+
.map((namePart) => namePart.charAt(0).toUpperCase() + ".")
|
|
148
|
+
.join("");
|
|
149
|
+
return `${author.lastName}, ${initials}`;
|
|
150
|
+
}
|
|
151
|
+
if (author.lastName)
|
|
152
|
+
return author.lastName; // Collective name
|
|
153
|
+
return "";
|
|
154
|
+
})
|
|
155
|
+
.filter(Boolean)
|
|
156
|
+
.join(", ");
|
|
157
|
+
if (parsedAuthors.length > 1) {
|
|
158
|
+
const lastCommaIndex = authorsString.lastIndexOf(", ");
|
|
159
|
+
if (lastCommaIndex !== -1) {
|
|
160
|
+
authorsString =
|
|
161
|
+
authorsString.substring(0, lastCommaIndex) +
|
|
162
|
+
" & " +
|
|
163
|
+
authorsString.substring(lastCommaIndex + 2);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const first19 = parsedAuthors
|
|
169
|
+
.slice(0, 19)
|
|
170
|
+
.map((author) => {
|
|
171
|
+
if (author.lastName && author.firstName) {
|
|
172
|
+
const initials = author.firstName
|
|
173
|
+
.split(/\s+|-/)
|
|
174
|
+
.map((namePart) => namePart.charAt(0).toUpperCase() + ".")
|
|
175
|
+
.join("");
|
|
176
|
+
return `${author.lastName}, ${initials}`;
|
|
177
|
+
}
|
|
178
|
+
if (author.lastName)
|
|
179
|
+
return author.lastName;
|
|
180
|
+
return "";
|
|
181
|
+
})
|
|
182
|
+
.filter(Boolean)
|
|
183
|
+
.join(", ");
|
|
184
|
+
const lastAuthor = parsedAuthors[parsedAuthors.length - 1];
|
|
185
|
+
let lastAuthorString = "";
|
|
186
|
+
if (lastAuthor.lastName && lastAuthor.firstName) {
|
|
187
|
+
const initials = lastAuthor.firstName
|
|
188
|
+
.split(/\s+|-/)
|
|
189
|
+
.map((namePart) => namePart.charAt(0).toUpperCase() + ".")
|
|
190
|
+
.join("");
|
|
191
|
+
lastAuthorString = `${lastAuthor.lastName}, ${initials}`;
|
|
192
|
+
}
|
|
193
|
+
else if (lastAuthor.lastName) {
|
|
194
|
+
lastAuthorString = lastAuthor.lastName;
|
|
195
|
+
}
|
|
196
|
+
authorsString = `${first19}, ..., ${lastAuthorString}`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const year = journalInfo?.publicationDate?.year || "n.d.";
|
|
200
|
+
const apaTitle = titleText.charAt(0).toUpperCase() + titleText.slice(1); // APA typically sentence case for article titles.
|
|
201
|
+
const journal = journalInfo?.title ? `<em>${journalInfo.title}</em>` : "N/A";
|
|
202
|
+
const volume = journalInfo?.volume ? `<em>${journalInfo.volume}</em>` : "";
|
|
203
|
+
const issue = journalInfo?.issue ? `(${journalInfo.issue})` : "";
|
|
204
|
+
const pages = journalInfo?.pages || "";
|
|
205
|
+
const doiLink = doi ? ` https://doi.org/${doi}` : "";
|
|
206
|
+
let apaString = `${authorsString}. (${year}). ${apaTitle}. ${journal}`;
|
|
207
|
+
if (volume)
|
|
208
|
+
apaString += `, ${volume}`;
|
|
209
|
+
if (issue)
|
|
210
|
+
apaString += issue;
|
|
211
|
+
if (pages)
|
|
212
|
+
apaString += `, ${pages}`;
|
|
213
|
+
apaString += `.${doiLink}`;
|
|
214
|
+
return apaString;
|
|
215
|
+
}
|
|
216
|
+
function formatAsMLA(article, context) {
|
|
217
|
+
const medlineCitation = article.MedlineCitation;
|
|
218
|
+
const articleDetails = medlineCitation?.Article;
|
|
219
|
+
const pmid = extractPmid(medlineCitation);
|
|
220
|
+
logger.debug("Formatting MLA for article", requestContextService.createRequestContext({ ...context, pmid }));
|
|
221
|
+
if (!articleDetails)
|
|
222
|
+
return "Article details not found.";
|
|
223
|
+
const parsedAuthors = extractAuthors(articleDetails.AuthorList);
|
|
224
|
+
const journalInfo = extractJournalInfo(articleDetails.Journal, medlineCitation);
|
|
225
|
+
const titleText = getText(articleDetails.ArticleTitle);
|
|
226
|
+
const doi = extractDoi(articleDetails);
|
|
227
|
+
let authorsString = "N/A";
|
|
228
|
+
if (parsedAuthors.length > 0) {
|
|
229
|
+
if (parsedAuthors.length <= 2) {
|
|
230
|
+
authorsString = parsedAuthors
|
|
231
|
+
.map((author, index) => {
|
|
232
|
+
if (author.lastName && author.firstName) {
|
|
233
|
+
return index === 0
|
|
234
|
+
? `${author.lastName}, ${author.firstName}`
|
|
235
|
+
: `${author.firstName} ${author.lastName}`;
|
|
236
|
+
}
|
|
237
|
+
if (author.lastName)
|
|
238
|
+
return author.lastName;
|
|
239
|
+
return "";
|
|
240
|
+
})
|
|
241
|
+
.filter(Boolean)
|
|
242
|
+
.join(" and ");
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const firstAuthor = parsedAuthors[0];
|
|
246
|
+
if (firstAuthor.lastName && firstAuthor.firstName) {
|
|
247
|
+
authorsString = `${firstAuthor.lastName}, ${firstAuthor.firstName}, et al`;
|
|
248
|
+
}
|
|
249
|
+
else if (firstAuthor.lastName) {
|
|
250
|
+
authorsString = `${firstAuthor.lastName}, et al`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const title = titleText ? `"${titleText}."` : "N/A.";
|
|
255
|
+
const journal = journalInfo?.title ? `<em>${journalInfo.title}</em>` : "N/A";
|
|
256
|
+
let publicationDateString = journalInfo?.publicationDate?.year || "";
|
|
257
|
+
if (journalInfo?.publicationDate?.month && journalInfo.publicationDate.year) {
|
|
258
|
+
const month = journalInfo.publicationDate.month.substring(0, 3) + "."; // Abbreviate month
|
|
259
|
+
publicationDateString = `${month} ${journalInfo.publicationDate.year}`;
|
|
260
|
+
if (journalInfo.publicationDate.day) {
|
|
261
|
+
publicationDateString = `${journalInfo.publicationDate.day} ${month} ${journalInfo.publicationDate.year}`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else if (journalInfo?.publicationDate?.medlineDate) {
|
|
265
|
+
publicationDateString = journalInfo.publicationDate.medlineDate;
|
|
266
|
+
}
|
|
267
|
+
const volume = journalInfo?.volume ? `vol. ${journalInfo.volume}` : "";
|
|
268
|
+
const issue = journalInfo?.issue ? `no. ${journalInfo.issue}` : "";
|
|
269
|
+
const pages = journalInfo?.pages
|
|
270
|
+
? `pp. ${journalInfo.pages.replace("-", "–")}`
|
|
271
|
+
: ""; // en-dash for MLA
|
|
272
|
+
const accessUrl = doi
|
|
273
|
+
? `doi:${doi}`
|
|
274
|
+
: pmid
|
|
275
|
+
? `https://pubmed.ncbi.nlm.nih.gov/${pmid}`
|
|
276
|
+
: "";
|
|
277
|
+
let mlaString = `${authorsString}. ${title} ${journal}`;
|
|
278
|
+
if (volume)
|
|
279
|
+
mlaString += `, ${volume}`;
|
|
280
|
+
if (issue)
|
|
281
|
+
mlaString += `, ${issue}`;
|
|
282
|
+
if (publicationDateString)
|
|
283
|
+
mlaString += `, ${publicationDateString}`;
|
|
284
|
+
if (pages)
|
|
285
|
+
mlaString += `, ${pages}`;
|
|
286
|
+
mlaString += `. PubMed Central, ${accessUrl}.`; // Assuming PubMed Central or just PubMed
|
|
287
|
+
return mlaString;
|
|
288
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handles ELink requests and enriches results with ESummary data
|
|
3
|
+
* for the getPubMedArticleConnections tool.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler
|
|
5
|
+
*/
|
|
6
|
+
import { RequestContext } from "../../../../utils/index.js";
|
|
7
|
+
import type { GetPubMedArticleConnectionsInput } from "../registration.js";
|
|
8
|
+
import type { ToolOutputData } from "./types.js";
|
|
9
|
+
export declare function handleELinkRelationships(input: GetPubMedArticleConnectionsInput, outputData: ToolOutputData, context: RequestContext): Promise<void>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handles ELink requests and enriches results with ESummary data
|
|
3
|
+
* for the getPubMedArticleConnections tool.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler
|
|
5
|
+
*/
|
|
6
|
+
import { ncbiService } from "../../../../services/NCBI/ncbiService.js";
|
|
7
|
+
import { logger } from "../../../../utils/index.js";
|
|
8
|
+
import { extractBriefSummaries } from "../../../../utils/parsing/ncbi-parsing/index.js";
|
|
9
|
+
import { ensureArray } from "../../../../utils/parsing/ncbi-parsing/xmlGenericHelpers.js"; // Added import
|
|
10
|
+
export async function handleELinkRelationships(input, outputData, context) {
|
|
11
|
+
const eLinkParams = {
|
|
12
|
+
dbfrom: "pubmed",
|
|
13
|
+
db: "pubmed",
|
|
14
|
+
id: input.sourcePmid,
|
|
15
|
+
retmode: "xml",
|
|
16
|
+
// cmd and linkname will be set below based on relationshipType
|
|
17
|
+
};
|
|
18
|
+
switch (input.relationshipType) {
|
|
19
|
+
case "pubmed_citedin":
|
|
20
|
+
eLinkParams.cmd = "neighbor_history";
|
|
21
|
+
eLinkParams.linkname = "pubmed_pubmed_citedin";
|
|
22
|
+
break;
|
|
23
|
+
case "pubmed_references":
|
|
24
|
+
eLinkParams.cmd = "neighbor_history";
|
|
25
|
+
eLinkParams.linkname = "pubmed_pubmed_refs";
|
|
26
|
+
break;
|
|
27
|
+
case "pubmed_similar_articles":
|
|
28
|
+
default: // Default to similar articles
|
|
29
|
+
eLinkParams.cmd = "neighbor_score";
|
|
30
|
+
// No linkname is explicitly needed for neighbor_score when dbfrom and db are pubmed
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const tempUrl = new URL("https://dummy.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi");
|
|
34
|
+
Object.keys(eLinkParams).forEach((key) => tempUrl.searchParams.append(key, String(eLinkParams[key])));
|
|
35
|
+
outputData.eUtilityUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?${tempUrl.search.substring(1)}`;
|
|
36
|
+
const eLinkResult = await ncbiService.eLink(eLinkParams, context);
|
|
37
|
+
// Log the full eLinkResult for debugging
|
|
38
|
+
logger.debug("Raw eLinkResult from ncbiService:", {
|
|
39
|
+
...context,
|
|
40
|
+
eLinkResultString: JSON.stringify(eLinkResult, null, 2),
|
|
41
|
+
});
|
|
42
|
+
// Use ensureArray for robust handling of potentially single or array eLinkResult
|
|
43
|
+
const eLinkResultsArray = ensureArray(eLinkResult?.eLinkResult);
|
|
44
|
+
const firstELinkResult = eLinkResultsArray[0]; // Get the first (or only) eLinkResult object
|
|
45
|
+
// Use ensureArray for LinkSet as well
|
|
46
|
+
const linkSetsArray = ensureArray(firstELinkResult?.LinkSet);
|
|
47
|
+
const linkSet = linkSetsArray[0]; // Get the first (or only) LinkSet object
|
|
48
|
+
let foundPmids = [];
|
|
49
|
+
if (firstELinkResult?.ERROR) {
|
|
50
|
+
const errorMsg = typeof firstELinkResult.ERROR === "string"
|
|
51
|
+
? firstELinkResult.ERROR
|
|
52
|
+
: JSON.stringify(firstELinkResult.ERROR);
|
|
53
|
+
logger.warning(`ELink returned an error: ${errorMsg}`, context);
|
|
54
|
+
outputData.message = `ELink error: ${errorMsg}`;
|
|
55
|
+
outputData.retrievedCount = 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (linkSet?.LinkSetDbHistory) {
|
|
59
|
+
// Handle cmd=neighbor_history response (citedin, references)
|
|
60
|
+
const history = Array.isArray(linkSet.LinkSetDbHistory)
|
|
61
|
+
? linkSet.LinkSetDbHistory[0]
|
|
62
|
+
: linkSet.LinkSetDbHistory;
|
|
63
|
+
if (history?.QueryKey && linkSet.WebEnv) {
|
|
64
|
+
const eSearchParams = {
|
|
65
|
+
db: "pubmed",
|
|
66
|
+
query_key: history.QueryKey,
|
|
67
|
+
WebEnv: linkSet.WebEnv,
|
|
68
|
+
retmode: "xml",
|
|
69
|
+
retmax: input.maxRelatedResults * 2, // Fetch a bit more to allow filtering sourcePmid
|
|
70
|
+
};
|
|
71
|
+
const eSearchResult = await ncbiService.eSearch(eSearchParams, context);
|
|
72
|
+
if (eSearchResult?.eSearchResult?.IdList?.Id) {
|
|
73
|
+
const ids = ensureArray(eSearchResult.eSearchResult.IdList.Id);
|
|
74
|
+
foundPmids = ids
|
|
75
|
+
.map((idNode) => {
|
|
76
|
+
// Allow number for idNode
|
|
77
|
+
let pmidVal;
|
|
78
|
+
if (typeof idNode === "object" && idNode !== null) {
|
|
79
|
+
pmidVal = idNode["#text"];
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
pmidVal = idNode;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
pmid: pmidVal !== undefined ? String(pmidVal) : "",
|
|
86
|
+
// No scores from this ESearch path
|
|
87
|
+
};
|
|
88
|
+
})
|
|
89
|
+
.filter((item) => item.pmid && item.pmid !== input.sourcePmid && item.pmid !== "0");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (linkSet?.LinkSetDb) {
|
|
94
|
+
// Handle cmd=neighbor_score response (similar_articles)
|
|
95
|
+
const linkSetDbArray = Array.isArray(linkSet.LinkSetDb)
|
|
96
|
+
? linkSet.LinkSetDb
|
|
97
|
+
: [linkSet.LinkSetDb];
|
|
98
|
+
const targetLinkSetDbEntry = linkSetDbArray.find((db) => db.LinkName === "pubmed_pubmed");
|
|
99
|
+
if (targetLinkSetDbEntry?.Link) {
|
|
100
|
+
const links = ensureArray(targetLinkSetDbEntry.Link); // Use ensureArray here too
|
|
101
|
+
foundPmids = links
|
|
102
|
+
.map((link) => {
|
|
103
|
+
let pmidValue;
|
|
104
|
+
if (typeof link.Id === "object" && link.Id !== null) {
|
|
105
|
+
pmidValue = link.Id["#text"];
|
|
106
|
+
}
|
|
107
|
+
else if (link.Id !== undefined) {
|
|
108
|
+
pmidValue = link.Id;
|
|
109
|
+
}
|
|
110
|
+
let scoreValue;
|
|
111
|
+
if (typeof link.Score === "object" && link.Score !== null) {
|
|
112
|
+
scoreValue = link.Score["#text"];
|
|
113
|
+
}
|
|
114
|
+
else if (link.Score !== undefined) {
|
|
115
|
+
scoreValue = link.Score;
|
|
116
|
+
}
|
|
117
|
+
const pmidString = pmidValue !== undefined ? String(pmidValue) : "";
|
|
118
|
+
return {
|
|
119
|
+
pmid: pmidString,
|
|
120
|
+
score: scoreValue !== undefined ? Number(scoreValue) : undefined,
|
|
121
|
+
};
|
|
122
|
+
})
|
|
123
|
+
.filter((item) => item.pmid && item.pmid !== input.sourcePmid && item.pmid !== "0");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (foundPmids.length === 0) {
|
|
127
|
+
logger.warning("No related PMIDs found after ELink/ESearch processing.", context);
|
|
128
|
+
outputData.message = "No related articles found or ELink error."; // Generic message if no PMIDs
|
|
129
|
+
outputData.retrievedCount = 0;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
logger.debug("Found PMIDs after initial parsing and filtering (before sort):", {
|
|
133
|
+
...context,
|
|
134
|
+
foundPmidsCount: foundPmids.length,
|
|
135
|
+
firstFewFoundPmids: foundPmids.slice(0, 3),
|
|
136
|
+
});
|
|
137
|
+
if (foundPmids.every((p) => p.score !== undefined)) {
|
|
138
|
+
foundPmids.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
139
|
+
}
|
|
140
|
+
logger.debug("Found PMIDs after sorting:", {
|
|
141
|
+
...context,
|
|
142
|
+
sortedFoundPmidsCount: foundPmids.length,
|
|
143
|
+
firstFewSortedFoundPmids: foundPmids.slice(0, 3),
|
|
144
|
+
});
|
|
145
|
+
const pmidsToEnrich = foundPmids
|
|
146
|
+
.slice(0, input.maxRelatedResults)
|
|
147
|
+
.map((p) => p.pmid);
|
|
148
|
+
logger.debug("PMIDs to enrich with ESummary:", {
|
|
149
|
+
...context,
|
|
150
|
+
pmidsToEnrichCount: pmidsToEnrich.length,
|
|
151
|
+
pmidsToEnrichList: pmidsToEnrich,
|
|
152
|
+
});
|
|
153
|
+
if (pmidsToEnrich.length > 0) {
|
|
154
|
+
try {
|
|
155
|
+
const summaryParams = {
|
|
156
|
+
db: "pubmed",
|
|
157
|
+
id: pmidsToEnrich.join(","),
|
|
158
|
+
version: "2.0",
|
|
159
|
+
retmode: "xml",
|
|
160
|
+
};
|
|
161
|
+
const summaryResultContainer = await ncbiService.eSummary(summaryParams, context);
|
|
162
|
+
const summaryResult = summaryResultContainer?.eSummaryResult ||
|
|
163
|
+
summaryResultContainer?.result ||
|
|
164
|
+
summaryResultContainer;
|
|
165
|
+
if (summaryResult) {
|
|
166
|
+
const briefSummaries = await extractBriefSummaries(summaryResult, context);
|
|
167
|
+
const pmidDetailsMap = new Map();
|
|
168
|
+
briefSummaries.forEach((bs) => pmidDetailsMap.set(bs.pmid, bs));
|
|
169
|
+
outputData.relatedArticles = foundPmids
|
|
170
|
+
.filter((p) => pmidsToEnrich.includes(p.pmid))
|
|
171
|
+
.map((p) => {
|
|
172
|
+
const details = pmidDetailsMap.get(p.pmid);
|
|
173
|
+
return {
|
|
174
|
+
pmid: p.pmid,
|
|
175
|
+
title: details?.title,
|
|
176
|
+
authors: details?.authors,
|
|
177
|
+
score: p.score,
|
|
178
|
+
linkUrl: `https://pubmed.ncbi.nlm.nih.gov/${p.pmid}/`,
|
|
179
|
+
};
|
|
180
|
+
})
|
|
181
|
+
.slice(0, input.maxRelatedResults);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
logger.warning("ESummary did not return usable data for enrichment.", context);
|
|
185
|
+
outputData.relatedArticles = foundPmids
|
|
186
|
+
.slice(0, input.maxRelatedResults)
|
|
187
|
+
.map((p) => ({
|
|
188
|
+
pmid: p.pmid,
|
|
189
|
+
score: p.score,
|
|
190
|
+
linkUrl: `https://pubmed.ncbi.nlm.nih.gov/${p.pmid}/`,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (summaryError) {
|
|
195
|
+
logger.error("Failed to enrich related articles with summaries", summaryError instanceof Error
|
|
196
|
+
? summaryError
|
|
197
|
+
: new Error(String(summaryError)), context);
|
|
198
|
+
outputData.relatedArticles = foundPmids
|
|
199
|
+
.slice(0, input.maxRelatedResults)
|
|
200
|
+
.map((p) => ({
|
|
201
|
+
pmid: p.pmid,
|
|
202
|
+
score: p.score,
|
|
203
|
+
linkUrl: `https://pubmed.ncbi.nlm.nih.gov/${p.pmid}/`,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
outputData.retrievedCount = outputData.relatedArticles.length;
|
|
208
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main logic handler for the 'get_pubmed_article_connections' MCP tool.
|
|
3
|
+
* Orchestrates calls to ELink or citation formatting handlers.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/index
|
|
5
|
+
*/
|
|
6
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { RequestContext } from "../../../../utils/index.js";
|
|
8
|
+
import type { GetPubMedArticleConnectionsInput } from "../registration.js";
|
|
9
|
+
/**
|
|
10
|
+
* Main handler for the 'get_pubmed_article_connections' tool.
|
|
11
|
+
* @param {GetPubMedArticleConnectionsInput} input - Validated input parameters.
|
|
12
|
+
* @param {RequestContext} context - The request context for this tool invocation.
|
|
13
|
+
* @returns {Promise<CallToolResult>} The result of the tool call.
|
|
14
|
+
*/
|
|
15
|
+
export declare function handleGetPubMedArticleConnections(input: GetPubMedArticleConnectionsInput, context: RequestContext): Promise<CallToolResult>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main logic handler for the 'get_pubmed_article_connections' MCP tool.
|
|
3
|
+
* Orchestrates calls to ELink or citation formatting handlers.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/index
|
|
5
|
+
*/
|
|
6
|
+
import { BaseErrorCode, McpError } from "../../../../types-global/errors.js";
|
|
7
|
+
import { logger, requestContextService, sanitizeInputForLogging, } from "../../../../utils/index.js";
|
|
8
|
+
import { handleCitationFormats } from "./citationFormatter.js";
|
|
9
|
+
import { handleELinkRelationships } from "./elinkHandler.js";
|
|
10
|
+
/**
|
|
11
|
+
* Main handler for the 'get_pubmed_article_connections' tool.
|
|
12
|
+
* @param {GetPubMedArticleConnectionsInput} input - Validated input parameters.
|
|
13
|
+
* @param {RequestContext} context - The request context for this tool invocation.
|
|
14
|
+
* @returns {Promise<CallToolResult>} The result of the tool call.
|
|
15
|
+
*/
|
|
16
|
+
export async function handleGetPubMedArticleConnections(input, context) {
|
|
17
|
+
const toolLogicContext = requestContextService.createRequestContext({
|
|
18
|
+
parentRequestId: context.requestId, // Link to parent context
|
|
19
|
+
operation: "handleGetPubMedArticleConnections",
|
|
20
|
+
toolName: "get_pubmed_article_connections",
|
|
21
|
+
input: sanitizeInputForLogging(input),
|
|
22
|
+
});
|
|
23
|
+
logger.info("Executing get_pubmed_article_connections tool", toolLogicContext);
|
|
24
|
+
const outputData = {
|
|
25
|
+
sourcePmid: input.sourcePmid,
|
|
26
|
+
relationshipType: input.relationshipType,
|
|
27
|
+
relatedArticles: [],
|
|
28
|
+
citations: {},
|
|
29
|
+
retrievedCount: 0,
|
|
30
|
+
eUtilityUrl: undefined, // Initialize
|
|
31
|
+
message: undefined, // Initialize
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
switch (input.relationshipType) {
|
|
35
|
+
case "pubmed_similar_articles":
|
|
36
|
+
case "pubmed_citedin":
|
|
37
|
+
case "pubmed_references":
|
|
38
|
+
await handleELinkRelationships(input, outputData, toolLogicContext);
|
|
39
|
+
break;
|
|
40
|
+
case "citation_formats":
|
|
41
|
+
await handleCitationFormats(input, outputData, toolLogicContext);
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
// This case should ideally be caught by Zod schema validation beforehand
|
|
45
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported relationshipType: ${input.relationshipType}`, { receivedType: input.relationshipType });
|
|
46
|
+
}
|
|
47
|
+
// Ensure message is set if no specific error but also no results from handlers
|
|
48
|
+
if (outputData.retrievedCount === 0 &&
|
|
49
|
+
!outputData.message &&
|
|
50
|
+
(input.relationshipType !== "citation_formats" ||
|
|
51
|
+
Object.keys(outputData.citations).length === 0)) {
|
|
52
|
+
outputData.message = "No results found for the given parameters.";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
logger.error("Error in getPubMedArticleConnections main logic", error, toolLogicContext);
|
|
57
|
+
outputData.message =
|
|
58
|
+
error instanceof McpError
|
|
59
|
+
? error.message
|
|
60
|
+
: "An unexpected error occurred while processing the request.";
|
|
61
|
+
// Ensure critical details from McpError are passed through if available
|
|
62
|
+
const errorDetails = error instanceof McpError
|
|
63
|
+
? error.details
|
|
64
|
+
: { originalError: error.message };
|
|
65
|
+
const errorPayload = {
|
|
66
|
+
...outputData,
|
|
67
|
+
error: {
|
|
68
|
+
code: error.code || BaseErrorCode.INTERNAL_ERROR,
|
|
69
|
+
message: outputData.message,
|
|
70
|
+
details: errorDetails,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
const content = [
|
|
74
|
+
{ type: "text", text: JSON.stringify(errorPayload) },
|
|
75
|
+
];
|
|
76
|
+
return { content, isError: true };
|
|
77
|
+
}
|
|
78
|
+
const responseContent = [
|
|
79
|
+
{ type: "text", text: JSON.stringify(outputData) },
|
|
80
|
+
];
|
|
81
|
+
return { content: responseContent, isError: false };
|
|
82
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared type definitions for the getPubMedArticleConnections tool logic.
|
|
3
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/types
|
|
4
|
+
*/
|
|
5
|
+
import type { GetPubMedArticleConnectionsInput } from "../registration.js";
|
|
6
|
+
export interface RelatedArticle {
|
|
7
|
+
pmid: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
authors?: string;
|
|
10
|
+
score?: number;
|
|
11
|
+
linkUrl: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CitationOutput {
|
|
14
|
+
ris?: string;
|
|
15
|
+
bibtex?: string;
|
|
16
|
+
apa_string?: string;
|
|
17
|
+
mla_string?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ToolOutputData {
|
|
20
|
+
sourcePmid: string;
|
|
21
|
+
relationshipType: GetPubMedArticleConnectionsInput["relationshipType"];
|
|
22
|
+
relatedArticles: RelatedArticle[];
|
|
23
|
+
citations: CitationOutput;
|
|
24
|
+
retrievedCount: number;
|
|
25
|
+
eUtilityUrl?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core logic for the 'get_pubmed_article_connections' MCP tool.
|
|
3
|
+
* This file now re-exports the main handler from the './logic/index.js' module.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic
|
|
5
|
+
*/
|
|
6
|
+
export { handleGetPubMedArticleConnections } from "./logic/index.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core logic for the 'get_pubmed_article_connections' MCP tool.
|
|
3
|
+
* This file now re-exports the main handler from the './logic/index.js' module.
|
|
4
|
+
* @module src/mcp-server/tools/getPubMedArticleConnections/logic
|
|
5
|
+
*/
|
|
6
|
+
export { handleGetPubMedArticleConnections } from "./logic/index.js";
|