@cyanheads/pubmed-mcp-server 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/dist/config/index.js +39 -1
- package/dist/mcp-server/server.d.ts +0 -7
- package/dist/mcp-server/server.js +17 -53
- package/dist/mcp-server/tools/fetchPubMedContent/logic.d.ts +6 -2
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +102 -311
- package/dist/mcp-server/tools/fetchPubMedContent/registration.d.ts +1 -1
- package/dist/mcp-server/tools/fetchPubMedContent/registration.js +50 -18
- package/dist/mcp-server/tools/generatePubMedChart/logic.d.ts +9 -28
- package/dist/mcp-server/tools/generatePubMedChart/logic.js +137 -198
- package/dist/mcp-server/tools/generatePubMedChart/registration.d.ts +1 -1
- package/dist/mcp-server/tools/generatePubMedChart/registration.js +62 -27
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +1 -1
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +3 -3
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.d.ts +1 -1
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.d.ts +27 -4
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +59 -51
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +1 -1
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +1 -26
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +58 -62
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +2 -5
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +7 -40
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.d.ts +1 -1
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +55 -19
- package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +12 -10
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +68 -121
- package/dist/mcp-server/tools/searchPubMedArticles/registration.d.ts +1 -1
- package/dist/mcp-server/tools/searchPubMedArticles/registration.js +54 -21
- package/dist/mcp-server/transports/httpTransport.d.ts +0 -8
- package/dist/mcp-server/transports/httpTransport.js +57 -345
- package/dist/utils/security/rateLimiter.d.ts +4 -0
- package/dist/utils/security/rateLimiter.js +4 -0
- package/package.json +7 -15
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +0 -79
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +0 -82
- package/dist/mcp-server/resources/echoResource/index.d.ts +0 -13
- package/dist/mcp-server/resources/echoResource/index.js +0 -13
- package/dist/mcp-server/resources/echoResource/registration.d.ts +0 -30
- package/dist/mcp-server/resources/echoResource/registration.js +0 -168
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.d.ts +0 -6
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.js +0 -6
|
@@ -61,14 +61,6 @@ export const SearchPubMedArticlesInputSchema = z.object({
|
|
|
61
61
|
.default(0)
|
|
62
62
|
.describe("Number of top PMIDs for which to fetch brief summaries using ESummary v2.0. Set to 0 to disable. Maximum is 50 for this tool. Default is 0."),
|
|
63
63
|
});
|
|
64
|
-
/**
|
|
65
|
-
* Logic for the searchPubMedArticles tool.
|
|
66
|
-
* Constructs and executes ESearch and optionally ESummary queries via NcbiService,
|
|
67
|
-
* then formats the results into a CallToolResult.
|
|
68
|
-
* @param input - Validated input arguments for the tool.
|
|
69
|
-
* @param parentRequestContext - The parent request context for logging and correlation.
|
|
70
|
-
* @returns A promise resolving to a CallToolResult.
|
|
71
|
-
*/
|
|
72
64
|
export async function searchPubMedArticlesLogic(input, parentRequestContext) {
|
|
73
65
|
const ncbiService = getNcbiService();
|
|
74
66
|
const toolLogicContext = requestContextService.createRequestContext({
|
|
@@ -107,124 +99,79 @@ export async function searchPubMedArticlesLogic(input, parentRequestContext) {
|
|
|
107
99
|
sort: input.sortBy,
|
|
108
100
|
usehistory: currentFetchBriefSummaries > 0 ? "y" : "n",
|
|
109
101
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for (const key in eSearchParams) {
|
|
117
|
-
if (eSearchParams[key] !== undefined) {
|
|
118
|
-
eSearchQueryStringParams[key] = String(eSearchParams[key]);
|
|
119
|
-
}
|
|
102
|
+
const eSearchResponse = await ncbiService.eSearch(eSearchParams, toolLogicContext);
|
|
103
|
+
const eSearchBase = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi";
|
|
104
|
+
const eSearchQueryStringParams = {};
|
|
105
|
+
for (const key in eSearchParams) {
|
|
106
|
+
if (eSearchParams[key] !== undefined) {
|
|
107
|
+
eSearchQueryStringParams[key] = String(eSearchParams[key]);
|
|
120
108
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
109
|
+
}
|
|
110
|
+
const eSearchQueryString = new URLSearchParams(eSearchQueryStringParams).toString();
|
|
111
|
+
const eSearchUrl = `${eSearchBase}?${eSearchQueryString}`;
|
|
112
|
+
if (!eSearchResponse || !eSearchResponse.eSearchResult) {
|
|
113
|
+
throw new McpError(BaseErrorCode.NCBI_PARSING_ERROR, "Invalid or empty ESearch response from NCBI.", {
|
|
114
|
+
...toolLogicContext,
|
|
115
|
+
responsePreview: sanitizeInputForLogging(JSON.stringify(eSearchResponse).substring(0, 200)),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const esResult = eSearchResponse.eSearchResult;
|
|
119
|
+
const pmids = esResult.IdList?.Id || [];
|
|
120
|
+
const totalFound = parseInt(esResult.Count || "0", 10);
|
|
121
|
+
const retrievedPmidCount = pmids.length;
|
|
122
|
+
let briefSummaries = [];
|
|
123
|
+
let eSummaryUrl;
|
|
124
|
+
if (currentFetchBriefSummaries > 0 && pmids.length > 0) {
|
|
125
|
+
const eSummaryParams = {
|
|
126
|
+
db: "pubmed",
|
|
127
|
+
version: "2.0",
|
|
128
|
+
retmode: "xml",
|
|
129
|
+
};
|
|
130
|
+
if (esResult.WebEnv && esResult.QueryKey) {
|
|
131
|
+
eSummaryParams.WebEnv = esResult.WebEnv;
|
|
132
|
+
eSummaryParams.query_key = esResult.QueryKey;
|
|
133
|
+
eSummaryParams.retmax = currentFetchBriefSummaries;
|
|
128
134
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (esResult.WebEnv && esResult.QueryKey) {
|
|
141
|
-
eSummaryParams.WebEnv = esResult.WebEnv;
|
|
142
|
-
eSummaryParams.query_key = esResult.QueryKey;
|
|
143
|
-
eSummaryParams.retmax = currentFetchBriefSummaries; // Use history with retmax
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
// Fallback to using explicit IDs if history is not available (should not happen if usehistory='y' was successful)
|
|
147
|
-
const pmidsForSummary = pmids
|
|
148
|
-
.slice(0, currentFetchBriefSummaries)
|
|
149
|
-
.join(",");
|
|
150
|
-
eSummaryParams.id = pmidsForSummary;
|
|
151
|
-
}
|
|
152
|
-
const eSummaryBase = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi";
|
|
153
|
-
const eSummaryQueryStringParams = {};
|
|
154
|
-
for (const key in eSummaryParams) {
|
|
155
|
-
if (eSummaryParams[key] !== undefined) {
|
|
156
|
-
eSummaryQueryStringParams[key] = String(eSummaryParams[key]);
|
|
157
|
-
}
|
|
135
|
+
else {
|
|
136
|
+
const pmidsForSummary = pmids
|
|
137
|
+
.slice(0, currentFetchBriefSummaries)
|
|
138
|
+
.join(",");
|
|
139
|
+
eSummaryParams.id = pmidsForSummary;
|
|
140
|
+
}
|
|
141
|
+
const eSummaryBase = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi";
|
|
142
|
+
const eSummaryQueryStringParams = {};
|
|
143
|
+
for (const key in eSummaryParams) {
|
|
144
|
+
if (eSummaryParams[key] !== undefined) {
|
|
145
|
+
eSummaryQueryStringParams[key] = String(eSummaryParams[key]);
|
|
158
146
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
147
|
+
}
|
|
148
|
+
const eSummaryQueryString = new URLSearchParams(eSummaryQueryStringParams).toString();
|
|
149
|
+
eSummaryUrl = `${eSummaryBase}?${eSummaryQueryString}`;
|
|
150
|
+
const eSummaryResponseXml = await ncbiService.eSummary(eSummaryParams, toolLogicContext);
|
|
151
|
+
if (eSummaryResponseXml && eSummaryResponseXml.eSummaryResult) {
|
|
152
|
+
briefSummaries = await extractBriefSummaries(eSummaryResponseXml.eSummaryResult, toolLogicContext);
|
|
153
|
+
}
|
|
154
|
+
else if (eSummaryResponseXml && eSummaryResponseXml.ERROR) {
|
|
155
|
+
logger.warning("ESummary returned a top-level error", {
|
|
163
156
|
...toolLogicContext,
|
|
164
|
-
|
|
157
|
+
errorDetails: eSummaryResponseXml.ERROR,
|
|
165
158
|
});
|
|
166
|
-
if (eSummaryResponseXml && eSummaryResponseXml.eSummaryResult) {
|
|
167
|
-
briefSummaries = await extractBriefSummaries(eSummaryResponseXml.eSummaryResult, toolLogicContext);
|
|
168
|
-
}
|
|
169
|
-
else if (eSummaryResponseXml && eSummaryResponseXml.ERROR) {
|
|
170
|
-
logger.warning("ESummary returned a top-level error", {
|
|
171
|
-
...toolLogicContext,
|
|
172
|
-
errorDetails: eSummaryResponseXml.ERROR,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
159
|
}
|
|
176
|
-
const resultPayload = {
|
|
177
|
-
searchParameters: {
|
|
178
|
-
queryTerm: input.queryTerm,
|
|
179
|
-
maxResults: input.maxResults,
|
|
180
|
-
sortBy: input.sortBy,
|
|
181
|
-
dateRange: input.dateRange,
|
|
182
|
-
filterByPublicationTypes: input.filterByPublicationTypes,
|
|
183
|
-
fetchBriefSummaries: currentFetchBriefSummaries,
|
|
184
|
-
},
|
|
185
|
-
effectiveESearchTerm: effectiveQuery,
|
|
186
|
-
totalFound,
|
|
187
|
-
retrievedPmidCount,
|
|
188
|
-
pmids, // These are the PMIDs from ESearch, limited by input.maxResults
|
|
189
|
-
briefSummaries, // These should now be limited by currentFetchBriefSummaries
|
|
190
|
-
eSearchUrl,
|
|
191
|
-
eSummaryUrl: currentFetchBriefSummaries > 0 && pmids.length > 0
|
|
192
|
-
? eSummaryUrl
|
|
193
|
-
: undefined,
|
|
194
|
-
};
|
|
195
|
-
return {
|
|
196
|
-
content: [{ type: "text", text: JSON.stringify(resultPayload) }],
|
|
197
|
-
isError: false,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
logger.error("Error in searchPubMedArticlesLogic", error, toolLogicContext);
|
|
202
|
-
const mcpError = error instanceof McpError
|
|
203
|
-
? error
|
|
204
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "Failed to search PubMed articles due to an unexpected error.", {
|
|
205
|
-
originalErrorName: error.name,
|
|
206
|
-
originalErrorMessage: error.message,
|
|
207
|
-
requestId: toolLogicContext.requestId,
|
|
208
|
-
});
|
|
209
|
-
return {
|
|
210
|
-
content: [
|
|
211
|
-
{
|
|
212
|
-
type: "text",
|
|
213
|
-
text: JSON.stringify({
|
|
214
|
-
error: {
|
|
215
|
-
code: mcpError.code,
|
|
216
|
-
message: mcpError.message,
|
|
217
|
-
details: mcpError.details,
|
|
218
|
-
},
|
|
219
|
-
searchParameters: sanitizeInputForLogging(input),
|
|
220
|
-
eSearchUrl,
|
|
221
|
-
eSummaryUrl: (input.fetchBriefSummaries ?? 0) > 0 && eSummaryUrl
|
|
222
|
-
? eSummaryUrl
|
|
223
|
-
: undefined,
|
|
224
|
-
}),
|
|
225
|
-
},
|
|
226
|
-
],
|
|
227
|
-
isError: true,
|
|
228
|
-
};
|
|
229
160
|
}
|
|
161
|
+
logger.notice("Successfully executed searchPubMedArticles tool.", {
|
|
162
|
+
...toolLogicContext,
|
|
163
|
+
totalFound,
|
|
164
|
+
retrievedPmidCount,
|
|
165
|
+
summariesFetched: briefSummaries.length,
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
searchParameters: input,
|
|
169
|
+
effectiveESearchTerm: effectiveQuery,
|
|
170
|
+
totalFound,
|
|
171
|
+
retrievedPmidCount,
|
|
172
|
+
pmids,
|
|
173
|
+
briefSummaries,
|
|
174
|
+
eSearchUrl,
|
|
175
|
+
eSummaryUrl,
|
|
176
|
+
};
|
|
230
177
|
}
|
|
@@ -7,4 +7,4 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
7
7
|
* Registers the searchPubMedArticles tool with the MCP server.
|
|
8
8
|
* @param server - The McpServer instance.
|
|
9
9
|
*/
|
|
10
|
-
export declare function registerSearchPubMedArticlesTool(server: McpServer): void
|
|
10
|
+
export declare function registerSearchPubMedArticlesTool(server: McpServer): Promise<void>;
|
|
@@ -2,37 +2,70 @@
|
|
|
2
2
|
* @fileoverview Registration for the searchPubMedArticles MCP tool.
|
|
3
3
|
* @module src/mcp-server/tools/searchPubMedArticles/registration
|
|
4
4
|
*/
|
|
5
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
6
|
-
import { ErrorHandler, requestContextService } from "../../../utils/index.js";
|
|
5
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
6
|
+
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
7
7
|
import { SearchPubMedArticlesInputSchema, searchPubMedArticlesLogic, } from "./logic.js";
|
|
8
8
|
/**
|
|
9
9
|
* Registers the searchPubMedArticles tool with the MCP server.
|
|
10
10
|
* @param server - The McpServer instance.
|
|
11
11
|
*/
|
|
12
|
-
export function registerSearchPubMedArticlesTool(server) {
|
|
12
|
+
export async function registerSearchPubMedArticlesTool(server) {
|
|
13
13
|
const operation = "registerSearchPubMedArticlesTool";
|
|
14
|
+
const toolName = "search_pubmed_articles";
|
|
15
|
+
const toolDescription = "Searches PubMed for articles using a query term and optional filters (max results, sort, date range, publication types). Uses NCBI ESearch to find PMIDs and ESummary (optional) for brief summaries. Returns a JSON object with search parameters, ESearch term, result counts, PMIDs, optional summaries (PMID, title, authors, source, dates), and E-utility URLs.";
|
|
14
16
|
const context = requestContextService.createRequestContext({ operation });
|
|
15
|
-
|
|
16
|
-
server.tool(
|
|
17
|
-
async (input, toolContext) => {
|
|
18
|
-
// Explicitly type input
|
|
17
|
+
await ErrorHandler.tryCatch(async () => {
|
|
18
|
+
server.tool(toolName, toolDescription, SearchPubMedArticlesInputSchema.shape, async (input, mcpProvidedContext) => {
|
|
19
19
|
const richContext = requestContextService.createRequestContext({
|
|
20
20
|
parentRequestId: context.requestId,
|
|
21
21
|
operation: "searchPubMedArticlesToolHandler",
|
|
22
|
-
mcpToolContext:
|
|
22
|
+
mcpToolContext: mcpProvidedContext,
|
|
23
|
+
input,
|
|
23
24
|
});
|
|
24
|
-
|
|
25
|
+
try {
|
|
26
|
+
const result = await searchPubMedArticlesLogic(input, richContext);
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
30
|
+
],
|
|
31
|
+
isError: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const handledError = ErrorHandler.handleError(error, {
|
|
36
|
+
operation: "searchPubMedArticlesToolHandler",
|
|
37
|
+
context: richContext,
|
|
38
|
+
input,
|
|
39
|
+
rethrow: false,
|
|
40
|
+
});
|
|
41
|
+
const mcpError = handledError instanceof McpError
|
|
42
|
+
? handledError
|
|
43
|
+
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred while searching PubMed articles.", {
|
|
44
|
+
originalErrorName: handledError.name,
|
|
45
|
+
originalErrorMessage: handledError.message,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: JSON.stringify({
|
|
52
|
+
error: {
|
|
53
|
+
code: mcpError.code,
|
|
54
|
+
message: mcpError.message,
|
|
55
|
+
details: mcpError.details,
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
25
63
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
context,
|
|
34
|
-
errorCode: BaseErrorCode.INITIALIZATION_FAILED, // errorCode is still useful for categorization
|
|
35
|
-
critical: true,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
64
|
+
logger.notice(`Tool '${toolName}' registered.`, context);
|
|
65
|
+
}, {
|
|
66
|
+
operation,
|
|
67
|
+
context,
|
|
68
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
69
|
+
critical: true,
|
|
70
|
+
});
|
|
38
71
|
}
|
|
@@ -13,12 +13,4 @@
|
|
|
13
13
|
import { ServerType } from "@hono/node-server";
|
|
14
14
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
15
|
import { RequestContext } from "../../utils/index.js";
|
|
16
|
-
/**
|
|
17
|
-
* Sets up and starts the Streamable HTTP transport layer for the MCP server.
|
|
18
|
-
*
|
|
19
|
-
* @param createServerInstanceFn - An asynchronous factory function that returns a new `McpServer` instance.
|
|
20
|
-
* @param parentContext - Logging context from the main server startup process.
|
|
21
|
-
* @returns A promise that resolves with the Node.js `http.Server` instance when the HTTP server is successfully listening.
|
|
22
|
-
* @throws {Error} If the server fails to start after all port retries.
|
|
23
|
-
*/
|
|
24
16
|
export declare function startHttpTransport(createServerInstanceFn: () => Promise<McpServer>, parentContext: RequestContext): Promise<ServerType>;
|