@cyanheads/pubmed-mcp-server 1.1.1 → 1.1.4
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 +19 -16
- package/dist/config/index.d.ts +4 -2
- package/dist/config/index.js +52 -7
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +59 -160
- package/dist/mcp-server/tools/fetchPubMedContent/registration.d.ts +1 -1
- package/dist/mcp-server/tools/fetchPubMedContent/registration.js +11 -15
- package/dist/mcp-server/tools/generatePubMedChart/logic.js +21 -22
- package/dist/mcp-server/tools/generatePubMedChart/registration.d.ts +1 -1
- package/dist/mcp-server/tools/generatePubMedChart/registration.js +10 -19
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +3 -3
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +9 -6
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +1 -2
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +16 -26
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +1 -3
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +1 -2
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +7 -6
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.d.ts +1 -1
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +9 -14
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +9 -4
- package/dist/mcp-server/tools/searchPubMedArticles/registration.d.ts +1 -1
- package/dist/mcp-server/tools/searchPubMedArticles/registration.js +13 -20
- package/dist/mcp-server/transports/httpTransport.js +9 -0
- package/dist/utils/internal/logger.js +49 -51
- package/dist/utils/security/rateLimiter.d.ts +4 -0
- package/dist/utils/security/rateLimiter.js +4 -0
- package/package.json +2 -3
|
@@ -2,6 +2,11 @@ import * as vega from "vega";
|
|
|
2
2
|
import * as vegaLite from "vega-lite";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
5
|
+
// IMPORTANT DEPLOYMENT NOTE:
|
|
6
|
+
// This tool uses the 'canvas' package to render PNG images on the server.
|
|
7
|
+
// The 'canvas' package has native system dependencies (e.g., cairo, pango, libjpeg).
|
|
8
|
+
// Ensure these are installed on the deployment environment.
|
|
9
|
+
// See: https://www.npmjs.com/package/canvas#compiling
|
|
5
10
|
import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
|
|
6
11
|
export const GeneratePubMedChartInputSchema = z.object({
|
|
7
12
|
chartType: z
|
|
@@ -73,8 +78,6 @@ export const GeneratePubMedChartInputSchema = z.object({
|
|
|
73
78
|
.enum(["quantitative", "ordinal"])
|
|
74
79
|
.optional()
|
|
75
80
|
.describe("Optional. Specifies the data type of the `sizeField`. Options: 'quantitative', 'ordinal'. Defaults to 'quantitative' if `sizeField` is provided and this is omitted."),
|
|
76
|
-
// shapeField: z.string().optional().describe("Optional field for encoding point shape in scatter plots."), // Future enhancement
|
|
77
|
-
// shapeFieldType: z.enum(["nominal", "ordinal"]).optional().describe("Type of the shape field."), // Future enhancement
|
|
78
81
|
});
|
|
79
82
|
export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
80
83
|
const operationContext = requestContextService.createRequestContext({
|
|
@@ -83,9 +86,8 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
|
83
86
|
input: sanitizeInputForLogging(input),
|
|
84
87
|
});
|
|
85
88
|
logger.info(`Executing 'generate_pubmed_chart'. Chart type: ${input.chartType}, Output format: ${input.outputFormat}`, operationContext);
|
|
86
|
-
if (input.outputFormat !== "png") {
|
|
87
|
-
const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'png' is supported.`,
|
|
88
|
-
{ requestedFormat: input.outputFormat });
|
|
89
|
+
if (input.outputFormat !== "png") {
|
|
90
|
+
const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'png' is supported.`, { requestedFormat: input.outputFormat });
|
|
89
91
|
logger.warning(unsupportedFormatError.message, operationContext);
|
|
90
92
|
return {
|
|
91
93
|
content: [
|
|
@@ -104,15 +106,14 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
try {
|
|
107
|
-
|
|
109
|
+
const vegaLiteSpec = {
|
|
108
110
|
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
|
|
109
111
|
title: input.title,
|
|
110
112
|
width: input.width,
|
|
111
113
|
height: input.height,
|
|
112
114
|
data: { values: input.dataValues },
|
|
113
|
-
encoding: {},
|
|
115
|
+
encoding: {},
|
|
114
116
|
};
|
|
115
|
-
// Default field types if not provided
|
|
116
117
|
const yEncType = input.yFieldType || "quantitative";
|
|
117
118
|
const colorEncType = input.colorFieldType || "nominal";
|
|
118
119
|
const seriesEncType = input.seriesFieldType || "nominal";
|
|
@@ -157,14 +158,12 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
|
157
158
|
},
|
|
158
159
|
};
|
|
159
160
|
if (input.seriesField) {
|
|
160
|
-
// For line charts, seriesField is typically used for color
|
|
161
161
|
vegaLiteSpec.encoding.color = {
|
|
162
162
|
field: input.seriesField,
|
|
163
163
|
type: seriesEncType,
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
else if (input.colorField) {
|
|
167
|
-
// Allow direct colorField as well
|
|
168
167
|
vegaLiteSpec.encoding.color = {
|
|
169
168
|
field: input.colorField,
|
|
170
169
|
type: colorEncType,
|
|
@@ -173,7 +172,7 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
|
173
172
|
break;
|
|
174
173
|
case "scatter":
|
|
175
174
|
xEncType = input.xFieldType || "quantitative";
|
|
176
|
-
vegaLiteSpec.mark = "point";
|
|
175
|
+
vegaLiteSpec.mark = "point";
|
|
177
176
|
vegaLiteSpec.encoding = {
|
|
178
177
|
x: {
|
|
179
178
|
field: input.xField,
|
|
@@ -198,28 +197,28 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
|
198
197
|
type: sizeEncType,
|
|
199
198
|
};
|
|
200
199
|
}
|
|
201
|
-
// Add shape encoding here if shapeField is implemented
|
|
202
200
|
break;
|
|
203
|
-
// No default case needed as chartType is an enum and Zod validates it.
|
|
204
201
|
}
|
|
205
202
|
const compiledVegaSpec = vegaLite.compile(vegaLiteSpec).spec;
|
|
206
203
|
const view = new vega.View(vega.parse(compiledVegaSpec), {
|
|
207
|
-
renderer: "canvas",
|
|
204
|
+
renderer: "canvas",
|
|
208
205
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
await view.runAsync(); // Initialize and run the view
|
|
213
|
-
const canvas = await view.toCanvas(); // Render to canvas
|
|
214
|
-
// Cast to 'any' to access toBuffer, assuming it's a Node Canvas instance at runtime
|
|
215
|
-
const imageBuffer = await canvas.toBuffer("image/png"); // Get PNG buffer from canvas
|
|
206
|
+
await view.runAsync();
|
|
207
|
+
const canvas = await view.toCanvas();
|
|
208
|
+
const imageBuffer = await canvas.toBuffer("image/png");
|
|
216
209
|
const base64Data = imageBuffer.toString("base64");
|
|
210
|
+
logger.notice("Successfully generated chart.", {
|
|
211
|
+
...operationContext,
|
|
212
|
+
chartType: input.chartType,
|
|
213
|
+
outputFormat: input.outputFormat,
|
|
214
|
+
dataPoints: input.dataValues.length,
|
|
215
|
+
});
|
|
217
216
|
return {
|
|
218
217
|
content: [
|
|
219
218
|
{
|
|
220
219
|
type: "image",
|
|
221
220
|
data: base64Data,
|
|
222
|
-
mimeType: "image/png",
|
|
221
|
+
mimeType: "image/png",
|
|
223
222
|
},
|
|
224
223
|
],
|
|
225
224
|
isError: false,
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* @module src/mcp-server/tools/generatePubMedChart/registration
|
|
5
5
|
*/
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
export declare function registerGeneratePubMedChartTool(server: McpServer): void
|
|
7
|
+
export declare function registerGeneratePubMedChartTool(server: McpServer): Promise<void>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { BaseErrorCode
|
|
1
|
+
import { BaseErrorCode } from "../../../types-global/errors.js";
|
|
2
2
|
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
3
3
|
import { GeneratePubMedChartInputSchema, generatePubMedChartLogic, } from "./logic.js";
|
|
4
|
-
export function registerGeneratePubMedChartTool(server) {
|
|
4
|
+
export async function registerGeneratePubMedChartTool(server) {
|
|
5
5
|
const operation = "registerGeneratePubMedChartTool";
|
|
6
6
|
const regContext = requestContextService.createRequestContext({ operation });
|
|
7
|
-
|
|
7
|
+
await ErrorHandler.tryCatch(() => {
|
|
8
8
|
server.tool("generate_pubmed_chart", "Generates a customizable chart (PNG) from structured data. " +
|
|
9
9
|
"Supports 'bar', 'line', and 'scatter' plots. " +
|
|
10
10
|
"Requires data values and field mappings for axes. " +
|
|
@@ -17,20 +17,11 @@ export function registerGeneratePubMedChartTool(server) {
|
|
|
17
17
|
});
|
|
18
18
|
return generatePubMedChartLogic(validatedInput, handlerRequestContext);
|
|
19
19
|
});
|
|
20
|
-
logger.notice(
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
details: "Error during server.tool() call for generate_pubmed_chart.",
|
|
28
|
-
});
|
|
29
|
-
ErrorHandler.handleError(mcpError, {
|
|
30
|
-
operation,
|
|
31
|
-
context: regContext,
|
|
32
|
-
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
33
|
-
critical: true,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
20
|
+
logger.notice("Tool 'generate_pubmed_chart' registered.", regContext);
|
|
21
|
+
}, {
|
|
22
|
+
operation,
|
|
23
|
+
context: regContext,
|
|
24
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
25
|
+
critical: true,
|
|
26
|
+
});
|
|
36
27
|
}
|
|
@@ -199,8 +199,8 @@ function formatAsAPA(article, context) {
|
|
|
199
199
|
}
|
|
200
200
|
const year = journalInfo?.publicationDate?.year || "n.d.";
|
|
201
201
|
const apaTitle = titleText.charAt(0).toUpperCase() + titleText.slice(1); // APA typically sentence case for article titles.
|
|
202
|
-
const journal = journalInfo?.title
|
|
203
|
-
const volume = journalInfo?.volume
|
|
202
|
+
const journal = journalInfo?.title || "N/A";
|
|
203
|
+
const volume = journalInfo?.volume || "";
|
|
204
204
|
const issue = journalInfo?.issue ? `(${journalInfo.issue})` : "";
|
|
205
205
|
const pages = journalInfo?.pages || "";
|
|
206
206
|
const doiLink = doi ? ` https://doi.org/${doi}` : "";
|
|
@@ -253,7 +253,7 @@ function formatAsMLA(article, context) {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
const title = titleText ? `"${titleText}."` : "N/A.";
|
|
256
|
-
const journal = journalInfo?.title
|
|
256
|
+
const journal = journalInfo?.title || "N/A";
|
|
257
257
|
let publicationDateString = journalInfo?.publicationDate?.year || "";
|
|
258
258
|
if (journalInfo?.publicationDate?.month && journalInfo.publicationDate.year) {
|
|
259
259
|
const month = journalInfo.publicationDate.month.substring(0, 3) + "."; // Abbreviate month
|
|
@@ -15,7 +15,7 @@ import { handleELinkRelationships } from "./elinkHandler.js";
|
|
|
15
15
|
*/
|
|
16
16
|
export async function handleGetPubMedArticleConnections(input, context) {
|
|
17
17
|
const toolLogicContext = requestContextService.createRequestContext({
|
|
18
|
-
parentRequestId: context.requestId,
|
|
18
|
+
parentRequestId: context.requestId,
|
|
19
19
|
operation: "handleGetPubMedArticleConnections",
|
|
20
20
|
toolName: "get_pubmed_article_connections",
|
|
21
21
|
input: sanitizeInputForLogging(input),
|
|
@@ -27,8 +27,8 @@ export async function handleGetPubMedArticleConnections(input, context) {
|
|
|
27
27
|
relatedArticles: [],
|
|
28
28
|
citations: {},
|
|
29
29
|
retrievedCount: 0,
|
|
30
|
-
eUtilityUrl: undefined,
|
|
31
|
-
message: undefined,
|
|
30
|
+
eUtilityUrl: undefined,
|
|
31
|
+
message: undefined,
|
|
32
32
|
};
|
|
33
33
|
try {
|
|
34
34
|
switch (input.relationshipType) {
|
|
@@ -41,10 +41,8 @@ export async function handleGetPubMedArticleConnections(input, context) {
|
|
|
41
41
|
await handleCitationFormats(input, outputData, toolLogicContext);
|
|
42
42
|
break;
|
|
43
43
|
default:
|
|
44
|
-
// This case should ideally be caught by Zod schema validation beforehand
|
|
45
44
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported relationshipType: ${input.relationshipType}`, { receivedType: input.relationshipType });
|
|
46
45
|
}
|
|
47
|
-
// Ensure message is set if no specific error but also no results from handlers
|
|
48
46
|
if (outputData.retrievedCount === 0 &&
|
|
49
47
|
!outputData.message &&
|
|
50
48
|
(input.relationshipType !== "citation_formats" ||
|
|
@@ -58,7 +56,6 @@ export async function handleGetPubMedArticleConnections(input, context) {
|
|
|
58
56
|
error instanceof McpError
|
|
59
57
|
? error.message
|
|
60
58
|
: "An unexpected error occurred while processing the request.";
|
|
61
|
-
// Ensure critical details from McpError are passed through if available
|
|
62
59
|
const errorDetails = error instanceof McpError
|
|
63
60
|
? error.details
|
|
64
61
|
: { originalError: error.message };
|
|
@@ -75,6 +72,12 @@ export async function handleGetPubMedArticleConnections(input, context) {
|
|
|
75
72
|
];
|
|
76
73
|
return { content, isError: true };
|
|
77
74
|
}
|
|
75
|
+
logger.notice("Successfully executed get_pubmed_article_connections tool.", {
|
|
76
|
+
...toolLogicContext,
|
|
77
|
+
relationshipType: input.relationshipType,
|
|
78
|
+
retrievedCount: outputData.retrievedCount,
|
|
79
|
+
citationsGenerated: Object.keys(outputData.citations).length,
|
|
80
|
+
});
|
|
78
81
|
const responseContent = [
|
|
79
82
|
{ type: "text", text: JSON.stringify(outputData) },
|
|
80
83
|
];
|
|
@@ -31,6 +31,5 @@ export type GetPubMedArticleConnectionsInput = z.infer<typeof GetPubMedArticleCo
|
|
|
31
31
|
/**
|
|
32
32
|
* Registers the 'get_pubmed_article_connections' tool with the given MCP server instance.
|
|
33
33
|
* @param {McpServer} server - The MCP server instance.
|
|
34
|
-
* @param {RequestContext} registrationContext - The context for this registration operation.
|
|
35
34
|
*/
|
|
36
|
-
export declare function registerGetPubMedArticleConnectionsTool(server: McpServer): void
|
|
35
|
+
export declare function registerGetPubMedArticleConnectionsTool(server: McpServer): Promise<void>;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @module src/mcp-server/tools/getPubMedArticleConnections/registration
|
|
5
5
|
*/
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { BaseErrorCode
|
|
8
|
-
import { ErrorHandler, requestContextService, } from "../../../utils/index.js";
|
|
7
|
+
import { BaseErrorCode } from "../../../types-global/errors.js";
|
|
8
|
+
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
9
9
|
import { handleGetPubMedArticleConnections } from "./logic.js";
|
|
10
10
|
/**
|
|
11
11
|
* Zod schema for the input parameters of the 'get_pubmed_article_connections' tool.
|
|
@@ -23,7 +23,7 @@ export const GetPubMedArticleConnectionsInputSchema = z.object({
|
|
|
23
23
|
"citation_formats",
|
|
24
24
|
])
|
|
25
25
|
.default("pubmed_similar_articles")
|
|
26
|
-
.describe("Specifies the type of connection or action: \n- 'pubmed_similar_articles': Finds articles similar to the source PMID (uses ELink `cmd=neighbor`). \n- 'pubmed_citedin': Finds articles in PubMed that cite the source PMID (uses ELink `linkname=pubmed_pubmed_citedin`). \n- 'pubmed_references': Finds articles in PubMed referenced by the source PMID (uses ELink `linkname=pubmed_pubmed_refs`). \n- 'citation_formats': Retrieves data for the source PMID and formats it into specified citation styles (
|
|
26
|
+
.describe("Specifies the type of connection or action: \n- 'pubmed_similar_articles': Finds articles similar to the source PMID (uses ELink `cmd=neighbor`). \n- 'pubmed_citedin': Finds articles in PubMed that cite the source PMID (uses ELink `linkname=pubmed_pubmed_citedin`). \n- 'pubmed_references': Finds articles in PubMed referenced by the source PMID (uses ELink `linkname=pubmed_pubmed_refs`). \n- 'citation_formats': Retrieves data for the source PMID and formats it into specified citation styles (RIS, BibTeX, APA, MLA via NCBI EFetch and server-side formatting)."),
|
|
27
27
|
maxRelatedResults: z
|
|
28
28
|
.number()
|
|
29
29
|
.int()
|
|
@@ -41,36 +41,26 @@ export const GetPubMedArticleConnectionsInputSchema = z.object({
|
|
|
41
41
|
/**
|
|
42
42
|
* Registers the 'get_pubmed_article_connections' tool with the given MCP server instance.
|
|
43
43
|
* @param {McpServer} server - The MCP server instance.
|
|
44
|
-
* @param {RequestContext} registrationContext - The context for this registration operation.
|
|
45
44
|
*/
|
|
46
|
-
export function registerGetPubMedArticleConnectionsTool(server) {
|
|
45
|
+
export async function registerGetPubMedArticleConnectionsTool(server) {
|
|
47
46
|
const operation = "registerGetPubMedArticleConnectionsTool";
|
|
48
47
|
const registrationContext = requestContextService.createRequestContext({
|
|
49
48
|
operation,
|
|
50
49
|
});
|
|
51
|
-
|
|
52
|
-
server.tool("get_pubmed_article_connections", "Finds articles related to a source PubMed ID (PMID) or retrieves formatted citations for it. Supports finding similar articles, articles that cite the source, articles referenced by the source (via NCBI ELink), or fetching data to generate citations in various styles (RIS, BibTeX, APA, MLA via NCBI EFetch and server-side formatting). Returns a JSON object detailing the connections or formatted citations.", GetPubMedArticleConnectionsInputSchema.shape,
|
|
53
|
-
async (validatedInput, toolContext) => {
|
|
54
|
-
// Create a new rich context for the logic handler
|
|
50
|
+
await ErrorHandler.tryCatch(() => {
|
|
51
|
+
server.tool("get_pubmed_article_connections", "Finds articles related to a source PubMed ID (PMID) or retrieves formatted citations for it. Supports finding similar articles, articles that cite the source, articles referenced by the source (via NCBI ELink), or fetching data to generate citations in various styles (RIS, BibTeX, APA, MLA via NCBI EFetch and server-side formatting). Returns a JSON object detailing the connections or formatted citations.", GetPubMedArticleConnectionsInputSchema.shape, async (validatedInput, toolContext) => {
|
|
55
52
|
const richLogicContext = requestContextService.createRequestContext({
|
|
56
|
-
parentRequestId: registrationContext.requestId,
|
|
53
|
+
parentRequestId: registrationContext.requestId,
|
|
57
54
|
operation: "getPubMedArticleConnectionsToolHandler",
|
|
58
|
-
mcpToolContext: toolContext,
|
|
55
|
+
mcpToolContext: toolContext,
|
|
59
56
|
});
|
|
60
|
-
return
|
|
57
|
+
return handleGetPubMedArticleConnections(validatedInput, richLogicContext);
|
|
61
58
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}), {
|
|
70
|
-
operation,
|
|
71
|
-
context: registrationContext, // Use the context of the registration operation
|
|
72
|
-
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
73
|
-
critical: true, // Registration failure is critical
|
|
74
|
-
});
|
|
75
|
-
}
|
|
59
|
+
logger.notice("Tool 'get_pubmed_article_connections' registered.", registrationContext);
|
|
60
|
+
}, {
|
|
61
|
+
operation,
|
|
62
|
+
context: registrationContext,
|
|
63
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
64
|
+
critical: true,
|
|
65
|
+
});
|
|
76
66
|
}
|
|
@@ -35,9 +35,7 @@ function removeEmptyObjectsRecursively(obj) {
|
|
|
35
35
|
}
|
|
36
36
|
if (Array.isArray(obj)) {
|
|
37
37
|
// If it's an array, recurse on each element and filter out empty objects/arrays
|
|
38
|
-
const newArr = obj
|
|
39
|
-
.map(removeEmptyObjectsRecursively)
|
|
40
|
-
.filter((item) => {
|
|
38
|
+
const newArr = obj.map(removeEmptyObjectsRecursively).filter((item) => {
|
|
41
39
|
if (item === null || item === undefined)
|
|
42
40
|
return false;
|
|
43
41
|
if (Array.isArray(item) && item.length === 0)
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
import { RequestContext } from "../../../utils/index.js";
|
|
9
|
-
import {
|
|
10
|
-
PubMedResearchAgentInput, PubMedResearchAgentInputSchema } from "./logic/index.js";
|
|
9
|
+
import { PubMedResearchAgentInput, PubMedResearchAgentInputSchema } from "./logic/index.js";
|
|
11
10
|
export { PubMedResearchAgentInput, PubMedResearchAgentInputSchema };
|
|
12
11
|
export declare function pubmedResearchAgentLogic(input: PubMedResearchAgentInput, parentRequestContext: RequestContext): Promise<CallToolResult>;
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
8
8
|
import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
|
|
9
|
-
import { generateFullResearchPlanOutline, PubMedResearchAgentInputSchema,
|
|
10
|
-
} from "./logic/index.js";
|
|
11
|
-
// Re-export schema and input type for easy access by registration.ts
|
|
9
|
+
import { generateFullResearchPlanOutline, PubMedResearchAgentInputSchema, } from "./logic/index.js";
|
|
12
10
|
export { PubMedResearchAgentInputSchema };
|
|
13
11
|
export async function pubmedResearchAgentLogic(input, parentRequestContext) {
|
|
14
12
|
const operationContext = requestContextService.createRequestContext({
|
|
@@ -19,6 +17,10 @@ export async function pubmedResearchAgentLogic(input, parentRequestContext) {
|
|
|
19
17
|
logger.info(`Executing 'pubmed_research_agent' to generate research plan outline. Keywords: ${input.research_keywords.join(", ")}`, operationContext);
|
|
20
18
|
try {
|
|
21
19
|
const researchPlanOutline = generateFullResearchPlanOutline(input, operationContext);
|
|
20
|
+
logger.notice("Successfully generated research plan outline.", {
|
|
21
|
+
...operationContext,
|
|
22
|
+
projectTitle: input.project_title_suggestion,
|
|
23
|
+
});
|
|
22
24
|
return {
|
|
23
25
|
content: [
|
|
24
26
|
{ type: "text", text: JSON.stringify(researchPlanOutline, null, 2) },
|
|
@@ -30,11 +32,10 @@ export async function pubmedResearchAgentLogic(input, parentRequestContext) {
|
|
|
30
32
|
logger.error("Execution failed for 'pubmed_research_agent'", error, operationContext);
|
|
31
33
|
const mcpError = error instanceof McpError
|
|
32
34
|
? error
|
|
33
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR,
|
|
34
|
-
`'pubmed_research_agent' tool failed during plan outline generation: ${error.message || "Internal server error."}`, {
|
|
35
|
+
: new McpError(BaseErrorCode.INTERNAL_ERROR, `'pubmed_research_agent' tool failed during plan outline generation: ${error.message || "Internal server error."}`, {
|
|
35
36
|
originalErrorName: error.name,
|
|
36
37
|
requestId: operationContext.requestId,
|
|
37
|
-
inputKeywords: input.research_keywords,
|
|
38
|
+
inputKeywords: input.research_keywords,
|
|
38
39
|
});
|
|
39
40
|
return {
|
|
40
41
|
content: [
|
|
@@ -7,4 +7,4 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
7
7
|
* Registers the pubmed_research_agent tool with the MCP server.
|
|
8
8
|
* @param server - The McpServer instance.
|
|
9
9
|
*/
|
|
10
|
-
export declare function registerPubMedResearchAgentTool(server: McpServer): void
|
|
10
|
+
export declare function registerPubMedResearchAgentTool(server: McpServer): Promise<void>;
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
* @fileoverview Registration for the pubmed_research_agent tool.
|
|
3
3
|
* @module pubmedResearchAgent/registration
|
|
4
4
|
*/
|
|
5
|
-
import { BaseErrorCode
|
|
5
|
+
import { BaseErrorCode } from "../../../types-global/errors.js";
|
|
6
6
|
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
7
7
|
import { PubMedResearchAgentInputSchema, pubmedResearchAgentLogic, } from "./logic.js";
|
|
8
8
|
/**
|
|
9
9
|
* Registers the pubmed_research_agent tool with the MCP server.
|
|
10
10
|
* @param server - The McpServer instance.
|
|
11
11
|
*/
|
|
12
|
-
export function registerPubMedResearchAgentTool(server) {
|
|
12
|
+
export async function registerPubMedResearchAgentTool(server) {
|
|
13
13
|
const operation = "registerPubMedResearchAgentTool";
|
|
14
14
|
const context = requestContextService.createRequestContext({ operation });
|
|
15
|
-
|
|
15
|
+
await ErrorHandler.tryCatch(() => {
|
|
16
16
|
server.tool("pubmed_research_agent", "Generates a standardized JSON research plan outline from component details you provide. It accepts granular inputs for all research phases (conception, data collection, analysis, dissemination, cross-cutting concerns). If `include_detailed_prompts_for_agent` is true, the output plan will embed instructive prompts and detailed guidance notes to aid the research agent. The tool's primary function is to organize and structure your rough ideas into a formal, machine-readable plan. This plan is intended for further processing; as the research agent, you should then utilize your full suite of tools (e.g., file manipulation, `get_pubmed_article_connections` for literature/data search via PMID) to execute the outlined research, tailored to the user's request.", PubMedResearchAgentInputSchema.shape, async (validatedInput, mcpProvidedContext) => {
|
|
17
17
|
const handlerRequestContext = requestContextService.createRequestContext({
|
|
18
18
|
parentRequestId: context.requestId,
|
|
@@ -22,15 +22,10 @@ export function registerPubMedResearchAgentTool(server) {
|
|
|
22
22
|
return pubmedResearchAgentLogic(validatedInput, handlerRequestContext);
|
|
23
23
|
});
|
|
24
24
|
logger.notice(`Tool 'pubmed_research_agent' registered.`, context);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
context,
|
|
32
|
-
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
33
|
-
critical: true,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
25
|
+
}, {
|
|
26
|
+
operation,
|
|
27
|
+
context,
|
|
28
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
29
|
+
critical: true,
|
|
30
|
+
});
|
|
36
31
|
}
|
|
@@ -140,10 +140,9 @@ export async function searchPubMedArticlesLogic(input, parentRequestContext) {
|
|
|
140
140
|
if (esResult.WebEnv && esResult.QueryKey) {
|
|
141
141
|
eSummaryParams.WebEnv = esResult.WebEnv;
|
|
142
142
|
eSummaryParams.query_key = esResult.QueryKey;
|
|
143
|
-
eSummaryParams.retmax = currentFetchBriefSummaries;
|
|
143
|
+
eSummaryParams.retmax = currentFetchBriefSummaries;
|
|
144
144
|
}
|
|
145
145
|
else {
|
|
146
|
-
// Fallback to using explicit IDs if history is not available (should not happen if usehistory='y' was successful)
|
|
147
146
|
const pmidsForSummary = pmids
|
|
148
147
|
.slice(0, currentFetchBriefSummaries)
|
|
149
148
|
.join(",");
|
|
@@ -185,13 +184,19 @@ export async function searchPubMedArticlesLogic(input, parentRequestContext) {
|
|
|
185
184
|
effectiveESearchTerm: effectiveQuery,
|
|
186
185
|
totalFound,
|
|
187
186
|
retrievedPmidCount,
|
|
188
|
-
pmids,
|
|
189
|
-
briefSummaries,
|
|
187
|
+
pmids,
|
|
188
|
+
briefSummaries,
|
|
190
189
|
eSearchUrl,
|
|
191
190
|
eSummaryUrl: currentFetchBriefSummaries > 0 && pmids.length > 0
|
|
192
191
|
? eSummaryUrl
|
|
193
192
|
: undefined,
|
|
194
193
|
};
|
|
194
|
+
logger.notice("Successfully executed searchPubMedArticles tool.", {
|
|
195
|
+
...toolLogicContext,
|
|
196
|
+
totalFound,
|
|
197
|
+
retrievedPmidCount,
|
|
198
|
+
summariesFetched: briefSummaries.length,
|
|
199
|
+
});
|
|
195
200
|
return {
|
|
196
201
|
content: [{ type: "text", text: JSON.stringify(resultPayload) }],
|
|
197
202
|
isError: false,
|
|
@@ -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,30 @@
|
|
|
2
2
|
* @fileoverview Registration for the searchPubMedArticles MCP tool.
|
|
3
3
|
* @module src/mcp-server/tools/searchPubMedArticles/registration
|
|
4
4
|
*/
|
|
5
|
-
import { BaseErrorCode
|
|
6
|
-
import { ErrorHandler, requestContextService } from "../../../utils/index.js";
|
|
5
|
+
import { BaseErrorCode } 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
14
|
const context = requestContextService.createRequestContext({ operation });
|
|
15
|
-
|
|
16
|
-
server.tool("search_pubmed_articles", "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.", SearchPubMedArticlesInputSchema.shape,
|
|
17
|
-
async (input, toolContext) => {
|
|
18
|
-
// Explicitly type input
|
|
15
|
+
await ErrorHandler.tryCatch(() => {
|
|
16
|
+
server.tool("search_pubmed_articles", "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.", SearchPubMedArticlesInputSchema.shape, async (input, toolContext) => {
|
|
19
17
|
const richContext = requestContextService.createRequestContext({
|
|
20
18
|
parentRequestId: context.requestId,
|
|
21
19
|
operation: "searchPubMedArticlesToolHandler",
|
|
22
|
-
mcpToolContext: toolContext,
|
|
20
|
+
mcpToolContext: toolContext,
|
|
23
21
|
});
|
|
24
22
|
return searchPubMedArticlesLogic(input, richContext);
|
|
25
23
|
});
|
|
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
|
-
}
|
|
24
|
+
logger.notice("Tool 'search_pubmed_articles' registered.", context);
|
|
25
|
+
}, {
|
|
26
|
+
operation,
|
|
27
|
+
context,
|
|
28
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
29
|
+
critical: true,
|
|
30
|
+
});
|
|
38
31
|
}
|
|
@@ -54,6 +54,11 @@ const MAX_PORT_RETRIES = 15;
|
|
|
54
54
|
* Stores active `StreamableHTTPServerTransport` instances from the SDK, keyed by their session ID.
|
|
55
55
|
* This is essential for routing subsequent HTTP requests (GET, DELETE, non-initialize POST)
|
|
56
56
|
* to the correct stateful session transport instance.
|
|
57
|
+
*
|
|
58
|
+
* @scalability Note: This is an in-memory store. For horizontal scaling across
|
|
59
|
+
* multiple processes or machines, this state would need to be moved to a shared,
|
|
60
|
+
* distributed store like Redis or a database.
|
|
61
|
+
*
|
|
57
62
|
* @type {Record<string, StreamableHTTPServerTransport>}
|
|
58
63
|
* @private
|
|
59
64
|
*/
|
|
@@ -61,6 +66,10 @@ const httpTransports = {};
|
|
|
61
66
|
/**
|
|
62
67
|
* Stores the last activity timestamp for each session, keyed by session ID.
|
|
63
68
|
* Used for garbage collecting stale/abandoned sessions.
|
|
69
|
+
*
|
|
70
|
+
* @scalability Note: This is an in-memory store. Like `httpTransports`, this
|
|
71
|
+
* would need to be moved to a distributed store for multi-instance deployments.
|
|
72
|
+
*
|
|
64
73
|
* @type {Record<string, number>}
|
|
65
74
|
* @private
|
|
66
75
|
*/
|