@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.
Files changed (41) hide show
  1. package/README.md +25 -24
  2. package/dist/config/index.js +39 -1
  3. package/dist/mcp-server/server.d.ts +0 -7
  4. package/dist/mcp-server/server.js +17 -53
  5. package/dist/mcp-server/tools/fetchPubMedContent/logic.d.ts +6 -2
  6. package/dist/mcp-server/tools/fetchPubMedContent/logic.js +102 -311
  7. package/dist/mcp-server/tools/fetchPubMedContent/registration.d.ts +1 -1
  8. package/dist/mcp-server/tools/fetchPubMedContent/registration.js +50 -18
  9. package/dist/mcp-server/tools/generatePubMedChart/logic.d.ts +9 -28
  10. package/dist/mcp-server/tools/generatePubMedChart/logic.js +137 -198
  11. package/dist/mcp-server/tools/generatePubMedChart/registration.d.ts +1 -1
  12. package/dist/mcp-server/tools/generatePubMedChart/registration.js +62 -27
  13. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +1 -1
  14. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +3 -3
  15. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.d.ts +1 -1
  16. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.d.ts +27 -4
  17. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +59 -51
  18. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +1 -1
  19. package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +1 -26
  20. package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +58 -62
  21. package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +2 -5
  22. package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +7 -40
  23. package/dist/mcp-server/tools/pubmedResearchAgent/registration.d.ts +1 -1
  24. package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +55 -19
  25. package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +12 -10
  26. package/dist/mcp-server/tools/searchPubMedArticles/logic.js +68 -121
  27. package/dist/mcp-server/tools/searchPubMedArticles/registration.d.ts +1 -1
  28. package/dist/mcp-server/tools/searchPubMedArticles/registration.js +54 -21
  29. package/dist/mcp-server/transports/httpTransport.d.ts +0 -8
  30. package/dist/mcp-server/transports/httpTransport.js +57 -345
  31. package/dist/utils/security/rateLimiter.d.ts +4 -0
  32. package/dist/utils/security/rateLimiter.js +4 -0
  33. package/package.json +7 -15
  34. package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +0 -79
  35. package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +0 -82
  36. package/dist/mcp-server/resources/echoResource/index.d.ts +0 -13
  37. package/dist/mcp-server/resources/echoResource/index.js +0 -13
  38. package/dist/mcp-server/resources/echoResource/registration.d.ts +0 -30
  39. package/dist/mcp-server/resources/echoResource/registration.js +0 -168
  40. package/dist/mcp-server/tools/getPubMedArticleConnections/logic.d.ts +0 -6
  41. package/dist/mcp-server/tools/getPubMedArticleConnections/logic.js +0 -6
