@cyanheads/pubmed-mcp-server 1.1.4 → 1.2.2
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 +10 -12
- 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 +94 -202
- package/dist/mcp-server/tools/fetchPubMedContent/registration.js +43 -7
- package/dist/mcp-server/tools/generatePubMedChart/logic.d.ts +9 -28
- package/dist/mcp-server/tools/generatePubMedChart/logic.js +133 -192
- package/dist/mcp-server/tools/generatePubMedChart/registration.js +57 -13
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +1 -1
- 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 +50 -45
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +1 -1
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +0 -24
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +51 -45
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +2 -4
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +7 -41
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +49 -8
- package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +12 -10
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +68 -126
- package/dist/mcp-server/tools/searchPubMedArticles/registration.js +46 -6
- package/dist/mcp-server/transports/{authentication → auth/core}/authContext.d.ts +2 -2
- package/dist/mcp-server/transports/{authentication → auth/core}/authContext.js +1 -1
- package/dist/mcp-server/transports/{authentication/types.d.ts → auth/core/authTypes.d.ts} +1 -1
- package/dist/mcp-server/transports/{authentication/types.js → auth/core/authTypes.js} +1 -1
- package/dist/mcp-server/transports/{authentication → auth/core}/authUtils.d.ts +1 -1
- package/dist/mcp-server/transports/{authentication → auth/core}/authUtils.js +3 -3
- package/dist/mcp-server/transports/auth/index.d.ts +10 -0
- package/dist/mcp-server/transports/auth/index.js +9 -0
- package/dist/mcp-server/transports/{authentication/authMiddleware.d.ts → auth/strategies/jwt/jwtMiddleware.d.ts} +4 -12
- package/dist/mcp-server/transports/{authentication/authMiddleware.js → auth/strategies/jwt/jwtMiddleware.js} +36 -43
- package/dist/mcp-server/transports/{authentication → auth/strategies/oauth}/oauthMiddleware.d.ts +2 -6
- package/dist/mcp-server/transports/{authentication → auth/strategies/oauth}/oauthMiddleware.js +33 -18
- package/dist/mcp-server/transports/httpErrorHandler.d.ts +26 -0
- package/dist/mcp-server/transports/httpErrorHandler.js +73 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +11 -14
- package/dist/mcp-server/transports/httpTransport.js +91 -389
- package/package.json +13 -21
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Registration for the fetch_pubmed_content MCP tool.
|
|
3
3
|
* @module src/mcp-server/tools/fetchPubMedContent/registration
|
|
4
4
|
*/
|
|
5
|
-
import { BaseErrorCode } from "../../../types-global/errors.js";
|
|
5
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
6
6
|
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
7
7
|
import { FetchPubMedContentInputSchema, fetchPubMedContentLogic, } from "./logic.js";
|
|
8
8
|
/**
|
|
@@ -11,19 +11,55 @@ import { FetchPubMedContentInputSchema, fetchPubMedContentLogic, } from "./logic
|
|
|
11
11
|
*/
|
|
12
12
|
export async function registerFetchPubMedContentTool(server) {
|
|
13
13
|
const operation = "registerFetchPubMedContentTool";
|
|
14
|
+
const toolName = "fetch_pubmed_content";
|
|
15
|
+
const toolDescription = "Fetches detailed information from PubMed using NCBI EFetch. Can be used with a direct list of PMIDs or with queryKey/webEnv from an ESearch history entry. Supports pagination (retstart, retmax) when using history. Available 'detailLevel' options: 'abstract_plus' (parsed title, abstract, authors, journal, keywords, DOI, optional MeSH/grant info), 'full_xml' (JSON representation of the PubMedArticle XML structure), 'medline_text' (MEDLINE format), or 'citation_data' (minimal data for citations). Returns a JSON object containing results, any PMIDs not found (if applicable), and EFetch details.";
|
|
14
16
|
const context = requestContextService.createRequestContext({ operation });
|
|
15
|
-
await ErrorHandler.tryCatch(() => {
|
|
16
|
-
server.tool(
|
|
17
|
-
async (input, toolContext) => {
|
|
18
|
-
// Added 'any' type for toolContext
|
|
17
|
+
await ErrorHandler.tryCatch(async () => {
|
|
18
|
+
server.tool(toolName, toolDescription, FetchPubMedContentInputSchema._def.schema.shape, async (input, toolContext) => {
|
|
19
19
|
const richContext = requestContextService.createRequestContext({
|
|
20
20
|
parentRequestId: context.requestId,
|
|
21
21
|
operation: "fetchPubMedContentToolHandler",
|
|
22
22
|
mcpToolContext: toolContext,
|
|
23
|
+
input,
|
|
23
24
|
});
|
|
24
|
-
|
|
25
|
+
try {
|
|
26
|
+
const result = await fetchPubMedContentLogic(input, richContext);
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: result.content }],
|
|
29
|
+
isError: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const handledError = ErrorHandler.handleError(error, {
|
|
34
|
+
operation: "fetchPubMedContentToolHandler",
|
|
35
|
+
context: richContext,
|
|
36
|
+
input,
|
|
37
|
+
rethrow: false,
|
|
38
|
+
});
|
|
39
|
+
const mcpError = handledError instanceof McpError
|
|
40
|
+
? handledError
|
|
41
|
+
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred while fetching PubMed content.", {
|
|
42
|
+
originalErrorName: handledError.name,
|
|
43
|
+
originalErrorMessage: handledError.message,
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify({
|
|
50
|
+
error: {
|
|
51
|
+
code: mcpError.code,
|
|
52
|
+
message: mcpError.message,
|
|
53
|
+
details: mcpError.details,
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
25
61
|
});
|
|
26
|
-
logger.notice(
|
|
62
|
+
logger.notice(`Tool '${toolName}' registered.`, context);
|
|
27
63
|
}, {
|
|
28
64
|
operation,
|
|
29
65
|
context,
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Core logic for the generate_pubmed_chart tool.
|
|
3
|
-
* Generates charts from parameterized input by creating Vega-Lite specifications.
|
|
4
|
-
* @module src/mcp-server/tools/generatePubMedChart/logic
|
|
5
|
-
*/
|
|
6
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
7
1
|
import { z } from "zod";
|
|
8
2
|
import { RequestContext } from "../../../utils/index.js";
|
|
9
3
|
export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
|
|
10
|
-
chartType: z.ZodEnum<["bar", "line", "scatter"]>;
|
|
4
|
+
chartType: z.ZodEnum<["bar", "line", "scatter", "pie", "doughnut", "bubble", "radar", "polarArea"]>;
|
|
11
5
|
title: z.ZodOptional<z.ZodString>;
|
|
12
6
|
width: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
13
7
|
height: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
@@ -15,33 +9,21 @@ export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
|
|
|
15
9
|
outputFormat: z.ZodDefault<z.ZodEnum<["png"]>>;
|
|
16
10
|
xField: z.ZodString;
|
|
17
11
|
yField: z.ZodString;
|
|
18
|
-
xFieldType: z.ZodOptional<z.ZodEnum<["nominal", "ordinal", "quantitative", "temporal"]>>;
|
|
19
|
-
yFieldType: z.ZodOptional<z.ZodEnum<["nominal", "ordinal", "quantitative", "temporal"]>>;
|
|
20
|
-
colorField: z.ZodOptional<z.ZodString>;
|
|
21
|
-
colorFieldType: z.ZodOptional<z.ZodEnum<["nominal", "ordinal", "quantitative", "temporal"]>>;
|
|
22
12
|
seriesField: z.ZodOptional<z.ZodString>;
|
|
23
|
-
seriesFieldType: z.ZodOptional<z.ZodEnum<["nominal", "ordinal", "quantitative", "temporal"]>>;
|
|
24
13
|
sizeField: z.ZodOptional<z.ZodString>;
|
|
25
|
-
sizeFieldType: z.ZodOptional<z.ZodEnum<["quantitative", "ordinal"]>>;
|
|
26
14
|
}, "strip", z.ZodTypeAny, {
|
|
27
15
|
width: number;
|
|
28
16
|
height: number;
|
|
29
17
|
outputFormat: "png";
|
|
30
|
-
chartType: "bar" | "line" | "scatter";
|
|
18
|
+
chartType: "bar" | "line" | "scatter" | "pie" | "doughnut" | "bubble" | "radar" | "polarArea";
|
|
31
19
|
dataValues: Record<string, any>[];
|
|
32
20
|
xField: string;
|
|
33
21
|
yField: string;
|
|
34
22
|
title?: string | undefined;
|
|
35
|
-
xFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
36
|
-
yFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
37
|
-
colorField?: string | undefined;
|
|
38
|
-
colorFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
39
23
|
seriesField?: string | undefined;
|
|
40
|
-
seriesFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
41
24
|
sizeField?: string | undefined;
|
|
42
|
-
sizeFieldType?: "ordinal" | "quantitative" | undefined;
|
|
43
25
|
}, {
|
|
44
|
-
chartType: "bar" | "line" | "scatter";
|
|
26
|
+
chartType: "bar" | "line" | "scatter" | "pie" | "doughnut" | "bubble" | "radar" | "polarArea";
|
|
45
27
|
dataValues: Record<string, any>[];
|
|
46
28
|
xField: string;
|
|
47
29
|
yField: string;
|
|
@@ -49,14 +31,13 @@ export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
|
|
|
49
31
|
width?: number | undefined;
|
|
50
32
|
height?: number | undefined;
|
|
51
33
|
outputFormat?: "png" | undefined;
|
|
52
|
-
xFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
53
|
-
yFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
54
|
-
colorField?: string | undefined;
|
|
55
|
-
colorFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
56
34
|
seriesField?: string | undefined;
|
|
57
|
-
seriesFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
|
|
58
35
|
sizeField?: string | undefined;
|
|
59
|
-
sizeFieldType?: "ordinal" | "quantitative" | undefined;
|
|
60
36
|
}>;
|
|
61
37
|
export type GeneratePubMedChartInput = z.infer<typeof GeneratePubMedChartInputSchema>;
|
|
62
|
-
export
|
|
38
|
+
export type GeneratePubMedChartOutput = {
|
|
39
|
+
base64Data: string;
|
|
40
|
+
chartType: string;
|
|
41
|
+
dataPoints: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function generatePubMedChartLogic(input: GeneratePubMedChartInput, parentRequestContext: RequestContext): Promise<GeneratePubMedChartOutput>;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core logic for the generate_pubmed_chart tool.
|
|
3
|
+
* Generates charts from parameterized input by creating Chart.js configurations
|
|
4
|
+
* and rendering them on the server using chartjs-node-canvas.
|
|
5
|
+
* @module src/mcp-server/tools/generatePubMedChart/logic
|
|
6
|
+
*/
|
|
7
|
+
import { ChartJSNodeCanvas } from "chartjs-node-canvas";
|
|
3
8
|
import { z } from "zod";
|
|
4
9
|
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
|
|
10
10
|
import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
|
|
11
11
|
export const GeneratePubMedChartInputSchema = z.object({
|
|
12
12
|
chartType: z
|
|
13
|
-
.enum([
|
|
14
|
-
|
|
13
|
+
.enum([
|
|
14
|
+
"bar",
|
|
15
|
+
"line",
|
|
16
|
+
"scatter",
|
|
17
|
+
"pie",
|
|
18
|
+
"doughnut",
|
|
19
|
+
"bubble",
|
|
20
|
+
"radar",
|
|
21
|
+
"polarArea",
|
|
22
|
+
])
|
|
23
|
+
.describe("Required. Specifies the type of chart to generate. Options: 'bar', 'line', 'scatter', 'pie', 'doughnut', 'bubble', 'radar', 'polarArea'."),
|
|
15
24
|
title: z
|
|
16
25
|
.string()
|
|
17
26
|
.optional()
|
|
@@ -21,22 +30,22 @@ export const GeneratePubMedChartInputSchema = z.object({
|
|
|
21
30
|
.int()
|
|
22
31
|
.positive()
|
|
23
32
|
.optional()
|
|
24
|
-
.default(
|
|
25
|
-
.describe("Optional. The width of the chart canvas in pixels. Must be a positive integer. Default:
|
|
33
|
+
.default(800)
|
|
34
|
+
.describe("Optional. The width of the chart canvas in pixels. Must be a positive integer. Default: 800."),
|
|
26
35
|
height: z
|
|
27
36
|
.number()
|
|
28
37
|
.int()
|
|
29
38
|
.positive()
|
|
30
39
|
.optional()
|
|
31
|
-
.default(
|
|
32
|
-
.describe("Optional. The height of the chart canvas in pixels. Must be a positive integer. Default:
|
|
40
|
+
.default(600)
|
|
41
|
+
.describe("Optional. The height of the chart canvas in pixels. Must be a positive integer. Default: 600."),
|
|
33
42
|
dataValues: z
|
|
34
43
|
.array(z.record(z.string(), z.any()))
|
|
35
44
|
.min(1)
|
|
36
45
|
.describe("Required. An array of data objects used to plot the chart. Each object represents a data point or bar, structured as key-value pairs (e.g., [{ 'year': '2020', 'articles': 150 }, { 'year': '2021', 'articles': 180 }]). Must contain at least one data object."),
|
|
37
46
|
outputFormat: z
|
|
38
|
-
.enum(["png"])
|
|
39
|
-
.default("png")
|
|
47
|
+
.enum(["png"])
|
|
48
|
+
.default("png")
|
|
40
49
|
.describe("Specifies the output format for the chart. Currently, only 'png' (Portable Network Graphics) is supported and is the default."),
|
|
41
50
|
xField: z
|
|
42
51
|
.string()
|
|
@@ -44,209 +53,141 @@ export const GeneratePubMedChartInputSchema = z.object({
|
|
|
44
53
|
yField: z
|
|
45
54
|
.string()
|
|
46
55
|
.describe("Required. The name of the field in `dataValues` to be used for the Y-axis (vertical). This field determines the values plotted upwards on the chart (e.g., 'articles', 'expressionLevel', 'citationCount')."),
|
|
47
|
-
xFieldType: z
|
|
48
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
49
|
-
.optional()
|
|
50
|
-
.describe("Optional. Specifies the data type of the X-axis field. Options: 'nominal' (categories), 'ordinal' (ordered categories), 'quantitative' (numerical), 'temporal' (dates/times). If omitted, a suitable default is chosen based on `chartType` (e.g., 'nominal' for bar charts, 'temporal' for line charts, 'quantitative' for scatter plots)."),
|
|
51
|
-
yFieldType: z
|
|
52
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
53
|
-
.optional()
|
|
54
|
-
.describe("Optional. Specifies the data type of the Y-axis field. Options: 'nominal', 'ordinal', 'quantitative', 'temporal'. Defaults to 'quantitative' if omitted."),
|
|
55
|
-
// Optional fields for various chart types
|
|
56
|
-
colorField: z
|
|
57
|
-
.string()
|
|
58
|
-
.optional()
|
|
59
|
-
.describe("Optional. The name of the field in `dataValues` to use for color encoding. This can differentiate bars, lines, or points by color based on the values in this field (e.g., 'studyType', 'country')."),
|
|
60
|
-
colorFieldType: z
|
|
61
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
62
|
-
.optional()
|
|
63
|
-
.describe("Optional. Specifies the data type of the `colorField`. Options: 'nominal', 'ordinal', 'quantitative', 'temporal'. Defaults to 'nominal' if `colorField` is provided and this is omitted."),
|
|
64
56
|
seriesField: z
|
|
65
57
|
.string()
|
|
66
58
|
.optional()
|
|
67
|
-
.describe("Optional.
|
|
68
|
-
seriesFieldType: z
|
|
69
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
70
|
-
.optional()
|
|
71
|
-
.describe("Optional. Specifies the data type of the `seriesField`. Options: 'nominal', 'ordinal', 'quantitative', 'temporal'. Defaults to 'nominal' if `seriesField` is provided and this is omitted."),
|
|
72
|
-
// Scatter plot specific optional fields (can be expanded)
|
|
59
|
+
.describe("Optional. The name of the field in `dataValues` used to create multiple distinct lines or bar groups (series) on the same chart. Each unique value in this field will correspond to a separate dataset."),
|
|
73
60
|
sizeField: z
|
|
74
61
|
.string()
|
|
75
62
|
.optional()
|
|
76
|
-
.describe("Optional. For
|
|
77
|
-
sizeFieldType: z
|
|
78
|
-
.enum(["quantitative", "ordinal"])
|
|
79
|
-
.optional()
|
|
80
|
-
.describe("Optional. Specifies the data type of the `sizeField`. Options: 'quantitative', 'ordinal'. Defaults to 'quantitative' if `sizeField` is provided and this is omitted."),
|
|
63
|
+
.describe("Optional. For bubble charts. The name of the field in `dataValues` to use for encoding the size of the bubbles. Larger values in this field will result in larger bubbles (e.g., 'sampleSize', 'effectMagnitude')."),
|
|
81
64
|
});
|
|
65
|
+
// Helper to group data by a series field
|
|
66
|
+
function groupDataBySeries(data, xField, yField, seriesField) {
|
|
67
|
+
const series = new Map();
|
|
68
|
+
for (const item of data) {
|
|
69
|
+
const seriesName = item[seriesField];
|
|
70
|
+
if (!series.has(seriesName)) {
|
|
71
|
+
series.set(seriesName, []);
|
|
72
|
+
}
|
|
73
|
+
series.get(seriesName).push({ x: item[xField], y: item[yField] });
|
|
74
|
+
}
|
|
75
|
+
return series;
|
|
76
|
+
}
|
|
82
77
|
export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
83
78
|
const operationContext = requestContextService.createRequestContext({
|
|
84
79
|
parentRequestId: parentRequestContext.requestId,
|
|
85
80
|
operation: "generatePubMedChartLogicExecution",
|
|
86
81
|
input: sanitizeInputForLogging(input),
|
|
87
82
|
});
|
|
88
|
-
logger.info(`Executing 'generate_pubmed_chart'. Chart type: ${input.chartType}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
logger.info(`Executing 'generate_pubmed_chart' with Chart.js. Chart type: ${input.chartType}`, operationContext);
|
|
84
|
+
const { width, height, chartType, dataValues, xField, yField, title, seriesField, sizeField, } = input;
|
|
85
|
+
const chartJSNodeCanvas = new ChartJSNodeCanvas({
|
|
86
|
+
width,
|
|
87
|
+
height,
|
|
88
|
+
chartCallback: (ChartJS) => {
|
|
89
|
+
ChartJS.defaults.responsive = false;
|
|
90
|
+
ChartJS.defaults.maintainAspectRatio = false;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const labels = [...new Set(dataValues.map((item) => item[xField]))];
|
|
94
|
+
let datasets;
|
|
95
|
+
if (seriesField) {
|
|
96
|
+
const groupedData = groupDataBySeries(dataValues, xField, yField, seriesField);
|
|
97
|
+
datasets = Array.from(groupedData.entries()).map(([seriesName, data]) => ({
|
|
98
|
+
label: seriesName,
|
|
99
|
+
data: labels.map(label => {
|
|
100
|
+
const point = data.find(p => p.x === label);
|
|
101
|
+
return point ? point.y : null;
|
|
102
|
+
}),
|
|
103
|
+
// You can add backgroundColor, borderColor etc. here for styling
|
|
104
|
+
}));
|
|
107
105
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
if (input.seriesField) {
|
|
161
|
-
vegaLiteSpec.encoding.color = {
|
|
162
|
-
field: input.seriesField,
|
|
163
|
-
type: seriesEncType,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
else if (input.colorField) {
|
|
167
|
-
vegaLiteSpec.encoding.color = {
|
|
168
|
-
field: input.colorField,
|
|
169
|
-
type: colorEncType,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
break;
|
|
173
|
-
case "scatter":
|
|
174
|
-
xEncType = input.xFieldType || "quantitative";
|
|
175
|
-
vegaLiteSpec.mark = "point";
|
|
176
|
-
vegaLiteSpec.encoding = {
|
|
106
|
+
else {
|
|
107
|
+
datasets = [
|
|
108
|
+
{
|
|
109
|
+
label: yField,
|
|
110
|
+
data: labels.map(label => {
|
|
111
|
+
const item = dataValues.find(d => d[xField] === label);
|
|
112
|
+
return item ? item[yField] : null;
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
// For scatter and bubble charts, the data format is different
|
|
118
|
+
if (chartType === 'scatter' || chartType === 'bubble') {
|
|
119
|
+
if (seriesField) {
|
|
120
|
+
const groupedData = groupDataBySeries(dataValues, xField, yField, seriesField);
|
|
121
|
+
datasets = Array.from(groupedData.entries()).map(([seriesName, data]) => ({
|
|
122
|
+
label: seriesName,
|
|
123
|
+
data: data.map(point => ({
|
|
124
|
+
x: point.x,
|
|
125
|
+
y: point.y,
|
|
126
|
+
r: chartType === 'bubble' && sizeField ? dataValues.find(d => d[xField] === point.x)[sizeField] : undefined
|
|
127
|
+
})),
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
datasets = [{
|
|
132
|
+
label: yField,
|
|
133
|
+
data: dataValues.map(item => ({
|
|
134
|
+
x: item[xField],
|
|
135
|
+
y: item[yField],
|
|
136
|
+
r: chartType === 'bubble' && sizeField ? item[sizeField] : undefined
|
|
137
|
+
})),
|
|
138
|
+
}];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const configuration = {
|
|
142
|
+
type: chartType,
|
|
143
|
+
data: {
|
|
144
|
+
labels: (chartType !== 'scatter' && chartType !== 'bubble') ? labels : undefined,
|
|
145
|
+
datasets: datasets,
|
|
146
|
+
},
|
|
147
|
+
options: {
|
|
148
|
+
plugins: {
|
|
149
|
+
title: {
|
|
150
|
+
display: !!title,
|
|
151
|
+
text: title,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
scales: chartType === "pie" || chartType === "doughnut" || chartType === "polarArea"
|
|
155
|
+
? undefined
|
|
156
|
+
: {
|
|
177
157
|
x: {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
158
|
+
title: {
|
|
159
|
+
display: true,
|
|
160
|
+
text: xField,
|
|
161
|
+
},
|
|
181
162
|
},
|
|
182
163
|
y: {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
164
|
+
title: {
|
|
165
|
+
display: true,
|
|
166
|
+
text: yField,
|
|
167
|
+
},
|
|
186
168
|
},
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
if (input.sizeField) {
|
|
195
|
-
vegaLiteSpec.encoding.size = {
|
|
196
|
-
field: input.sizeField,
|
|
197
|
-
type: sizeEncType,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
const compiledVegaSpec = vegaLite.compile(vegaLiteSpec).spec;
|
|
203
|
-
const view = new vega.View(vega.parse(compiledVegaSpec), {
|
|
204
|
-
renderer: "canvas",
|
|
205
|
-
});
|
|
206
|
-
await view.runAsync();
|
|
207
|
-
const canvas = await view.toCanvas();
|
|
208
|
-
const imageBuffer = await canvas.toBuffer("image/png");
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration);
|
|
209
174
|
const base64Data = imageBuffer.toString("base64");
|
|
210
|
-
logger.notice("Successfully generated chart.", {
|
|
175
|
+
logger.notice("Successfully generated chart with Chart.js.", {
|
|
211
176
|
...operationContext,
|
|
212
177
|
chartType: input.chartType,
|
|
213
|
-
outputFormat: input.outputFormat,
|
|
214
178
|
dataPoints: input.dataValues.length,
|
|
215
179
|
});
|
|
216
180
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
data: base64Data,
|
|
221
|
-
mimeType: "image/png",
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
|
-
isError: false,
|
|
181
|
+
base64Data,
|
|
182
|
+
chartType: input.chartType,
|
|
183
|
+
dataPoints: input.dataValues.length,
|
|
225
184
|
};
|
|
226
185
|
}
|
|
227
186
|
catch (error) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
:
|
|
232
|
-
|
|
233
|
-
originalErrorMessage: error.message,
|
|
234
|
-
requestId: operationContext.requestId,
|
|
235
|
-
});
|
|
236
|
-
return {
|
|
237
|
-
content: [
|
|
238
|
-
{
|
|
239
|
-
type: "text",
|
|
240
|
-
text: JSON.stringify({
|
|
241
|
-
error: {
|
|
242
|
-
code: mcpError.code,
|
|
243
|
-
message: mcpError.message,
|
|
244
|
-
details: mcpError.details,
|
|
245
|
-
},
|
|
246
|
-
}),
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
isError: true,
|
|
250
|
-
};
|
|
187
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Chart generation failed: ${error.message || "Internal server error during chart generation."}`, {
|
|
188
|
+
...operationContext,
|
|
189
|
+
originalErrorName: error.name,
|
|
190
|
+
originalErrorMessage: error.message,
|
|
191
|
+
});
|
|
251
192
|
}
|
|
252
193
|
}
|
|
@@ -1,26 +1,70 @@
|
|
|
1
|
-
import { BaseErrorCode } from "../../../types-global/errors.js";
|
|
1
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
2
2
|
import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
|
|
3
3
|
import { GeneratePubMedChartInputSchema, generatePubMedChartLogic, } from "./logic.js";
|
|
4
4
|
export async function registerGeneratePubMedChartTool(server) {
|
|
5
5
|
const operation = "registerGeneratePubMedChartTool";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
const toolName = "generate_pubmed_chart";
|
|
7
|
+
const toolDescription = "Generates a customizable chart (PNG) from structured data. " +
|
|
8
|
+
"Supports 'bar', 'line', 'scatter', 'pie', 'doughnut', 'bubble', 'radar', and 'polarArea' plots. " +
|
|
9
|
+
"Requires data values and field mappings for axes. " +
|
|
10
|
+
"Optional parameters allow for titles and dimensions. " +
|
|
11
|
+
"Internally uses Chart.js and chartjs-node-canvas to produce a Base64-encoded PNG image.";
|
|
12
|
+
const context = requestContextService.createRequestContext({ operation });
|
|
13
|
+
await ErrorHandler.tryCatch(async () => {
|
|
14
|
+
server.tool(toolName, toolDescription, GeneratePubMedChartInputSchema.shape, async (input, mcpProvidedContext) => {
|
|
15
|
+
const richContext = requestContextService.createRequestContext({
|
|
16
|
+
parentRequestId: context.requestId,
|
|
15
17
|
operation: "generatePubMedChartToolHandler",
|
|
16
18
|
mcpToolContext: mcpProvidedContext,
|
|
19
|
+
input,
|
|
17
20
|
});
|
|
18
|
-
|
|
21
|
+
try {
|
|
22
|
+
const result = await generatePubMedChartLogic(input, richContext);
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: "image",
|
|
27
|
+
data: result.base64Data,
|
|
28
|
+
mimeType: "image/png",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
isError: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const handledError = ErrorHandler.handleError(error, {
|
|
36
|
+
operation: "generatePubMedChartToolHandler",
|
|
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 generating the chart.", {
|
|
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
|
+
}
|
|
19
63
|
});
|
|
20
|
-
logger.notice(
|
|
64
|
+
logger.notice(`Tool '${toolName}' registered.`, context);
|
|
21
65
|
}, {
|
|
22
66
|
operation,
|
|
23
|
-
context
|
|
67
|
+
context,
|
|
24
68
|
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
25
69
|
critical: true,
|
|
26
70
|
});
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter
|
|
5
5
|
*/
|
|
6
6
|
import { RequestContext } from "../../../../utils/index.js";
|
|
7
|
-
import type { GetPubMedArticleConnectionsInput } from "
|
|
7
|
+
import type { GetPubMedArticleConnectionsInput } from "./index.js";
|
|
8
8
|
import type { ToolOutputData } from "./types.js";
|
|
9
9
|
export declare function handleCitationFormats(input: GetPubMedArticleConnectionsInput, outputData: ToolOutputData, context: RequestContext): Promise<void>;
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler
|
|
5
5
|
*/
|
|
6
6
|
import { RequestContext } from "../../../../utils/index.js";
|
|
7
|
-
import type { GetPubMedArticleConnectionsInput } from "
|
|
7
|
+
import type { GetPubMedArticleConnectionsInput } from "./index.js";
|
|
8
8
|
import type { ToolOutputData } from "./types.js";
|
|
9
9
|
export declare function handleELinkRelationships(input: GetPubMedArticleConnectionsInput, outputData: ToolOutputData, context: RequestContext): Promise<void>;
|