@cyanheads/pubmed-mcp-server 1.0.12 → 1.0.14

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
4
  [![Model Context Protocol SDK](https://img.shields.io/badge/MCP%20SDK-1.12.0-green.svg)](https://github.com/modelcontextprotocol/typescript-sdk)
5
5
  [![MCP Spec Version](https://img.shields.io/badge/MCP%20Spec-2025--03--26-lightgrey.svg)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/changelog.mdx)
6
- [![Version](https://img.shields.io/badge/Version-1.0.12-blue.svg)](./CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/Version-1.0.14-blue.svg)](./CHANGELOG.md)
7
7
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
8
  [![Status](https://img.shields.io/badge/Status-Beta-orange.svg)](https://github.com/cyanheads/pubmed-mcp-server/issues)
9
9
  [![GitHub](https://img.shields.io/github/stars/cyanheads/pubmed-mcp-server?style=social)](https://github.com/cyanheads/pubmed-mcp-server)
@@ -16,12 +16,13 @@ This server acts as a bridge, connecting your AI to NCBI's PubMed and E-utilitie
16
16
 
17
17
  This server equips your AI with specialized tools to interact with PubMed:
18
18
 
19
- | Tool Name | Description | Key Features | Output Structure (JSON Example) |
20
- | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
21
- | `search_pubmed_articles` | Searches PubMed for articles based on your query. | - Filter by max results, sort order, date range, publication types.<br/>- Uses NCBI ESearch for PMIDs.<br/>- Optionally fetches brief summaries (title, authors, source, dates) via ESummary. | `{"searchParameters": {"queryTerm": "oncology", "maxResults": 5},"effectiveESearchTerm": "oncology","totalFound": 250,"retrievedPmidCount": 5,"pmids": ["12345", "67890"],"briefSummaries": [{"pmid": "12345", "title": "Example Title"}],"eSearchUrl": "https://ncbi...","eSummaryUrl": "https://ncbi..."}` |
22
- | `fetch_pubmed_content` | Retrieves detailed information for PubMed articles. Can use a list of PMIDs or ESearch history (queryKey/webEnv) with pagination. | - Flexible `detailLevel`: `abstract_plus` (parsed details, optional MeSH/grant), `full_xml` (JSON representation of the PubMedArticle XML structure), `medline_text` (MEDLINE format), `citation_data` (minimal for citations).<br/>- Supports direct PMID list or `queryKey`/`webEnv` from ESearch history.<br/>- Supports `retstart`/`retmax` for pagination with history.<br/>- Uses NCBI EFetch. | `{"requestedPmids": ["12345"],"articles": [{"pmid": "12345", "title": "Example Title", "abstractText": "..."}],"notFoundPmids": [],"eFetchDetails": {"urls": ["https://ncbi..."], "requestMethod": "GET"}}` (Or raw string for`detailLevel: 'full_xml'/'medline_text'`with`outputFormat: 'raw_text'`) |
23
- | `get_pubmed_article_connections` | Finds related articles (cited by, similar, references) or formats citations for a PMID. | - Uses NCBI ELink for relationships.<br/>- Uses NCBI EFetch for citation data (RIS, BibTeX, APA, MLA).<br/>- Filter by max related results. | `{"sourcePmid": "12345","relationshipType": "pubmed_similar_articles","relatedArticles": [{"pmid": "67890", "title": "Related Title"}],"citations": {"ris": "RIS formatted citation..." },"retrievedCount": 1,"eUtilityUrl": "https://ncbi...","message": "Success"}` |
24
- | `pubmed_research_agent` | Generates a standardized JSON research plan outline from component details. | - Accepts granular inputs for all research phases.<br/>- Optionally embeds instructive prompts for agent execution.<br/>- Structures rough ideas into a formal, machine-readable plan for further processing. | `{"plan_title": "Comprehensive Research Plan on Topic X","input_summary": {"keywords_received": ["Topic X", "Gene Y"]},"phase_1_conception_and_planning": {"step_1_1_research_question_and_hypothesis": {"primary_research_question": "What is the role of Gene Y in Topic X?"}},"phase_2_data_collection_and_processing": {}}` |
19
+ | Tool Name | Description | Key Features |
20
+ | :-------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | [`search_pubmed_articles`](./src/mcp-server/tools/searchPubMedArticles/) | Searches PubMed for articles based on your query. (See [Example](./examples/search_pubmed_articles_example.md)) | - Filter by max results, sort order, date range, publication types.<br/>- Uses NCBI ESearch for PMIDs.<br/>- Optionally fetches brief summaries (title, authors, source, dates) via ESummary. |
22
+ | [`fetch_pubmed_content`](./src/mcp-server/tools/fetchPubMedContent/) | Retrieves detailed information for PubMed articles. Can use a list of PMIDs or ESearch history (queryKey/webEnv) with pagination. (See [Example](./examples/fetch_pubmed_content_example.md)) | - Flexible `detailLevel`: `abstract_plus` (parsed details, optional MeSH/grant), `full_xml` (JSON representation of the PubMedArticle XML structure), `medline_text` (MEDLINE format), `citation_data` (minimal for citations).<br/>- Supports direct PMID list or `queryKey`/`webEnv` from ESearch history.<br/>- Supports `retstart`/`retmax` for pagination with history.<br/>- Uses NCBI EFetch. |
23
+ | [`get_pubmed_article_connections`](./src/mcp-server/tools/getPubMedArticleConnections/) | Finds related articles (cited by, similar, references) or formats citations for a PMID. (See [Ex.1](./examples/get_pubmed_article_connections_1.md), [Ex.2](./examples/get_pubmed_article_connections_2.md)) | - Uses NCBI ELink for relationships.<br/>- Uses NCBI EFetch for citation data (RIS, BibTeX, APA, MLA).<br/>- Filter by max related results. |
24
+ | [`pubmed_research_agent`](./src/mcp-server/tools/pubmedResearchAgent/) | Generates a standardized JSON research plan outline from component details. (See [Example](./examples/pubmed_research_agent_example.md)) | - Accepts granular inputs for all research phases.<br/>- Optionally embeds instructive prompts for agent execution.<br/>- Structures rough ideas into a formal, machine-readable plan for further processing. |
25
+ | [`generate_pubmed_chart`](./src/mcp-server/tools/generatePubMedChart/) | Generates a chart image (SVG) from given input data. (See [Bar](./examples/generate_pubmed_chart_example_bar.svg), [Line](./examples/generate_pubmed_chart_example_line.svg), [Scatter](./examples/generate_pubmed_chart_example_scatter.svg)) | - Supports 'bar', 'line', and 'scatter' chart types.<br/>- Takes data values and field specifications for axes and encoding.<br/>- Constructs a Vega-Lite specification internally and renders it as an SVG. |
25
26
 
26
27
  ---
27
28
 
@@ -64,11 +65,13 @@ Get the PubMed MCP server running in minutes:
64
65
  # RECOMMENDED FOR NCBI E-UTILITIES (for higher rate limits)
65
66
  # NCBI_API_KEY=your_ncbi_api_key_here
66
67
  # NCBI_ADMIN_EMAIL=your_email@example.com # Recommended if using an API key
67
- # NCBI_TOOL_IDENTIFIER=@cyanheads/pubmed-mcp-server/1.0.11 # Optional: Tool identifier for NCBI (defaults to current version)
68
+ # NCBI_TOOL_IDENTIFIER=@cyanheads/pubmed-mcp-server/1.0.13 # Optional: Tool identifier for NCBI (defaults to current version)
68
69
  ```
69
70
 
70
71
  For all options, see the [Configuration](#⚙️-configuration) section below or the [Developer Cheatsheet (.clinerules)](./.clinerules).
71
72
 
73
+ New example files for each tool are available in the `examples/` directory (e.g., `examples/search_pubmed_articles_example.md`, `examples/generate_pubmed_chart_example_bar.svg`).
74
+
72
75
  4. **Build the project:**
73
76
 
74
77
  ```bash
@@ -18,6 +18,8 @@ import { config } from "../config/index.js";
18
18
  import { ErrorHandler, logger, requestContextService } from "../utils/index.js";
19
19
  // import { registerEchoResource } from "./resources/echoResource/index.js"; // To be removed after resource implementations.
20
20
  import { registerFetchPubMedContentTool } from "./tools/fetchPubMedContent/index.js";
21
+ // Removed: import { registerFetchImageTestTool } from "./tools/imageTest/index.js";
22
+ import { registerGeneratePubMedChartTool } from "./tools/generatePubMedChart/index.js"; // Added import
21
23
  import { registerGetPubMedArticleConnectionsTool } from "./tools/getPubMedArticleConnections/index.js";
22
24
  import { registerPubMedResearchAgentTool } from "./tools/pubmedResearchAgent/index.js"; // Added import
23
25
  import { registerSearchPubMedArticlesTool } from "./tools/searchPubMedArticles/index.js";
@@ -68,6 +70,8 @@ async function createMcpServerInstance() {
68
70
  logger.debug("Registering resources and tools...", context);
69
71
  // IMPORTANT: Keep tool registrations in alphabetical order. Do not remove this comment.
70
72
  await registerFetchPubMedContentTool(server);
73
+ // Removed: await registerFetchImageTestTool(server);
74
+ await registerGeneratePubMedChartTool(server); // Added new tool registration
71
75
  await registerGetPubMedArticleConnectionsTool(server);
72
76
  await registerPubMedResearchAgentTool(server); // Added new tool registration
73
77
  await registerSearchPubMedArticlesTool(server);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Barrel export for the 'generatePubMedChart' tool.
3
+ * This file re-exports the registration function for the tool,
4
+ * making it easier to import and register with the MCP server.
5
+ * @module src/mcp-server/tools/generatePubMedChart/index
6
+ */
7
+ export * from "./registration.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Barrel export for the 'generatePubMedChart' tool.
3
+ * This file re-exports the registration function for the tool,
4
+ * making it easier to import and register with the MCP server.
5
+ * @module src/mcp-server/tools/generatePubMedChart/index
6
+ */
7
+ export * from "./registration.js";
@@ -0,0 +1,62 @@
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
+ import { z } from "zod";
8
+ import { RequestContext } from "../../../utils/index.js";
9
+ export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
10
+ chartType: z.ZodEnum<["bar", "line", "scatter"]>;
11
+ title: z.ZodOptional<z.ZodString>;
12
+ width: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
13
+ height: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
14
+ dataValues: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodAny>, "many">;
15
+ outputFormat: z.ZodDefault<z.ZodEnum<["svg"]>>;
16
+ xField: z.ZodString;
17
+ 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
+ seriesField: z.ZodOptional<z.ZodString>;
23
+ seriesFieldType: z.ZodOptional<z.ZodEnum<["nominal", "ordinal", "quantitative", "temporal"]>>;
24
+ sizeField: z.ZodOptional<z.ZodString>;
25
+ sizeFieldType: z.ZodOptional<z.ZodEnum<["quantitative", "ordinal"]>>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ width: number;
28
+ height: number;
29
+ outputFormat: "svg";
30
+ chartType: "bar" | "line" | "scatter";
31
+ dataValues: Record<string, any>[];
32
+ xField: string;
33
+ yField: string;
34
+ 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
+ seriesField?: string | undefined;
40
+ seriesFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
41
+ sizeField?: string | undefined;
42
+ sizeFieldType?: "ordinal" | "quantitative" | undefined;
43
+ }, {
44
+ chartType: "bar" | "line" | "scatter";
45
+ dataValues: Record<string, any>[];
46
+ xField: string;
47
+ yField: string;
48
+ title?: string | undefined;
49
+ width?: number | undefined;
50
+ height?: number | undefined;
51
+ outputFormat?: "svg" | 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
+ seriesField?: string | undefined;
57
+ seriesFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
58
+ sizeField?: string | undefined;
59
+ sizeFieldType?: "ordinal" | "quantitative" | undefined;
60
+ }>;
61
+ export type GeneratePubMedChartInput = z.infer<typeof GeneratePubMedChartInputSchema>;
62
+ export declare function generatePubMedChartLogic(input: GeneratePubMedChartInput, parentRequestContext: RequestContext): Promise<CallToolResult>;
@@ -0,0 +1,242 @@
1
+ import * as vega from "vega";
2
+ import * as vegaLite from "vega-lite";
3
+ import { z } from "zod";
4
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
5
+ import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
6
+ export const GeneratePubMedChartInputSchema = z.object({
7
+ chartType: z
8
+ .enum(["bar", "line", "scatter"])
9
+ .describe("Required. Specifies the type of chart to generate. Options: 'bar', 'line', 'scatter'."),
10
+ title: z
11
+ .string()
12
+ .optional()
13
+ .describe("Optional. The main title displayed above the chart. If omitted, no title is shown."),
14
+ width: z
15
+ .number()
16
+ .int()
17
+ .positive()
18
+ .optional()
19
+ .default(400)
20
+ .describe("Optional. The width of the chart canvas in pixels. Must be a positive integer. Default: 400."),
21
+ height: z
22
+ .number()
23
+ .int()
24
+ .positive()
25
+ .optional()
26
+ .default(300)
27
+ .describe("Optional. The height of the chart canvas in pixels. Must be a positive integer. Default: 300."),
28
+ dataValues: z
29
+ .array(z.record(z.string(), z.any()))
30
+ .min(1)
31
+ .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
+ outputFormat: z
33
+ .enum(["svg"])
34
+ .default("svg")
35
+ .describe("Specifies the output format for the chart. Currently, only 'svg' (Scalable Vector Graphics) is supported and is the default."),
36
+ xField: z
37
+ .string()
38
+ .describe("Required. The name of the field in `dataValues` to be used for the X-axis (horizontal). This field determines the categories or values along the bottom of the chart (e.g., 'year', 'geneName', 'publicationCount')."),
39
+ yField: z
40
+ .string()
41
+ .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
+ seriesField: z
60
+ .string()
61
+ .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)
68
+ sizeField: z
69
+ .string()
70
+ .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
78
+ });
79
+ export async function generatePubMedChartLogic(input, parentRequestContext) {
80
+ const operationContext = requestContextService.createRequestContext({
81
+ parentRequestId: parentRequestContext.requestId,
82
+ operation: "generatePubMedChartLogicExecution",
83
+ input: sanitizeInputForLogging(input),
84
+ });
85
+ logger.info(`Executing 'generate_pubmed_chart'. Chart type: ${input.chartType}, Output format: ${input.outputFormat}`, operationContext);
86
+ if (input.outputFormat !== "svg") {
87
+ const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'svg' is supported.`, { requestedFormat: input.outputFormat });
88
+ logger.warning(unsupportedFormatError.message, operationContext);
89
+ return {
90
+ content: [
91
+ {
92
+ type: "text",
93
+ text: JSON.stringify({
94
+ error: {
95
+ code: unsupportedFormatError.code,
96
+ message: unsupportedFormatError.message,
97
+ details: unsupportedFormatError.details,
98
+ },
99
+ }),
100
+ },
101
+ ],
102
+ isError: true,
103
+ };
104
+ }
105
+ try {
106
+ let vegaLiteSpec = {
107
+ $schema: "https://vega.github.io/schema/vega-lite/v5.json",
108
+ title: input.title,
109
+ width: input.width,
110
+ height: input.height,
111
+ data: { values: input.dataValues },
112
+ encoding: {}, // To be populated by chart type specific logic
113
+ };
114
+ // Default field types if not provided
115
+ const yEncType = input.yFieldType || "quantitative";
116
+ const colorEncType = input.colorFieldType || "nominal";
117
+ const seriesEncType = input.seriesFieldType || "nominal";
118
+ const sizeEncType = input.sizeFieldType || "quantitative";
119
+ let xEncType;
120
+ switch (input.chartType) {
121
+ case "bar":
122
+ xEncType = input.xFieldType || "nominal";
123
+ vegaLiteSpec.mark = "bar";
124
+ vegaLiteSpec.encoding = {
125
+ x: {
126
+ field: input.xField,
127
+ type: xEncType,
128
+ axis: { title: input.xField },
129
+ },
130
+ y: {
131
+ field: input.yField,
132
+ type: yEncType,
133
+ axis: { title: input.yField },
134
+ },
135
+ };
136
+ if (input.colorField) {
137
+ vegaLiteSpec.encoding.color = {
138
+ field: input.colorField,
139
+ type: colorEncType,
140
+ };
141
+ }
142
+ break;
143
+ case "line":
144
+ xEncType = input.xFieldType || "temporal";
145
+ vegaLiteSpec.mark = "line";
146
+ vegaLiteSpec.encoding = {
147
+ x: {
148
+ field: input.xField,
149
+ type: xEncType,
150
+ axis: { title: input.xField },
151
+ },
152
+ y: {
153
+ field: input.yField,
154
+ type: yEncType,
155
+ axis: { title: input.yField },
156
+ },
157
+ };
158
+ if (input.seriesField) {
159
+ // For line charts, seriesField is typically used for color
160
+ vegaLiteSpec.encoding.color = {
161
+ field: input.seriesField,
162
+ type: seriesEncType,
163
+ };
164
+ }
165
+ else if (input.colorField) {
166
+ // Allow direct colorField as well
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"; // "circle" is also an option
176
+ vegaLiteSpec.encoding = {
177
+ x: {
178
+ field: input.xField,
179
+ type: xEncType,
180
+ axis: { title: input.xField },
181
+ },
182
+ y: {
183
+ field: input.yField,
184
+ type: yEncType,
185
+ axis: { title: input.yField },
186
+ },
187
+ };
188
+ if (input.colorField) {
189
+ vegaLiteSpec.encoding.color = {
190
+ field: input.colorField,
191
+ type: colorEncType,
192
+ };
193
+ }
194
+ if (input.sizeField) {
195
+ vegaLiteSpec.encoding.size = {
196
+ field: input.sizeField,
197
+ type: sizeEncType,
198
+ };
199
+ }
200
+ // Add shape encoding here if shapeField is implemented
201
+ break;
202
+ // No default case needed as chartType is an enum and Zod validates it.
203
+ }
204
+ const compiledVegaSpec = vegaLite.compile(vegaLiteSpec).spec;
205
+ const view = new vega.View(vega.parse(compiledVegaSpec), {
206
+ renderer: "none",
207
+ });
208
+ const svgString = await view.toSVG();
209
+ const imageBuffer = Buffer.from(svgString, "utf-8");
210
+ const base64Data = imageBuffer.toString("base64");
211
+ const dataUriSvg = `data:image/svg+xml;base64,${base64Data}`;
212
+ return {
213
+ content: [{ type: "text", text: dataUriSvg }],
214
+ isError: false,
215
+ };
216
+ }
217
+ catch (error) {
218
+ logger.error("Execution failed for 'generate_pubmed_chart'", error, operationContext);
219
+ const mcpError = error instanceof McpError
220
+ ? error
221
+ : new McpError(BaseErrorCode.INTERNAL_ERROR, `'generate_pubmed_chart' failed: ${error.message || "Internal server error during chart generation."}`, {
222
+ originalErrorName: error.name,
223
+ originalErrorMessage: error.message,
224
+ requestId: operationContext.requestId,
225
+ });
226
+ return {
227
+ content: [
228
+ {
229
+ type: "text",
230
+ text: JSON.stringify({
231
+ error: {
232
+ code: mcpError.code,
233
+ message: mcpError.message,
234
+ details: mcpError.details,
235
+ },
236
+ }),
237
+ },
238
+ ],
239
+ isError: true,
240
+ };
241
+ }
242
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Registers the 'generate_pubmed_chart' tool with the MCP server.
3
+ * This tool now accepts parameterized input for generating charts.
4
+ * @module src/mcp-server/tools/generatePubMedChart/registration
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ export declare function registerGeneratePubMedChartTool(server: McpServer): void;
@@ -0,0 +1,36 @@
1
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
2
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
3
+ import { GeneratePubMedChartInputSchema, generatePubMedChartLogic, } from "./logic.js";
4
+ export function registerGeneratePubMedChartTool(server) {
5
+ const operation = "registerGeneratePubMedChartTool";
6
+ const regContext = requestContextService.createRequestContext({ operation });
7
+ try {
8
+ server.tool("generate_pubmed_chart", "Generates a customizable chart (SVG) 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 to produce an SVG image.", GeneratePubMedChartInputSchema.shape, async (validatedInput, mcpProvidedContext) => {
13
+ const handlerRequestContext = requestContextService.createRequestContext({
14
+ parentRequestId: regContext.requestId,
15
+ operation: "generatePubMedChartToolHandler",
16
+ mcpToolContext: mcpProvidedContext,
17
+ });
18
+ return generatePubMedChartLogic(validatedInput, handlerRequestContext);
19
+ });
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
+ }
36
+ }
@@ -4,4 +4,4 @@
4
4
  * for accessing various services within the application.
5
5
  * @module src/services/index
6
6
  */
7
- export * from "./llm-providers";
7
+ export * from "./llm-providers/index.js";
@@ -4,4 +4,4 @@
4
4
  * for accessing various services within the application.
5
5
  * @module src/services/index
6
6
  */
7
- export * from "./llm-providers";
7
+ export * from "./llm-providers/index.js";
@@ -4,4 +4,4 @@
4
4
  * making them easily accessible from a single import path.
5
5
  * @module src/services/llm-providers/index
6
6
  */
7
- export * from "./openRouter";
7
+ export * from "./openRouter/index.js";
@@ -4,4 +4,4 @@
4
4
  * making them easily accessible from a single import path.
5
5
  * @module src/services/llm-providers/index
6
6
  */
7
- export * from "./openRouter"; // Changed to export from the new barrel file
7
+ export * from "./openRouter/index.js"; // Changed to export from the new barrel file
@@ -3,4 +3,4 @@
3
3
  * Exports the OpenRouterProvider class and any related types.
4
4
  * @module services/llm-providers/openRouter/index
5
5
  */
6
- export * from "./openRouterProvider";
6
+ export * from "./openRouterProvider.js";
@@ -3,5 +3,5 @@
3
3
  * Exports the OpenRouterProvider class and any related types.
4
4
  * @module services/llm-providers/openRouter/index
5
5
  */
6
- export * from "./openRouterProvider";
6
+ export * from "./openRouterProvider.js";
7
7
  // Add other exports from this module if any in the future
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/pubmed-mcp-server",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "A Model Context Protocol (MCP) server enabling AI agents to intelligently search, retrieve, and analyze biomedical literature from PubMed via NCBI E-utilities. Built on the mcp-ts-template for robust, production-ready performance.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -33,19 +33,19 @@
33
33
  "start:client-cli": "node dist/mcp-client/cli/mcp-client-cli.js"
34
34
  },
35
35
  "dependencies": {
36
- "axios": "^1.9.0",
37
- "fast-xml-parser": "^5.2.3",
38
36
  "@google/genai": "^1.0.1",
39
37
  "@modelcontextprotocol/sdk": "^1.12.0",
40
38
  "@types/jsonwebtoken": "^9.0.9",
41
39
  "@types/node": "^22.15.21",
42
40
  "@types/sanitize-html": "^2.16.0",
43
41
  "@types/validator": "13.15.1",
42
+ "axios": "^1.9.0",
44
43
  "chalk": "^5.4.1",
45
44
  "chrono-node": "^2.8.0",
46
45
  "cli-table3": "^0.6.5",
47
46
  "dotenv": "^16.5.0",
48
47
  "express": "^5.1.0",
48
+ "fast-xml-parser": "^5.2.3",
49
49
  "ignore": "^7.0.4",
50
50
  "jsonwebtoken": "^9.0.2",
51
51
  "openai": "^4.103.0",
@@ -55,6 +55,8 @@
55
55
  "ts-node": "^10.9.2",
56
56
  "typescript": "^5.8.3",
57
57
  "validator": "13.15.0",
58
+ "vega": "^6.1.2",
59
+ "vega-lite": "^6.1.0",
58
60
  "winston": "^3.17.0",
59
61
  "winston-daily-rotate-file": "^5.0.0",
60
62
  "yargs": "^17.7.2",