@cyanheads/pubmed-mcp-server 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -12
- package/dist/index.js +1313 -794
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -135501,7 +135501,7 @@ config(en_default());
|
|
|
135501
135501
|
// package.json
|
|
135502
135502
|
var package_default = {
|
|
135503
135503
|
name: "@cyanheads/pubmed-mcp-server",
|
|
135504
|
-
version: "2.
|
|
135504
|
+
version: "2.1.1",
|
|
135505
135505
|
mcpName: "io.github.cyanheads/pubmed-mcp-server",
|
|
135506
135506
|
description: "MCP server for PubMed/NCBI E-utilities integration. Search articles, fetch metadata, generate citations, explore MeSH terms, and discover related research.",
|
|
135507
135507
|
main: "dist/index.js",
|
|
@@ -139088,7 +139088,16 @@ var NCBI_ARRAY_JPATHS = new Set([
|
|
|
139088
139088
|
"DescriptorRecordSet.DescriptorRecord",
|
|
139089
139089
|
"ConceptList.Concept",
|
|
139090
139090
|
"TermList.Term",
|
|
139091
|
-
"TreeNumberList.TreeNumber"
|
|
139091
|
+
"TreeNumberList.TreeNumber",
|
|
139092
|
+
"pmc-articleset.article",
|
|
139093
|
+
"article-meta.article-id",
|
|
139094
|
+
"article-meta.pub-date",
|
|
139095
|
+
"contrib-group.contrib",
|
|
139096
|
+
"kwd-group.kwd",
|
|
139097
|
+
"body.sec",
|
|
139098
|
+
"sec.sec",
|
|
139099
|
+
"sec.p",
|
|
139100
|
+
"ref-list.ref"
|
|
139092
139101
|
]);
|
|
139093
139102
|
var ERROR_PATHS = [
|
|
139094
139103
|
"eLinkResult.ERROR",
|
|
@@ -139117,8 +139126,8 @@ function extractTextValues(source, prefix = "") {
|
|
|
139117
139126
|
const items = Array.isArray(source) ? source : [source];
|
|
139118
139127
|
const messages = [];
|
|
139119
139128
|
for (const item of items) {
|
|
139120
|
-
if (typeof item === "string") {
|
|
139121
|
-
messages.push(`${prefix}${item}`);
|
|
139129
|
+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
|
|
139130
|
+
messages.push(`${prefix}${String(item)}`);
|
|
139122
139131
|
} else if (item && typeof item["#text"] === "string") {
|
|
139123
139132
|
messages.push(`${prefix}${item["#text"]}`);
|
|
139124
139133
|
}
|
|
@@ -139178,7 +139187,7 @@ class NcbiResponseHandler {
|
|
|
139178
139187
|
});
|
|
139179
139188
|
}
|
|
139180
139189
|
const parsedXml = this.xmlParser.parse(responseText);
|
|
139181
|
-
const hasError =
|
|
139190
|
+
const hasError = ERROR_PATHS.some((path) => resolvePath(parsedXml, path) !== undefined);
|
|
139182
139191
|
if (hasError) {
|
|
139183
139192
|
const errorMessages = this.extractNcbiErrorMessages(parsedXml);
|
|
139184
139193
|
logger.error("NCBI API returned an error in XML response.", {
|
|
@@ -150769,809 +150778,1318 @@ class TaskManager {
|
|
|
150769
150778
|
}
|
|
150770
150779
|
}
|
|
150771
150780
|
|
|
150772
|
-
// src/services/ncbi/
|
|
150773
|
-
function
|
|
150774
|
-
|
|
150775
|
-
}
|
|
150776
|
-
function splitPages(pages) {
|
|
150777
|
-
if (!pages)
|
|
150778
|
-
return {};
|
|
150779
|
-
const parts = pages.split(/[-\u2013\u2014]/).map((p) => p.trim());
|
|
150780
|
-
if (parts.length >= 2) {
|
|
150781
|
-
const start2 = parts[0];
|
|
150782
|
-
const end = parts[1];
|
|
150783
|
-
return {
|
|
150784
|
-
...start2 !== undefined && { start: start2 },
|
|
150785
|
-
...end !== undefined && { end }
|
|
150786
|
-
};
|
|
150787
|
-
}
|
|
150788
|
-
const start = parts[0];
|
|
150789
|
-
return start !== undefined ? { start } : {};
|
|
150790
|
-
}
|
|
150791
|
-
function escapeBibtex(text) {
|
|
150792
|
-
return text.replace(/\\/g, "\\textbackslash{}").replace(/&/g, "\\&").replace(/%/g, "\\%").replace(/\$/g, "\\$").replace(/#/g, "\\#").replace(/_/g, "\\_").replace(/\{/g, "\\{").replace(/\}/g, "\\}").replace(/~/g, "\\textasciitilde{}").replace(/\^/g, "\\textasciicircum{}");
|
|
150793
|
-
}
|
|
150794
|
-
function formatAuthorApa(author) {
|
|
150795
|
-
if (author.collectiveName)
|
|
150796
|
-
return author.collectiveName;
|
|
150797
|
-
const last = author.lastName ?? "";
|
|
150798
|
-
const initials = author.initials ?? author.firstName?.split(/[\s-]+/).map((part) => `${part[0]}.`).join(" ");
|
|
150799
|
-
if (!last)
|
|
150800
|
-
return initials ?? "";
|
|
150801
|
-
if (!initials)
|
|
150802
|
-
return last;
|
|
150803
|
-
const formatted = initials.replace(/[^A-Za-z]/g, "").split("").map((c) => `${c}.`).join(" ");
|
|
150804
|
-
return `${last}, ${formatted}`;
|
|
150805
|
-
}
|
|
150806
|
-
function formatAuthorsApa(authors) {
|
|
150807
|
-
const formatted = authors.map(formatAuthorApa);
|
|
150808
|
-
if (formatted.length === 0)
|
|
150781
|
+
// src/services/ncbi/parsing/pmc-article-parser.ts
|
|
150782
|
+
function extractTextContent(node) {
|
|
150783
|
+
if (node === undefined || node === null)
|
|
150809
150784
|
return "";
|
|
150810
|
-
if (
|
|
150811
|
-
return
|
|
150812
|
-
if (
|
|
150813
|
-
return
|
|
150814
|
-
if (
|
|
150815
|
-
|
|
150816
|
-
return `${allButLast}, & ${formatted.at(-1)}`;
|
|
150785
|
+
if (typeof node === "string")
|
|
150786
|
+
return node;
|
|
150787
|
+
if (typeof node === "number" || typeof node === "boolean")
|
|
150788
|
+
return String(node);
|
|
150789
|
+
if (Array.isArray(node)) {
|
|
150790
|
+
return node.map(extractTextContent).filter(Boolean).join(" ");
|
|
150817
150791
|
}
|
|
150818
|
-
|
|
150819
|
-
|
|
150820
|
-
|
|
150821
|
-
|
|
150822
|
-
|
|
150823
|
-
|
|
150824
|
-
|
|
150825
|
-
|
|
150826
|
-
|
|
150827
|
-
|
|
150828
|
-
|
|
150829
|
-
|
|
150830
|
-
|
|
150831
|
-
|
|
150832
|
-
|
|
150833
|
-
|
|
150834
|
-
function formatAuthorsMla(authors) {
|
|
150835
|
-
const first = authors[0];
|
|
150836
|
-
if (!first)
|
|
150837
|
-
return "";
|
|
150838
|
-
if (authors.length === 1)
|
|
150839
|
-
return formatAuthorMla(first, true);
|
|
150840
|
-
if (authors.length === 2) {
|
|
150841
|
-
const second = authors[1];
|
|
150842
|
-
return second ? `${formatAuthorMla(first, true)}, and ${formatAuthorMla(second, false)}` : formatAuthorMla(first, true);
|
|
150792
|
+
if (typeof node === "object") {
|
|
150793
|
+
const obj = node;
|
|
150794
|
+
const parts = [];
|
|
150795
|
+
if (obj["#text"] !== undefined) {
|
|
150796
|
+
const text = typeof obj["#text"] === "string" ? obj["#text"] : String(obj["#text"]);
|
|
150797
|
+
if (text)
|
|
150798
|
+
parts.push(text);
|
|
150799
|
+
}
|
|
150800
|
+
for (const key of Object.keys(obj)) {
|
|
150801
|
+
if (key === "#text" || key.startsWith("@_"))
|
|
150802
|
+
continue;
|
|
150803
|
+
const childText = extractTextContent(obj[key]);
|
|
150804
|
+
if (childText)
|
|
150805
|
+
parts.push(childText);
|
|
150806
|
+
}
|
|
150807
|
+
return parts.join(" ").replace(/\s+/g, " ").trim();
|
|
150843
150808
|
}
|
|
150844
|
-
return
|
|
150845
|
-
}
|
|
150846
|
-
function formatAuthorBibtex(author) {
|
|
150847
|
-
if (author.collectiveName)
|
|
150848
|
-
return `{${escapeBibtex(author.collectiveName)}}`;
|
|
150849
|
-
const last = author.lastName ? escapeBibtex(author.lastName) : "";
|
|
150850
|
-
const first = author.firstName ? escapeBibtex(author.firstName) : "";
|
|
150851
|
-
if (!last && !first)
|
|
150852
|
-
return "";
|
|
150853
|
-
if (!first)
|
|
150854
|
-
return `{${last}}`;
|
|
150855
|
-
if (!last)
|
|
150856
|
-
return first;
|
|
150857
|
-
return `{${last}}, ${first}`;
|
|
150809
|
+
return "";
|
|
150858
150810
|
}
|
|
150859
|
-
function
|
|
150860
|
-
const
|
|
150861
|
-
|
|
150862
|
-
|
|
150863
|
-
|
|
150864
|
-
|
|
150865
|
-
|
|
150866
|
-
parts.push(`(${year}).`);
|
|
150867
|
-
if (article.title) {
|
|
150868
|
-
const title = article.title.replace(/\.\s*$/, "");
|
|
150869
|
-
parts.push(`${title}.`);
|
|
150811
|
+
function extractArticleId(articleIds, pubIdType) {
|
|
150812
|
+
for (const id of ensureArray(articleIds)) {
|
|
150813
|
+
if (getAttribute(id, "pub-id-type") === pubIdType) {
|
|
150814
|
+
const val = id["#text"];
|
|
150815
|
+
if (val !== undefined)
|
|
150816
|
+
return String(val);
|
|
150817
|
+
}
|
|
150870
150818
|
}
|
|
150871
|
-
|
|
150872
|
-
|
|
150873
|
-
|
|
150874
|
-
|
|
150875
|
-
|
|
150876
|
-
|
|
150877
|
-
|
|
150819
|
+
return;
|
|
150820
|
+
}
|
|
150821
|
+
function extractJatsAuthors(contribGroups) {
|
|
150822
|
+
if (!contribGroups)
|
|
150823
|
+
return [];
|
|
150824
|
+
const authors = [];
|
|
150825
|
+
for (const group of ensureArray(contribGroups)) {
|
|
150826
|
+
for (const contrib of ensureArray(group.contrib)) {
|
|
150827
|
+
if (contrib["@_contrib-type"] && contrib["@_contrib-type"] !== "author")
|
|
150828
|
+
continue;
|
|
150829
|
+
const collectiveName = extractTextContent(contrib.collab);
|
|
150830
|
+
if (collectiveName) {
|
|
150831
|
+
authors.push({ collectiveName });
|
|
150832
|
+
continue;
|
|
150833
|
+
}
|
|
150834
|
+
if (contrib.name) {
|
|
150835
|
+
const lastName = extractTextContent(contrib.name.surname) || undefined;
|
|
150836
|
+
const givenNames = extractTextContent(contrib.name["given-names"]) || undefined;
|
|
150837
|
+
authors.push({
|
|
150838
|
+
...lastName && { lastName },
|
|
150839
|
+
...givenNames && { givenNames }
|
|
150840
|
+
});
|
|
150878
150841
|
}
|
|
150879
150842
|
}
|
|
150880
|
-
if (journal.pages) {
|
|
150881
|
-
journalPart += `, ${journal.pages}`;
|
|
150882
|
-
}
|
|
150883
|
-
journalPart += ".";
|
|
150884
|
-
parts.push(journalPart);
|
|
150885
|
-
}
|
|
150886
|
-
if (article.doi) {
|
|
150887
|
-
parts.push(`https://doi.org/${article.doi}`);
|
|
150888
150843
|
}
|
|
150889
|
-
return
|
|
150844
|
+
return authors;
|
|
150890
150845
|
}
|
|
150891
|
-
function
|
|
150892
|
-
|
|
150893
|
-
|
|
150894
|
-
|
|
150895
|
-
|
|
150896
|
-
|
|
150897
|
-
|
|
150898
|
-
|
|
150899
|
-
parts.push(`"${title}."`);
|
|
150846
|
+
function extractAffiliations(affs) {
|
|
150847
|
+
if (!affs)
|
|
150848
|
+
return [];
|
|
150849
|
+
const result = [];
|
|
150850
|
+
for (const aff of ensureArray(affs)) {
|
|
150851
|
+
const text = extractTextContent(aff);
|
|
150852
|
+
if (text)
|
|
150853
|
+
result.push(text);
|
|
150900
150854
|
}
|
|
150901
|
-
|
|
150902
|
-
|
|
150903
|
-
|
|
150904
|
-
|
|
150905
|
-
|
|
150906
|
-
|
|
150907
|
-
|
|
150908
|
-
|
|
150909
|
-
|
|
150855
|
+
return result;
|
|
150856
|
+
}
|
|
150857
|
+
function extractJournal(journalMeta, articleMeta) {
|
|
150858
|
+
if (!journalMeta)
|
|
150859
|
+
return;
|
|
150860
|
+
const title = extractTextContent(journalMeta["journal-title-group"]?.["journal-title"]) || extractTextContent(journalMeta["journal-title"]) || undefined;
|
|
150861
|
+
const issns = ensureArray(journalMeta.issn);
|
|
150862
|
+
const issn = issns.length > 0 ? getText(issns[0]) || undefined : undefined;
|
|
150863
|
+
const volume = articleMeta?.volume ? extractTextContent(articleMeta.volume) || undefined : undefined;
|
|
150864
|
+
const issue2 = articleMeta?.issue ? extractTextContent(articleMeta.issue) || undefined : undefined;
|
|
150865
|
+
const fpage = articleMeta?.fpage ? extractTextContent(articleMeta.fpage) : undefined;
|
|
150866
|
+
const lpage = articleMeta?.lpage ? extractTextContent(articleMeta.lpage) : undefined;
|
|
150867
|
+
const pages = fpage && lpage ? `${fpage}-${lpage}` : fpage || undefined;
|
|
150868
|
+
if (!title && !issn && !volume && !issue2 && !pages)
|
|
150869
|
+
return;
|
|
150870
|
+
return {
|
|
150871
|
+
...title && { title },
|
|
150872
|
+
...issn && { issn },
|
|
150873
|
+
...volume && { volume },
|
|
150874
|
+
...issue2 && { issue: issue2 },
|
|
150875
|
+
...pages && { pages }
|
|
150876
|
+
};
|
|
150877
|
+
}
|
|
150878
|
+
function extractPubDate(pubDates) {
|
|
150879
|
+
if (!pubDates)
|
|
150880
|
+
return;
|
|
150881
|
+
const dates = ensureArray(pubDates);
|
|
150882
|
+
const preferred = dates.find((d) => getAttribute(d, "pub-type") === "epub") ?? dates.find((d) => getAttribute(d, "pub-type") === "ppub") ?? dates.find((d) => getAttribute(d, "date-type") === "pub") ?? dates[0];
|
|
150883
|
+
if (!preferred)
|
|
150884
|
+
return;
|
|
150885
|
+
const year = extractTextContent(preferred.year) || undefined;
|
|
150886
|
+
const month = extractTextContent(preferred.month) || undefined;
|
|
150887
|
+
const day = extractTextContent(preferred.day) || undefined;
|
|
150888
|
+
if (!year)
|
|
150889
|
+
return;
|
|
150890
|
+
return {
|
|
150891
|
+
year,
|
|
150892
|
+
...month && { month },
|
|
150893
|
+
...day && { day }
|
|
150894
|
+
};
|
|
150895
|
+
}
|
|
150896
|
+
function extractAbstract(abstractNode) {
|
|
150897
|
+
if (!abstractNode)
|
|
150898
|
+
return;
|
|
150899
|
+
if (typeof abstractNode === "object" && !Array.isArray(abstractNode)) {
|
|
150900
|
+
const obj = abstractNode;
|
|
150901
|
+
if (obj.sec) {
|
|
150902
|
+
const sections = ensureArray(obj.sec);
|
|
150903
|
+
const parts = [];
|
|
150904
|
+
for (const sec of sections) {
|
|
150905
|
+
const secObj = sec;
|
|
150906
|
+
const title = extractTextContent(secObj.title);
|
|
150907
|
+
const text2 = extractTextContent(secObj.p);
|
|
150908
|
+
if (title && text2) {
|
|
150909
|
+
parts.push(`${title}: ${text2}`);
|
|
150910
|
+
} else if (text2) {
|
|
150911
|
+
parts.push(text2);
|
|
150912
|
+
}
|
|
150913
|
+
}
|
|
150914
|
+
const result = parts.join(`
|
|
150915
|
+
|
|
150916
|
+
`).trim();
|
|
150917
|
+
return result || undefined;
|
|
150910
150918
|
}
|
|
150911
|
-
|
|
150912
|
-
|
|
150913
|
-
|
|
150919
|
+
if (obj.p) {
|
|
150920
|
+
const text2 = extractTextContent(obj.p);
|
|
150921
|
+
return text2 || undefined;
|
|
150914
150922
|
}
|
|
150915
|
-
|
|
150916
|
-
|
|
150923
|
+
}
|
|
150924
|
+
const text = extractTextContent(abstractNode);
|
|
150925
|
+
return text || undefined;
|
|
150926
|
+
}
|
|
150927
|
+
function extractKeywords(kwdGroups) {
|
|
150928
|
+
if (!kwdGroups)
|
|
150929
|
+
return [];
|
|
150930
|
+
const keywords = [];
|
|
150931
|
+
for (const group of ensureArray(kwdGroups)) {
|
|
150932
|
+
for (const kwd of ensureArray(group.kwd)) {
|
|
150933
|
+
const text = extractTextContent(kwd);
|
|
150934
|
+
if (text)
|
|
150935
|
+
keywords.push(text);
|
|
150917
150936
|
}
|
|
150918
|
-
parts.push(`${detailParts.join(", ")}.`);
|
|
150919
150937
|
}
|
|
150920
|
-
|
|
150921
|
-
|
|
150938
|
+
return keywords;
|
|
150939
|
+
}
|
|
150940
|
+
function extractBodySections(body) {
|
|
150941
|
+
if (!body)
|
|
150942
|
+
return [];
|
|
150943
|
+
if (!body.sec) {
|
|
150944
|
+
if (body.p) {
|
|
150945
|
+
const text = extractTextContent(body.p);
|
|
150946
|
+
return text ? [{ text }] : [];
|
|
150947
|
+
}
|
|
150948
|
+
return [];
|
|
150922
150949
|
}
|
|
150923
|
-
return
|
|
150950
|
+
return ensureArray(body.sec).map(extractSection).filter((s) => s !== null);
|
|
150924
150951
|
}
|
|
150925
|
-
function
|
|
150926
|
-
const
|
|
150927
|
-
const
|
|
150928
|
-
|
|
150929
|
-
|
|
150930
|
-
|
|
150931
|
-
|
|
150952
|
+
function extractSection(sec) {
|
|
150953
|
+
const title = extractTextContent(sec.title) || undefined;
|
|
150954
|
+
const label = extractTextContent(sec.label) || undefined;
|
|
150955
|
+
const paragraphs = ensureArray(sec.p);
|
|
150956
|
+
const textParts = [];
|
|
150957
|
+
for (const p of paragraphs) {
|
|
150958
|
+
const text2 = extractTextContent(p);
|
|
150959
|
+
if (text2)
|
|
150960
|
+
textParts.push(text2);
|
|
150932
150961
|
}
|
|
150933
|
-
|
|
150934
|
-
|
|
150962
|
+
const subsections = sec.sec ? ensureArray(sec.sec).map(extractSection).filter((s) => s !== null) : undefined;
|
|
150963
|
+
const text = textParts.join(`
|
|
150964
|
+
|
|
150965
|
+
`);
|
|
150966
|
+
if (!text && (!subsections || subsections.length === 0))
|
|
150967
|
+
return null;
|
|
150968
|
+
return {
|
|
150969
|
+
...title && { title },
|
|
150970
|
+
...label && { label },
|
|
150971
|
+
text,
|
|
150972
|
+
...subsections && subsections.length > 0 && { subsections }
|
|
150973
|
+
};
|
|
150974
|
+
}
|
|
150975
|
+
function extractReferences(back) {
|
|
150976
|
+
if (!back?.["ref-list"]?.ref)
|
|
150977
|
+
return [];
|
|
150978
|
+
const refs = ensureArray(back["ref-list"].ref);
|
|
150979
|
+
const results = [];
|
|
150980
|
+
for (const ref of refs) {
|
|
150981
|
+
const citationNode = ref["mixed-citation"] ?? ref["element-citation"];
|
|
150982
|
+
const citation = extractTextContent(citationNode);
|
|
150983
|
+
if (!citation)
|
|
150984
|
+
continue;
|
|
150985
|
+
const label = ref.label ? extractTextContent(ref.label) || undefined : undefined;
|
|
150986
|
+
results.push({
|
|
150987
|
+
...ref["@_id"] && { id: ref["@_id"] },
|
|
150988
|
+
...label && { label },
|
|
150989
|
+
citation
|
|
150990
|
+
});
|
|
150991
|
+
}
|
|
150992
|
+
return results;
|
|
150993
|
+
}
|
|
150994
|
+
function parsePmcArticle(xmlArticle) {
|
|
150995
|
+
const front = xmlArticle.front;
|
|
150996
|
+
const articleMeta = front?.["article-meta"];
|
|
150997
|
+
const journalMeta = front?.["journal-meta"];
|
|
150998
|
+
const articleIds = articleMeta?.["article-id"];
|
|
150999
|
+
const pmcId = extractArticleId(articleIds, "pmcid") ?? extractArticleId(articleIds, "pmc-uid") ?? "";
|
|
151000
|
+
const pmid = extractArticleId(articleIds, "pmid");
|
|
151001
|
+
const doi = extractArticleId(articleIds, "doi");
|
|
151002
|
+
const title = articleMeta?.["title-group"] ? extractTextContent(articleMeta["title-group"]["article-title"]) || undefined : undefined;
|
|
151003
|
+
const authors = extractJatsAuthors(articleMeta?.["contrib-group"]);
|
|
151004
|
+
const affiliations = extractAffiliations(articleMeta?.aff);
|
|
151005
|
+
const journal = extractJournal(journalMeta, articleMeta);
|
|
151006
|
+
const publicationDate = extractPubDate(articleMeta?.["pub-date"]);
|
|
151007
|
+
const abstract = extractAbstract(articleMeta?.abstract);
|
|
151008
|
+
const keywords = extractKeywords(articleMeta?.["kwd-group"]);
|
|
151009
|
+
const sections = extractBodySections(xmlArticle.body);
|
|
151010
|
+
const references = extractReferences(xmlArticle.back);
|
|
151011
|
+
const normalizedPmcId = !pmcId ? "" : pmcId.startsWith("PMC") ? pmcId : `PMC${pmcId}`;
|
|
151012
|
+
return {
|
|
151013
|
+
pmcId: normalizedPmcId,
|
|
151014
|
+
...pmid && { pmid },
|
|
151015
|
+
...doi && { doi },
|
|
151016
|
+
...title && { title },
|
|
151017
|
+
...authors.length > 0 && { authors },
|
|
151018
|
+
...affiliations.length > 0 && { affiliations },
|
|
151019
|
+
...journal && { journal },
|
|
151020
|
+
...publicationDate && { publicationDate },
|
|
151021
|
+
...abstract && { abstract },
|
|
151022
|
+
...keywords.length > 0 && { keywords },
|
|
151023
|
+
sections,
|
|
151024
|
+
...references.length > 0 && { references },
|
|
151025
|
+
...xmlArticle["@_article-type"] && { articleType: xmlArticle["@_article-type"] },
|
|
151026
|
+
pmcUrl: `https://www.ncbi.nlm.nih.gov/pmc/articles/${normalizedPmcId}/`,
|
|
151027
|
+
...pmid && { pubmedUrl: `https://pubmed.ncbi.nlm.nih.gov/${pmid}/` }
|
|
151028
|
+
};
|
|
151029
|
+
}
|
|
151030
|
+
|
|
151031
|
+
// src/utils/formatting/markdownBuilder.ts
|
|
151032
|
+
class MarkdownBuilder {
|
|
151033
|
+
sections = [];
|
|
151034
|
+
h1(text, emoji3) {
|
|
151035
|
+
const prefix = emoji3 ? `${emoji3} ` : "";
|
|
151036
|
+
this.sections.push(`# ${prefix}${text}
|
|
151037
|
+
|
|
151038
|
+
`);
|
|
151039
|
+
return this;
|
|
150935
151040
|
}
|
|
150936
|
-
|
|
150937
|
-
|
|
150938
|
-
|
|
151041
|
+
h2(text, emoji3) {
|
|
151042
|
+
const prefix = emoji3 ? `${emoji3} ` : "";
|
|
151043
|
+
this.sections.push(`## ${prefix}${text}
|
|
151044
|
+
|
|
151045
|
+
`);
|
|
151046
|
+
return this;
|
|
150939
151047
|
}
|
|
150940
|
-
|
|
150941
|
-
|
|
150942
|
-
|
|
151048
|
+
h3(text, emoji3) {
|
|
151049
|
+
const prefix = emoji3 ? `${emoji3} ` : "";
|
|
151050
|
+
this.sections.push(`### ${prefix}${text}
|
|
151051
|
+
|
|
151052
|
+
`);
|
|
151053
|
+
return this;
|
|
150943
151054
|
}
|
|
150944
|
-
|
|
150945
|
-
|
|
151055
|
+
h4(text, emoji3) {
|
|
151056
|
+
const prefix = emoji3 ? `${emoji3} ` : "";
|
|
151057
|
+
this.sections.push(`#### ${prefix}${text}
|
|
151058
|
+
|
|
151059
|
+
`);
|
|
151060
|
+
return this;
|
|
150946
151061
|
}
|
|
150947
|
-
|
|
150948
|
-
|
|
151062
|
+
keyValue(key, value) {
|
|
151063
|
+
const displayValue = value === null ? "null" : String(value);
|
|
151064
|
+
this.sections.push(`**${key}:** ${displayValue}
|
|
151065
|
+
`);
|
|
151066
|
+
return this;
|
|
150949
151067
|
}
|
|
150950
|
-
|
|
150951
|
-
|
|
151068
|
+
keyValuePlain(key, value) {
|
|
151069
|
+
const displayValue = value === null ? "null" : String(value);
|
|
151070
|
+
this.sections.push(`${key}: ${displayValue}
|
|
151071
|
+
`);
|
|
151072
|
+
return this;
|
|
150952
151073
|
}
|
|
150953
|
-
|
|
150954
|
-
|
|
151074
|
+
list(items, ordered = false) {
|
|
151075
|
+
if (items.length === 0)
|
|
151076
|
+
return this;
|
|
151077
|
+
const marker = ordered ? (i) => `${i + 1}.` : () => "-";
|
|
151078
|
+
this.sections.push(`${items.map((item, i) => `${marker(i)} ${item}`).join(`
|
|
151079
|
+
`)}
|
|
151080
|
+
|
|
151081
|
+
`);
|
|
151082
|
+
return this;
|
|
150955
151083
|
}
|
|
150956
|
-
|
|
150957
|
-
|
|
150958
|
-
|
|
151084
|
+
codeBlock(content, language = "") {
|
|
151085
|
+
this.sections.push(`\`\`\`${language}
|
|
151086
|
+
${content}
|
|
151087
|
+
\`\`\`
|
|
151088
|
+
|
|
150959
151089
|
`);
|
|
150960
|
-
|
|
150961
|
-
${fieldLines}
|
|
150962
|
-
}`;
|
|
150963
|
-
}
|
|
150964
|
-
function formatRis(article) {
|
|
150965
|
-
const lines = [];
|
|
150966
|
-
const tag = (code, value) => {
|
|
150967
|
-
if (value)
|
|
150968
|
-
lines.push(`${code} - ${value}`);
|
|
150969
|
-
};
|
|
150970
|
-
lines.push("TY - JOUR");
|
|
150971
|
-
if (article.authors?.length) {
|
|
150972
|
-
for (const author of article.authors) {
|
|
150973
|
-
if (author.collectiveName) {
|
|
150974
|
-
tag("AU", author.collectiveName);
|
|
150975
|
-
} else {
|
|
150976
|
-
const last = author.lastName ?? "";
|
|
150977
|
-
const first = author.firstName ?? "";
|
|
150978
|
-
if (last || first) {
|
|
150979
|
-
tag("AU", first ? `${last}, ${first}` : last);
|
|
150980
|
-
}
|
|
150981
|
-
}
|
|
150982
|
-
}
|
|
151090
|
+
return this;
|
|
150983
151091
|
}
|
|
150984
|
-
|
|
150985
|
-
|
|
150986
|
-
|
|
150987
|
-
tag("JF", journal.title);
|
|
151092
|
+
inlineCode(code) {
|
|
151093
|
+
this.sections.push(`\`${code}\``);
|
|
151094
|
+
return this;
|
|
150988
151095
|
}
|
|
150989
|
-
|
|
150990
|
-
|
|
151096
|
+
paragraph(text) {
|
|
151097
|
+
this.sections.push(`${text}
|
|
151098
|
+
|
|
151099
|
+
`);
|
|
151100
|
+
return this;
|
|
150991
151101
|
}
|
|
150992
|
-
|
|
150993
|
-
|
|
150994
|
-
|
|
151102
|
+
blockquote(text) {
|
|
151103
|
+
const lines = text.split(`
|
|
151104
|
+
`);
|
|
151105
|
+
const quoted = lines.map((line) => `> ${line}`).join(`
|
|
151106
|
+
`);
|
|
151107
|
+
this.sections.push(`${quoted}
|
|
151108
|
+
|
|
151109
|
+
`);
|
|
151110
|
+
return this;
|
|
150995
151111
|
}
|
|
150996
|
-
|
|
150997
|
-
|
|
150998
|
-
|
|
150999
|
-
|
|
151000
|
-
|
|
151001
|
-
tag("EP", end);
|
|
151112
|
+
hr() {
|
|
151113
|
+
this.sections.push(`---
|
|
151114
|
+
|
|
151115
|
+
`);
|
|
151116
|
+
return this;
|
|
151002
151117
|
}
|
|
151003
|
-
|
|
151004
|
-
|
|
151005
|
-
|
|
151006
|
-
|
|
151007
|
-
|
|
151008
|
-
|
|
151118
|
+
link(text, url2) {
|
|
151119
|
+
this.sections.push(`[${text}](${url2})`);
|
|
151120
|
+
return this;
|
|
151121
|
+
}
|
|
151122
|
+
table(headers, rows) {
|
|
151123
|
+
if (headers.length === 0 || rows.length === 0)
|
|
151124
|
+
return this;
|
|
151125
|
+
this.sections.push(`| ${headers.join(" | ")} |
|
|
151126
|
+
`);
|
|
151127
|
+
this.sections.push(`| ${headers.map(() => "---").join(" | ")} |
|
|
151128
|
+
`);
|
|
151129
|
+
rows.forEach((row) => {
|
|
151130
|
+
this.sections.push(`| ${row.join(" | ")} |
|
|
151131
|
+
`);
|
|
151132
|
+
});
|
|
151133
|
+
this.sections.push(`
|
|
151134
|
+
`);
|
|
151135
|
+
return this;
|
|
151136
|
+
}
|
|
151137
|
+
section(title, levelOrContent, content) {
|
|
151138
|
+
let level;
|
|
151139
|
+
let callback;
|
|
151140
|
+
if (typeof levelOrContent === "function") {
|
|
151141
|
+
level = 2;
|
|
151142
|
+
callback = levelOrContent;
|
|
151143
|
+
} else {
|
|
151144
|
+
level = levelOrContent;
|
|
151145
|
+
callback = content ?? (() => {});
|
|
151146
|
+
}
|
|
151147
|
+
switch (level) {
|
|
151148
|
+
case 2:
|
|
151149
|
+
this.h2(title);
|
|
151150
|
+
break;
|
|
151151
|
+
case 3:
|
|
151152
|
+
this.h3(title);
|
|
151153
|
+
break;
|
|
151154
|
+
case 4:
|
|
151155
|
+
this.h4(title);
|
|
151156
|
+
break;
|
|
151009
151157
|
}
|
|
151158
|
+
callback();
|
|
151159
|
+
return this;
|
|
151010
151160
|
}
|
|
151011
|
-
|
|
151012
|
-
|
|
151013
|
-
|
|
151161
|
+
details(summary, details) {
|
|
151162
|
+
this.sections.push(`<details>
|
|
151163
|
+
<summary>${summary}</summary>
|
|
151164
|
+
|
|
151014
151165
|
`);
|
|
151015
|
-
}
|
|
151016
|
-
|
|
151017
|
-
|
|
151018
|
-
|
|
151019
|
-
|
|
151020
|
-
|
|
151021
|
-
|
|
151022
|
-
case "bibtex":
|
|
151023
|
-
return formatBibtex(article);
|
|
151024
|
-
case "ris":
|
|
151025
|
-
return formatRis(article);
|
|
151166
|
+
this.sections.push(`${details}
|
|
151167
|
+
|
|
151168
|
+
`);
|
|
151169
|
+
this.sections.push(`</details>
|
|
151170
|
+
|
|
151171
|
+
`);
|
|
151172
|
+
return this;
|
|
151026
151173
|
}
|
|
151027
|
-
|
|
151028
|
-
|
|
151029
|
-
|
|
151030
|
-
|
|
151031
|
-
|
|
151174
|
+
alert(type, content) {
|
|
151175
|
+
const typeUpper = type.toUpperCase();
|
|
151176
|
+
const lines = content.split(`
|
|
151177
|
+
`);
|
|
151178
|
+
this.sections.push(`> [!${typeUpper}]
|
|
151179
|
+
`);
|
|
151180
|
+
lines.forEach((line) => {
|
|
151181
|
+
this.sections.push(`> ${line}
|
|
151182
|
+
`);
|
|
151183
|
+
});
|
|
151184
|
+
this.sections.push(`
|
|
151185
|
+
`);
|
|
151186
|
+
return this;
|
|
151032
151187
|
}
|
|
151033
|
-
|
|
151034
|
-
|
|
151188
|
+
taskList(items) {
|
|
151189
|
+
if (items.length === 0)
|
|
151190
|
+
return this;
|
|
151191
|
+
this.sections.push(`${items.map((item) => `- [${item.checked ? "x" : " "}] ${item.text}`).join(`
|
|
151192
|
+
`)}
|
|
151035
151193
|
|
|
151036
|
-
|
|
151037
|
-
|
|
151038
|
-
if (!authorListXml)
|
|
151039
|
-
return { authors: [], affiliations: [] };
|
|
151040
|
-
const affiliationMap = new Map;
|
|
151041
|
-
const affiliationList = [];
|
|
151042
|
-
function getAffiliationIndex(text) {
|
|
151043
|
-
const existing = affiliationMap.get(text);
|
|
151044
|
-
if (existing !== undefined)
|
|
151045
|
-
return existing;
|
|
151046
|
-
const idx = affiliationList.length;
|
|
151047
|
-
affiliationList.push(text);
|
|
151048
|
-
affiliationMap.set(text, idx);
|
|
151049
|
-
return idx;
|
|
151194
|
+
`);
|
|
151195
|
+
return this;
|
|
151050
151196
|
}
|
|
151051
|
-
|
|
151052
|
-
|
|
151053
|
-
|
|
151054
|
-
|
|
151055
|
-
|
|
151197
|
+
image(altText, url2, title) {
|
|
151198
|
+
const titlePart = title ? ` "${title}"` : "";
|
|
151199
|
+
this.sections.push(`
|
|
151200
|
+
|
|
151201
|
+
`);
|
|
151202
|
+
return this;
|
|
151203
|
+
}
|
|
151204
|
+
strikethrough(text) {
|
|
151205
|
+
this.sections.push(`~~${text}~~`);
|
|
151206
|
+
return this;
|
|
151207
|
+
}
|
|
151208
|
+
diff(changes) {
|
|
151209
|
+
const lines = [];
|
|
151210
|
+
if (changes.context) {
|
|
151211
|
+
lines.push(...changes.context.map((line) => ` ${line}`));
|
|
151056
151212
|
}
|
|
151057
|
-
|
|
151058
|
-
|
|
151059
|
-
for (const info of authorAffiliationInfos) {
|
|
151060
|
-
const text = getText(info?.Affiliation);
|
|
151061
|
-
if (text)
|
|
151062
|
-
indices.push(getAffiliationIndex(text));
|
|
151213
|
+
if (changes.deletions) {
|
|
151214
|
+
lines.push(...changes.deletions.map((line) => `- ${line}`));
|
|
151063
151215
|
}
|
|
151064
|
-
|
|
151065
|
-
|
|
151066
|
-
for (const id of identifiers) {
|
|
151067
|
-
if (getAttribute(id, "Source") === "ORCID") {
|
|
151068
|
-
const val = getText(id);
|
|
151069
|
-
if (val) {
|
|
151070
|
-
orcid = val;
|
|
151071
|
-
break;
|
|
151072
|
-
}
|
|
151073
|
-
}
|
|
151216
|
+
if (changes.additions) {
|
|
151217
|
+
lines.push(...changes.additions.map((line) => `+ ${line}`));
|
|
151074
151218
|
}
|
|
151075
|
-
|
|
151076
|
-
|
|
151077
|
-
|
|
151078
|
-
initials: getText(auth.Initials),
|
|
151079
|
-
...indices.length > 0 && { affiliationIndices: indices },
|
|
151080
|
-
...orcid && { orcid }
|
|
151081
|
-
};
|
|
151082
|
-
});
|
|
151083
|
-
return { authors, affiliations: affiliationList };
|
|
151084
|
-
}
|
|
151085
|
-
function extractJournalInfo(journalXml, articleXml) {
|
|
151086
|
-
if (!journalXml)
|
|
151087
|
-
return;
|
|
151088
|
-
const pubDate = journalXml.JournalIssue?.PubDate;
|
|
151089
|
-
const year = getText(pubDate?.Year, getText(pubDate?.MedlineDate, "").match(/\d{4}/)?.[0]);
|
|
151090
|
-
const issnElement = journalXml.ISSN;
|
|
151091
|
-
const issnValue = getText(issnElement);
|
|
151092
|
-
const issnType = getAttribute(issnElement, "IssnType");
|
|
151093
|
-
const issn = issnType === "Electronic" ? undefined : issnValue || undefined;
|
|
151094
|
-
const eIssn = issnType === "Electronic" ? issnValue || undefined : undefined;
|
|
151095
|
-
return {
|
|
151096
|
-
title: getText(journalXml.Title),
|
|
151097
|
-
isoAbbreviation: getText(journalXml.ISOAbbreviation),
|
|
151098
|
-
...issn && { issn },
|
|
151099
|
-
...eIssn && { eIssn },
|
|
151100
|
-
volume: getText(journalXml.JournalIssue?.Volume),
|
|
151101
|
-
issue: getText(journalXml.JournalIssue?.Issue),
|
|
151102
|
-
pages: getText(articleXml?.Pagination?.MedlinePgn),
|
|
151103
|
-
publicationDate: {
|
|
151104
|
-
...year && { year },
|
|
151105
|
-
...getText(pubDate?.Month) && { month: getText(pubDate?.Month) },
|
|
151106
|
-
...getText(pubDate?.Day) && { day: getText(pubDate?.Day) },
|
|
151107
|
-
...getText(pubDate?.MedlineDate) && { medlineDate: getText(pubDate?.MedlineDate) }
|
|
151219
|
+
if (lines.length > 0) {
|
|
151220
|
+
this.codeBlock(lines.join(`
|
|
151221
|
+
`), "diff");
|
|
151108
151222
|
}
|
|
151109
|
-
|
|
151223
|
+
return this;
|
|
151224
|
+
}
|
|
151225
|
+
badge(label, message, color = "blue") {
|
|
151226
|
+
const encodedLabel = encodeURIComponent(label);
|
|
151227
|
+
const encodedMessage = encodeURIComponent(message);
|
|
151228
|
+
const url2 = `https://img.shields.io/badge/${encodedLabel}-${encodedMessage}-${color}`;
|
|
151229
|
+
this.sections.push(``);
|
|
151230
|
+
return this;
|
|
151231
|
+
}
|
|
151232
|
+
bold(text) {
|
|
151233
|
+
this.sections.push(`**${text}**`);
|
|
151234
|
+
return this;
|
|
151235
|
+
}
|
|
151236
|
+
italic(text) {
|
|
151237
|
+
this.sections.push(`*${text}*`);
|
|
151238
|
+
return this;
|
|
151239
|
+
}
|
|
151240
|
+
boldItalic(text) {
|
|
151241
|
+
this.sections.push(`***${text}***`);
|
|
151242
|
+
return this;
|
|
151243
|
+
}
|
|
151244
|
+
raw(markdown) {
|
|
151245
|
+
this.sections.push(markdown);
|
|
151246
|
+
return this;
|
|
151247
|
+
}
|
|
151248
|
+
blankLine() {
|
|
151249
|
+
this.sections.push(`
|
|
151250
|
+
`);
|
|
151251
|
+
return this;
|
|
151252
|
+
}
|
|
151253
|
+
text(text) {
|
|
151254
|
+
this.sections.push(text);
|
|
151255
|
+
return this;
|
|
151256
|
+
}
|
|
151257
|
+
when(condition, content) {
|
|
151258
|
+
if (condition) {
|
|
151259
|
+
content();
|
|
151260
|
+
}
|
|
151261
|
+
return this;
|
|
151262
|
+
}
|
|
151263
|
+
build() {
|
|
151264
|
+
return this.sections.join("").trim();
|
|
151265
|
+
}
|
|
151266
|
+
reset() {
|
|
151267
|
+
this.sections = [];
|
|
151268
|
+
return this;
|
|
151269
|
+
}
|
|
151110
151270
|
}
|
|
151111
|
-
function
|
|
151112
|
-
|
|
151113
|
-
return [];
|
|
151114
|
-
const meshHeadings = ensureArray(meshHeadingListXml.MeshHeading);
|
|
151115
|
-
return meshHeadings.map((mh) => {
|
|
151116
|
-
const isMajorDescriptor = getAttribute(mh.DescriptorName, "MajorTopicYN") === "Y";
|
|
151117
|
-
const isMajorRoot = getAttribute(mh, "MajorTopicYN") === "Y";
|
|
151118
|
-
const descriptorUi = getAttribute(mh.DescriptorName, "UI");
|
|
151119
|
-
const rawQualifiers = ensureArray(mh.QualifierName);
|
|
151120
|
-
const qualifiers = rawQualifiers.flatMap((q) => {
|
|
151121
|
-
const name = getText(q);
|
|
151122
|
-
if (!name)
|
|
151123
|
-
return [];
|
|
151124
|
-
const ui = getAttribute(q, "UI");
|
|
151125
|
-
return {
|
|
151126
|
-
qualifierName: name,
|
|
151127
|
-
...ui && { qualifierUi: ui },
|
|
151128
|
-
isMajorTopic: getAttribute(q, "MajorTopicYN") === "Y"
|
|
151129
|
-
};
|
|
151130
|
-
});
|
|
151131
|
-
const isMajorAnyQualifier = qualifiers.some((q) => q.isMajorTopic);
|
|
151132
|
-
return {
|
|
151133
|
-
descriptorName: getText(mh.DescriptorName),
|
|
151134
|
-
...descriptorUi && { descriptorUi },
|
|
151135
|
-
...qualifiers.length > 0 && { qualifiers },
|
|
151136
|
-
isMajorTopic: isMajorRoot || isMajorDescriptor || isMajorAnyQualifier
|
|
151137
|
-
};
|
|
151138
|
-
});
|
|
151271
|
+
function markdown() {
|
|
151272
|
+
return new MarkdownBuilder;
|
|
151139
151273
|
}
|
|
151140
|
-
|
|
151141
|
-
|
|
151142
|
-
|
|
151143
|
-
|
|
151144
|
-
|
|
151145
|
-
|
|
151146
|
-
|
|
151147
|
-
|
|
151148
|
-
|
|
151149
|
-
|
|
151150
|
-
|
|
151151
|
-
|
|
151152
|
-
|
|
151153
|
-
|
|
151154
|
-
|
|
151155
|
-
|
|
151274
|
+
|
|
151275
|
+
// src/mcp-server/tools/definitions/pmc-fetch.tool.ts
|
|
151276
|
+
var ncbi2 = () => container.resolve(NcbiServiceToken);
|
|
151277
|
+
var TOOL_NAME = "pmc_fetch";
|
|
151278
|
+
var TOOL_TITLE = "PMC Full-Text Fetch";
|
|
151279
|
+
var TOOL_DESCRIPTION = "Fetch full-text articles from PubMed Central (PMC). Returns complete article body text, sections, and references for open-access articles. Accepts PMC IDs directly or PubMed IDs (auto-resolved via ELink). Only articles available in PMC will return full text.";
|
|
151280
|
+
var TOOL_ANNOTATIONS = {
|
|
151281
|
+
idempotentHint: true,
|
|
151282
|
+
openWorldHint: true,
|
|
151283
|
+
readOnlyHint: true
|
|
151284
|
+
};
|
|
151285
|
+
var InputSchema = exports_external.object({
|
|
151286
|
+
includeReferences: exports_external.boolean().default(false).describe("Include reference list from back matter"),
|
|
151287
|
+
maxSections: exports_external.number().int().min(1).max(50).optional().describe("Maximum number of top-level body sections to return"),
|
|
151288
|
+
pmcids: exports_external.array(exports_external.string()).min(1).max(10).optional().describe('PMC IDs to fetch (e.g., ["PMC9575052"] or ["9575052"]). Provide pmcids or pmids, not both.'),
|
|
151289
|
+
pmids: exports_external.array(exports_external.string().regex(/^\d+$/)).min(1).max(10).optional().describe("PubMed IDs to resolve to PMC articles via ELink. Only articles available in PMC will be returned."),
|
|
151290
|
+
sections: exports_external.array(exports_external.string()).optional().describe('Filter to specific sections by title (case-insensitive match, e.g., ["methods", "results"])')
|
|
151291
|
+
}).refine((data) => data.pmcids ?? data.pmids, {
|
|
151292
|
+
message: "Either pmcids or pmids must be provided"
|
|
151293
|
+
}).refine((data) => !(data.pmcids && data.pmids), {
|
|
151294
|
+
message: "Provide pmcids or pmids, not both"
|
|
151295
|
+
});
|
|
151296
|
+
var SectionSchema = exports_external.object({
|
|
151297
|
+
label: exports_external.string().optional().describe('Section label (e.g., "1", "2.1")'),
|
|
151298
|
+
subsections: exports_external.lazy(() => exports_external.array(SectionSchema)).optional().describe("Nested subsections"),
|
|
151299
|
+
text: exports_external.string().describe("Section body text"),
|
|
151300
|
+
title: exports_external.string().optional().describe("Section heading")
|
|
151301
|
+
});
|
|
151302
|
+
var ArticleSchema = exports_external.object({
|
|
151303
|
+
abstract: exports_external.string().optional().describe("Article abstract"),
|
|
151304
|
+
affiliations: exports_external.array(exports_external.string()).optional().describe("Author affiliations"),
|
|
151305
|
+
articleType: exports_external.string().optional().describe('Article type (e.g., "research-article")'),
|
|
151306
|
+
authors: exports_external.array(exports_external.object({
|
|
151307
|
+
collectiveName: exports_external.string().optional(),
|
|
151308
|
+
givenNames: exports_external.string().optional(),
|
|
151309
|
+
lastName: exports_external.string().optional()
|
|
151310
|
+
})).optional().describe("Author list"),
|
|
151311
|
+
doi: exports_external.string().optional().describe("Digital Object Identifier"),
|
|
151312
|
+
journal: exports_external.any().optional().describe("Journal information"),
|
|
151313
|
+
keywords: exports_external.array(exports_external.string()).optional().describe("Article keywords"),
|
|
151314
|
+
pmcId: exports_external.string().describe("PubMed Central ID"),
|
|
151315
|
+
pmcUrl: exports_external.string().describe("PMC article URL"),
|
|
151316
|
+
pmid: exports_external.string().optional().describe("PubMed ID"),
|
|
151317
|
+
pubmedUrl: exports_external.string().optional().describe("PubMed article URL"),
|
|
151318
|
+
publicationDate: exports_external.any().optional().describe("Publication date"),
|
|
151319
|
+
references: exports_external.array(exports_external.object({ citation: exports_external.string(), id: exports_external.string().optional(), label: exports_external.string().optional() })).optional().describe("Reference list"),
|
|
151320
|
+
sections: exports_external.array(SectionSchema).describe("Article body sections with full text"),
|
|
151321
|
+
title: exports_external.string().optional().describe("Article title")
|
|
151322
|
+
});
|
|
151323
|
+
var OutputSchema2 = exports_external.object({
|
|
151324
|
+
articles: exports_external.array(ArticleSchema).describe("Parsed full-text articles"),
|
|
151325
|
+
totalReturned: exports_external.number().describe("Number of articles returned"),
|
|
151326
|
+
unavailablePmids: exports_external.array(exports_external.string()).optional().describe("PMIDs that could not be resolved to PMC articles")
|
|
151327
|
+
});
|
|
151328
|
+
function normalizePmcId(id) {
|
|
151329
|
+
return id.replace(/^PMC/i, "");
|
|
151156
151330
|
}
|
|
151157
|
-
function
|
|
151158
|
-
if (
|
|
151159
|
-
return;
|
|
151160
|
-
|
|
151161
|
-
|
|
151162
|
-
if (getAttribute(eloc, "EIdType") === "doi" && getAttribute(eloc, "ValidYN") === "Y") {
|
|
151163
|
-
const doi = getText(eloc);
|
|
151164
|
-
if (doi)
|
|
151165
|
-
return doi;
|
|
151166
|
-
}
|
|
151167
|
-
}
|
|
151168
|
-
for (const eloc of eLocationIDs) {
|
|
151169
|
-
if (getAttribute(eloc, "EIdType") === "doi") {
|
|
151170
|
-
const doi = getText(eloc);
|
|
151171
|
-
if (doi)
|
|
151172
|
-
return doi;
|
|
151173
|
-
}
|
|
151331
|
+
function extractLinkId(field) {
|
|
151332
|
+
if (field === undefined || field === null)
|
|
151333
|
+
return "";
|
|
151334
|
+
if (typeof field === "object") {
|
|
151335
|
+
return field["#text"] !== undefined ? String(field["#text"]) : "";
|
|
151174
151336
|
}
|
|
151175
|
-
|
|
151176
|
-
|
|
151177
|
-
|
|
151178
|
-
|
|
151179
|
-
|
|
151180
|
-
|
|
151337
|
+
return String(field);
|
|
151338
|
+
}
|
|
151339
|
+
async function resolvePmidsToPmcIds(pmids, appContext) {
|
|
151340
|
+
const eLinkResult = await ncbi2().eLink({
|
|
151341
|
+
cmd: "neighbor",
|
|
151342
|
+
db: "pmc",
|
|
151343
|
+
dbfrom: "pubmed",
|
|
151344
|
+
id: pmids.join(","),
|
|
151345
|
+
linkname: "pubmed_pmc",
|
|
151346
|
+
retmode: "xml"
|
|
151347
|
+
}, appContext);
|
|
151348
|
+
const resolved = new Map;
|
|
151349
|
+
const eLinkResults = ensureArray(eLinkResult?.eLinkResult);
|
|
151350
|
+
for (const result of eLinkResults) {
|
|
151351
|
+
if (result?.ERROR) {
|
|
151352
|
+
logger.warning("ELink error during PMID resolution", {
|
|
151353
|
+
...appContext,
|
|
151354
|
+
error: result.ERROR
|
|
151355
|
+
});
|
|
151356
|
+
continue;
|
|
151181
151357
|
}
|
|
151182
|
-
|
|
151183
|
-
|
|
151184
|
-
|
|
151185
|
-
|
|
151186
|
-
|
|
151187
|
-
|
|
151188
|
-
|
|
151189
|
-
|
|
151358
|
+
const linkSet = result?.LinkSet;
|
|
151359
|
+
if (!linkSet?.LinkSetDb)
|
|
151360
|
+
continue;
|
|
151361
|
+
const linkSetDbArray = ensureArray(linkSet.LinkSetDb);
|
|
151362
|
+
const pmcLinkSet = linkSetDbArray.find((db) => db.LinkName === "pubmed_pmc") ?? linkSetDbArray[0];
|
|
151363
|
+
if (pmcLinkSet?.Link) {
|
|
151364
|
+
const sourcePmid = extractLinkId(linkSet.IdList?.Id);
|
|
151365
|
+
const links = ensureArray(pmcLinkSet.Link);
|
|
151366
|
+
for (const link of links) {
|
|
151367
|
+
const pmcId = extractLinkId(link.Id);
|
|
151368
|
+
if (pmcId && sourcePmid) {
|
|
151369
|
+
resolved.set(sourcePmid, pmcId);
|
|
151370
|
+
}
|
|
151190
151371
|
}
|
|
151191
151372
|
}
|
|
151192
151373
|
}
|
|
151193
|
-
|
|
151374
|
+
const unavailable = pmids.filter((pmid) => !resolved.has(pmid));
|
|
151375
|
+
return { resolved, unavailable };
|
|
151194
151376
|
}
|
|
151195
|
-
function
|
|
151196
|
-
const
|
|
151197
|
-
|
|
151198
|
-
if (
|
|
151199
|
-
|
|
151200
|
-
|
|
151201
|
-
|
|
151377
|
+
function filterSections(sections, sectionFilter) {
|
|
151378
|
+
const lowerFilter = sectionFilter.map((s) => s.toLowerCase());
|
|
151379
|
+
return sections.filter((s) => {
|
|
151380
|
+
if (!s.title)
|
|
151381
|
+
return false;
|
|
151382
|
+
return lowerFilter.some((f) => s.title?.toLowerCase().includes(f));
|
|
151383
|
+
});
|
|
151384
|
+
}
|
|
151385
|
+
async function logic(input, appContext, _sdkContext) {
|
|
151386
|
+
logger.info("Executing pmc_fetch tool", {
|
|
151387
|
+
...appContext,
|
|
151388
|
+
hasPmcids: !!input.pmcids,
|
|
151389
|
+
hasPmids: !!input.pmids,
|
|
151390
|
+
idCount: (input.pmcids ?? input.pmids)?.length
|
|
151391
|
+
});
|
|
151392
|
+
let pmcIds;
|
|
151393
|
+
let unavailablePmids;
|
|
151394
|
+
if (input.pmids) {
|
|
151395
|
+
const resolution = await resolvePmidsToPmcIds(input.pmids, appContext);
|
|
151396
|
+
if (resolution.resolved.size === 0) {
|
|
151397
|
+
logger.notice("No PMC articles found for provided PMIDs", {
|
|
151398
|
+
...appContext,
|
|
151399
|
+
pmids: input.pmids
|
|
151400
|
+
});
|
|
151401
|
+
return {
|
|
151402
|
+
articles: [],
|
|
151403
|
+
totalReturned: 0,
|
|
151404
|
+
unavailablePmids: input.pmids
|
|
151405
|
+
};
|
|
151202
151406
|
}
|
|
151203
|
-
|
|
151204
|
-
|
|
151205
|
-
|
|
151206
|
-
|
|
151207
|
-
|
|
151208
|
-
|
|
151209
|
-
|
|
151210
|
-
return val;
|
|
151211
|
-
}
|
|
151407
|
+
pmcIds = [...resolution.resolved.values()];
|
|
151408
|
+
if (resolution.unavailable.length > 0) {
|
|
151409
|
+
unavailablePmids = resolution.unavailable;
|
|
151410
|
+
logger.debug("Some PMIDs not available in PMC", {
|
|
151411
|
+
...appContext,
|
|
151412
|
+
unavailable: resolution.unavailable
|
|
151413
|
+
});
|
|
151212
151414
|
}
|
|
151415
|
+
} else {
|
|
151416
|
+
pmcIds = (input.pmcids ?? []).map(normalizePmcId);
|
|
151417
|
+
}
|
|
151418
|
+
const xmlData = await ncbi2().eFetch({ db: "pmc", id: pmcIds.join(","), retmode: "xml" }, appContext, { retmode: "xml", usePost: pmcIds.length > 5 });
|
|
151419
|
+
if (!xmlData || !("pmc-articleset" in xmlData)) {
|
|
151420
|
+
throw new McpError(-32603 /* InternalError */, "Invalid PMC EFetch response: missing pmc-articleset", { requestId: appContext.requestId });
|
|
151421
|
+
}
|
|
151422
|
+
const articleSet = xmlData["pmc-articleset"];
|
|
151423
|
+
if (!articleSet?.article) {
|
|
151424
|
+
return { articles: [], totalReturned: 0, ...unavailablePmids && { unavailablePmids } };
|
|
151425
|
+
}
|
|
151426
|
+
const xmlArticles = ensureArray(articleSet.article);
|
|
151427
|
+
let articles = xmlArticles.map((xmlArticle) => parsePmcArticle(xmlArticle));
|
|
151428
|
+
if (input.sections && input.sections.length > 0) {
|
|
151429
|
+
const sectionFilter = input.sections;
|
|
151430
|
+
articles = articles.map((article) => ({
|
|
151431
|
+
...article,
|
|
151432
|
+
sections: filterSections(article.sections, sectionFilter)
|
|
151433
|
+
}));
|
|
151213
151434
|
}
|
|
151214
|
-
|
|
151215
|
-
|
|
151216
|
-
|
|
151217
|
-
|
|
151218
|
-
|
|
151219
|
-
|
|
151220
|
-
|
|
151435
|
+
if (input.maxSections !== undefined) {
|
|
151436
|
+
articles = articles.map((article) => ({
|
|
151437
|
+
...article,
|
|
151438
|
+
sections: article.sections.slice(0, input.maxSections)
|
|
151439
|
+
}));
|
|
151440
|
+
}
|
|
151441
|
+
if (!input.includeReferences) {
|
|
151442
|
+
articles = articles.map((article) => {
|
|
151443
|
+
const { references: _refs, ...rest } = article;
|
|
151444
|
+
return rest;
|
|
151445
|
+
});
|
|
151446
|
+
}
|
|
151447
|
+
logger.notice("pmc_fetch completed", {
|
|
151448
|
+
...appContext,
|
|
151449
|
+
requested: pmcIds.length,
|
|
151450
|
+
returned: articles.length
|
|
151451
|
+
});
|
|
151452
|
+
return {
|
|
151453
|
+
articles,
|
|
151454
|
+
totalReturned: articles.length,
|
|
151455
|
+
...unavailablePmids && { unavailablePmids }
|
|
151456
|
+
};
|
|
151221
151457
|
}
|
|
151222
|
-
function
|
|
151223
|
-
|
|
151224
|
-
|
|
151225
|
-
|
|
151226
|
-
|
|
151227
|
-
|
|
151228
|
-
|
|
151229
|
-
|
|
151230
|
-
|
|
151231
|
-
|
|
151232
|
-
|
|
151458
|
+
function formatSection(md, section, depth) {
|
|
151459
|
+
const headingLevel = Math.min(depth, 6);
|
|
151460
|
+
const prefix = "#".repeat(headingLevel);
|
|
151461
|
+
if (section.title) {
|
|
151462
|
+
md.text(`${prefix} ${section.title}
|
|
151463
|
+
|
|
151464
|
+
`);
|
|
151465
|
+
}
|
|
151466
|
+
if (section.text) {
|
|
151467
|
+
md.paragraph(section.text);
|
|
151468
|
+
}
|
|
151469
|
+
if (section.subsections) {
|
|
151470
|
+
for (const sub of section.subsections) {
|
|
151471
|
+
formatSection(md, sub, depth + 1);
|
|
151233
151472
|
}
|
|
151234
151473
|
}
|
|
151235
|
-
return allKeywords;
|
|
151236
151474
|
}
|
|
151237
|
-
function
|
|
151238
|
-
|
|
151239
|
-
|
|
151240
|
-
|
|
151241
|
-
|
|
151242
|
-
|
|
151243
|
-
|
|
151244
|
-
|
|
151245
|
-
|
|
151475
|
+
function responseFormatter(result) {
|
|
151476
|
+
const md = markdown().h2("PMC Full-Text Articles").keyValue("Articles Returned", result.totalReturned);
|
|
151477
|
+
if (result.unavailablePmids?.length) {
|
|
151478
|
+
md.keyValue("Unavailable PMIDs", result.unavailablePmids.join(", "));
|
|
151479
|
+
}
|
|
151480
|
+
for (const article of result.articles) {
|
|
151481
|
+
md.h3(article.title ?? article.pmcId);
|
|
151482
|
+
md.keyValue("PMCID", article.pmcId);
|
|
151483
|
+
if (article.pmid)
|
|
151484
|
+
md.keyValue("PMID", article.pmid);
|
|
151485
|
+
if (article.doi)
|
|
151486
|
+
md.keyValue("DOI", article.doi);
|
|
151487
|
+
md.keyValue("PMC", article.pmcUrl);
|
|
151488
|
+
if (article.pubmedUrl)
|
|
151489
|
+
md.keyValue("PubMed", article.pubmedUrl);
|
|
151490
|
+
if (article.authors?.length) {
|
|
151491
|
+
const authorStr = article.authors.slice(0, 5).map((a) => a.collectiveName ?? `${a.lastName ?? ""} ${a.givenNames ?? ""}`.trim()).join(", ");
|
|
151492
|
+
md.keyValue("Authors", article.authors.length > 5 ? `${authorStr}, et al.` : authorStr);
|
|
151246
151493
|
}
|
|
151247
|
-
|
|
151248
|
-
|
|
151249
|
-
|
|
151250
|
-
|
|
151494
|
+
if (article.journal) {
|
|
151495
|
+
const parts = [article.journal.title, article.journal.volume, article.journal.pages].filter(Boolean);
|
|
151496
|
+
if (parts.length > 0)
|
|
151497
|
+
md.keyValue("Journal", parts.join(", "));
|
|
151251
151498
|
}
|
|
151252
|
-
|
|
151253
|
-
|
|
151254
|
-
|
|
151255
|
-
|
|
151256
|
-
|
|
151257
|
-
|
|
151258
|
-
|
|
151499
|
+
if (article.abstract) {
|
|
151500
|
+
md.h4("Abstract").paragraph(article.abstract);
|
|
151501
|
+
}
|
|
151502
|
+
if (article.sections.length > 0) {
|
|
151503
|
+
for (const section of article.sections) {
|
|
151504
|
+
formatSection(md, section, 4);
|
|
151505
|
+
}
|
|
151506
|
+
}
|
|
151507
|
+
if (article.references?.length) {
|
|
151508
|
+
md.h4("References");
|
|
151509
|
+
const refItems = article.references.map((r) => `${r.label ? `[${r.label}] ` : ""}${r.citation}`);
|
|
151510
|
+
md.list(refItems);
|
|
151511
|
+
}
|
|
151512
|
+
}
|
|
151513
|
+
return [{ type: "text", text: md.build() }];
|
|
151259
151514
|
}
|
|
151260
|
-
|
|
151261
|
-
|
|
151262
|
-
|
|
151263
|
-
|
|
151515
|
+
var pmcFetchTool = {
|
|
151516
|
+
annotations: TOOL_ANNOTATIONS,
|
|
151517
|
+
description: TOOL_DESCRIPTION,
|
|
151518
|
+
inputSchema: InputSchema,
|
|
151519
|
+
logic: withToolAuth(["tool:pmc_fetch:read"], logic),
|
|
151520
|
+
name: TOOL_NAME,
|
|
151521
|
+
outputSchema: OutputSchema2,
|
|
151522
|
+
responseFormatter,
|
|
151523
|
+
title: TOOL_TITLE
|
|
151524
|
+
};
|
|
151525
|
+
|
|
151526
|
+
// src/services/ncbi/formatting/citation-formatter.ts
|
|
151527
|
+
function getYear(article) {
|
|
151528
|
+
return article.journalInfo?.publicationDate?.year ?? "n.d.";
|
|
151264
151529
|
}
|
|
151265
|
-
function
|
|
151266
|
-
if (!
|
|
151267
|
-
return
|
|
151268
|
-
const
|
|
151269
|
-
|
|
151270
|
-
|
|
151271
|
-
|
|
151272
|
-
|
|
151273
|
-
day: getText(ad.Day)
|
|
151274
|
-
}));
|
|
151530
|
+
function splitPages(pages) {
|
|
151531
|
+
if (!pages)
|
|
151532
|
+
return {};
|
|
151533
|
+
const parts = pages.split(/[-\u2013\u2014]/).map((p) => p.trim());
|
|
151534
|
+
const [start, end] = parts;
|
|
151535
|
+
if (start && end)
|
|
151536
|
+
return { start, end };
|
|
151537
|
+
return start ? { start } : {};
|
|
151275
151538
|
}
|
|
151276
|
-
function
|
|
151277
|
-
|
|
151278
|
-
|
|
151279
|
-
|
|
151280
|
-
|
|
151281
|
-
|
|
151282
|
-
|
|
151283
|
-
|
|
151284
|
-
|
|
151285
|
-
|
|
151286
|
-
|
|
151287
|
-
|
|
151288
|
-
|
|
151289
|
-
...abstractText !== undefined && { abstractText },
|
|
151290
|
-
...affiliations.length > 0 && { affiliations },
|
|
151291
|
-
authors,
|
|
151292
|
-
...journalInfo !== undefined && { journalInfo },
|
|
151293
|
-
publicationTypes: extractPublicationTypes(article?.PublicationTypeList),
|
|
151294
|
-
keywords: extractKeywords(medlineCitation?.KeywordList ?? article?.KeywordList),
|
|
151295
|
-
...includeMesh && { meshTerms: extractMeshTerms(medlineCitation?.MeshHeadingList) },
|
|
151296
|
-
...includeGrants && { grantList: extractGrants(article?.GrantList) },
|
|
151297
|
-
...doi !== undefined && { doi },
|
|
151298
|
-
...pmcId !== undefined && { pmcId },
|
|
151299
|
-
articleDates: extractArticleDates(article)
|
|
151300
|
-
};
|
|
151539
|
+
function escapeBibtex(text) {
|
|
151540
|
+
return text.replace(/[\\&%$#_{}~^]/g, (ch) => {
|
|
151541
|
+
switch (ch) {
|
|
151542
|
+
case "\\":
|
|
151543
|
+
return "\\textbackslash{}";
|
|
151544
|
+
case "~":
|
|
151545
|
+
return "\\textasciitilde{}";
|
|
151546
|
+
case "^":
|
|
151547
|
+
return "\\textasciicircum{}";
|
|
151548
|
+
default:
|
|
151549
|
+
return `\\${ch}`;
|
|
151550
|
+
}
|
|
151551
|
+
});
|
|
151301
151552
|
}
|
|
151302
|
-
|
|
151303
|
-
|
|
151304
|
-
|
|
151305
|
-
|
|
151306
|
-
|
|
151307
|
-
|
|
151308
|
-
|
|
151309
|
-
|
|
151310
|
-
|
|
151311
|
-
return
|
|
151553
|
+
function formatAuthorApa(author) {
|
|
151554
|
+
if (author.collectiveName)
|
|
151555
|
+
return author.collectiveName;
|
|
151556
|
+
const last = author.lastName ?? "";
|
|
151557
|
+
const initials = author.initials ?? author.firstName?.split(/[\s-]+/).filter(Boolean).map((part) => `${part[0]}.`).join(" ");
|
|
151558
|
+
if (!initials)
|
|
151559
|
+
return last;
|
|
151560
|
+
const formatted = initials.replace(/[^A-Za-z]/g, "").split("").map((c) => `${c}.`).join(" ");
|
|
151561
|
+
if (!last)
|
|
151562
|
+
return formatted;
|
|
151563
|
+
return `${last}, ${formatted}`;
|
|
151564
|
+
}
|
|
151565
|
+
function formatAuthorsApa(authors) {
|
|
151566
|
+
const formatted = authors.map(formatAuthorApa);
|
|
151567
|
+
if (formatted.length === 0)
|
|
151568
|
+
return "";
|
|
151569
|
+
if (formatted.length === 1)
|
|
151570
|
+
return formatted[0] ?? "";
|
|
151571
|
+
if (formatted.length === 2)
|
|
151572
|
+
return `${formatted[0]}, & ${formatted[1]}`;
|
|
151573
|
+
if (formatted.length <= 20) {
|
|
151574
|
+
const allButLast = formatted.slice(0, -1).join(", ");
|
|
151575
|
+
return `${allButLast}, & ${formatted.at(-1)}`;
|
|
151312
151576
|
}
|
|
151313
|
-
|
|
151314
|
-
|
|
151315
|
-
|
|
151316
|
-
|
|
151317
|
-
|
|
151318
|
-
return
|
|
151577
|
+
const first19 = formatted.slice(0, 19).join(", ");
|
|
151578
|
+
return `${first19}, ... ${formatted.at(-1)}`;
|
|
151579
|
+
}
|
|
151580
|
+
function formatAuthorMla(author, isFirst) {
|
|
151581
|
+
if (author.collectiveName)
|
|
151582
|
+
return author.collectiveName;
|
|
151583
|
+
const last = author.lastName ?? "";
|
|
151584
|
+
const first = author.firstName ?? "";
|
|
151585
|
+
if (!last && !first)
|
|
151586
|
+
return "";
|
|
151587
|
+
if (!first)
|
|
151588
|
+
return last;
|
|
151589
|
+
if (!last)
|
|
151590
|
+
return first;
|
|
151591
|
+
return isFirst ? `${last}, ${first}` : `${first} ${last}`;
|
|
151592
|
+
}
|
|
151593
|
+
function formatAuthorsMla(authors) {
|
|
151594
|
+
const first = authors[0];
|
|
151595
|
+
if (!first)
|
|
151596
|
+
return "";
|
|
151597
|
+
if (authors.length === 1)
|
|
151598
|
+
return formatAuthorMla(first, true);
|
|
151599
|
+
if (authors.length === 2) {
|
|
151600
|
+
const second = authors[1];
|
|
151601
|
+
return second ? `${formatAuthorMla(first, true)}, and ${formatAuthorMla(second, false)}` : formatAuthorMla(first, true);
|
|
151319
151602
|
}
|
|
151320
|
-
|
|
151321
|
-
|
|
151322
|
-
|
|
151323
|
-
|
|
151324
|
-
`)
|
|
151325
|
-
|
|
151603
|
+
return `${formatAuthorMla(first, true)}, et al.`;
|
|
151604
|
+
}
|
|
151605
|
+
function formatAuthorBibtex(author) {
|
|
151606
|
+
if (author.collectiveName)
|
|
151607
|
+
return `{${escapeBibtex(author.collectiveName)}}`;
|
|
151608
|
+
const last = author.lastName ? escapeBibtex(author.lastName) : "";
|
|
151609
|
+
const first = author.firstName ? escapeBibtex(author.firstName) : "";
|
|
151610
|
+
if (!last && !first)
|
|
151611
|
+
return "";
|
|
151612
|
+
if (!first)
|
|
151613
|
+
return `{${last}}`;
|
|
151614
|
+
if (!last)
|
|
151615
|
+
return first;
|
|
151616
|
+
return `{${last}}, ${first}`;
|
|
151617
|
+
}
|
|
151618
|
+
function formatApa(article) {
|
|
151619
|
+
const parts = [];
|
|
151620
|
+
const authorStr = article.authors?.length ? formatAuthorsApa(article.authors) : "";
|
|
151621
|
+
if (authorStr) {
|
|
151622
|
+
parts.push(authorStr);
|
|
151326
151623
|
}
|
|
151327
|
-
|
|
151328
|
-
|
|
151329
|
-
|
|
151330
|
-
|
|
151331
|
-
|
|
151332
|
-
return this;
|
|
151624
|
+
const year = getYear(article);
|
|
151625
|
+
parts.push(`(${year}).`);
|
|
151626
|
+
if (article.title) {
|
|
151627
|
+
const title = article.title.replace(/\.\s*$/, "");
|
|
151628
|
+
parts.push(`${title}.`);
|
|
151333
151629
|
}
|
|
151334
|
-
|
|
151335
|
-
|
|
151336
|
-
|
|
151337
|
-
|
|
151338
|
-
|
|
151630
|
+
const journal = article.journalInfo;
|
|
151631
|
+
if (journal?.title) {
|
|
151632
|
+
let journalPart = `*${journal.title}*`;
|
|
151633
|
+
if (journal.volume) {
|
|
151634
|
+
journalPart += `, *${journal.volume}*`;
|
|
151635
|
+
if (journal.issue) {
|
|
151636
|
+
journalPart += `(${journal.issue})`;
|
|
151637
|
+
}
|
|
151638
|
+
}
|
|
151639
|
+
if (journal.pages) {
|
|
151640
|
+
journalPart += `, ${journal.pages}`;
|
|
151641
|
+
}
|
|
151642
|
+
journalPart += ".";
|
|
151643
|
+
parts.push(journalPart);
|
|
151339
151644
|
}
|
|
151340
|
-
|
|
151341
|
-
|
|
151342
|
-
this.sections.push(`${key}: ${displayValue}
|
|
151343
|
-
`);
|
|
151344
|
-
return this;
|
|
151645
|
+
if (article.doi) {
|
|
151646
|
+
parts.push(`https://doi.org/${article.doi}`);
|
|
151345
151647
|
}
|
|
151346
|
-
|
|
151347
|
-
|
|
151348
|
-
|
|
151349
|
-
|
|
151350
|
-
|
|
151351
|
-
|
|
151352
|
-
|
|
151353
|
-
`);
|
|
151354
|
-
return this;
|
|
151648
|
+
return parts.join(" ");
|
|
151649
|
+
}
|
|
151650
|
+
function formatMla(article) {
|
|
151651
|
+
const parts = [];
|
|
151652
|
+
const authorStr = article.authors?.length ? formatAuthorsMla(article.authors) : "";
|
|
151653
|
+
if (authorStr) {
|
|
151654
|
+
parts.push(authorStr.endsWith(".") ? authorStr : `${authorStr}.`);
|
|
151355
151655
|
}
|
|
151356
|
-
|
|
151357
|
-
|
|
151358
|
-
${
|
|
151359
|
-
\`\`\`
|
|
151360
|
-
|
|
151361
|
-
`);
|
|
151362
|
-
return this;
|
|
151656
|
+
if (article.title) {
|
|
151657
|
+
const title = article.title.replace(/\.\s*$/, "");
|
|
151658
|
+
parts.push(`"${title}."`);
|
|
151363
151659
|
}
|
|
151364
|
-
|
|
151365
|
-
|
|
151366
|
-
|
|
151660
|
+
const journal = article.journalInfo;
|
|
151661
|
+
if (journal?.title) {
|
|
151662
|
+
const detailParts = [];
|
|
151663
|
+
detailParts.push(`*${journal.title}*`);
|
|
151664
|
+
if (journal.volume) {
|
|
151665
|
+
detailParts.push(`vol. ${journal.volume}`);
|
|
151666
|
+
}
|
|
151667
|
+
if (journal.issue) {
|
|
151668
|
+
detailParts.push(`no. ${journal.issue}`);
|
|
151669
|
+
}
|
|
151670
|
+
const year = getYear(article);
|
|
151671
|
+
if (year !== "n.d.") {
|
|
151672
|
+
detailParts.push(year);
|
|
151673
|
+
}
|
|
151674
|
+
if (journal.pages) {
|
|
151675
|
+
detailParts.push(`pp. ${journal.pages}`);
|
|
151676
|
+
}
|
|
151677
|
+
parts.push(`${detailParts.join(", ")}.`);
|
|
151367
151678
|
}
|
|
151368
|
-
|
|
151369
|
-
|
|
151370
|
-
|
|
151371
|
-
`);
|
|
151372
|
-
return this;
|
|
151679
|
+
if (article.doi) {
|
|
151680
|
+
parts.push(`https://doi.org/${article.doi}.`);
|
|
151373
151681
|
}
|
|
151374
|
-
|
|
151375
|
-
|
|
151376
|
-
|
|
151377
|
-
|
|
151378
|
-
|
|
151379
|
-
|
|
151380
|
-
|
|
151381
|
-
|
|
151382
|
-
|
|
151682
|
+
return parts.join(" ");
|
|
151683
|
+
}
|
|
151684
|
+
function formatBibtex(article) {
|
|
151685
|
+
const key = `pmid${article.pmid}`;
|
|
151686
|
+
const fields = [];
|
|
151687
|
+
if (article.authors?.length) {
|
|
151688
|
+
const authorStr = article.authors.map(formatAuthorBibtex).filter(Boolean).join(" and ");
|
|
151689
|
+
if (authorStr)
|
|
151690
|
+
fields.push(["author", authorStr]);
|
|
151383
151691
|
}
|
|
151384
|
-
|
|
151385
|
-
|
|
151386
|
-
|
|
151387
|
-
`);
|
|
151388
|
-
return this;
|
|
151692
|
+
if (article.title) {
|
|
151693
|
+
fields.push(["title", `{${escapeBibtex(article.title)}}`]);
|
|
151389
151694
|
}
|
|
151390
|
-
|
|
151391
|
-
|
|
151392
|
-
|
|
151695
|
+
const journal = article.journalInfo;
|
|
151696
|
+
if (journal?.title) {
|
|
151697
|
+
fields.push(["journal", escapeBibtex(journal.title)]);
|
|
151393
151698
|
}
|
|
151394
|
-
|
|
151395
|
-
|
|
151396
|
-
|
|
151397
|
-
this.sections.push(`| ${headers.join(" | ")} |
|
|
151398
|
-
`);
|
|
151399
|
-
this.sections.push(`| ${headers.map(() => "---").join(" | ")} |
|
|
151400
|
-
`);
|
|
151401
|
-
rows.forEach((row) => {
|
|
151402
|
-
this.sections.push(`| ${row.join(" | ")} |
|
|
151403
|
-
`);
|
|
151404
|
-
});
|
|
151405
|
-
this.sections.push(`
|
|
151406
|
-
`);
|
|
151407
|
-
return this;
|
|
151699
|
+
const year = getYear(article);
|
|
151700
|
+
if (year !== "n.d.") {
|
|
151701
|
+
fields.push(["year", year]);
|
|
151408
151702
|
}
|
|
151409
|
-
|
|
151410
|
-
|
|
151411
|
-
|
|
151412
|
-
|
|
151413
|
-
|
|
151414
|
-
|
|
151415
|
-
|
|
151416
|
-
|
|
151417
|
-
|
|
151418
|
-
|
|
151419
|
-
|
|
151420
|
-
|
|
151421
|
-
|
|
151422
|
-
|
|
151423
|
-
|
|
151424
|
-
|
|
151425
|
-
|
|
151426
|
-
|
|
151427
|
-
|
|
151428
|
-
|
|
151703
|
+
if (journal?.volume) {
|
|
151704
|
+
fields.push(["volume", escapeBibtex(journal.volume)]);
|
|
151705
|
+
}
|
|
151706
|
+
if (journal?.issue) {
|
|
151707
|
+
fields.push(["number", escapeBibtex(journal.issue)]);
|
|
151708
|
+
}
|
|
151709
|
+
if (journal?.pages) {
|
|
151710
|
+
fields.push(["pages", escapeBibtex(journal.pages)]);
|
|
151711
|
+
}
|
|
151712
|
+
if (article.doi) {
|
|
151713
|
+
fields.push(["doi", article.doi]);
|
|
151714
|
+
}
|
|
151715
|
+
fields.push(["pmid", article.pmid]);
|
|
151716
|
+
const maxKeyLen = Math.max(...fields.map(([k]) => k.length));
|
|
151717
|
+
const fieldLines = fields.map(([k, v]) => ` ${k.padEnd(maxKeyLen)} = {${v}}`).join(`,
|
|
151718
|
+
`);
|
|
151719
|
+
return `@article{${key},
|
|
151720
|
+
${fieldLines}
|
|
151721
|
+
}`;
|
|
151722
|
+
}
|
|
151723
|
+
function formatRis(article) {
|
|
151724
|
+
const lines = [];
|
|
151725
|
+
const tag = (code, value) => {
|
|
151726
|
+
if (value)
|
|
151727
|
+
lines.push(`${code} - ${value}`);
|
|
151728
|
+
};
|
|
151729
|
+
lines.push("TY - JOUR");
|
|
151730
|
+
if (article.authors?.length) {
|
|
151731
|
+
for (const author of article.authors) {
|
|
151732
|
+
if (author.collectiveName) {
|
|
151733
|
+
tag("AU", author.collectiveName);
|
|
151734
|
+
} else {
|
|
151735
|
+
const last = author.lastName ?? "";
|
|
151736
|
+
const first = author.firstName ?? "";
|
|
151737
|
+
if (last || first) {
|
|
151738
|
+
tag("AU", first ? `${last}, ${first}` : last);
|
|
151739
|
+
}
|
|
151740
|
+
}
|
|
151429
151741
|
}
|
|
151430
|
-
callback();
|
|
151431
|
-
return this;
|
|
151432
151742
|
}
|
|
151433
|
-
|
|
151434
|
-
|
|
151435
|
-
|
|
151436
|
-
|
|
151437
|
-
|
|
151438
|
-
|
|
151439
|
-
|
|
151440
|
-
|
|
151441
|
-
|
|
151442
|
-
|
|
151443
|
-
|
|
151444
|
-
|
|
151743
|
+
tag("TI", article.title);
|
|
151744
|
+
const journal = article.journalInfo;
|
|
151745
|
+
if (journal?.title) {
|
|
151746
|
+
tag("JF", journal.title);
|
|
151747
|
+
}
|
|
151748
|
+
if (journal?.isoAbbreviation) {
|
|
151749
|
+
tag("JO", journal.isoAbbreviation);
|
|
151750
|
+
}
|
|
151751
|
+
const year = getYear(article);
|
|
151752
|
+
if (year !== "n.d.") {
|
|
151753
|
+
tag("PY", year);
|
|
151754
|
+
}
|
|
151755
|
+
tag("VL", journal?.volume);
|
|
151756
|
+
tag("IS", journal?.issue);
|
|
151757
|
+
if (journal?.pages) {
|
|
151758
|
+
const { start, end } = splitPages(journal.pages);
|
|
151759
|
+
tag("SP", start);
|
|
151760
|
+
tag("EP", end);
|
|
151445
151761
|
}
|
|
151446
|
-
|
|
151447
|
-
|
|
151448
|
-
|
|
151449
|
-
|
|
151450
|
-
|
|
151451
|
-
|
|
151452
|
-
|
|
151453
|
-
this.sections.push(`> ${line}
|
|
151454
|
-
`);
|
|
151455
|
-
});
|
|
151456
|
-
this.sections.push(`
|
|
151457
|
-
`);
|
|
151458
|
-
return this;
|
|
151762
|
+
tag("DO", article.doi);
|
|
151763
|
+
tag("AN", article.pmid);
|
|
151764
|
+
lines.push(`UR - https://pubmed.ncbi.nlm.nih.gov/${article.pmid}/`);
|
|
151765
|
+
if (article.keywords?.length) {
|
|
151766
|
+
for (const kw of article.keywords) {
|
|
151767
|
+
tag("KW", kw);
|
|
151768
|
+
}
|
|
151459
151769
|
}
|
|
151460
|
-
|
|
151461
|
-
|
|
151462
|
-
|
|
151463
|
-
this.sections.push(`${items.map((item) => `- [${item.checked ? "x" : " "}] ${item.text}`).join(`
|
|
151464
|
-
`)}
|
|
151465
|
-
|
|
151770
|
+
tag("AB", article.abstractText);
|
|
151771
|
+
lines.push("ER - ");
|
|
151772
|
+
return lines.join(`
|
|
151466
151773
|
`);
|
|
151467
|
-
|
|
151774
|
+
}
|
|
151775
|
+
function formatCitation(article, style) {
|
|
151776
|
+
switch (style) {
|
|
151777
|
+
case "apa":
|
|
151778
|
+
return formatApa(article);
|
|
151779
|
+
case "mla":
|
|
151780
|
+
return formatMla(article);
|
|
151781
|
+
case "bibtex":
|
|
151782
|
+
return formatBibtex(article);
|
|
151783
|
+
case "ris":
|
|
151784
|
+
return formatRis(article);
|
|
151468
151785
|
}
|
|
151469
|
-
|
|
151470
|
-
|
|
151471
|
-
|
|
151472
|
-
|
|
151473
|
-
|
|
151474
|
-
return this;
|
|
151786
|
+
}
|
|
151787
|
+
function formatCitations(article, styles) {
|
|
151788
|
+
const result = {};
|
|
151789
|
+
for (const style of styles) {
|
|
151790
|
+
result[style] = formatCitation(article, style);
|
|
151475
151791
|
}
|
|
151476
|
-
|
|
151477
|
-
|
|
151478
|
-
|
|
151792
|
+
return result;
|
|
151793
|
+
}
|
|
151794
|
+
|
|
151795
|
+
// src/services/ncbi/parsing/article-parser.ts
|
|
151796
|
+
function extractAuthors(authorListXml) {
|
|
151797
|
+
if (!authorListXml)
|
|
151798
|
+
return { authors: [], affiliations: [] };
|
|
151799
|
+
const affiliationMap = new Map;
|
|
151800
|
+
const affiliationList = [];
|
|
151801
|
+
function getAffiliationIndex(text) {
|
|
151802
|
+
const existing = affiliationMap.get(text);
|
|
151803
|
+
if (existing !== undefined)
|
|
151804
|
+
return existing;
|
|
151805
|
+
const idx = affiliationList.length;
|
|
151806
|
+
affiliationList.push(text);
|
|
151807
|
+
affiliationMap.set(text, idx);
|
|
151808
|
+
return idx;
|
|
151479
151809
|
}
|
|
151480
|
-
|
|
151481
|
-
|
|
151482
|
-
|
|
151483
|
-
|
|
151810
|
+
const xmlAuthors = ensureArray(authorListXml.Author);
|
|
151811
|
+
const authors = xmlAuthors.map((auth) => {
|
|
151812
|
+
const collectiveName = getText(auth.CollectiveName);
|
|
151813
|
+
if (collectiveName) {
|
|
151814
|
+
return { collectiveName };
|
|
151484
151815
|
}
|
|
151485
|
-
|
|
151486
|
-
|
|
151816
|
+
const authorAffiliationInfos = ensureArray(auth.AffiliationInfo);
|
|
151817
|
+
const indices = [];
|
|
151818
|
+
for (const info of authorAffiliationInfos) {
|
|
151819
|
+
const text = getText(info?.Affiliation);
|
|
151820
|
+
if (text)
|
|
151821
|
+
indices.push(getAffiliationIndex(text));
|
|
151487
151822
|
}
|
|
151488
|
-
|
|
151489
|
-
|
|
151823
|
+
let orcid;
|
|
151824
|
+
const identifiers = ensureArray(auth.Identifier);
|
|
151825
|
+
for (const id of identifiers) {
|
|
151826
|
+
if (getAttribute(id, "Source") === "ORCID") {
|
|
151827
|
+
const val = getText(id);
|
|
151828
|
+
if (val) {
|
|
151829
|
+
orcid = val;
|
|
151830
|
+
break;
|
|
151831
|
+
}
|
|
151832
|
+
}
|
|
151490
151833
|
}
|
|
151491
|
-
|
|
151492
|
-
|
|
151493
|
-
|
|
151834
|
+
return {
|
|
151835
|
+
lastName: getText(auth.LastName),
|
|
151836
|
+
firstName: getText(auth.ForeName),
|
|
151837
|
+
initials: getText(auth.Initials),
|
|
151838
|
+
...indices.length > 0 && { affiliationIndices: indices },
|
|
151839
|
+
...orcid && { orcid }
|
|
151840
|
+
};
|
|
151841
|
+
});
|
|
151842
|
+
return { authors, affiliations: affiliationList };
|
|
151843
|
+
}
|
|
151844
|
+
function extractJournalInfo(journalXml, articleXml) {
|
|
151845
|
+
if (!journalXml)
|
|
151846
|
+
return;
|
|
151847
|
+
const pubDate = journalXml.JournalIssue?.PubDate;
|
|
151848
|
+
const year = getText(pubDate?.Year, getText(pubDate?.MedlineDate, "").match(/\d{4}/)?.[0]);
|
|
151849
|
+
const issnElement = journalXml.ISSN;
|
|
151850
|
+
const issnValue = getText(issnElement);
|
|
151851
|
+
const issnType = getAttribute(issnElement, "IssnType");
|
|
151852
|
+
const issn = issnType === "Electronic" ? undefined : issnValue || undefined;
|
|
151853
|
+
const eIssn = issnType === "Electronic" ? issnValue || undefined : undefined;
|
|
151854
|
+
const month = getText(pubDate?.Month);
|
|
151855
|
+
const day = getText(pubDate?.Day);
|
|
151856
|
+
const medlineDate = getText(pubDate?.MedlineDate);
|
|
151857
|
+
return {
|
|
151858
|
+
title: getText(journalXml.Title),
|
|
151859
|
+
isoAbbreviation: getText(journalXml.ISOAbbreviation),
|
|
151860
|
+
...issn && { issn },
|
|
151861
|
+
...eIssn && { eIssn },
|
|
151862
|
+
volume: getText(journalXml.JournalIssue?.Volume),
|
|
151863
|
+
issue: getText(journalXml.JournalIssue?.Issue),
|
|
151864
|
+
pages: getText(articleXml?.Pagination?.MedlinePgn),
|
|
151865
|
+
publicationDate: {
|
|
151866
|
+
...year && { year },
|
|
151867
|
+
...month && { month },
|
|
151868
|
+
...day && { day },
|
|
151869
|
+
...medlineDate && { medlineDate }
|
|
151870
|
+
}
|
|
151871
|
+
};
|
|
151872
|
+
}
|
|
151873
|
+
function extractMeshTerms(meshHeadingListXml) {
|
|
151874
|
+
if (!meshHeadingListXml)
|
|
151875
|
+
return [];
|
|
151876
|
+
const meshHeadings = ensureArray(meshHeadingListXml.MeshHeading);
|
|
151877
|
+
return meshHeadings.map((mh) => {
|
|
151878
|
+
const isMajorDescriptor = getAttribute(mh.DescriptorName, "MajorTopicYN") === "Y";
|
|
151879
|
+
const isMajorRoot = getAttribute(mh, "MajorTopicYN") === "Y";
|
|
151880
|
+
const descriptorUi = getAttribute(mh.DescriptorName, "UI");
|
|
151881
|
+
const rawQualifiers = ensureArray(mh.QualifierName);
|
|
151882
|
+
const qualifiers = rawQualifiers.flatMap((q) => {
|
|
151883
|
+
const name = getText(q);
|
|
151884
|
+
if (!name)
|
|
151885
|
+
return [];
|
|
151886
|
+
const ui = getAttribute(q, "UI");
|
|
151887
|
+
return {
|
|
151888
|
+
qualifierName: name,
|
|
151889
|
+
...ui && { qualifierUi: ui },
|
|
151890
|
+
isMajorTopic: getAttribute(q, "MajorTopicYN") === "Y"
|
|
151891
|
+
};
|
|
151892
|
+
});
|
|
151893
|
+
const isMajorAnyQualifier = qualifiers.some((q) => q.isMajorTopic);
|
|
151894
|
+
return {
|
|
151895
|
+
descriptorName: getText(mh.DescriptorName),
|
|
151896
|
+
...descriptorUi && { descriptorUi },
|
|
151897
|
+
...qualifiers.length > 0 && { qualifiers },
|
|
151898
|
+
isMajorTopic: isMajorRoot || isMajorDescriptor || isMajorAnyQualifier
|
|
151899
|
+
};
|
|
151900
|
+
});
|
|
151901
|
+
}
|
|
151902
|
+
function extractGrants(grantListXml) {
|
|
151903
|
+
if (!grantListXml)
|
|
151904
|
+
return [];
|
|
151905
|
+
const grants = ensureArray(grantListXml.Grant);
|
|
151906
|
+
return grants.map((g) => {
|
|
151907
|
+
const grantId = getText(g.GrantID);
|
|
151908
|
+
const acronym = getText(g.Acronym);
|
|
151909
|
+
const agency = getText(g.Agency);
|
|
151910
|
+
const country = getText(g.Country);
|
|
151911
|
+
return {
|
|
151912
|
+
...grantId && { grantId },
|
|
151913
|
+
...acronym && { acronym },
|
|
151914
|
+
...agency && { agency },
|
|
151915
|
+
...country && { country }
|
|
151916
|
+
};
|
|
151917
|
+
});
|
|
151918
|
+
}
|
|
151919
|
+
function extractDoi(articleXml, pubmedDataArticleIdList) {
|
|
151920
|
+
if (!articleXml)
|
|
151921
|
+
return;
|
|
151922
|
+
const eLocationIDs = ensureArray(articleXml.ELocationID);
|
|
151923
|
+
for (const eloc of eLocationIDs) {
|
|
151924
|
+
if (getAttribute(eloc, "EIdType") === "doi" && getAttribute(eloc, "ValidYN") === "Y") {
|
|
151925
|
+
const doi = getText(eloc);
|
|
151926
|
+
if (doi)
|
|
151927
|
+
return doi;
|
|
151494
151928
|
}
|
|
151495
|
-
return this;
|
|
151496
|
-
}
|
|
151497
|
-
badge(label, message, color = "blue") {
|
|
151498
|
-
const encodedLabel = encodeURIComponent(label);
|
|
151499
|
-
const encodedMessage = encodeURIComponent(message);
|
|
151500
|
-
const url2 = `https://img.shields.io/badge/${encodedLabel}-${encodedMessage}-${color}`;
|
|
151501
|
-
this.sections.push(``);
|
|
151502
|
-
return this;
|
|
151503
|
-
}
|
|
151504
|
-
bold(text) {
|
|
151505
|
-
this.sections.push(`**${text}**`);
|
|
151506
|
-
return this;
|
|
151507
|
-
}
|
|
151508
|
-
italic(text) {
|
|
151509
|
-
this.sections.push(`*${text}*`);
|
|
151510
|
-
return this;
|
|
151511
|
-
}
|
|
151512
|
-
boldItalic(text) {
|
|
151513
|
-
this.sections.push(`***${text}***`);
|
|
151514
|
-
return this;
|
|
151515
151929
|
}
|
|
151516
|
-
|
|
151517
|
-
|
|
151518
|
-
|
|
151930
|
+
for (const eloc of eLocationIDs) {
|
|
151931
|
+
if (getAttribute(eloc, "EIdType") === "doi") {
|
|
151932
|
+
const doi = getText(eloc);
|
|
151933
|
+
if (doi)
|
|
151934
|
+
return doi;
|
|
151935
|
+
}
|
|
151519
151936
|
}
|
|
151520
|
-
|
|
151521
|
-
|
|
151522
|
-
|
|
151523
|
-
|
|
151937
|
+
const articleIds = ensureArray(articleXml.ArticleIdList?.ArticleId);
|
|
151938
|
+
for (const aid of articleIds) {
|
|
151939
|
+
if (getAttribute(aid, "IdType") === "doi") {
|
|
151940
|
+
const doi = getText(aid);
|
|
151941
|
+
if (doi)
|
|
151942
|
+
return doi;
|
|
151943
|
+
}
|
|
151524
151944
|
}
|
|
151525
|
-
|
|
151526
|
-
|
|
151527
|
-
|
|
151945
|
+
if (pubmedDataArticleIdList) {
|
|
151946
|
+
const pubmedDataIds = ensureArray(pubmedDataArticleIdList.ArticleId);
|
|
151947
|
+
for (const aid of pubmedDataIds) {
|
|
151948
|
+
if (getAttribute(aid, "IdType") === "doi") {
|
|
151949
|
+
const doi = getText(aid);
|
|
151950
|
+
if (doi)
|
|
151951
|
+
return doi;
|
|
151952
|
+
}
|
|
151953
|
+
}
|
|
151528
151954
|
}
|
|
151529
|
-
|
|
151530
|
-
|
|
151531
|
-
|
|
151955
|
+
return;
|
|
151956
|
+
}
|
|
151957
|
+
function extractPmcId(articleXml, pubmedDataArticleIdList) {
|
|
151958
|
+
const articleIds = ensureArray(articleXml?.ArticleIdList?.ArticleId);
|
|
151959
|
+
for (const aid of articleIds) {
|
|
151960
|
+
if (getAttribute(aid, "IdType") === "pmc") {
|
|
151961
|
+
const val = getText(aid);
|
|
151962
|
+
if (val)
|
|
151963
|
+
return val;
|
|
151532
151964
|
}
|
|
151533
|
-
return this;
|
|
151534
151965
|
}
|
|
151535
|
-
|
|
151536
|
-
|
|
151966
|
+
if (pubmedDataArticleIdList) {
|
|
151967
|
+
const pubmedDataIds = ensureArray(pubmedDataArticleIdList.ArticleId);
|
|
151968
|
+
for (const aid of pubmedDataIds) {
|
|
151969
|
+
if (getAttribute(aid, "IdType") === "pmc") {
|
|
151970
|
+
const val = getText(aid);
|
|
151971
|
+
if (val)
|
|
151972
|
+
return val;
|
|
151973
|
+
}
|
|
151974
|
+
}
|
|
151537
151975
|
}
|
|
151538
|
-
|
|
151539
|
-
|
|
151540
|
-
|
|
151976
|
+
return;
|
|
151977
|
+
}
|
|
151978
|
+
function extractPublicationTypes(publicationTypeListXml) {
|
|
151979
|
+
if (!publicationTypeListXml)
|
|
151980
|
+
return [];
|
|
151981
|
+
const pubTypes = ensureArray(publicationTypeListXml.PublicationType);
|
|
151982
|
+
return pubTypes.map((pt) => getText(pt)).filter(Boolean);
|
|
151983
|
+
}
|
|
151984
|
+
function extractKeywords2(keywordListsXml) {
|
|
151985
|
+
if (!keywordListsXml)
|
|
151986
|
+
return [];
|
|
151987
|
+
const lists = ensureArray(keywordListsXml);
|
|
151988
|
+
const allKeywords = [];
|
|
151989
|
+
for (const list of lists) {
|
|
151990
|
+
for (const kw of ensureArray(list.Keyword)) {
|
|
151991
|
+
const keywordText = getText(kw);
|
|
151992
|
+
if (keywordText) {
|
|
151993
|
+
allKeywords.push(keywordText);
|
|
151994
|
+
}
|
|
151995
|
+
}
|
|
151541
151996
|
}
|
|
151997
|
+
return allKeywords;
|
|
151542
151998
|
}
|
|
151543
|
-
function
|
|
151544
|
-
|
|
151999
|
+
function extractAbstractText(abstractXml) {
|
|
152000
|
+
if (!abstractXml || !abstractXml.AbstractText)
|
|
152001
|
+
return;
|
|
152002
|
+
const abstractTexts = ensureArray(abstractXml.AbstractText);
|
|
152003
|
+
if (abstractTexts.length === 0)
|
|
152004
|
+
return;
|
|
152005
|
+
const processedTexts = abstractTexts.map((at) => {
|
|
152006
|
+
if (typeof at === "string") {
|
|
152007
|
+
return at;
|
|
152008
|
+
}
|
|
152009
|
+
const sectionText = getText(at);
|
|
152010
|
+
const label = getAttribute(at, "Label");
|
|
152011
|
+
if (label && sectionText) {
|
|
152012
|
+
return `${label.trim()}: ${sectionText.trim()}`;
|
|
152013
|
+
}
|
|
152014
|
+
return sectionText.trim();
|
|
152015
|
+
}).filter(Boolean);
|
|
152016
|
+
if (processedTexts.length === 0)
|
|
152017
|
+
return;
|
|
152018
|
+
return processedTexts.join(`
|
|
152019
|
+
|
|
152020
|
+
`).trim() || undefined;
|
|
152021
|
+
}
|
|
152022
|
+
function extractPmid(medlineCitationXml) {
|
|
152023
|
+
if (!medlineCitationXml || !medlineCitationXml.PMID)
|
|
152024
|
+
return;
|
|
152025
|
+
return getText(medlineCitationXml.PMID);
|
|
152026
|
+
}
|
|
152027
|
+
function extractArticleDates(articleXml) {
|
|
152028
|
+
if (!articleXml || !articleXml.ArticleDate)
|
|
152029
|
+
return [];
|
|
152030
|
+
const articleDatesXml = ensureArray(articleXml.ArticleDate);
|
|
152031
|
+
return articleDatesXml.map((ad) => ({
|
|
152032
|
+
dateType: getAttribute(ad, "DateType"),
|
|
152033
|
+
year: getText(ad.Year),
|
|
152034
|
+
month: getText(ad.Month),
|
|
152035
|
+
day: getText(ad.Day)
|
|
152036
|
+
}));
|
|
152037
|
+
}
|
|
152038
|
+
function parseFullArticle(xmlArticle, options = {}) {
|
|
152039
|
+
const medlineCitation = xmlArticle.MedlineCitation;
|
|
152040
|
+
const article = medlineCitation?.Article;
|
|
152041
|
+
const { includeMesh = true, includeGrants = false } = options;
|
|
152042
|
+
const abstractText = extractAbstractText(article?.Abstract);
|
|
152043
|
+
const journalInfo = extractJournalInfo(article?.Journal, article);
|
|
152044
|
+
const pubmedDataArticleIdList = xmlArticle.PubmedData?.ArticleIdList;
|
|
152045
|
+
const doi = extractDoi(article, pubmedDataArticleIdList);
|
|
152046
|
+
const pmcId = extractPmcId(article, pubmedDataArticleIdList);
|
|
152047
|
+
const { authors, affiliations } = extractAuthors(article?.AuthorList);
|
|
152048
|
+
return {
|
|
152049
|
+
pmid: extractPmid(medlineCitation) ?? "",
|
|
152050
|
+
title: getText(article?.ArticleTitle),
|
|
152051
|
+
...abstractText !== undefined && { abstractText },
|
|
152052
|
+
...affiliations.length > 0 && { affiliations },
|
|
152053
|
+
authors,
|
|
152054
|
+
...journalInfo !== undefined && { journalInfo },
|
|
152055
|
+
publicationTypes: extractPublicationTypes(article?.PublicationTypeList),
|
|
152056
|
+
keywords: extractKeywords2(medlineCitation?.KeywordList ?? article?.KeywordList),
|
|
152057
|
+
...includeMesh && { meshTerms: extractMeshTerms(medlineCitation?.MeshHeadingList) },
|
|
152058
|
+
...includeGrants && { grantList: extractGrants(article?.GrantList) },
|
|
152059
|
+
...doi !== undefined && { doi },
|
|
152060
|
+
...pmcId !== undefined && { pmcId },
|
|
152061
|
+
articleDates: extractArticleDates(article)
|
|
152062
|
+
};
|
|
151545
152063
|
}
|
|
151546
152064
|
|
|
151547
152065
|
// src/mcp-server/tools/definitions/pubmed-cite.tool.ts
|
|
151548
|
-
var
|
|
151549
|
-
var
|
|
151550
|
-
var
|
|
151551
|
-
var
|
|
151552
|
-
var
|
|
152066
|
+
var ncbi3 = () => container.resolve(NcbiServiceToken);
|
|
152067
|
+
var TOOL_NAME2 = "pubmed_cite";
|
|
152068
|
+
var TOOL_TITLE2 = "PubMed Citations";
|
|
152069
|
+
var TOOL_DESCRIPTION2 = "Get formatted citations for PubMed articles in APA, MLA, BibTeX, or RIS format.";
|
|
152070
|
+
var TOOL_ANNOTATIONS2 = {
|
|
151553
152071
|
readOnlyHint: true,
|
|
151554
152072
|
idempotentHint: true,
|
|
151555
152073
|
openWorldHint: true
|
|
151556
152074
|
};
|
|
151557
|
-
var
|
|
152075
|
+
var InputSchema2 = exports_external.object({
|
|
151558
152076
|
pmids: exports_external.array(exports_external.string().regex(/^\d+$/)).min(1).max(50).describe("PubMed IDs to cite"),
|
|
151559
152077
|
styles: exports_external.array(exports_external.enum(["apa", "mla", "bibtex", "ris"])).default(["apa"]).describe("Citation styles to generate")
|
|
151560
152078
|
});
|
|
151561
|
-
var
|
|
152079
|
+
var OutputSchema3 = exports_external.object({
|
|
151562
152080
|
citations: exports_external.array(exports_external.object({
|
|
151563
152081
|
pmid: exports_external.string(),
|
|
151564
152082
|
title: exports_external.string().optional(),
|
|
151565
152083
|
citations: exports_external.record(exports_external.string(), exports_external.string())
|
|
151566
152084
|
})).describe("Citations per article")
|
|
151567
152085
|
});
|
|
151568
|
-
async function
|
|
152086
|
+
async function logic2(input, appContext, _sdkContext) {
|
|
151569
152087
|
logger.debug("Fetching articles for citation generation", {
|
|
151570
152088
|
...appContext,
|
|
151571
152089
|
pmids: input.pmids,
|
|
151572
152090
|
styles: input.styles
|
|
151573
152091
|
});
|
|
151574
|
-
const raw = await
|
|
152092
|
+
const raw = await ncbi3().eFetch({ db: "pubmed", id: input.pmids.join(",") }, appContext);
|
|
151575
152093
|
const xmlArticles = ensureArray(raw?.PubmedArticleSet?.PubmedArticle);
|
|
151576
152094
|
if (xmlArticles.length === 0) {
|
|
151577
152095
|
throw new McpError(-32001 /* NotFound */, `No articles found for PMIDs: ${input.pmids.join(", ")}`, { requestId: appContext.requestId });
|
|
@@ -151590,7 +152108,7 @@ async function logic(input, appContext, _sdkContext) {
|
|
|
151590
152108
|
});
|
|
151591
152109
|
return { citations };
|
|
151592
152110
|
}
|
|
151593
|
-
function
|
|
152111
|
+
function responseFormatter2(result) {
|
|
151594
152112
|
const md = markdown();
|
|
151595
152113
|
md.text(`# PubMed Citations
|
|
151596
152114
|
`);
|
|
@@ -151617,32 +152135,32 @@ ${citation}
|
|
|
151617
152135
|
return [{ type: "text", text: md.build() }];
|
|
151618
152136
|
}
|
|
151619
152137
|
var pubmedCiteTool = {
|
|
151620
|
-
name:
|
|
151621
|
-
title:
|
|
151622
|
-
description:
|
|
151623
|
-
annotations:
|
|
151624
|
-
inputSchema:
|
|
151625
|
-
outputSchema:
|
|
151626
|
-
logic: withToolAuth(["tool:pubmed_cite:read"],
|
|
151627
|
-
responseFormatter
|
|
152138
|
+
name: TOOL_NAME2,
|
|
152139
|
+
title: TOOL_TITLE2,
|
|
152140
|
+
description: TOOL_DESCRIPTION2,
|
|
152141
|
+
annotations: TOOL_ANNOTATIONS2,
|
|
152142
|
+
inputSchema: InputSchema2,
|
|
152143
|
+
outputSchema: OutputSchema3,
|
|
152144
|
+
logic: withToolAuth(["tool:pubmed_cite:read"], logic2),
|
|
152145
|
+
responseFormatter: responseFormatter2
|
|
151628
152146
|
};
|
|
151629
152147
|
|
|
151630
152148
|
// src/mcp-server/tools/definitions/pubmed-fetch.tool.ts
|
|
151631
|
-
var
|
|
151632
|
-
var
|
|
151633
|
-
var
|
|
151634
|
-
var
|
|
151635
|
-
var
|
|
152149
|
+
var ncbi4 = () => container.resolve(NcbiServiceToken);
|
|
152150
|
+
var TOOL_NAME3 = "pubmed_fetch";
|
|
152151
|
+
var TOOL_TITLE3 = "PubMed Fetch";
|
|
152152
|
+
var TOOL_DESCRIPTION3 = "Fetch full article metadata by PubMed IDs. Returns detailed article information including abstract, authors, journal, MeSH terms.";
|
|
152153
|
+
var TOOL_ANNOTATIONS3 = {
|
|
151636
152154
|
readOnlyHint: true,
|
|
151637
152155
|
idempotentHint: true,
|
|
151638
152156
|
openWorldHint: true
|
|
151639
152157
|
};
|
|
151640
|
-
var
|
|
152158
|
+
var InputSchema3 = exports_external.object({
|
|
151641
152159
|
pmids: exports_external.array(exports_external.string().regex(/^\d+$/)).min(1).max(200).describe("PubMed IDs to fetch"),
|
|
151642
152160
|
includeMesh: exports_external.boolean().default(true).describe("Include MeSH terms"),
|
|
151643
152161
|
includeGrants: exports_external.boolean().default(false).describe("Include grant information")
|
|
151644
152162
|
});
|
|
151645
|
-
var
|
|
152163
|
+
var ArticleSchema2 = exports_external.object({
|
|
151646
152164
|
pmid: exports_external.string().optional().describe("PubMed ID"),
|
|
151647
152165
|
title: exports_external.string().optional().describe("Article title"),
|
|
151648
152166
|
abstractText: exports_external.string().optional().describe("Abstract text"),
|
|
@@ -151657,16 +152175,16 @@ var ArticleSchema = exports_external.object({
|
|
|
151657
152175
|
meshTerms: exports_external.array(exports_external.any()).optional().describe("MeSH terms"),
|
|
151658
152176
|
grantList: exports_external.array(exports_external.any()).optional().describe("Grant information")
|
|
151659
152177
|
});
|
|
151660
|
-
var
|
|
151661
|
-
articles: exports_external.array(
|
|
152178
|
+
var OutputSchema4 = exports_external.object({
|
|
152179
|
+
articles: exports_external.array(ArticleSchema2).describe("Parsed articles"),
|
|
151662
152180
|
totalReturned: exports_external.number().describe("Number of articles returned")
|
|
151663
152181
|
});
|
|
151664
|
-
async function
|
|
152182
|
+
async function logic3(input, appContext, _sdkContext) {
|
|
151665
152183
|
logger.info("Executing pubmed_fetch tool", {
|
|
151666
152184
|
...appContext,
|
|
151667
152185
|
pmidCount: input.pmids.length
|
|
151668
152186
|
});
|
|
151669
|
-
const xmlData = await
|
|
152187
|
+
const xmlData = await ncbi4().eFetch({ db: "pubmed", id: input.pmids.join(","), retmode: "xml" }, appContext, { retmode: "xml", usePost: input.pmids.length > 200 });
|
|
151670
152188
|
if (!xmlData || !("PubmedArticleSet" in xmlData)) {
|
|
151671
152189
|
throw new McpError(-32603 /* InternalError */, "Invalid EFetch response from NCBI: missing PubmedArticleSet", { requestId: appContext.requestId });
|
|
151672
152190
|
}
|
|
@@ -151694,7 +152212,7 @@ async function logic2(input, appContext, _sdkContext) {
|
|
|
151694
152212
|
});
|
|
151695
152213
|
return { articles, totalReturned: articles.length };
|
|
151696
152214
|
}
|
|
151697
|
-
function
|
|
152215
|
+
function responseFormatter3(result) {
|
|
151698
152216
|
const md = markdown().h2("PubMed Articles").keyValue("Articles Returned", result.totalReturned);
|
|
151699
152217
|
for (const article of result.articles) {
|
|
151700
152218
|
md.h3(article.title ?? article.pmid ?? "Unknown");
|
|
@@ -151729,27 +152247,27 @@ function responseFormatter2(result) {
|
|
|
151729
152247
|
return [{ type: "text", text: md.build() }];
|
|
151730
152248
|
}
|
|
151731
152249
|
var pubmedFetchTool = {
|
|
151732
|
-
name:
|
|
151733
|
-
title:
|
|
151734
|
-
description:
|
|
151735
|
-
annotations:
|
|
151736
|
-
inputSchema:
|
|
151737
|
-
outputSchema:
|
|
151738
|
-
logic: withToolAuth(["tool:pubmed_fetch:read"],
|
|
151739
|
-
responseFormatter:
|
|
152250
|
+
name: TOOL_NAME3,
|
|
152251
|
+
title: TOOL_TITLE3,
|
|
152252
|
+
description: TOOL_DESCRIPTION3,
|
|
152253
|
+
annotations: TOOL_ANNOTATIONS3,
|
|
152254
|
+
inputSchema: InputSchema3,
|
|
152255
|
+
outputSchema: OutputSchema4,
|
|
152256
|
+
logic: withToolAuth(["tool:pubmed_fetch:read"], logic3),
|
|
152257
|
+
responseFormatter: responseFormatter3
|
|
151740
152258
|
};
|
|
151741
152259
|
|
|
151742
152260
|
// src/mcp-server/tools/definitions/pubmed-mesh-lookup.tool.ts
|
|
151743
|
-
var
|
|
151744
|
-
var
|
|
151745
|
-
var
|
|
151746
|
-
var
|
|
151747
|
-
var
|
|
152261
|
+
var ncbi5 = () => container.resolve(NcbiServiceToken);
|
|
152262
|
+
var TOOL_NAME4 = "pubmed_mesh_lookup";
|
|
152263
|
+
var TOOL_TITLE4 = "MeSH Term Lookup";
|
|
152264
|
+
var TOOL_DESCRIPTION4 = "Search and explore MeSH (Medical Subject Headings) vocabulary. Essential for building precise PubMed queries.";
|
|
152265
|
+
var TOOL_ANNOTATIONS4 = {
|
|
151748
152266
|
readOnlyHint: true,
|
|
151749
152267
|
idempotentHint: true,
|
|
151750
152268
|
openWorldHint: true
|
|
151751
152269
|
};
|
|
151752
|
-
var
|
|
152270
|
+
var InputSchema4 = exports_external.object({
|
|
151753
152271
|
term: exports_external.string().min(1).describe("MeSH term to look up"),
|
|
151754
152272
|
maxResults: exports_external.number().int().min(1).max(50).default(10).describe("Maximum results"),
|
|
151755
152273
|
includeDetails: exports_external.boolean().default(true).describe("Fetch full MeSH records (scope notes, tree numbers, entry terms)")
|
|
@@ -151761,7 +152279,7 @@ var MeshResultItem = exports_external.object({
|
|
|
151761
152279
|
scopeNote: exports_external.string().optional().describe("Scope note describing the descriptor"),
|
|
151762
152280
|
entryTerms: exports_external.array(exports_external.string()).optional().describe("Synonyms / entry terms")
|
|
151763
152281
|
});
|
|
151764
|
-
var
|
|
152282
|
+
var OutputSchema5 = exports_external.object({
|
|
151765
152283
|
term: exports_external.string().describe("Original search term"),
|
|
151766
152284
|
results: exports_external.array(MeshResultItem).describe("Matching MeSH records")
|
|
151767
152285
|
});
|
|
@@ -151831,8 +152349,8 @@ async function meshLookupLogic(input, context, _sdkContext) {
|
|
|
151831
152349
|
const { term, maxResults, includeDetails } = input;
|
|
151832
152350
|
logger.debug("MeSH lookup started.", { ...context, term, maxResults, includeDetails });
|
|
151833
152351
|
const hasFieldTag = /\[.+\]/.test(term);
|
|
151834
|
-
const broadSearch =
|
|
151835
|
-
const exactSearch = hasFieldTag ? undefined :
|
|
152352
|
+
const broadSearch = ncbi5().eSearch({ db: "mesh", term, retmax: maxResults }, context);
|
|
152353
|
+
const exactSearch = hasFieldTag ? undefined : ncbi5().eSearch({ db: "mesh", term: `${term}[MH]`, retmax: 1 }, context);
|
|
151836
152354
|
const [broadResult, exactResult] = await Promise.all([broadSearch, exactSearch]);
|
|
151837
152355
|
const seen = new Set;
|
|
151838
152356
|
const ids = [];
|
|
@@ -151847,7 +152365,7 @@ async function meshLookupLogic(input, context, _sdkContext) {
|
|
|
151847
152365
|
logger.debug("No MeSH results found.", { ...context, term });
|
|
151848
152366
|
return { term, results: [] };
|
|
151849
152367
|
}
|
|
151850
|
-
const summaryData = await
|
|
152368
|
+
const summaryData = await ncbi5().eSummary({ db: "mesh", id: ids.join(",") }, context);
|
|
151851
152369
|
const results = parseSummaryRecords(summaryData, ids, includeDetails);
|
|
151852
152370
|
const termLower = term.toLowerCase();
|
|
151853
152371
|
results.sort((a, b) => {
|
|
@@ -151894,12 +152412,12 @@ function formatResponse(result) {
|
|
|
151894
152412
|
return [{ type: "text", text: md.build() }];
|
|
151895
152413
|
}
|
|
151896
152414
|
var pubmedMeshLookupTool = {
|
|
151897
|
-
name:
|
|
151898
|
-
title:
|
|
151899
|
-
description:
|
|
151900
|
-
annotations:
|
|
151901
|
-
inputSchema:
|
|
151902
|
-
outputSchema:
|
|
152415
|
+
name: TOOL_NAME4,
|
|
152416
|
+
title: TOOL_TITLE4,
|
|
152417
|
+
description: TOOL_DESCRIPTION4,
|
|
152418
|
+
annotations: TOOL_ANNOTATIONS4,
|
|
152419
|
+
inputSchema: InputSchema4,
|
|
152420
|
+
outputSchema: OutputSchema5,
|
|
151903
152421
|
logic: withToolAuth(["tool:pubmed_mesh_lookup:read"], meshLookupLogic),
|
|
151904
152422
|
responseFormatter: formatResponse
|
|
151905
152423
|
};
|
|
@@ -155051,21 +155569,21 @@ async function extractBriefSummaries(eSummaryResult, context) {
|
|
|
155051
155569
|
}
|
|
155052
155570
|
|
|
155053
155571
|
// src/mcp-server/tools/definitions/pubmed-related.tool.ts
|
|
155054
|
-
var
|
|
155055
|
-
var
|
|
155056
|
-
var
|
|
155057
|
-
var
|
|
155058
|
-
var
|
|
155572
|
+
var ncbi6 = () => container.resolve(NcbiServiceToken);
|
|
155573
|
+
var TOOL_NAME5 = "pubmed_related";
|
|
155574
|
+
var TOOL_TITLE5 = "PubMed Related Articles";
|
|
155575
|
+
var TOOL_DESCRIPTION5 = "Find articles related to a source article — similar content, citing articles, or references.";
|
|
155576
|
+
var TOOL_ANNOTATIONS5 = {
|
|
155059
155577
|
readOnlyHint: true,
|
|
155060
155578
|
idempotentHint: true,
|
|
155061
155579
|
openWorldHint: true
|
|
155062
155580
|
};
|
|
155063
|
-
var
|
|
155581
|
+
var InputSchema5 = exports_external.object({
|
|
155064
155582
|
pmid: exports_external.string().regex(/^\d+$/).describe("Source PubMed ID"),
|
|
155065
155583
|
relationship: exports_external.enum(["similar", "cited_by", "references"]).default("similar").describe("Type of relationship"),
|
|
155066
155584
|
maxResults: exports_external.number().int().min(1).max(50).default(10).describe("Maximum related articles")
|
|
155067
155585
|
});
|
|
155068
|
-
var
|
|
155586
|
+
var OutputSchema6 = exports_external.object({
|
|
155069
155587
|
sourcePmid: exports_external.string().describe("Source PubMed ID"),
|
|
155070
155588
|
relationship: exports_external.string().describe("Relationship type used"),
|
|
155071
155589
|
articles: exports_external.array(exports_external.object({
|
|
@@ -155084,7 +155602,7 @@ function extractValue(field) {
|
|
|
155084
155602
|
}
|
|
155085
155603
|
return String(field);
|
|
155086
155604
|
}
|
|
155087
|
-
async function
|
|
155605
|
+
async function logic4(input, appContext, _sdkContext) {
|
|
155088
155606
|
logger.debug("Finding related articles", {
|
|
155089
155607
|
...appContext,
|
|
155090
155608
|
pmid: input.pmid,
|
|
@@ -155109,7 +155627,7 @@ async function logic3(input, appContext, _sdkContext) {
|
|
|
155109
155627
|
eLinkParams.linkname = "pubmed_pubmed_refs";
|
|
155110
155628
|
break;
|
|
155111
155629
|
}
|
|
155112
|
-
const eLinkResult = await
|
|
155630
|
+
const eLinkResult = await ncbi6().eLink(eLinkParams, appContext);
|
|
155113
155631
|
logger.debug("Raw ELink response received", {
|
|
155114
155632
|
...appContext,
|
|
155115
155633
|
hasResult: !!eLinkResult?.eLinkResult
|
|
@@ -155151,7 +155669,7 @@ async function logic3(input, appContext, _sdkContext) {
|
|
|
155151
155669
|
}
|
|
155152
155670
|
const pmidsToEnrich = foundPmids.slice(0, input.maxResults);
|
|
155153
155671
|
const pmidIds = pmidsToEnrich.map((p) => p.pmid);
|
|
155154
|
-
const summaryResult = await
|
|
155672
|
+
const summaryResult = await ncbi6().eSummary({ db: "pubmed", id: pmidIds.join(",") }, appContext);
|
|
155155
155673
|
const briefSummaries = await extractBriefSummaries(summaryResult, appContext);
|
|
155156
155674
|
const summaryMap = new Map(briefSummaries.map((bs) => [bs.pmid, bs]));
|
|
155157
155675
|
const articles = pmidsToEnrich.map((p) => {
|
|
@@ -155175,7 +155693,7 @@ async function logic3(input, appContext, _sdkContext) {
|
|
|
155175
155693
|
totalFound
|
|
155176
155694
|
};
|
|
155177
155695
|
}
|
|
155178
|
-
function
|
|
155696
|
+
function responseFormatter4(result) {
|
|
155179
155697
|
const md = markdown();
|
|
155180
155698
|
md.text(`# Related Articles for PMID ${result.sourcePmid}
|
|
155181
155699
|
`);
|
|
@@ -155198,27 +155716,27 @@ function responseFormatter3(result) {
|
|
|
155198
155716
|
return [{ type: "text", text: md.build() }];
|
|
155199
155717
|
}
|
|
155200
155718
|
var pubmedRelatedTool = {
|
|
155201
|
-
name:
|
|
155202
|
-
title:
|
|
155203
|
-
description:
|
|
155204
|
-
annotations:
|
|
155205
|
-
inputSchema:
|
|
155206
|
-
outputSchema:
|
|
155207
|
-
logic: withToolAuth(["tool:pubmed_related:read"],
|
|
155208
|
-
responseFormatter:
|
|
155719
|
+
name: TOOL_NAME5,
|
|
155720
|
+
title: TOOL_TITLE5,
|
|
155721
|
+
description: TOOL_DESCRIPTION5,
|
|
155722
|
+
annotations: TOOL_ANNOTATIONS5,
|
|
155723
|
+
inputSchema: InputSchema5,
|
|
155724
|
+
outputSchema: OutputSchema6,
|
|
155725
|
+
logic: withToolAuth(["tool:pubmed_related:read"], logic4),
|
|
155726
|
+
responseFormatter: responseFormatter4
|
|
155209
155727
|
};
|
|
155210
155728
|
|
|
155211
155729
|
// src/mcp-server/tools/definitions/pubmed-search.tool.ts
|
|
155212
|
-
var
|
|
155213
|
-
var
|
|
155214
|
-
var
|
|
155215
|
-
var
|
|
155216
|
-
var
|
|
155730
|
+
var ncbi7 = () => container.resolve(NcbiServiceToken);
|
|
155731
|
+
var TOOL_NAME6 = "pubmed_search";
|
|
155732
|
+
var TOOL_TITLE6 = "PubMed Search";
|
|
155733
|
+
var TOOL_DESCRIPTION6 = "Search PubMed with full query syntax, filters, and date ranges. Returns PMIDs and optional brief summaries. " + "Supports field-specific filters (author, journal, MeSH terms), common filters (language, species, free full text), " + "and pagination via offset for paging through large result sets.";
|
|
155734
|
+
var TOOL_ANNOTATIONS6 = {
|
|
155217
155735
|
readOnlyHint: true,
|
|
155218
155736
|
idempotentHint: true,
|
|
155219
155737
|
openWorldHint: true
|
|
155220
155738
|
};
|
|
155221
|
-
var
|
|
155739
|
+
var InputSchema6 = exports_external.object({
|
|
155222
155740
|
query: exports_external.string().min(1).describe("PubMed search query (supports full NCBI syntax)"),
|
|
155223
155741
|
maxResults: exports_external.number().int().min(1).max(1000).default(20).describe("Maximum results to return"),
|
|
155224
155742
|
offset: exports_external.number().int().min(0).default(0).describe("Result offset for pagination (0-based). Use with maxResults to page through results"),
|
|
@@ -155238,7 +155756,7 @@ var InputSchema5 = exports_external.object({
|
|
|
155238
155756
|
species: exports_external.enum(["humans", "animals"]).optional().describe("Filter by species"),
|
|
155239
155757
|
summaryCount: exports_external.number().int().min(0).max(50).default(0).describe("Fetch brief summaries for top N results (0 = PMIDs only)")
|
|
155240
155758
|
});
|
|
155241
|
-
var
|
|
155759
|
+
var OutputSchema7 = exports_external.object({
|
|
155242
155760
|
query: exports_external.string().describe("Original query"),
|
|
155243
155761
|
totalFound: exports_external.number().describe("Total matching articles"),
|
|
155244
155762
|
offset: exports_external.number().describe("Result offset used"),
|
|
@@ -155256,7 +155774,7 @@ var OutputSchema6 = exports_external.object({
|
|
|
155256
155774
|
})).optional().describe("Brief summaries"),
|
|
155257
155775
|
searchUrl: exports_external.string().describe("PubMed search URL")
|
|
155258
155776
|
});
|
|
155259
|
-
async function
|
|
155777
|
+
async function logic5(input, appContext, _sdkContext) {
|
|
155260
155778
|
logger.info("Executing pubmed_search tool", { ...appContext, query: input.query });
|
|
155261
155779
|
let effectiveQuery = sanitization.sanitizeString(input.query, { context: "text" });
|
|
155262
155780
|
if (input.dateRange) {
|
|
@@ -155291,7 +155809,7 @@ async function logic4(input, appContext, _sdkContext) {
|
|
|
155291
155809
|
if (input.species) {
|
|
155292
155810
|
effectiveQuery += ` AND ${input.species}[MeSH Terms]`;
|
|
155293
155811
|
}
|
|
155294
|
-
const esResult = await
|
|
155812
|
+
const esResult = await ncbi7().eSearch({
|
|
155295
155813
|
db: "pubmed",
|
|
155296
155814
|
term: effectiveQuery,
|
|
155297
155815
|
retmax: input.maxResults,
|
|
@@ -155315,7 +155833,7 @@ async function logic4(input, appContext, _sdkContext) {
|
|
|
155315
155833
|
} else {
|
|
155316
155834
|
eSummaryParams.id = pmids.slice(0, input.summaryCount).join(",");
|
|
155317
155835
|
}
|
|
155318
|
-
const eSummaryResult = await
|
|
155836
|
+
const eSummaryResult = await ncbi7().eSummary(eSummaryParams, appContext);
|
|
155319
155837
|
if (eSummaryResult) {
|
|
155320
155838
|
const briefSummaries = await extractBriefSummaries(eSummaryResult, appContext);
|
|
155321
155839
|
summaries = briefSummaries.map((s) => ({
|
|
@@ -155340,7 +155858,7 @@ async function logic4(input, appContext, _sdkContext) {
|
|
|
155340
155858
|
});
|
|
155341
155859
|
return { query: input.query, totalFound, offset: input.offset, pmids, summaries, searchUrl };
|
|
155342
155860
|
}
|
|
155343
|
-
function
|
|
155861
|
+
function responseFormatter5(result) {
|
|
155344
155862
|
const md = markdown().h2("PubMed Search Results").keyValue("Query", result.query).keyValue("Total Found", result.totalFound).keyValue("Offset", result.offset).keyValue("PMIDs Returned", result.pmids.length).keyValue("Search URL", result.searchUrl);
|
|
155345
155863
|
if (result.pmids.length > 0) {
|
|
155346
155864
|
md.h3("PMIDs").paragraph(result.pmids.join(", "));
|
|
@@ -155367,37 +155885,37 @@ function responseFormatter4(result) {
|
|
|
155367
155885
|
return [{ type: "text", text: md.build() }];
|
|
155368
155886
|
}
|
|
155369
155887
|
var pubmedSearchTool = {
|
|
155370
|
-
name:
|
|
155371
|
-
title:
|
|
155372
|
-
description:
|
|
155373
|
-
annotations:
|
|
155374
|
-
inputSchema:
|
|
155375
|
-
outputSchema:
|
|
155376
|
-
logic: withToolAuth(["tool:pubmed_search:read"],
|
|
155377
|
-
responseFormatter:
|
|
155888
|
+
name: TOOL_NAME6,
|
|
155889
|
+
title: TOOL_TITLE6,
|
|
155890
|
+
description: TOOL_DESCRIPTION6,
|
|
155891
|
+
annotations: TOOL_ANNOTATIONS6,
|
|
155892
|
+
inputSchema: InputSchema6,
|
|
155893
|
+
outputSchema: OutputSchema7,
|
|
155894
|
+
logic: withToolAuth(["tool:pubmed_search:read"], logic5),
|
|
155895
|
+
responseFormatter: responseFormatter5
|
|
155378
155896
|
};
|
|
155379
155897
|
|
|
155380
155898
|
// src/mcp-server/tools/definitions/pubmed-spell.tool.ts
|
|
155381
|
-
var
|
|
155382
|
-
var
|
|
155383
|
-
var
|
|
155384
|
-
var
|
|
155385
|
-
var
|
|
155899
|
+
var ncbi8 = () => container.resolve(NcbiServiceToken);
|
|
155900
|
+
var TOOL_NAME7 = "pubmed_spell";
|
|
155901
|
+
var TOOL_TITLE7 = "PubMed Spell Check";
|
|
155902
|
+
var TOOL_DESCRIPTION7 = "Spell-check a query and get NCBI's suggested correction. Useful for refining search queries.";
|
|
155903
|
+
var TOOL_ANNOTATIONS7 = {
|
|
155386
155904
|
readOnlyHint: true,
|
|
155387
155905
|
idempotentHint: true,
|
|
155388
155906
|
openWorldHint: true
|
|
155389
155907
|
};
|
|
155390
|
-
var
|
|
155908
|
+
var InputSchema7 = exports_external.object({
|
|
155391
155909
|
query: exports_external.string().min(2).describe("Query to spell-check")
|
|
155392
155910
|
});
|
|
155393
|
-
var
|
|
155911
|
+
var OutputSchema8 = exports_external.object({
|
|
155394
155912
|
original: exports_external.string().describe("Original query"),
|
|
155395
155913
|
corrected: exports_external.string().describe("Corrected query (same as original if no suggestion)"),
|
|
155396
155914
|
hasSuggestion: exports_external.boolean().describe("Whether NCBI suggested a correction")
|
|
155397
155915
|
});
|
|
155398
|
-
async function
|
|
155916
|
+
async function logic6(input, appContext, _sdkContext) {
|
|
155399
155917
|
logger.info("Executing pubmed_spell tool", { ...appContext, query: input.query });
|
|
155400
|
-
const result = await
|
|
155918
|
+
const result = await ncbi8().eSpell({ db: "pubmed", term: input.query }, appContext);
|
|
155401
155919
|
logger.notice("pubmed_spell completed", {
|
|
155402
155920
|
...appContext,
|
|
155403
155921
|
hasSuggestion: result.hasSuggestion
|
|
@@ -155408,7 +155926,7 @@ async function logic5(input, appContext, _sdkContext) {
|
|
|
155408
155926
|
hasSuggestion: result.hasSuggestion
|
|
155409
155927
|
};
|
|
155410
155928
|
}
|
|
155411
|
-
function
|
|
155929
|
+
function responseFormatter6(result) {
|
|
155412
155930
|
const md = markdown().h2("PubMed Spell Check");
|
|
155413
155931
|
if (result.hasSuggestion) {
|
|
155414
155932
|
md.keyValue("Original", result.original).keyValue("Suggested", result.corrected);
|
|
@@ -155418,20 +155936,21 @@ function responseFormatter5(result) {
|
|
|
155418
155936
|
return [{ type: "text", text: md.build() }];
|
|
155419
155937
|
}
|
|
155420
155938
|
var pubmedSpellTool = {
|
|
155421
|
-
name:
|
|
155422
|
-
title:
|
|
155423
|
-
description:
|
|
155424
|
-
annotations:
|
|
155425
|
-
inputSchema:
|
|
155426
|
-
outputSchema:
|
|
155427
|
-
logic: withToolAuth(["tool:pubmed_spell:read"],
|
|
155428
|
-
responseFormatter:
|
|
155939
|
+
name: TOOL_NAME7,
|
|
155940
|
+
title: TOOL_TITLE7,
|
|
155941
|
+
description: TOOL_DESCRIPTION7,
|
|
155942
|
+
annotations: TOOL_ANNOTATIONS7,
|
|
155943
|
+
inputSchema: InputSchema7,
|
|
155944
|
+
outputSchema: OutputSchema8,
|
|
155945
|
+
logic: withToolAuth(["tool:pubmed_spell:read"], logic6),
|
|
155946
|
+
responseFormatter: responseFormatter6
|
|
155429
155947
|
};
|
|
155430
155948
|
|
|
155431
155949
|
// src/mcp-server/tools/definitions/index.ts
|
|
155432
155950
|
var allToolDefinitions = [
|
|
155433
155951
|
pubmedSearchTool,
|
|
155434
155952
|
pubmedFetchTool,
|
|
155953
|
+
pmcFetchTool,
|
|
155435
155954
|
pubmedSpellTool,
|
|
155436
155955
|
pubmedCiteTool,
|
|
155437
155956
|
pubmedRelatedTool,
|
|
@@ -155633,8 +156152,8 @@ var defaultResponseFormatter2 = (result) => [
|
|
|
155633
156152
|
function createMcpToolHandler({
|
|
155634
156153
|
toolName,
|
|
155635
156154
|
inputSchema,
|
|
155636
|
-
logic:
|
|
155637
|
-
responseFormatter:
|
|
156155
|
+
logic: logic7,
|
|
156156
|
+
responseFormatter: responseFormatter7 = defaultResponseFormatter2
|
|
155638
156157
|
}) {
|
|
155639
156158
|
return async (input, callContext) => {
|
|
155640
156159
|
const sdkContext = callContext;
|
|
@@ -155649,10 +156168,10 @@ function createMcpToolHandler({
|
|
|
155649
156168
|
});
|
|
155650
156169
|
try {
|
|
155651
156170
|
const validatedInput = inputSchema.parse(input);
|
|
155652
|
-
const result = await measureToolExecution(() =>
|
|
156171
|
+
const result = await measureToolExecution(() => logic7(validatedInput, appContext, sdkContext), { ...appContext, toolName }, validatedInput);
|
|
155653
156172
|
return {
|
|
155654
156173
|
structuredContent: result,
|
|
155655
|
-
content:
|
|
156174
|
+
content: responseFormatter7(result)
|
|
155656
156175
|
};
|
|
155657
156176
|
} catch (error48) {
|
|
155658
156177
|
const handled = ErrorHandler.handleError(error48, {
|