@cyanheads/pubmed-mcp-server 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -12
- package/dist/index.js +1301 -790
- 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.0
|
|
135504
|
+
version: "2.1.0",
|
|
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",
|
|
@@ -150769,809 +150778,1310 @@ 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.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
|
-
|
|
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() }];
|
|
151514
|
+
}
|
|
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
|
+
};
|
|
151257
151525
|
|
|
151258
|
-
|
|
151526
|
+
// src/services/ncbi/formatting/citation-formatter.ts
|
|
151527
|
+
function getYear(article) {
|
|
151528
|
+
return article.journalInfo?.publicationDate?.year ?? "n.d.";
|
|
151259
151529
|
}
|
|
151260
|
-
function
|
|
151261
|
-
if (!
|
|
151262
|
-
return;
|
|
151263
|
-
|
|
151530
|
+
function splitPages(pages) {
|
|
151531
|
+
if (!pages)
|
|
151532
|
+
return {};
|
|
151533
|
+
const parts = pages.split(/[-\u2013\u2014]/).map((p) => p.trim());
|
|
151534
|
+
if (parts.length >= 2) {
|
|
151535
|
+
const start2 = parts[0];
|
|
151536
|
+
const end = parts[1];
|
|
151537
|
+
return {
|
|
151538
|
+
...start2 !== undefined && { start: start2 },
|
|
151539
|
+
...end !== undefined && { end }
|
|
151540
|
+
};
|
|
151541
|
+
}
|
|
151542
|
+
const start = parts[0];
|
|
151543
|
+
return start !== undefined ? { start } : {};
|
|
151264
151544
|
}
|
|
151265
|
-
function
|
|
151266
|
-
|
|
151267
|
-
return [];
|
|
151268
|
-
const articleDatesXml = ensureArray(articleXml.ArticleDate);
|
|
151269
|
-
return articleDatesXml.map((ad) => ({
|
|
151270
|
-
dateType: getAttribute(ad, "DateType"),
|
|
151271
|
-
year: getText(ad.Year),
|
|
151272
|
-
month: getText(ad.Month),
|
|
151273
|
-
day: getText(ad.Day)
|
|
151274
|
-
}));
|
|
151545
|
+
function escapeBibtex(text) {
|
|
151546
|
+
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{}");
|
|
151275
151547
|
}
|
|
151276
|
-
function
|
|
151277
|
-
|
|
151278
|
-
|
|
151279
|
-
const
|
|
151280
|
-
const
|
|
151281
|
-
|
|
151282
|
-
|
|
151283
|
-
|
|
151284
|
-
|
|
151285
|
-
const
|
|
151286
|
-
return {
|
|
151287
|
-
pmid: extractPmid(medlineCitation) ?? "",
|
|
151288
|
-
title: getText(article?.ArticleTitle),
|
|
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
|
-
};
|
|
151548
|
+
function formatAuthorApa(author) {
|
|
151549
|
+
if (author.collectiveName)
|
|
151550
|
+
return author.collectiveName;
|
|
151551
|
+
const last = author.lastName ?? "";
|
|
151552
|
+
const initials = author.initials ?? author.firstName?.split(/[\s-]+/).map((part) => `${part[0]}.`).join(" ");
|
|
151553
|
+
if (!last)
|
|
151554
|
+
return initials ?? "";
|
|
151555
|
+
if (!initials)
|
|
151556
|
+
return last;
|
|
151557
|
+
const formatted = initials.replace(/[^A-Za-z]/g, "").split("").map((c) => `${c}.`).join(" ");
|
|
151558
|
+
return `${last}, ${formatted}`;
|
|
151301
151559
|
}
|
|
151302
|
-
|
|
151303
|
-
|
|
151304
|
-
|
|
151305
|
-
|
|
151306
|
-
|
|
151307
|
-
|
|
151308
|
-
|
|
151309
|
-
|
|
151310
|
-
|
|
151311
|
-
|
|
151560
|
+
function formatAuthorsApa(authors) {
|
|
151561
|
+
const formatted = authors.map(formatAuthorApa);
|
|
151562
|
+
if (formatted.length === 0)
|
|
151563
|
+
return "";
|
|
151564
|
+
if (formatted.length === 1)
|
|
151565
|
+
return formatted[0] ?? "";
|
|
151566
|
+
if (formatted.length === 2)
|
|
151567
|
+
return `${formatted[0]}, & ${formatted[1]}`;
|
|
151568
|
+
if (formatted.length <= 20) {
|
|
151569
|
+
const allButLast = formatted.slice(0, -1).join(", ");
|
|
151570
|
+
return `${allButLast}, & ${formatted.at(-1)}`;
|
|
151312
151571
|
}
|
|
151313
|
-
|
|
151314
|
-
|
|
151315
|
-
|
|
151316
|
-
|
|
151317
|
-
|
|
151318
|
-
return
|
|
151572
|
+
const first19 = formatted.slice(0, 19).join(", ");
|
|
151573
|
+
return `${first19}, ... ${formatted.at(-1)}`;
|
|
151574
|
+
}
|
|
151575
|
+
function formatAuthorMla(author, isFirst) {
|
|
151576
|
+
if (author.collectiveName)
|
|
151577
|
+
return author.collectiveName;
|
|
151578
|
+
const last = author.lastName ?? "";
|
|
151579
|
+
const first = author.firstName ?? "";
|
|
151580
|
+
if (!last && !first)
|
|
151581
|
+
return "";
|
|
151582
|
+
if (!first)
|
|
151583
|
+
return last;
|
|
151584
|
+
if (!last)
|
|
151585
|
+
return first;
|
|
151586
|
+
return isFirst ? `${last}, ${first}` : `${first} ${last}`;
|
|
151587
|
+
}
|
|
151588
|
+
function formatAuthorsMla(authors) {
|
|
151589
|
+
const first = authors[0];
|
|
151590
|
+
if (!first)
|
|
151591
|
+
return "";
|
|
151592
|
+
if (authors.length === 1)
|
|
151593
|
+
return formatAuthorMla(first, true);
|
|
151594
|
+
if (authors.length === 2) {
|
|
151595
|
+
const second = authors[1];
|
|
151596
|
+
return second ? `${formatAuthorMla(first, true)}, and ${formatAuthorMla(second, false)}` : formatAuthorMla(first, true);
|
|
151319
151597
|
}
|
|
151320
|
-
|
|
151321
|
-
|
|
151322
|
-
|
|
151323
|
-
|
|
151324
|
-
`)
|
|
151325
|
-
|
|
151598
|
+
return `${formatAuthorMla(first, true)}, et al.`;
|
|
151599
|
+
}
|
|
151600
|
+
function formatAuthorBibtex(author) {
|
|
151601
|
+
if (author.collectiveName)
|
|
151602
|
+
return `{${escapeBibtex(author.collectiveName)}}`;
|
|
151603
|
+
const last = author.lastName ? escapeBibtex(author.lastName) : "";
|
|
151604
|
+
const first = author.firstName ? escapeBibtex(author.firstName) : "";
|
|
151605
|
+
if (!last && !first)
|
|
151606
|
+
return "";
|
|
151607
|
+
if (!first)
|
|
151608
|
+
return `{${last}}`;
|
|
151609
|
+
if (!last)
|
|
151610
|
+
return first;
|
|
151611
|
+
return `{${last}}, ${first}`;
|
|
151612
|
+
}
|
|
151613
|
+
function formatApa(article) {
|
|
151614
|
+
const parts = [];
|
|
151615
|
+
const authorStr = article.authors?.length ? formatAuthorsApa(article.authors) : "";
|
|
151616
|
+
if (authorStr) {
|
|
151617
|
+
parts.push(authorStr);
|
|
151326
151618
|
}
|
|
151327
|
-
|
|
151328
|
-
|
|
151329
|
-
|
|
151330
|
-
|
|
151331
|
-
|
|
151332
|
-
return this;
|
|
151619
|
+
const year = getYear(article);
|
|
151620
|
+
parts.push(`(${year}).`);
|
|
151621
|
+
if (article.title) {
|
|
151622
|
+
const title = article.title.replace(/\.\s*$/, "");
|
|
151623
|
+
parts.push(`${title}.`);
|
|
151333
151624
|
}
|
|
151334
|
-
|
|
151335
|
-
|
|
151336
|
-
|
|
151337
|
-
|
|
151338
|
-
|
|
151625
|
+
const journal = article.journalInfo;
|
|
151626
|
+
if (journal?.title) {
|
|
151627
|
+
let journalPart = `*${journal.title}*`;
|
|
151628
|
+
if (journal.volume) {
|
|
151629
|
+
journalPart += `, *${journal.volume}*`;
|
|
151630
|
+
if (journal.issue) {
|
|
151631
|
+
journalPart += `(${journal.issue})`;
|
|
151632
|
+
}
|
|
151633
|
+
}
|
|
151634
|
+
if (journal.pages) {
|
|
151635
|
+
journalPart += `, ${journal.pages}`;
|
|
151636
|
+
}
|
|
151637
|
+
journalPart += ".";
|
|
151638
|
+
parts.push(journalPart);
|
|
151339
151639
|
}
|
|
151340
|
-
|
|
151341
|
-
|
|
151342
|
-
this.sections.push(`${key}: ${displayValue}
|
|
151343
|
-
`);
|
|
151344
|
-
return this;
|
|
151640
|
+
if (article.doi) {
|
|
151641
|
+
parts.push(`https://doi.org/${article.doi}`);
|
|
151345
151642
|
}
|
|
151346
|
-
|
|
151347
|
-
|
|
151348
|
-
|
|
151349
|
-
|
|
151350
|
-
|
|
151351
|
-
|
|
151352
|
-
|
|
151353
|
-
`);
|
|
151354
|
-
return this;
|
|
151643
|
+
return parts.join(" ");
|
|
151644
|
+
}
|
|
151645
|
+
function formatMla(article) {
|
|
151646
|
+
const parts = [];
|
|
151647
|
+
const authorStr = article.authors?.length ? formatAuthorsMla(article.authors) : "";
|
|
151648
|
+
if (authorStr) {
|
|
151649
|
+
parts.push(authorStr.endsWith(".") ? authorStr : `${authorStr}.`);
|
|
151355
151650
|
}
|
|
151356
|
-
|
|
151357
|
-
|
|
151358
|
-
${
|
|
151359
|
-
\`\`\`
|
|
151360
|
-
|
|
151361
|
-
`);
|
|
151362
|
-
return this;
|
|
151651
|
+
if (article.title) {
|
|
151652
|
+
const title = article.title.replace(/\.\s*$/, "");
|
|
151653
|
+
parts.push(`"${title}."`);
|
|
151363
151654
|
}
|
|
151364
|
-
|
|
151365
|
-
|
|
151366
|
-
|
|
151655
|
+
const journal = article.journalInfo;
|
|
151656
|
+
if (journal?.title) {
|
|
151657
|
+
const detailParts = [];
|
|
151658
|
+
detailParts.push(`*${journal.title}*`);
|
|
151659
|
+
if (journal.volume) {
|
|
151660
|
+
detailParts.push(`vol. ${journal.volume}`);
|
|
151661
|
+
}
|
|
151662
|
+
if (journal.issue) {
|
|
151663
|
+
detailParts.push(`no. ${journal.issue}`);
|
|
151664
|
+
}
|
|
151665
|
+
const year = getYear(article);
|
|
151666
|
+
if (year !== "n.d.") {
|
|
151667
|
+
detailParts.push(year);
|
|
151668
|
+
}
|
|
151669
|
+
if (journal.pages) {
|
|
151670
|
+
detailParts.push(`pp. ${journal.pages}`);
|
|
151671
|
+
}
|
|
151672
|
+
parts.push(`${detailParts.join(", ")}.`);
|
|
151367
151673
|
}
|
|
151368
|
-
|
|
151369
|
-
|
|
151370
|
-
|
|
151371
|
-
`);
|
|
151372
|
-
return this;
|
|
151674
|
+
if (article.doi) {
|
|
151675
|
+
parts.push(`https://doi.org/${article.doi}.`);
|
|
151373
151676
|
}
|
|
151374
|
-
|
|
151375
|
-
|
|
151376
|
-
|
|
151377
|
-
|
|
151378
|
-
|
|
151379
|
-
|
|
151380
|
-
|
|
151381
|
-
|
|
151382
|
-
|
|
151677
|
+
return parts.join(" ");
|
|
151678
|
+
}
|
|
151679
|
+
function formatBibtex(article) {
|
|
151680
|
+
const key = `pmid${article.pmid}`;
|
|
151681
|
+
const fields = [];
|
|
151682
|
+
if (article.authors?.length) {
|
|
151683
|
+
const authorStr = article.authors.map(formatAuthorBibtex).filter(Boolean).join(" and ");
|
|
151684
|
+
if (authorStr)
|
|
151685
|
+
fields.push(["author", authorStr]);
|
|
151383
151686
|
}
|
|
151384
|
-
|
|
151385
|
-
|
|
151386
|
-
|
|
151387
|
-
`);
|
|
151388
|
-
return this;
|
|
151687
|
+
if (article.title) {
|
|
151688
|
+
fields.push(["title", `{${escapeBibtex(article.title)}}`]);
|
|
151389
151689
|
}
|
|
151390
|
-
|
|
151391
|
-
|
|
151392
|
-
|
|
151690
|
+
const journal = article.journalInfo;
|
|
151691
|
+
if (journal?.title) {
|
|
151692
|
+
fields.push(["journal", escapeBibtex(journal.title)]);
|
|
151393
151693
|
}
|
|
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;
|
|
151694
|
+
const year = getYear(article);
|
|
151695
|
+
if (year !== "n.d.") {
|
|
151696
|
+
fields.push(["year", year]);
|
|
151408
151697
|
}
|
|
151409
|
-
|
|
151410
|
-
|
|
151411
|
-
let callback;
|
|
151412
|
-
if (typeof levelOrContent === "function") {
|
|
151413
|
-
level = 2;
|
|
151414
|
-
callback = levelOrContent;
|
|
151415
|
-
} else {
|
|
151416
|
-
level = levelOrContent;
|
|
151417
|
-
callback = content ?? (() => {});
|
|
151418
|
-
}
|
|
151419
|
-
switch (level) {
|
|
151420
|
-
case 2:
|
|
151421
|
-
this.h2(title);
|
|
151422
|
-
break;
|
|
151423
|
-
case 3:
|
|
151424
|
-
this.h3(title);
|
|
151425
|
-
break;
|
|
151426
|
-
case 4:
|
|
151427
|
-
this.h4(title);
|
|
151428
|
-
break;
|
|
151429
|
-
}
|
|
151430
|
-
callback();
|
|
151431
|
-
return this;
|
|
151698
|
+
if (journal?.volume) {
|
|
151699
|
+
fields.push(["volume", escapeBibtex(journal.volume)]);
|
|
151432
151700
|
}
|
|
151433
|
-
|
|
151434
|
-
|
|
151435
|
-
<summary>${summary}</summary>
|
|
151436
|
-
|
|
151437
|
-
`);
|
|
151438
|
-
this.sections.push(`${details}
|
|
151439
|
-
|
|
151440
|
-
`);
|
|
151441
|
-
this.sections.push(`</details>
|
|
151442
|
-
|
|
151443
|
-
`);
|
|
151444
|
-
return this;
|
|
151701
|
+
if (journal?.issue) {
|
|
151702
|
+
fields.push(["number", escapeBibtex(journal.issue)]);
|
|
151445
151703
|
}
|
|
151446
|
-
|
|
151447
|
-
|
|
151448
|
-
const lines = content.split(`
|
|
151449
|
-
`);
|
|
151450
|
-
this.sections.push(`> [!${typeUpper}]
|
|
151451
|
-
`);
|
|
151452
|
-
lines.forEach((line) => {
|
|
151453
|
-
this.sections.push(`> ${line}
|
|
151454
|
-
`);
|
|
151455
|
-
});
|
|
151456
|
-
this.sections.push(`
|
|
151457
|
-
`);
|
|
151458
|
-
return this;
|
|
151704
|
+
if (journal?.pages) {
|
|
151705
|
+
fields.push(["pages", escapeBibtex(journal.pages)]);
|
|
151459
151706
|
}
|
|
151460
|
-
|
|
151461
|
-
|
|
151462
|
-
return this;
|
|
151463
|
-
this.sections.push(`${items.map((item) => `- [${item.checked ? "x" : " "}] ${item.text}`).join(`
|
|
151464
|
-
`)}
|
|
151465
|
-
|
|
151466
|
-
`);
|
|
151467
|
-
return this;
|
|
151707
|
+
if (article.doi) {
|
|
151708
|
+
fields.push(["doi", article.doi]);
|
|
151468
151709
|
}
|
|
151469
|
-
|
|
151470
|
-
|
|
151471
|
-
|
|
151472
|
-
|
|
151710
|
+
fields.push(["pmid", article.pmid]);
|
|
151711
|
+
const maxKeyLen = Math.max(...fields.map(([k]) => k.length));
|
|
151712
|
+
const fieldLines = fields.map(([k, v]) => ` ${k.padEnd(maxKeyLen)} = {${v}}`).join(`,
|
|
151473
151713
|
`);
|
|
151474
|
-
|
|
151714
|
+
return `@article{${key},
|
|
151715
|
+
${fieldLines}
|
|
151716
|
+
}`;
|
|
151717
|
+
}
|
|
151718
|
+
function formatRis(article) {
|
|
151719
|
+
const lines = [];
|
|
151720
|
+
const tag = (code, value) => {
|
|
151721
|
+
if (value)
|
|
151722
|
+
lines.push(`${code} - ${value}`);
|
|
151723
|
+
};
|
|
151724
|
+
lines.push("TY - JOUR");
|
|
151725
|
+
if (article.authors?.length) {
|
|
151726
|
+
for (const author of article.authors) {
|
|
151727
|
+
if (author.collectiveName) {
|
|
151728
|
+
tag("AU", author.collectiveName);
|
|
151729
|
+
} else {
|
|
151730
|
+
const last = author.lastName ?? "";
|
|
151731
|
+
const first = author.firstName ?? "";
|
|
151732
|
+
if (last || first) {
|
|
151733
|
+
tag("AU", first ? `${last}, ${first}` : last);
|
|
151734
|
+
}
|
|
151735
|
+
}
|
|
151736
|
+
}
|
|
151475
151737
|
}
|
|
151476
|
-
|
|
151477
|
-
|
|
151478
|
-
|
|
151738
|
+
tag("TI", article.title);
|
|
151739
|
+
const journal = article.journalInfo;
|
|
151740
|
+
if (journal?.title) {
|
|
151741
|
+
tag("JF", journal.title);
|
|
151479
151742
|
}
|
|
151480
|
-
|
|
151481
|
-
|
|
151482
|
-
|
|
151483
|
-
|
|
151484
|
-
|
|
151485
|
-
|
|
151486
|
-
|
|
151487
|
-
|
|
151488
|
-
|
|
151489
|
-
|
|
151490
|
-
}
|
|
151491
|
-
|
|
151492
|
-
|
|
151493
|
-
|
|
151743
|
+
if (journal?.isoAbbreviation) {
|
|
151744
|
+
tag("JO", journal.isoAbbreviation);
|
|
151745
|
+
}
|
|
151746
|
+
const year = getYear(article);
|
|
151747
|
+
if (year !== "n.d.") {
|
|
151748
|
+
tag("PY", year);
|
|
151749
|
+
}
|
|
151750
|
+
tag("VL", journal?.volume);
|
|
151751
|
+
tag("IS", journal?.issue);
|
|
151752
|
+
if (journal?.pages) {
|
|
151753
|
+
const { start, end } = splitPages(journal.pages);
|
|
151754
|
+
tag("SP", start);
|
|
151755
|
+
tag("EP", end);
|
|
151756
|
+
}
|
|
151757
|
+
tag("DO", article.doi);
|
|
151758
|
+
tag("AN", article.pmid);
|
|
151759
|
+
lines.push(`UR - https://pubmed.ncbi.nlm.nih.gov/${article.pmid}/`);
|
|
151760
|
+
if (article.keywords?.length) {
|
|
151761
|
+
for (const kw of article.keywords) {
|
|
151762
|
+
tag("KW", kw);
|
|
151494
151763
|
}
|
|
151495
|
-
return this;
|
|
151496
151764
|
}
|
|
151497
|
-
|
|
151498
|
-
|
|
151499
|
-
|
|
151500
|
-
|
|
151501
|
-
|
|
151502
|
-
|
|
151765
|
+
tag("AB", article.abstractText);
|
|
151766
|
+
lines.push("ER - ");
|
|
151767
|
+
return lines.join(`
|
|
151768
|
+
`);
|
|
151769
|
+
}
|
|
151770
|
+
function formatCitation(article, style) {
|
|
151771
|
+
switch (style) {
|
|
151772
|
+
case "apa":
|
|
151773
|
+
return formatApa(article);
|
|
151774
|
+
case "mla":
|
|
151775
|
+
return formatMla(article);
|
|
151776
|
+
case "bibtex":
|
|
151777
|
+
return formatBibtex(article);
|
|
151778
|
+
case "ris":
|
|
151779
|
+
return formatRis(article);
|
|
151503
151780
|
}
|
|
151504
|
-
|
|
151505
|
-
|
|
151506
|
-
|
|
151781
|
+
}
|
|
151782
|
+
function formatCitations(article, styles) {
|
|
151783
|
+
const result = {};
|
|
151784
|
+
for (const style of styles) {
|
|
151785
|
+
result[style] = formatCitation(article, style);
|
|
151507
151786
|
}
|
|
151508
|
-
|
|
151509
|
-
|
|
151510
|
-
|
|
151787
|
+
return result;
|
|
151788
|
+
}
|
|
151789
|
+
|
|
151790
|
+
// src/services/ncbi/parsing/article-parser.ts
|
|
151791
|
+
function extractAuthors(authorListXml) {
|
|
151792
|
+
if (!authorListXml)
|
|
151793
|
+
return { authors: [], affiliations: [] };
|
|
151794
|
+
const affiliationMap = new Map;
|
|
151795
|
+
const affiliationList = [];
|
|
151796
|
+
function getAffiliationIndex(text) {
|
|
151797
|
+
const existing = affiliationMap.get(text);
|
|
151798
|
+
if (existing !== undefined)
|
|
151799
|
+
return existing;
|
|
151800
|
+
const idx = affiliationList.length;
|
|
151801
|
+
affiliationList.push(text);
|
|
151802
|
+
affiliationMap.set(text, idx);
|
|
151803
|
+
return idx;
|
|
151511
151804
|
}
|
|
151512
|
-
|
|
151513
|
-
|
|
151514
|
-
|
|
151805
|
+
const xmlAuthors = ensureArray(authorListXml.Author);
|
|
151806
|
+
const authors = xmlAuthors.map((auth) => {
|
|
151807
|
+
const collectiveName = getText(auth.CollectiveName);
|
|
151808
|
+
if (collectiveName) {
|
|
151809
|
+
return { collectiveName };
|
|
151810
|
+
}
|
|
151811
|
+
const authorAffiliationInfos = ensureArray(auth.AffiliationInfo);
|
|
151812
|
+
const indices = [];
|
|
151813
|
+
for (const info of authorAffiliationInfos) {
|
|
151814
|
+
const text = getText(info?.Affiliation);
|
|
151815
|
+
if (text)
|
|
151816
|
+
indices.push(getAffiliationIndex(text));
|
|
151817
|
+
}
|
|
151818
|
+
let orcid;
|
|
151819
|
+
const identifiers = ensureArray(auth.Identifier);
|
|
151820
|
+
for (const id of identifiers) {
|
|
151821
|
+
if (getAttribute(id, "Source") === "ORCID") {
|
|
151822
|
+
const val = getText(id);
|
|
151823
|
+
if (val) {
|
|
151824
|
+
orcid = val;
|
|
151825
|
+
break;
|
|
151826
|
+
}
|
|
151827
|
+
}
|
|
151828
|
+
}
|
|
151829
|
+
return {
|
|
151830
|
+
lastName: getText(auth.LastName),
|
|
151831
|
+
firstName: getText(auth.ForeName),
|
|
151832
|
+
initials: getText(auth.Initials),
|
|
151833
|
+
...indices.length > 0 && { affiliationIndices: indices },
|
|
151834
|
+
...orcid && { orcid }
|
|
151835
|
+
};
|
|
151836
|
+
});
|
|
151837
|
+
return { authors, affiliations: affiliationList };
|
|
151838
|
+
}
|
|
151839
|
+
function extractJournalInfo(journalXml, articleXml) {
|
|
151840
|
+
if (!journalXml)
|
|
151841
|
+
return;
|
|
151842
|
+
const pubDate = journalXml.JournalIssue?.PubDate;
|
|
151843
|
+
const year = getText(pubDate?.Year, getText(pubDate?.MedlineDate, "").match(/\d{4}/)?.[0]);
|
|
151844
|
+
const issnElement = journalXml.ISSN;
|
|
151845
|
+
const issnValue = getText(issnElement);
|
|
151846
|
+
const issnType = getAttribute(issnElement, "IssnType");
|
|
151847
|
+
const issn = issnType === "Electronic" ? undefined : issnValue || undefined;
|
|
151848
|
+
const eIssn = issnType === "Electronic" ? issnValue || undefined : undefined;
|
|
151849
|
+
return {
|
|
151850
|
+
title: getText(journalXml.Title),
|
|
151851
|
+
isoAbbreviation: getText(journalXml.ISOAbbreviation),
|
|
151852
|
+
...issn && { issn },
|
|
151853
|
+
...eIssn && { eIssn },
|
|
151854
|
+
volume: getText(journalXml.JournalIssue?.Volume),
|
|
151855
|
+
issue: getText(journalXml.JournalIssue?.Issue),
|
|
151856
|
+
pages: getText(articleXml?.Pagination?.MedlinePgn),
|
|
151857
|
+
publicationDate: {
|
|
151858
|
+
...year && { year },
|
|
151859
|
+
...getText(pubDate?.Month) && { month: getText(pubDate?.Month) },
|
|
151860
|
+
...getText(pubDate?.Day) && { day: getText(pubDate?.Day) },
|
|
151861
|
+
...getText(pubDate?.MedlineDate) && { medlineDate: getText(pubDate?.MedlineDate) }
|
|
151862
|
+
}
|
|
151863
|
+
};
|
|
151864
|
+
}
|
|
151865
|
+
function extractMeshTerms(meshHeadingListXml) {
|
|
151866
|
+
if (!meshHeadingListXml)
|
|
151867
|
+
return [];
|
|
151868
|
+
const meshHeadings = ensureArray(meshHeadingListXml.MeshHeading);
|
|
151869
|
+
return meshHeadings.map((mh) => {
|
|
151870
|
+
const isMajorDescriptor = getAttribute(mh.DescriptorName, "MajorTopicYN") === "Y";
|
|
151871
|
+
const isMajorRoot = getAttribute(mh, "MajorTopicYN") === "Y";
|
|
151872
|
+
const descriptorUi = getAttribute(mh.DescriptorName, "UI");
|
|
151873
|
+
const rawQualifiers = ensureArray(mh.QualifierName);
|
|
151874
|
+
const qualifiers = rawQualifiers.flatMap((q) => {
|
|
151875
|
+
const name = getText(q);
|
|
151876
|
+
if (!name)
|
|
151877
|
+
return [];
|
|
151878
|
+
const ui = getAttribute(q, "UI");
|
|
151879
|
+
return {
|
|
151880
|
+
qualifierName: name,
|
|
151881
|
+
...ui && { qualifierUi: ui },
|
|
151882
|
+
isMajorTopic: getAttribute(q, "MajorTopicYN") === "Y"
|
|
151883
|
+
};
|
|
151884
|
+
});
|
|
151885
|
+
const isMajorAnyQualifier = qualifiers.some((q) => q.isMajorTopic);
|
|
151886
|
+
return {
|
|
151887
|
+
descriptorName: getText(mh.DescriptorName),
|
|
151888
|
+
...descriptorUi && { descriptorUi },
|
|
151889
|
+
...qualifiers.length > 0 && { qualifiers },
|
|
151890
|
+
isMajorTopic: isMajorRoot || isMajorDescriptor || isMajorAnyQualifier
|
|
151891
|
+
};
|
|
151892
|
+
});
|
|
151893
|
+
}
|
|
151894
|
+
function extractGrants(grantListXml) {
|
|
151895
|
+
if (!grantListXml)
|
|
151896
|
+
return [];
|
|
151897
|
+
const grants = ensureArray(grantListXml.Grant);
|
|
151898
|
+
return grants.map((g) => {
|
|
151899
|
+
const grantId = getText(g.GrantID);
|
|
151900
|
+
const acronym = getText(g.Acronym);
|
|
151901
|
+
const agency = getText(g.Agency);
|
|
151902
|
+
const country = getText(g.Country);
|
|
151903
|
+
return {
|
|
151904
|
+
...grantId && { grantId },
|
|
151905
|
+
...acronym && { acronym },
|
|
151906
|
+
...agency && { agency },
|
|
151907
|
+
...country && { country }
|
|
151908
|
+
};
|
|
151909
|
+
});
|
|
151910
|
+
}
|
|
151911
|
+
function extractDoi(articleXml, pubmedDataArticleIdList) {
|
|
151912
|
+
if (!articleXml)
|
|
151913
|
+
return;
|
|
151914
|
+
const eLocationIDs = ensureArray(articleXml.ELocationID);
|
|
151915
|
+
for (const eloc of eLocationIDs) {
|
|
151916
|
+
if (getAttribute(eloc, "EIdType") === "doi" && getAttribute(eloc, "ValidYN") === "Y") {
|
|
151917
|
+
const doi = getText(eloc);
|
|
151918
|
+
if (doi)
|
|
151919
|
+
return doi;
|
|
151920
|
+
}
|
|
151515
151921
|
}
|
|
151516
|
-
|
|
151517
|
-
|
|
151518
|
-
|
|
151922
|
+
for (const eloc of eLocationIDs) {
|
|
151923
|
+
if (getAttribute(eloc, "EIdType") === "doi") {
|
|
151924
|
+
const doi = getText(eloc);
|
|
151925
|
+
if (doi)
|
|
151926
|
+
return doi;
|
|
151927
|
+
}
|
|
151519
151928
|
}
|
|
151520
|
-
|
|
151521
|
-
|
|
151522
|
-
|
|
151523
|
-
|
|
151929
|
+
const articleIds = ensureArray(articleXml.ArticleIdList?.ArticleId);
|
|
151930
|
+
for (const aid of articleIds) {
|
|
151931
|
+
if (getAttribute(aid, "IdType") === "doi") {
|
|
151932
|
+
const doi = getText(aid);
|
|
151933
|
+
if (doi)
|
|
151934
|
+
return doi;
|
|
151935
|
+
}
|
|
151524
151936
|
}
|
|
151525
|
-
|
|
151526
|
-
|
|
151527
|
-
|
|
151937
|
+
if (pubmedDataArticleIdList) {
|
|
151938
|
+
const pubmedDataIds = ensureArray(pubmedDataArticleIdList.ArticleId);
|
|
151939
|
+
for (const aid of pubmedDataIds) {
|
|
151940
|
+
if (getAttribute(aid, "IdType") === "doi") {
|
|
151941
|
+
const doi = getText(aid);
|
|
151942
|
+
if (doi)
|
|
151943
|
+
return doi;
|
|
151944
|
+
}
|
|
151945
|
+
}
|
|
151528
151946
|
}
|
|
151529
|
-
|
|
151530
|
-
|
|
151531
|
-
|
|
151947
|
+
return;
|
|
151948
|
+
}
|
|
151949
|
+
function extractPmcId(articleXml, pubmedDataArticleIdList) {
|
|
151950
|
+
const articleIds = ensureArray(articleXml?.ArticleIdList?.ArticleId);
|
|
151951
|
+
for (const aid of articleIds) {
|
|
151952
|
+
if (getAttribute(aid, "IdType") === "pmc") {
|
|
151953
|
+
const val = getText(aid);
|
|
151954
|
+
if (val)
|
|
151955
|
+
return val;
|
|
151532
151956
|
}
|
|
151533
|
-
return this;
|
|
151534
151957
|
}
|
|
151535
|
-
|
|
151536
|
-
|
|
151958
|
+
if (pubmedDataArticleIdList) {
|
|
151959
|
+
const pubmedDataIds = ensureArray(pubmedDataArticleIdList.ArticleId);
|
|
151960
|
+
for (const aid of pubmedDataIds) {
|
|
151961
|
+
if (getAttribute(aid, "IdType") === "pmc") {
|
|
151962
|
+
const val = getText(aid);
|
|
151963
|
+
if (val)
|
|
151964
|
+
return val;
|
|
151965
|
+
}
|
|
151966
|
+
}
|
|
151537
151967
|
}
|
|
151538
|
-
|
|
151539
|
-
|
|
151540
|
-
|
|
151968
|
+
return;
|
|
151969
|
+
}
|
|
151970
|
+
function extractPublicationTypes(publicationTypeListXml) {
|
|
151971
|
+
if (!publicationTypeListXml)
|
|
151972
|
+
return [];
|
|
151973
|
+
const pubTypes = ensureArray(publicationTypeListXml.PublicationType);
|
|
151974
|
+
return pubTypes.map((pt) => getText(pt)).filter(Boolean);
|
|
151975
|
+
}
|
|
151976
|
+
function extractKeywords2(keywordListsXml) {
|
|
151977
|
+
if (!keywordListsXml)
|
|
151978
|
+
return [];
|
|
151979
|
+
const lists = ensureArray(keywordListsXml);
|
|
151980
|
+
const allKeywords = [];
|
|
151981
|
+
for (const list of lists) {
|
|
151982
|
+
for (const kw of ensureArray(list.Keyword)) {
|
|
151983
|
+
const keywordText = getText(kw);
|
|
151984
|
+
if (keywordText) {
|
|
151985
|
+
allKeywords.push(keywordText);
|
|
151986
|
+
}
|
|
151987
|
+
}
|
|
151541
151988
|
}
|
|
151989
|
+
return allKeywords;
|
|
151542
151990
|
}
|
|
151543
|
-
function
|
|
151544
|
-
|
|
151991
|
+
function extractAbstractText(abstractXml) {
|
|
151992
|
+
if (!abstractXml || !abstractXml.AbstractText)
|
|
151993
|
+
return;
|
|
151994
|
+
const abstractTexts = ensureArray(abstractXml.AbstractText);
|
|
151995
|
+
if (abstractTexts.length === 0)
|
|
151996
|
+
return;
|
|
151997
|
+
const processedTexts = abstractTexts.map((at) => {
|
|
151998
|
+
if (typeof at === "string") {
|
|
151999
|
+
return at;
|
|
152000
|
+
}
|
|
152001
|
+
const sectionText = getText(at);
|
|
152002
|
+
const label = getAttribute(at, "Label");
|
|
152003
|
+
if (label && sectionText) {
|
|
152004
|
+
return `${label.trim()}: ${sectionText.trim()}`;
|
|
152005
|
+
}
|
|
152006
|
+
return sectionText.trim();
|
|
152007
|
+
}).filter(Boolean);
|
|
152008
|
+
if (processedTexts.length === 0)
|
|
152009
|
+
return;
|
|
152010
|
+
return processedTexts.join(`
|
|
152011
|
+
|
|
152012
|
+
`).trim() || undefined;
|
|
152013
|
+
}
|
|
152014
|
+
function extractPmid(medlineCitationXml) {
|
|
152015
|
+
if (!medlineCitationXml || !medlineCitationXml.PMID)
|
|
152016
|
+
return;
|
|
152017
|
+
return getText(medlineCitationXml.PMID);
|
|
152018
|
+
}
|
|
152019
|
+
function extractArticleDates(articleXml) {
|
|
152020
|
+
if (!articleXml || !articleXml.ArticleDate)
|
|
152021
|
+
return [];
|
|
152022
|
+
const articleDatesXml = ensureArray(articleXml.ArticleDate);
|
|
152023
|
+
return articleDatesXml.map((ad) => ({
|
|
152024
|
+
dateType: getAttribute(ad, "DateType"),
|
|
152025
|
+
year: getText(ad.Year),
|
|
152026
|
+
month: getText(ad.Month),
|
|
152027
|
+
day: getText(ad.Day)
|
|
152028
|
+
}));
|
|
152029
|
+
}
|
|
152030
|
+
function parseFullArticle(xmlArticle, options = {}) {
|
|
152031
|
+
const medlineCitation = xmlArticle.MedlineCitation;
|
|
152032
|
+
const article = medlineCitation?.Article;
|
|
152033
|
+
const { includeMesh = true, includeGrants = false } = options;
|
|
152034
|
+
const abstractText = extractAbstractText(article?.Abstract);
|
|
152035
|
+
const journalInfo = extractJournalInfo(article?.Journal, article);
|
|
152036
|
+
const pubmedDataArticleIdList = xmlArticle.PubmedData?.ArticleIdList;
|
|
152037
|
+
const doi = extractDoi(article, pubmedDataArticleIdList);
|
|
152038
|
+
const pmcId = extractPmcId(article, pubmedDataArticleIdList);
|
|
152039
|
+
const { authors, affiliations } = extractAuthors(article?.AuthorList);
|
|
152040
|
+
return {
|
|
152041
|
+
pmid: extractPmid(medlineCitation) ?? "",
|
|
152042
|
+
title: getText(article?.ArticleTitle),
|
|
152043
|
+
...abstractText !== undefined && { abstractText },
|
|
152044
|
+
...affiliations.length > 0 && { affiliations },
|
|
152045
|
+
authors,
|
|
152046
|
+
...journalInfo !== undefined && { journalInfo },
|
|
152047
|
+
publicationTypes: extractPublicationTypes(article?.PublicationTypeList),
|
|
152048
|
+
keywords: extractKeywords2(medlineCitation?.KeywordList ?? article?.KeywordList),
|
|
152049
|
+
...includeMesh && { meshTerms: extractMeshTerms(medlineCitation?.MeshHeadingList) },
|
|
152050
|
+
...includeGrants && { grantList: extractGrants(article?.GrantList) },
|
|
152051
|
+
...doi !== undefined && { doi },
|
|
152052
|
+
...pmcId !== undefined && { pmcId },
|
|
152053
|
+
articleDates: extractArticleDates(article)
|
|
152054
|
+
};
|
|
151545
152055
|
}
|
|
151546
152056
|
|
|
151547
152057
|
// src/mcp-server/tools/definitions/pubmed-cite.tool.ts
|
|
151548
|
-
var
|
|
151549
|
-
var
|
|
151550
|
-
var
|
|
151551
|
-
var
|
|
151552
|
-
var
|
|
152058
|
+
var ncbi3 = () => container.resolve(NcbiServiceToken);
|
|
152059
|
+
var TOOL_NAME2 = "pubmed_cite";
|
|
152060
|
+
var TOOL_TITLE2 = "PubMed Citations";
|
|
152061
|
+
var TOOL_DESCRIPTION2 = "Get formatted citations for PubMed articles in APA, MLA, BibTeX, or RIS format.";
|
|
152062
|
+
var TOOL_ANNOTATIONS2 = {
|
|
151553
152063
|
readOnlyHint: true,
|
|
151554
152064
|
idempotentHint: true,
|
|
151555
152065
|
openWorldHint: true
|
|
151556
152066
|
};
|
|
151557
|
-
var
|
|
152067
|
+
var InputSchema2 = exports_external.object({
|
|
151558
152068
|
pmids: exports_external.array(exports_external.string().regex(/^\d+$/)).min(1).max(50).describe("PubMed IDs to cite"),
|
|
151559
152069
|
styles: exports_external.array(exports_external.enum(["apa", "mla", "bibtex", "ris"])).default(["apa"]).describe("Citation styles to generate")
|
|
151560
152070
|
});
|
|
151561
|
-
var
|
|
152071
|
+
var OutputSchema3 = exports_external.object({
|
|
151562
152072
|
citations: exports_external.array(exports_external.object({
|
|
151563
152073
|
pmid: exports_external.string(),
|
|
151564
152074
|
title: exports_external.string().optional(),
|
|
151565
152075
|
citations: exports_external.record(exports_external.string(), exports_external.string())
|
|
151566
152076
|
})).describe("Citations per article")
|
|
151567
152077
|
});
|
|
151568
|
-
async function
|
|
152078
|
+
async function logic2(input, appContext, _sdkContext) {
|
|
151569
152079
|
logger.debug("Fetching articles for citation generation", {
|
|
151570
152080
|
...appContext,
|
|
151571
152081
|
pmids: input.pmids,
|
|
151572
152082
|
styles: input.styles
|
|
151573
152083
|
});
|
|
151574
|
-
const raw = await
|
|
152084
|
+
const raw = await ncbi3().eFetch({ db: "pubmed", id: input.pmids.join(",") }, appContext);
|
|
151575
152085
|
const xmlArticles = ensureArray(raw?.PubmedArticleSet?.PubmedArticle);
|
|
151576
152086
|
if (xmlArticles.length === 0) {
|
|
151577
152087
|
throw new McpError(-32001 /* NotFound */, `No articles found for PMIDs: ${input.pmids.join(", ")}`, { requestId: appContext.requestId });
|
|
@@ -151590,7 +152100,7 @@ async function logic(input, appContext, _sdkContext) {
|
|
|
151590
152100
|
});
|
|
151591
152101
|
return { citations };
|
|
151592
152102
|
}
|
|
151593
|
-
function
|
|
152103
|
+
function responseFormatter2(result) {
|
|
151594
152104
|
const md = markdown();
|
|
151595
152105
|
md.text(`# PubMed Citations
|
|
151596
152106
|
`);
|
|
@@ -151617,32 +152127,32 @@ ${citation}
|
|
|
151617
152127
|
return [{ type: "text", text: md.build() }];
|
|
151618
152128
|
}
|
|
151619
152129
|
var pubmedCiteTool = {
|
|
151620
|
-
name:
|
|
151621
|
-
title:
|
|
151622
|
-
description:
|
|
151623
|
-
annotations:
|
|
151624
|
-
inputSchema:
|
|
151625
|
-
outputSchema:
|
|
151626
|
-
logic: withToolAuth(["tool:pubmed_cite:read"],
|
|
151627
|
-
responseFormatter
|
|
152130
|
+
name: TOOL_NAME2,
|
|
152131
|
+
title: TOOL_TITLE2,
|
|
152132
|
+
description: TOOL_DESCRIPTION2,
|
|
152133
|
+
annotations: TOOL_ANNOTATIONS2,
|
|
152134
|
+
inputSchema: InputSchema2,
|
|
152135
|
+
outputSchema: OutputSchema3,
|
|
152136
|
+
logic: withToolAuth(["tool:pubmed_cite:read"], logic2),
|
|
152137
|
+
responseFormatter: responseFormatter2
|
|
151628
152138
|
};
|
|
151629
152139
|
|
|
151630
152140
|
// src/mcp-server/tools/definitions/pubmed-fetch.tool.ts
|
|
151631
|
-
var
|
|
151632
|
-
var
|
|
151633
|
-
var
|
|
151634
|
-
var
|
|
151635
|
-
var
|
|
152141
|
+
var ncbi4 = () => container.resolve(NcbiServiceToken);
|
|
152142
|
+
var TOOL_NAME3 = "pubmed_fetch";
|
|
152143
|
+
var TOOL_TITLE3 = "PubMed Fetch";
|
|
152144
|
+
var TOOL_DESCRIPTION3 = "Fetch full article metadata by PubMed IDs. Returns detailed article information including abstract, authors, journal, MeSH terms.";
|
|
152145
|
+
var TOOL_ANNOTATIONS3 = {
|
|
151636
152146
|
readOnlyHint: true,
|
|
151637
152147
|
idempotentHint: true,
|
|
151638
152148
|
openWorldHint: true
|
|
151639
152149
|
};
|
|
151640
|
-
var
|
|
152150
|
+
var InputSchema3 = exports_external.object({
|
|
151641
152151
|
pmids: exports_external.array(exports_external.string().regex(/^\d+$/)).min(1).max(200).describe("PubMed IDs to fetch"),
|
|
151642
152152
|
includeMesh: exports_external.boolean().default(true).describe("Include MeSH terms"),
|
|
151643
152153
|
includeGrants: exports_external.boolean().default(false).describe("Include grant information")
|
|
151644
152154
|
});
|
|
151645
|
-
var
|
|
152155
|
+
var ArticleSchema2 = exports_external.object({
|
|
151646
152156
|
pmid: exports_external.string().optional().describe("PubMed ID"),
|
|
151647
152157
|
title: exports_external.string().optional().describe("Article title"),
|
|
151648
152158
|
abstractText: exports_external.string().optional().describe("Abstract text"),
|
|
@@ -151657,16 +152167,16 @@ var ArticleSchema = exports_external.object({
|
|
|
151657
152167
|
meshTerms: exports_external.array(exports_external.any()).optional().describe("MeSH terms"),
|
|
151658
152168
|
grantList: exports_external.array(exports_external.any()).optional().describe("Grant information")
|
|
151659
152169
|
});
|
|
151660
|
-
var
|
|
151661
|
-
articles: exports_external.array(
|
|
152170
|
+
var OutputSchema4 = exports_external.object({
|
|
152171
|
+
articles: exports_external.array(ArticleSchema2).describe("Parsed articles"),
|
|
151662
152172
|
totalReturned: exports_external.number().describe("Number of articles returned")
|
|
151663
152173
|
});
|
|
151664
|
-
async function
|
|
152174
|
+
async function logic3(input, appContext, _sdkContext) {
|
|
151665
152175
|
logger.info("Executing pubmed_fetch tool", {
|
|
151666
152176
|
...appContext,
|
|
151667
152177
|
pmidCount: input.pmids.length
|
|
151668
152178
|
});
|
|
151669
|
-
const xmlData = await
|
|
152179
|
+
const xmlData = await ncbi4().eFetch({ db: "pubmed", id: input.pmids.join(","), retmode: "xml" }, appContext, { retmode: "xml", usePost: input.pmids.length > 200 });
|
|
151670
152180
|
if (!xmlData || !("PubmedArticleSet" in xmlData)) {
|
|
151671
152181
|
throw new McpError(-32603 /* InternalError */, "Invalid EFetch response from NCBI: missing PubmedArticleSet", { requestId: appContext.requestId });
|
|
151672
152182
|
}
|
|
@@ -151694,7 +152204,7 @@ async function logic2(input, appContext, _sdkContext) {
|
|
|
151694
152204
|
});
|
|
151695
152205
|
return { articles, totalReturned: articles.length };
|
|
151696
152206
|
}
|
|
151697
|
-
function
|
|
152207
|
+
function responseFormatter3(result) {
|
|
151698
152208
|
const md = markdown().h2("PubMed Articles").keyValue("Articles Returned", result.totalReturned);
|
|
151699
152209
|
for (const article of result.articles) {
|
|
151700
152210
|
md.h3(article.title ?? article.pmid ?? "Unknown");
|
|
@@ -151729,27 +152239,27 @@ function responseFormatter2(result) {
|
|
|
151729
152239
|
return [{ type: "text", text: md.build() }];
|
|
151730
152240
|
}
|
|
151731
152241
|
var pubmedFetchTool = {
|
|
151732
|
-
name:
|
|
151733
|
-
title:
|
|
151734
|
-
description:
|
|
151735
|
-
annotations:
|
|
151736
|
-
inputSchema:
|
|
151737
|
-
outputSchema:
|
|
151738
|
-
logic: withToolAuth(["tool:pubmed_fetch:read"],
|
|
151739
|
-
responseFormatter:
|
|
152242
|
+
name: TOOL_NAME3,
|
|
152243
|
+
title: TOOL_TITLE3,
|
|
152244
|
+
description: TOOL_DESCRIPTION3,
|
|
152245
|
+
annotations: TOOL_ANNOTATIONS3,
|
|
152246
|
+
inputSchema: InputSchema3,
|
|
152247
|
+
outputSchema: OutputSchema4,
|
|
152248
|
+
logic: withToolAuth(["tool:pubmed_fetch:read"], logic3),
|
|
152249
|
+
responseFormatter: responseFormatter3
|
|
151740
152250
|
};
|
|
151741
152251
|
|
|
151742
152252
|
// src/mcp-server/tools/definitions/pubmed-mesh-lookup.tool.ts
|
|
151743
|
-
var
|
|
151744
|
-
var
|
|
151745
|
-
var
|
|
151746
|
-
var
|
|
151747
|
-
var
|
|
152253
|
+
var ncbi5 = () => container.resolve(NcbiServiceToken);
|
|
152254
|
+
var TOOL_NAME4 = "pubmed_mesh_lookup";
|
|
152255
|
+
var TOOL_TITLE4 = "MeSH Term Lookup";
|
|
152256
|
+
var TOOL_DESCRIPTION4 = "Search and explore MeSH (Medical Subject Headings) vocabulary. Essential for building precise PubMed queries.";
|
|
152257
|
+
var TOOL_ANNOTATIONS4 = {
|
|
151748
152258
|
readOnlyHint: true,
|
|
151749
152259
|
idempotentHint: true,
|
|
151750
152260
|
openWorldHint: true
|
|
151751
152261
|
};
|
|
151752
|
-
var
|
|
152262
|
+
var InputSchema4 = exports_external.object({
|
|
151753
152263
|
term: exports_external.string().min(1).describe("MeSH term to look up"),
|
|
151754
152264
|
maxResults: exports_external.number().int().min(1).max(50).default(10).describe("Maximum results"),
|
|
151755
152265
|
includeDetails: exports_external.boolean().default(true).describe("Fetch full MeSH records (scope notes, tree numbers, entry terms)")
|
|
@@ -151761,7 +152271,7 @@ var MeshResultItem = exports_external.object({
|
|
|
151761
152271
|
scopeNote: exports_external.string().optional().describe("Scope note describing the descriptor"),
|
|
151762
152272
|
entryTerms: exports_external.array(exports_external.string()).optional().describe("Synonyms / entry terms")
|
|
151763
152273
|
});
|
|
151764
|
-
var
|
|
152274
|
+
var OutputSchema5 = exports_external.object({
|
|
151765
152275
|
term: exports_external.string().describe("Original search term"),
|
|
151766
152276
|
results: exports_external.array(MeshResultItem).describe("Matching MeSH records")
|
|
151767
152277
|
});
|
|
@@ -151831,8 +152341,8 @@ async function meshLookupLogic(input, context, _sdkContext) {
|
|
|
151831
152341
|
const { term, maxResults, includeDetails } = input;
|
|
151832
152342
|
logger.debug("MeSH lookup started.", { ...context, term, maxResults, includeDetails });
|
|
151833
152343
|
const hasFieldTag = /\[.+\]/.test(term);
|
|
151834
|
-
const broadSearch =
|
|
151835
|
-
const exactSearch = hasFieldTag ? undefined :
|
|
152344
|
+
const broadSearch = ncbi5().eSearch({ db: "mesh", term, retmax: maxResults }, context);
|
|
152345
|
+
const exactSearch = hasFieldTag ? undefined : ncbi5().eSearch({ db: "mesh", term: `${term}[MH]`, retmax: 1 }, context);
|
|
151836
152346
|
const [broadResult, exactResult] = await Promise.all([broadSearch, exactSearch]);
|
|
151837
152347
|
const seen = new Set;
|
|
151838
152348
|
const ids = [];
|
|
@@ -151847,7 +152357,7 @@ async function meshLookupLogic(input, context, _sdkContext) {
|
|
|
151847
152357
|
logger.debug("No MeSH results found.", { ...context, term });
|
|
151848
152358
|
return { term, results: [] };
|
|
151849
152359
|
}
|
|
151850
|
-
const summaryData = await
|
|
152360
|
+
const summaryData = await ncbi5().eSummary({ db: "mesh", id: ids.join(",") }, context);
|
|
151851
152361
|
const results = parseSummaryRecords(summaryData, ids, includeDetails);
|
|
151852
152362
|
const termLower = term.toLowerCase();
|
|
151853
152363
|
results.sort((a, b) => {
|
|
@@ -151894,12 +152404,12 @@ function formatResponse(result) {
|
|
|
151894
152404
|
return [{ type: "text", text: md.build() }];
|
|
151895
152405
|
}
|
|
151896
152406
|
var pubmedMeshLookupTool = {
|
|
151897
|
-
name:
|
|
151898
|
-
title:
|
|
151899
|
-
description:
|
|
151900
|
-
annotations:
|
|
151901
|
-
inputSchema:
|
|
151902
|
-
outputSchema:
|
|
152407
|
+
name: TOOL_NAME4,
|
|
152408
|
+
title: TOOL_TITLE4,
|
|
152409
|
+
description: TOOL_DESCRIPTION4,
|
|
152410
|
+
annotations: TOOL_ANNOTATIONS4,
|
|
152411
|
+
inputSchema: InputSchema4,
|
|
152412
|
+
outputSchema: OutputSchema5,
|
|
151903
152413
|
logic: withToolAuth(["tool:pubmed_mesh_lookup:read"], meshLookupLogic),
|
|
151904
152414
|
responseFormatter: formatResponse
|
|
151905
152415
|
};
|
|
@@ -155051,21 +155561,21 @@ async function extractBriefSummaries(eSummaryResult, context) {
|
|
|
155051
155561
|
}
|
|
155052
155562
|
|
|
155053
155563
|
// src/mcp-server/tools/definitions/pubmed-related.tool.ts
|
|
155054
|
-
var
|
|
155055
|
-
var
|
|
155056
|
-
var
|
|
155057
|
-
var
|
|
155058
|
-
var
|
|
155564
|
+
var ncbi6 = () => container.resolve(NcbiServiceToken);
|
|
155565
|
+
var TOOL_NAME5 = "pubmed_related";
|
|
155566
|
+
var TOOL_TITLE5 = "PubMed Related Articles";
|
|
155567
|
+
var TOOL_DESCRIPTION5 = "Find articles related to a source article — similar content, citing articles, or references.";
|
|
155568
|
+
var TOOL_ANNOTATIONS5 = {
|
|
155059
155569
|
readOnlyHint: true,
|
|
155060
155570
|
idempotentHint: true,
|
|
155061
155571
|
openWorldHint: true
|
|
155062
155572
|
};
|
|
155063
|
-
var
|
|
155573
|
+
var InputSchema5 = exports_external.object({
|
|
155064
155574
|
pmid: exports_external.string().regex(/^\d+$/).describe("Source PubMed ID"),
|
|
155065
155575
|
relationship: exports_external.enum(["similar", "cited_by", "references"]).default("similar").describe("Type of relationship"),
|
|
155066
155576
|
maxResults: exports_external.number().int().min(1).max(50).default(10).describe("Maximum related articles")
|
|
155067
155577
|
});
|
|
155068
|
-
var
|
|
155578
|
+
var OutputSchema6 = exports_external.object({
|
|
155069
155579
|
sourcePmid: exports_external.string().describe("Source PubMed ID"),
|
|
155070
155580
|
relationship: exports_external.string().describe("Relationship type used"),
|
|
155071
155581
|
articles: exports_external.array(exports_external.object({
|
|
@@ -155084,7 +155594,7 @@ function extractValue(field) {
|
|
|
155084
155594
|
}
|
|
155085
155595
|
return String(field);
|
|
155086
155596
|
}
|
|
155087
|
-
async function
|
|
155597
|
+
async function logic4(input, appContext, _sdkContext) {
|
|
155088
155598
|
logger.debug("Finding related articles", {
|
|
155089
155599
|
...appContext,
|
|
155090
155600
|
pmid: input.pmid,
|
|
@@ -155109,7 +155619,7 @@ async function logic3(input, appContext, _sdkContext) {
|
|
|
155109
155619
|
eLinkParams.linkname = "pubmed_pubmed_refs";
|
|
155110
155620
|
break;
|
|
155111
155621
|
}
|
|
155112
|
-
const eLinkResult = await
|
|
155622
|
+
const eLinkResult = await ncbi6().eLink(eLinkParams, appContext);
|
|
155113
155623
|
logger.debug("Raw ELink response received", {
|
|
155114
155624
|
...appContext,
|
|
155115
155625
|
hasResult: !!eLinkResult?.eLinkResult
|
|
@@ -155151,7 +155661,7 @@ async function logic3(input, appContext, _sdkContext) {
|
|
|
155151
155661
|
}
|
|
155152
155662
|
const pmidsToEnrich = foundPmids.slice(0, input.maxResults);
|
|
155153
155663
|
const pmidIds = pmidsToEnrich.map((p) => p.pmid);
|
|
155154
|
-
const summaryResult = await
|
|
155664
|
+
const summaryResult = await ncbi6().eSummary({ db: "pubmed", id: pmidIds.join(",") }, appContext);
|
|
155155
155665
|
const briefSummaries = await extractBriefSummaries(summaryResult, appContext);
|
|
155156
155666
|
const summaryMap = new Map(briefSummaries.map((bs) => [bs.pmid, bs]));
|
|
155157
155667
|
const articles = pmidsToEnrich.map((p) => {
|
|
@@ -155175,7 +155685,7 @@ async function logic3(input, appContext, _sdkContext) {
|
|
|
155175
155685
|
totalFound
|
|
155176
155686
|
};
|
|
155177
155687
|
}
|
|
155178
|
-
function
|
|
155688
|
+
function responseFormatter4(result) {
|
|
155179
155689
|
const md = markdown();
|
|
155180
155690
|
md.text(`# Related Articles for PMID ${result.sourcePmid}
|
|
155181
155691
|
`);
|
|
@@ -155198,27 +155708,27 @@ function responseFormatter3(result) {
|
|
|
155198
155708
|
return [{ type: "text", text: md.build() }];
|
|
155199
155709
|
}
|
|
155200
155710
|
var pubmedRelatedTool = {
|
|
155201
|
-
name:
|
|
155202
|
-
title:
|
|
155203
|
-
description:
|
|
155204
|
-
annotations:
|
|
155205
|
-
inputSchema:
|
|
155206
|
-
outputSchema:
|
|
155207
|
-
logic: withToolAuth(["tool:pubmed_related:read"],
|
|
155208
|
-
responseFormatter:
|
|
155711
|
+
name: TOOL_NAME5,
|
|
155712
|
+
title: TOOL_TITLE5,
|
|
155713
|
+
description: TOOL_DESCRIPTION5,
|
|
155714
|
+
annotations: TOOL_ANNOTATIONS5,
|
|
155715
|
+
inputSchema: InputSchema5,
|
|
155716
|
+
outputSchema: OutputSchema6,
|
|
155717
|
+
logic: withToolAuth(["tool:pubmed_related:read"], logic4),
|
|
155718
|
+
responseFormatter: responseFormatter4
|
|
155209
155719
|
};
|
|
155210
155720
|
|
|
155211
155721
|
// src/mcp-server/tools/definitions/pubmed-search.tool.ts
|
|
155212
|
-
var
|
|
155213
|
-
var
|
|
155214
|
-
var
|
|
155215
|
-
var
|
|
155216
|
-
var
|
|
155722
|
+
var ncbi7 = () => container.resolve(NcbiServiceToken);
|
|
155723
|
+
var TOOL_NAME6 = "pubmed_search";
|
|
155724
|
+
var TOOL_TITLE6 = "PubMed Search";
|
|
155725
|
+
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.";
|
|
155726
|
+
var TOOL_ANNOTATIONS6 = {
|
|
155217
155727
|
readOnlyHint: true,
|
|
155218
155728
|
idempotentHint: true,
|
|
155219
155729
|
openWorldHint: true
|
|
155220
155730
|
};
|
|
155221
|
-
var
|
|
155731
|
+
var InputSchema6 = exports_external.object({
|
|
155222
155732
|
query: exports_external.string().min(1).describe("PubMed search query (supports full NCBI syntax)"),
|
|
155223
155733
|
maxResults: exports_external.number().int().min(1).max(1000).default(20).describe("Maximum results to return"),
|
|
155224
155734
|
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 +155748,7 @@ var InputSchema5 = exports_external.object({
|
|
|
155238
155748
|
species: exports_external.enum(["humans", "animals"]).optional().describe("Filter by species"),
|
|
155239
155749
|
summaryCount: exports_external.number().int().min(0).max(50).default(0).describe("Fetch brief summaries for top N results (0 = PMIDs only)")
|
|
155240
155750
|
});
|
|
155241
|
-
var
|
|
155751
|
+
var OutputSchema7 = exports_external.object({
|
|
155242
155752
|
query: exports_external.string().describe("Original query"),
|
|
155243
155753
|
totalFound: exports_external.number().describe("Total matching articles"),
|
|
155244
155754
|
offset: exports_external.number().describe("Result offset used"),
|
|
@@ -155256,7 +155766,7 @@ var OutputSchema6 = exports_external.object({
|
|
|
155256
155766
|
})).optional().describe("Brief summaries"),
|
|
155257
155767
|
searchUrl: exports_external.string().describe("PubMed search URL")
|
|
155258
155768
|
});
|
|
155259
|
-
async function
|
|
155769
|
+
async function logic5(input, appContext, _sdkContext) {
|
|
155260
155770
|
logger.info("Executing pubmed_search tool", { ...appContext, query: input.query });
|
|
155261
155771
|
let effectiveQuery = sanitization.sanitizeString(input.query, { context: "text" });
|
|
155262
155772
|
if (input.dateRange) {
|
|
@@ -155291,7 +155801,7 @@ async function logic4(input, appContext, _sdkContext) {
|
|
|
155291
155801
|
if (input.species) {
|
|
155292
155802
|
effectiveQuery += ` AND ${input.species}[MeSH Terms]`;
|
|
155293
155803
|
}
|
|
155294
|
-
const esResult = await
|
|
155804
|
+
const esResult = await ncbi7().eSearch({
|
|
155295
155805
|
db: "pubmed",
|
|
155296
155806
|
term: effectiveQuery,
|
|
155297
155807
|
retmax: input.maxResults,
|
|
@@ -155315,7 +155825,7 @@ async function logic4(input, appContext, _sdkContext) {
|
|
|
155315
155825
|
} else {
|
|
155316
155826
|
eSummaryParams.id = pmids.slice(0, input.summaryCount).join(",");
|
|
155317
155827
|
}
|
|
155318
|
-
const eSummaryResult = await
|
|
155828
|
+
const eSummaryResult = await ncbi7().eSummary(eSummaryParams, appContext);
|
|
155319
155829
|
if (eSummaryResult) {
|
|
155320
155830
|
const briefSummaries = await extractBriefSummaries(eSummaryResult, appContext);
|
|
155321
155831
|
summaries = briefSummaries.map((s) => ({
|
|
@@ -155340,7 +155850,7 @@ async function logic4(input, appContext, _sdkContext) {
|
|
|
155340
155850
|
});
|
|
155341
155851
|
return { query: input.query, totalFound, offset: input.offset, pmids, summaries, searchUrl };
|
|
155342
155852
|
}
|
|
155343
|
-
function
|
|
155853
|
+
function responseFormatter5(result) {
|
|
155344
155854
|
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
155855
|
if (result.pmids.length > 0) {
|
|
155346
155856
|
md.h3("PMIDs").paragraph(result.pmids.join(", "));
|
|
@@ -155367,37 +155877,37 @@ function responseFormatter4(result) {
|
|
|
155367
155877
|
return [{ type: "text", text: md.build() }];
|
|
155368
155878
|
}
|
|
155369
155879
|
var pubmedSearchTool = {
|
|
155370
|
-
name:
|
|
155371
|
-
title:
|
|
155372
|
-
description:
|
|
155373
|
-
annotations:
|
|
155374
|
-
inputSchema:
|
|
155375
|
-
outputSchema:
|
|
155376
|
-
logic: withToolAuth(["tool:pubmed_search:read"],
|
|
155377
|
-
responseFormatter:
|
|
155880
|
+
name: TOOL_NAME6,
|
|
155881
|
+
title: TOOL_TITLE6,
|
|
155882
|
+
description: TOOL_DESCRIPTION6,
|
|
155883
|
+
annotations: TOOL_ANNOTATIONS6,
|
|
155884
|
+
inputSchema: InputSchema6,
|
|
155885
|
+
outputSchema: OutputSchema7,
|
|
155886
|
+
logic: withToolAuth(["tool:pubmed_search:read"], logic5),
|
|
155887
|
+
responseFormatter: responseFormatter5
|
|
155378
155888
|
};
|
|
155379
155889
|
|
|
155380
155890
|
// src/mcp-server/tools/definitions/pubmed-spell.tool.ts
|
|
155381
|
-
var
|
|
155382
|
-
var
|
|
155383
|
-
var
|
|
155384
|
-
var
|
|
155385
|
-
var
|
|
155891
|
+
var ncbi8 = () => container.resolve(NcbiServiceToken);
|
|
155892
|
+
var TOOL_NAME7 = "pubmed_spell";
|
|
155893
|
+
var TOOL_TITLE7 = "PubMed Spell Check";
|
|
155894
|
+
var TOOL_DESCRIPTION7 = "Spell-check a query and get NCBI's suggested correction. Useful for refining search queries.";
|
|
155895
|
+
var TOOL_ANNOTATIONS7 = {
|
|
155386
155896
|
readOnlyHint: true,
|
|
155387
155897
|
idempotentHint: true,
|
|
155388
155898
|
openWorldHint: true
|
|
155389
155899
|
};
|
|
155390
|
-
var
|
|
155900
|
+
var InputSchema7 = exports_external.object({
|
|
155391
155901
|
query: exports_external.string().min(2).describe("Query to spell-check")
|
|
155392
155902
|
});
|
|
155393
|
-
var
|
|
155903
|
+
var OutputSchema8 = exports_external.object({
|
|
155394
155904
|
original: exports_external.string().describe("Original query"),
|
|
155395
155905
|
corrected: exports_external.string().describe("Corrected query (same as original if no suggestion)"),
|
|
155396
155906
|
hasSuggestion: exports_external.boolean().describe("Whether NCBI suggested a correction")
|
|
155397
155907
|
});
|
|
155398
|
-
async function
|
|
155908
|
+
async function logic6(input, appContext, _sdkContext) {
|
|
155399
155909
|
logger.info("Executing pubmed_spell tool", { ...appContext, query: input.query });
|
|
155400
|
-
const result = await
|
|
155910
|
+
const result = await ncbi8().eSpell({ db: "pubmed", term: input.query }, appContext);
|
|
155401
155911
|
logger.notice("pubmed_spell completed", {
|
|
155402
155912
|
...appContext,
|
|
155403
155913
|
hasSuggestion: result.hasSuggestion
|
|
@@ -155408,7 +155918,7 @@ async function logic5(input, appContext, _sdkContext) {
|
|
|
155408
155918
|
hasSuggestion: result.hasSuggestion
|
|
155409
155919
|
};
|
|
155410
155920
|
}
|
|
155411
|
-
function
|
|
155921
|
+
function responseFormatter6(result) {
|
|
155412
155922
|
const md = markdown().h2("PubMed Spell Check");
|
|
155413
155923
|
if (result.hasSuggestion) {
|
|
155414
155924
|
md.keyValue("Original", result.original).keyValue("Suggested", result.corrected);
|
|
@@ -155418,20 +155928,21 @@ function responseFormatter5(result) {
|
|
|
155418
155928
|
return [{ type: "text", text: md.build() }];
|
|
155419
155929
|
}
|
|
155420
155930
|
var pubmedSpellTool = {
|
|
155421
|
-
name:
|
|
155422
|
-
title:
|
|
155423
|
-
description:
|
|
155424
|
-
annotations:
|
|
155425
|
-
inputSchema:
|
|
155426
|
-
outputSchema:
|
|
155427
|
-
logic: withToolAuth(["tool:pubmed_spell:read"],
|
|
155428
|
-
responseFormatter:
|
|
155931
|
+
name: TOOL_NAME7,
|
|
155932
|
+
title: TOOL_TITLE7,
|
|
155933
|
+
description: TOOL_DESCRIPTION7,
|
|
155934
|
+
annotations: TOOL_ANNOTATIONS7,
|
|
155935
|
+
inputSchema: InputSchema7,
|
|
155936
|
+
outputSchema: OutputSchema8,
|
|
155937
|
+
logic: withToolAuth(["tool:pubmed_spell:read"], logic6),
|
|
155938
|
+
responseFormatter: responseFormatter6
|
|
155429
155939
|
};
|
|
155430
155940
|
|
|
155431
155941
|
// src/mcp-server/tools/definitions/index.ts
|
|
155432
155942
|
var allToolDefinitions = [
|
|
155433
155943
|
pubmedSearchTool,
|
|
155434
155944
|
pubmedFetchTool,
|
|
155945
|
+
pmcFetchTool,
|
|
155435
155946
|
pubmedSpellTool,
|
|
155436
155947
|
pubmedCiteTool,
|
|
155437
155948
|
pubmedRelatedTool,
|
|
@@ -155633,8 +156144,8 @@ var defaultResponseFormatter2 = (result) => [
|
|
|
155633
156144
|
function createMcpToolHandler({
|
|
155634
156145
|
toolName,
|
|
155635
156146
|
inputSchema,
|
|
155636
|
-
logic:
|
|
155637
|
-
responseFormatter:
|
|
156147
|
+
logic: logic7,
|
|
156148
|
+
responseFormatter: responseFormatter7 = defaultResponseFormatter2
|
|
155638
156149
|
}) {
|
|
155639
156150
|
return async (input, callContext) => {
|
|
155640
156151
|
const sdkContext = callContext;
|
|
@@ -155649,10 +156160,10 @@ function createMcpToolHandler({
|
|
|
155649
156160
|
});
|
|
155650
156161
|
try {
|
|
155651
156162
|
const validatedInput = inputSchema.parse(input);
|
|
155652
|
-
const result = await measureToolExecution(() =>
|
|
156163
|
+
const result = await measureToolExecution(() => logic7(validatedInput, appContext, sdkContext), { ...appContext, toolName }, validatedInput);
|
|
155653
156164
|
return {
|
|
155654
156165
|
structuredContent: result,
|
|
155655
|
-
content:
|
|
156166
|
+
content: responseFormatter7(result)
|
|
155656
156167
|
};
|
|
155657
156168
|
} catch (error48) {
|
|
155658
156169
|
const handled = ErrorHandler.handleError(error48, {
|