@@ -1,12 +1,26 @@
1
- import * as vega from "vega";
2
- import * as vegaLite from "vega-lite";
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(["bar", "line", "scatter"])
9
- .describe("Required. Specifies the type of chart to generate. Options: 'bar', 'line', 'scatter'."),
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(400)
20
- .describe("Optional. The width of the chart canvas in pixels. Must be a positive integer. Default: 400."),
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(300)
27
- .describe("Optional. The height of the chart canvas in pixels. Must be a positive integer. Default: 300."),
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"]) // Changed from svg to png
34
- .default("png") // Changed default to 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. Primarily for line charts. The name of the field in `dataValues` used to create multiple distinct lines (series) on the same chart. Each unique value in this field will correspond to a separate line (e.g., 'drugName' to plot different drug efficacy trends). Often used with `colorField` implicitly or explicitly."),
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 scatter plots. The name of the field in `dataValues` to use for encoding the size of the points. Larger values in this field will result in larger points on the scatter plot (e.g., 'sampleSize', 'effectMagnitude')."),
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}, Output format: ${input.outputFormat}`, operationContext);
86
- if (input.outputFormat !== "png") {
87
- // Changed from svg to png
88
- const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'png' is supported.`, // Changed message
89
- { requestedFormat: input.outputFormat });
90
- logger.warning(unsupportedFormatError.message, operationContext);
91
- return {
92
- content: [
93
- {
94
- type: "text",
95
- text: JSON.stringify({
96
- error: {
97
- code: unsupportedFormatError.code,
98
- message: unsupportedFormatError.message,
99
- details: unsupportedFormatError.details,
100
- },
101
- }),
102
- },
103
- ],
104
- isError: true,
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
- try {
108
- let vegaLiteSpec = {
109
- $schema: "https://vega.github.io/schema/vega-lite/v5.json",
110
- title: input.title,
111
- width: input.width,
112
- height: input.height,
113
- data: { values: input.dataValues },
114
- encoding: {}, // To be populated by chart type specific logic
115
- };
116
- // Default field types if not provided
117
- const yEncType = input.yFieldType || "quantitative";
118
- const colorEncType = input.colorFieldType || "nominal";
119
- const seriesEncType = input.seriesFieldType || "nominal";
120
- const sizeEncType = input.sizeFieldType || "quantitative";
121
- let xEncType;
122
- switch (input.chartType) {
123
- case "bar":
124
- xEncType = input.xFieldType || "nominal";
125
- vegaLiteSpec.mark = "bar";
126
- vegaLiteSpec.encoding = {
127
- x: {
128
- field: input.xField,
129
- type: xEncType,
130
- axis: { title: input.xField },
131
- },
132
- y: {
133
- field: input.yField,
134
- type: yEncType,
135
- axis: { title: input.yField },
136
- },
137
- };
138
- if (input.colorField) {
139
- vegaLiteSpec.encoding.color = {
140
- field: input.colorField,
141
- type: colorEncType,
142
- };
143
- }
144
- break;
145
- case "line":
146
- xEncType = input.xFieldType || "temporal";
147
- vegaLiteSpec.mark = "line";
148
- vegaLiteSpec.encoding = {
149
- x: {
150
- field: input.xField,
151
- type: xEncType,
152
- axis: { title: input.xField },
153
- },
154
- y: {
155
- field: input.yField,
156
- type: yEncType,
157
- axis: { title: input.yField },
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
- field: input.xField,
181
- type: xEncType,
182
- axis: { title: input.xField },
158
+ title: {
159
+ display: true,
160
+ text: xField,
161
+ },
183
162
  },
184
163
  y: {
185
- field: input.yField,
186
- type: yEncType,
187
- axis: { title: input.yField },
164
+ title: {
165
+ display: true,
166
+ text: yField,
167
+ },
188
168
  },
189
- };
190
- if (input.colorField) {
191
- vegaLiteSpec.encoding.color = {
192
- field: input.colorField,
193
- type: colorEncType,
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
- content: [
220
- {
221
- type: "image",
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
- logger.error("Execution failed for 'generate_pubmed_chart'", error, operationContext);
231
- const mcpError = error instanceof McpError
232
- ? error
233
- : new McpError(BaseErrorCode.INTERNAL_ERROR, `'generate_pubmed_chart' failed: ${error.message || "Internal server error during chart generation."}`, {
234
- originalErrorName: error.name,
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 regContext = requestContextService.createRequestContext({ operation });
7
- try {
8
- server.tool("generate_pubmed_chart", "Generates a customizable chart (PNG) from structured data. " +
9
- "Supports 'bar', 'line', and 'scatter' plots. " +
10
- "Requires data values and field mappings for axes. " +
11
- "Optional parameters allow for titles, dimensions, and color/size/series encoding. " +
12
- "Internally uses Vega-Lite and a canvas renderer to produce a Base64-encoded PNG image.", GeneratePubMedChartInputSchema.shape, async (validatedInput, mcpProvidedContext) => {
13
- const handlerRequestContext = requestContextService.createRequestContext({
14
- parentRequestId: regContext.requestId,
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
- return generatePubMedChartLogic(validatedInput, handlerRequestContext);
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 'generate_pubmed_chart' registered with updated schema (added scatter).`, regContext);
21
- }
22
- catch (error) {
23
- const mcpError = error instanceof McpError
24
- ? error
25
- : new McpError(BaseErrorCode.INITIALIZATION_FAILED, `Failed to register 'generate_pubmed_chart': ${error instanceof Error ? error.message : String(error)}`, {
26
- originalErrorName: error instanceof Error ? error.name : "UnknownError",
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 "../registration.js";
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 ? `<em>${journalInfo.title}</em>` : "N/A";
203
- const volume = journalInfo?.volume ? `<em>${journalInfo.volume}</em>` : "";
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 ? `<em>${journalInfo.title}</em>` : "N/A";
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 "../registration.js";
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 type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
6
+ import { z } from "zod";
7
7
  import { RequestContext } from "../../../../utils/index.js";
8
- import type { GetPubMedArticleConnectionsInput } from "../registration.js";
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<CallToolResult>} The result of the tool call.
36
+ * @returns {Promise<ToolOutputData>} The result of the tool call.
14
37
  */
15
- export declare function handleGetPubMedArticleConnections(input: GetPubMedArticleConnectionsInput, context: RequestContext): Promise<CallToolResult>;
38
+ export declare function handleGetPubMedArticleConnections(input: GetPubMedArticleConnectionsInput, context: RequestContext): Promise<ToolOutputData>;