UNPKG

hubble-mcp-tool

Version:
110 lines (109 loc) 4.08 kB
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; import fs from "fs/promises"; import { QUICKCHART_BASE_URL } from "../config/constants.js"; import { VALID_CHART_TYPES } from "../types/index.js"; import { ErrorHandler } from "../utils/errors.js"; /** * Service for handling chart generation and operations */ export class ChartService { /** * Validates that the chart type is supported */ static validateChartType(type) { if (!VALID_CHART_TYPES.includes(type)) { throw new McpError(ErrorCode.InvalidParams, `Invalid chart type. Must be one of: ${VALID_CHART_TYPES.join(", ")}`); } } /** * Generates a chart configuration object from input arguments */ static generateChartConfig(args) { const { type, labels, datasets, title, options = {} } = args; this.validateChartType(type); const config = { type: type, data: { labels: labels || [], datasets: datasets.map((dataset) => { // Extract known properties const { label, data, backgroundColor, borderColor, borderWidth, additionalConfig = {} } = dataset; // Create dataset with known properties const chartDataset = { label: label || "", data: data, backgroundColor: backgroundColor, borderColor: borderColor, }; // Add borderWidth if provided if (borderWidth !== undefined) { chartDataset.borderWidth = borderWidth; } // Add any additional properties from additionalConfig return { ...chartDataset, ...additionalConfig, }; }), }, options: { ...options, ...(title && { title: { display: true, text: title, }, }), }, }; // Special handling for specific chart types switch (type) { case "radialGauge": case "speedometer": if (!datasets?.[0]?.data?.[0]) { throw ErrorHandler.createInvalidParamsError(`${type} requires a single numeric value`); } config.options = { ...config.options, plugins: { datalabels: { display: true, formatter: (value) => value, }, }, }; break; case "scatter": case "bubble": datasets.forEach((dataset) => { if (!Array.isArray(dataset.data[0])) { throw ErrorHandler.createInvalidParamsError(`${type} requires data points in [x, y${type === "bubble" ? ", r" : ""}] format`); } }); break; } return config; } /** * Generates a QuickChart URL from a chart configuration */ static async generateChartUrl(config) { const encodedConfig = encodeURIComponent(JSON.stringify(config)); return `${QUICKCHART_BASE_URL}&c=${encodedConfig}`; } /** * Downloads a chart image to a local file */ static async downloadChart(config, outputPath) { try { const url = await this.generateChartUrl(config); const response = await axios.get(url, { responseType: "arraybuffer" }); await fs.writeFile(outputPath, response.data); return outputPath; } catch (error) { throw ErrorHandler.handleError(error, "Failed to download chart"); } } }