Skip to main content
Glama

pubmed_generate_chart

Transform structured data from PubMed into customizable charts. Specify chart type, axes, and dimensions to generate a Base64-encoded PNG image for analysis and visualization.

Instructions

Generates a customizable chart (PNG) from structured data. Supports various plot types and requires data values and field mappings for axes. Returns a Base64-encoded PNG image.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chartTypeYesSpecifies the type of chart to generate.
dataValuesYesAn array of data objects to plot the chart (e.g., [{ 'year': '2020', 'articles': 150 }]).
heightNoThe height of the chart canvas in pixels.
outputFormatNoSpecifies the output format for the chart.png
seriesFieldNoThe field name for creating multiple data series on the same chart.
sizeFieldNoFor bubble charts, the field name for encoding bubble size.
titleNoThe main title displayed above the chart.
widthNoThe width of the chart canvas in pixels.
xFieldYesThe field name from `dataValues` for the X-axis.
yFieldYesThe field name from `dataValues` for the Y-axis.

Implementation Reference

  • Main handler function that implements the core logic for generating charts from input data using Chart.js and chartjs-node-canvas, returning base64 PNG.
    export async function pubmedGenerateChartLogic( input: PubMedGenerateChartInput, parentRequestContext: RequestContext, ): Promise<PubMedGenerateChartOutput> { const operationContext = requestContextService.createRequestContext({ parentRequestId: parentRequestContext.requestId, operation: "pubmedGenerateChartLogicExecution", input: sanitizeInputForLogging(input), }); logger.info( `Executing 'pubmed_generate_chart' with Chart.js. Chart type: ${input.chartType}`, operationContext, ); const { width, height, chartType, dataValues, xField, yField, title, seriesField, sizeField, } = input; const chartJSNodeCanvas = new ChartJSNodeCanvas({ width, height, chartCallback: (ChartJS) => { ChartJS.defaults.responsive = false; ChartJS.defaults.maintainAspectRatio = false; }, }); const labels = [ ...new Set(dataValues.map((item) => item[xField])), ] as string[]; let datasets: ChartConfiguration["data"]["datasets"]; if (seriesField) { const groupedData = groupDataBySeries( dataValues, xField, yField, seriesField, ); datasets = Array.from(groupedData.entries()).map(([seriesName, data]) => ({ label: seriesName, data: labels.map((label) => { const point = data.find((p) => p.x === label); return point ? (point.y as number) : null; }), })); } else { datasets = [ { label: yField, data: labels.map((label) => { const item = dataValues.find((d) => d[xField] === label); return item ? (item[yField] as number) : null; }), }, ]; } // For scatter and bubble charts, the data format is different if (chartType === "scatter" || chartType === "bubble") { if (seriesField) { const groupedData = groupDataBySeries( dataValues, xField, yField, seriesField, ); datasets = Array.from(groupedData.entries()).map( ([seriesName, data]) => ({ label: seriesName, data: data.map((point) => ({ x: point.x as number, y: point.y as number, r: chartType === "bubble" && sizeField ? (dataValues.find((d) => d[xField] === point.x)![ sizeField ] as number) : undefined, })), }), ); } else { datasets = [ { label: yField, data: dataValues.map((item) => ({ x: item[xField] as number, y: item[yField] as number, r: chartType === "bubble" && sizeField ? (item[sizeField] as number) : undefined, })), }, ]; } } const configuration: ChartConfiguration = { type: chartType, data: { labels: chartType !== "scatter" && chartType !== "bubble" ? labels : undefined, datasets: datasets, }, options: { plugins: { title: { display: !!title, text: title, }, }, scales: chartType === "pie" || chartType === "doughnut" || chartType === "polarArea" ? undefined : { x: { title: { display: true, text: xField, }, }, y: { title: { display: true, text: yField, }, }, }, }, }; try { const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration); const base64Data = imageBuffer.toString("base64"); logger.notice("Successfully generated chart with Chart.js.", { ...operationContext, chartType: input.chartType, dataPoints: input.dataValues.length, }); return { base64Data, chartType: input.chartType, dataPoints: input.dataValues.length, }; } catch (error: unknown) { const err = error as Error; throw new McpError( BaseErrorCode.INTERNAL_ERROR, `Chart generation failed: ${err.message || "Internal server error during chart generation."}`, { ...operationContext, originalErrorName: err.name, originalErrorMessage: err.message, }, ); } }
  • Zod input schema defining parameters for chart generation: type, dimensions, data, axes fields.
    export const PubMedGenerateChartInputSchema = z.object({ chartType: z .enum([ "bar", "line", "scatter", "pie", "doughnut", "bubble", "radar", "polarArea", ]) .describe("Specifies the type of chart to generate."), title: z .string() .optional() .describe("The main title displayed above the chart."), width: z .number() .int() .positive() .optional() .default(800) .describe("The width of the chart canvas in pixels."), height: z .number() .int() .positive() .optional() .default(600) .describe("The height of the chart canvas in pixels."), dataValues: z .array(z.record(z.string(), z.any())) .min(1) .describe( "An array of data objects to plot the chart (e.g., [{ 'year': '2020', 'articles': 150 }]).", ), outputFormat: z .enum(["png"]) .default("png") .describe("Specifies the output format for the chart."), xField: z .string() .describe("The field name from `dataValues` for the X-axis."), yField: z .string() .describe("The field name from `dataValues` for the Y-axis."), seriesField: z .string() .optional() .describe( "The field name for creating multiple data series on the same chart.", ), sizeField: z .string() .optional() .describe("For bubble charts, the field name for encoding bubble size."), });
  • Registers the 'pubmed_generate_chart' tool on the MCP server with description, input schema, and handler that calls the logic function.
    export async function registerPubMedGenerateChartTool( server: McpServer, ): Promise<void> { const operation = "registerPubMedGenerateChartTool"; const toolName = "pubmed_generate_chart"; const toolDescription = "Generates a customizable chart (PNG) from structured data. Supports various plot types and requires data values and field mappings for axes. Returns a Base64-encoded PNG image."; const context = requestContextService.createRequestContext({ operation }); await ErrorHandler.tryCatch( async () => { server.tool( toolName, toolDescription, PubMedGenerateChartInputSchema.shape, async ( input: PubMedGenerateChartInput, mcpProvidedContext: unknown, ): Promise<CallToolResult> => { const richContext: RequestContext = requestContextService.createRequestContext({ parentRequestId: context.requestId, operation: "pubmedGenerateChartToolHandler", mcpToolContext: mcpProvidedContext, input, }); try { const result = await pubmedGenerateChartLogic(input, richContext); return { content: [ { type: "image", data: result.base64Data, mimeType: "image/png", }, ], isError: false, }; } catch (error) { const handledError = ErrorHandler.handleError(error, { operation: "pubmedGenerateChartToolHandler", context: richContext, input, rethrow: false, }); const mcpError = handledError instanceof McpError ? handledError : new McpError( BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred while generating the chart.", { originalErrorName: handledError.name, originalErrorMessage: handledError.message, }, ); return { content: [ { type: "text", text: JSON.stringify({ error: { code: mcpError.code, message: mcpError.message, details: mcpError.details, }, }), }, ], isError: true, }; } }, ); logger.notice(`Tool '${toolName}' registered.`, context); }, { operation, context, errorCode: BaseErrorCode.INITIALIZATION_FAILED, critical: true, }, ); }
  • Helper utility to group input data by series field for creating multiple datasets in the chart.
    function groupDataBySeries( data: Record<string, unknown>[], xField: string, yField: string, seriesField: string, ) { const series = new Map<string, { x: unknown; y: unknown }[]>(); for (const item of data) { const seriesName = item[seriesField] as string; if (!series.has(seriesName)) { series.set(seriesName, []); } series.get(seriesName)!.push({ x: item[xField], y: item[yField] }); } return series; }

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/pubmed-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server