@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
|
@@ -1,12 +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
10
|
import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
|
|
6
11
|
export const GeneratePubMedChartInputSchema = z.object({
|
|
7
12
|
chartType: z
|
|
8
|
-
.enum([
|
|
9
|
-
|
|
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'."),
|
|
10
24
|
title: z
|
|
11
25
|
.string()
|
|
12
26
|
.optional()
|
|
@@ -16,22 +30,22 @@ export const GeneratePubMedChartInputSchema = z.object({
|
|
|
16
30
|
.int()
|
|
17
31
|
.positive()
|
|
18
32
|
.optional()
|
|
19
|
-
.default(
|
|
20
|
-
.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."),
|
|
21
35
|
height: z
|
|
22
36
|
.number()
|
|
23
37
|
.int()
|
|
24
38
|
.positive()
|
|
25
39
|
.optional()
|
|
26
|
-
.default(
|
|
27
|
-
.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."),
|
|
28
42
|
dataValues: z
|
|
29
43
|
.array(z.record(z.string(), z.any()))
|
|
30
44
|
.min(1)
|
|
31
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."),
|
|
32
46
|
outputFormat: z
|
|
33
|
-
.enum(["png"])
|
|
34
|
-
.default("png")
|
|
47
|
+
.enum(["png"])
|
|
48
|
+
.default("png")
|
|
35
49
|
.describe("Specifies the output format for the chart. Currently, only 'png' (Portable Network Graphics) is supported and is the default."),
|
|
36
50
|
xField: z
|
|
37
51
|
.string()
|
|
@@ -39,216 +53,141 @@ export const GeneratePubMedChartInputSchema = z.object({
|
|
|
39
53
|
yField: z
|
|
40
54
|
.string()
|
|
41
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')."),
|
|
42
|
-
xFieldType: z
|
|
43
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
44
|
-
.optional()
|
|
45
|
-
.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)."),
|
|
46
|
-
yFieldType: z
|
|
47
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
48
|
-
.optional()
|
|
49
|
-
.describe("Optional. Specifies the data type of the Y-axis field. Options: 'nominal', 'ordinal', 'quantitative', 'temporal'. Defaults to 'quantitative' if omitted."),
|
|
50
|
-
// Optional fields for various chart types
|
|
51
|
-
colorField: z
|
|
52
|
-
.string()
|
|
53
|
-
.optional()
|
|
54
|
-
.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')."),
|
|
55
|
-
colorFieldType: z
|
|
56
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
57
|
-
.optional()
|
|
58
|
-
.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."),
|
|
59
56
|
seriesField: z
|
|
60
57
|
.string()
|
|
61
58
|
.optional()
|
|
62
|
-
.describe("Optional.
|
|
63
|
-
seriesFieldType: z
|
|
64
|
-
.enum(["nominal", "ordinal", "quantitative", "temporal"])
|
|
65
|
-
.optional()
|
|
66
|
-
.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."),
|
|
67
|
-
// 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."),
|
|
68
60
|
sizeField: z
|
|
69
61
|
.string()
|
|
70
62
|
.optional()
|
|
71
|
-
.describe("Optional. For
|
|
72
|
-
sizeFieldType: z
|
|
73
|
-
.enum(["quantitative", "ordinal"])
|
|
74
|
-
.optional()
|
|
75
|
-
.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
|
|
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')."),
|
|
78
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
|
+
}
|
|
79
77
|
export async function generatePubMedChartLogic(input, parentRequestContext) {
|
|
80
78
|
const operationContext = requestContextService.createRequestContext({
|
|
81
79
|
parentRequestId: parentRequestContext.requestId,
|
|
82
80
|
operation: "generatePubMedChartLogicExecution",
|
|
83
81
|
input: sanitizeInputForLogging(input),
|
|
84
82
|
});
|
|
85
|
-
logger.info(`Executing 'generate_pubmed_chart'. Chart type: ${input.chartType}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
}));
|
|
106
105
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
x:
|
|
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
|
-
// For line charts, seriesField is typically used for color
|
|
162
|
-
vegaLiteSpec.encoding.color = {
|
|
163
|
-
field: input.seriesField,
|
|
164
|
-
type: seriesEncType,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
else if (input.colorField) {
|
|
168
|
-
// Allow direct colorField as well
|
|
169
|
-
vegaLiteSpec.encoding.color = {
|
|
170
|
-
field: input.colorField,
|
|
171
|
-
type: colorEncType,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case "scatter":
|
|
176
|
-
xEncType = input.xFieldType || "quantitative";
|
|
177
|
-
vegaLiteSpec.mark = "point"; // "circle" is also an option
|
|
178
|
-
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
|
+
: {
|
|
179
157
|
x: {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
158
|
+
title: {
|
|
159
|
+
display: true,
|
|
160
|
+
text: xField,
|
|
161
|
+
},
|
|
183
162
|
},
|
|
184
163
|
y: {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
164
|
+
title: {
|
|
165
|
+
display: true,
|
|
166
|
+
text: yField,
|
|
167
|
+
},
|
|
188
168
|
},
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
if (input.sizeField) {
|
|
197
|
-
vegaLiteSpec.encoding.size = {
|
|
198
|
-
field: input.sizeField,
|
|
199
|
-
type: sizeEncType,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
// Add shape encoding here if shapeField is implemented
|
|
203
|
-
break;
|
|
204
|
-
// No default case needed as chartType is an enum and Zod validates it.
|
|
205
|
-
}
|
|
206
|
-
const compiledVegaSpec = vegaLite.compile(vegaLiteSpec).spec;
|
|
207
|
-
const view = new vega.View(vega.parse(compiledVegaSpec), {
|
|
208
|
-
renderer: "canvas", // Explicitly set renderer to 'canvas'
|
|
209
|
-
});
|
|
210
|
-
// const svgString = await view.toSVG(); // Old SVG method
|
|
211
|
-
// New PNG method
|
|
212
|
-
// Initialize the view to ensure canvas is ready
|
|
213
|
-
await view.runAsync(); // Initialize and run the view
|
|
214
|
-
const canvas = await view.toCanvas(); // Render to canvas
|
|
215
|
-
// Cast to 'any' to access toBuffer, assuming it's a Node Canvas instance at runtime
|
|
216
|
-
const imageBuffer = await canvas.toBuffer("image/png"); // Get PNG buffer from canvas
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration);
|
|
217
174
|
const base64Data = imageBuffer.toString("base64");
|
|
175
|
+
logger.notice("Successfully generated chart with Chart.js.", {
|
|
176
|
+
...operationContext,
|
|
177
|
+
chartType: input.chartType,
|
|
178
|
+
dataPoints: input.dataValues.length,
|
|
179
|
+
});
|
|
218
180
|
return {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
data: base64Data,
|
|
223
|
-
mimeType: "image/png", // Changed MIME type to image/png
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
isError: false,
|
|
181
|
+
base64Data,
|
|
182
|
+
chartType: input.chartType,
|
|
183
|
+
dataPoints: input.dataValues.length,
|
|
227
184
|
};
|
|
228
185
|
}
|
|
229
186
|
catch (error) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
:
|
|
234
|
-
|
|
235
|
-
originalErrorMessage: error.message,
|
|
236
|
-
requestId: operationContext.requestId,
|
|
237
|
-
});
|
|
238
|
-
return {
|
|
239
|
-
content: [
|
|
240
|
-
{
|
|
241
|
-
type: "text",
|
|
242
|
-
text: JSON.stringify({
|
|
243
|
-
error: {
|
|
244
|
-
code: mcpError.code,
|
|
245
|
-
message: mcpError.message,
|
|
246
|
-
details: mcpError.details,
|
|
247
|
-
},
|
|
248
|
-
}),
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
isError: true,
|
|
252
|
-
};
|
|
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
|
+
});
|
|
253
192
|
}
|
|
254
193
|
}
|
|
@@ -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,36 +1,71 @@
|
|
|
1
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
|
-
export function registerGeneratePubMedChartTool(server) {
|
|
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(`Tool '
|
|
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
|
-
}
|
|
64
|
+
logger.notice(`Tool '${toolName}' registered.`, context);
|
|
65
|
+
}, {
|
|
66
|
+
operation,
|
|
67
|
+
context,
|
|
68
|
+
errorCode: BaseErrorCode.INITIALIZATION_FAILED,
|
|
69
|
+
critical: true,
|
|
70
|
+
});
|
|
36
71
|
}
|
|
@@ -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>;
|
|
@@ -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
|
|
@@ -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>;
|
|
@@ -3,13 +3,36 @@
|
|
|
3
3
|
* Orchestrates calls to ELink or citation formatting handlers.
|
|
4
4
|
* @module src/mcp-server/tools/getPubMedArticleConnections/logic/index
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
6
|
+
import { z } from "zod";
|
|
7
7
|
import { RequestContext } from "../../../../utils/index.js";
|
|
8
|
-
import type {
|
|
8
|
+
import type { ToolOutputData } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Zod schema for the input parameters of the 'get_pubmed_article_connections' tool.
|
|
11
|
+
*/
|
|
12
|
+
export declare const GetPubMedArticleConnectionsInputSchema: z.ZodObject<{
|
|
13
|
+
sourcePmid: z.ZodString;
|
|
14
|
+
relationshipType: z.ZodDefault<z.ZodEnum<["pubmed_similar_articles", "pubmed_citedin", "pubmed_references", "citation_formats"]>>;
|
|
15
|
+
maxRelatedResults: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
16
|
+
citationStyles: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<["ris", "bibtex", "apa_string", "mla_string"]>, "many">>>;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
sourcePmid: string;
|
|
19
|
+
relationshipType: "pubmed_similar_articles" | "pubmed_citedin" | "pubmed_references" | "citation_formats";
|
|
20
|
+
maxRelatedResults: number;
|
|
21
|
+
citationStyles: ("ris" | "bibtex" | "apa_string" | "mla_string")[];
|
|
22
|
+
}, {
|
|
23
|
+
sourcePmid: string;
|
|
24
|
+
relationshipType?: "pubmed_similar_articles" | "pubmed_citedin" | "pubmed_references" | "citation_formats" | undefined;
|
|
25
|
+
maxRelatedResults?: number | undefined;
|
|
26
|
+
citationStyles?: ("ris" | "bibtex" | "apa_string" | "mla_string")[] | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Type alias for the validated input of the 'get_pubmed_article_connections' tool.
|
|
30
|
+
*/
|
|
31
|
+
export type GetPubMedArticleConnectionsInput = z.infer<typeof GetPubMedArticleConnectionsInputSchema>;
|
|
9
32
|
/**
|
|
10
33
|
* Main handler for the 'get_pubmed_article_connections' tool.
|
|
11
34
|
* @param {GetPubMedArticleConnectionsInput} input - Validated input parameters.
|
|
12
35
|
* @param {RequestContext} context - The request context for this tool invocation.
|
|
13
|
-
* @returns {Promise<
|
|
36
|
+
* @returns {Promise<ToolOutputData>} The result of the tool call.
|
|
14
37
|
*/
|
|
15
|
-
export declare function handleGetPubMedArticleConnections(input: GetPubMedArticleConnectionsInput, context: RequestContext): Promise<
|
|
38
|
+
export declare function handleGetPubMedArticleConnections(input: GetPubMedArticleConnectionsInput, context: RequestContext): Promise<ToolOutputData>;
|