Skip to main content
Glama
K02D

MCP Tabular Data Analysis Server

by K02D

generate_chart

Create visualizations from tabular data in CSV or SQLite files. Generate bar, line, scatter, histogram, pie, or box charts with customizable axes and grouping. Output charts as base64-encoded PNG images or save to files for data analysis.

Instructions

Generate a chart/visualization from tabular data.
Returns chart as base64-encoded PNG for display.

Args:
    file_path: Path to CSV or SQLite file
    chart_type: Type of chart - 'bar', 'line', 'scatter', 'histogram', 'pie', 'box'
    x_column: Column for X-axis (not needed for histogram/pie)
    y_column: Column for Y-axis values
    group_by: Optional column for grouping/coloring
    title: Chart title (auto-generated if not provided)
    output_format: 'base64' (default) or 'file' (saves to data/charts/)

Returns:
    Dictionary containing chart data as base64 or file path

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathYes
chart_typeYes
x_columnNo
y_columnNo
group_byNo
titleNo
output_formatNobase64

Implementation Reference

  • The generate_chart tool handler. Loads data, validates inputs, uses matplotlib to create various chart types (bar, line, scatter, histogram, pie, box), and returns either base64 PNG or saves to file.
    @mcp.tool()
    def generate_chart(
        file_path: str,
        chart_type: str,
        x_column: str | None = None,
        y_column: str | None = None,
        group_by: str | None = None,
        title: str | None = None,
        output_format: str = "base64",
    ) -> dict[str, Any]:
        """
        Generate a chart/visualization from tabular data.
        Returns chart as base64-encoded PNG for display.
        
        Args:
            file_path: Path to CSV or SQLite file
            chart_type: Type of chart - 'bar', 'line', 'scatter', 'histogram', 'pie', 'box'
            x_column: Column for X-axis (not needed for histogram/pie)
            y_column: Column for Y-axis values
            group_by: Optional column for grouping/coloring
            title: Chart title (auto-generated if not provided)
            output_format: 'base64' (default) or 'file' (saves to data/charts/)
        
        Returns:
            Dictionary containing chart data as base64 or file path
        """
        df = _load_data(file_path)
        
        valid_types = ['bar', 'line', 'scatter', 'histogram', 'pie', 'box']
        if chart_type not in valid_types:
            raise ValueError(f"Unknown chart_type: {chart_type}. Use: {valid_types}")
        
        # Set up the figure with a clean style
        plt.style.use('seaborn-v0_8-whitegrid')
        fig, ax = plt.subplots(figsize=(10, 6))
        
        # Generate chart based on type
        if chart_type == 'histogram':
            if y_column is None:
                numeric_cols = _get_numeric_columns(df)
                if not numeric_cols:
                    raise ValueError("No numeric columns found for histogram")
                y_column = numeric_cols[0]
            
            ax.hist(df[y_column].dropna(), bins=30, edgecolor='black', alpha=0.7, color='#4C72B0')
            ax.set_xlabel(y_column)
            ax.set_ylabel('Frequency')
            auto_title = f'Distribution of {y_column}'
            
        elif chart_type == 'pie':
            if x_column is None:
                cat_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
                if not cat_cols:
                    raise ValueError("No categorical columns found for pie chart")
                x_column = cat_cols[0]
            
            value_counts = df[x_column].value_counts().head(10)
            colors = plt.cm.Set3(np.linspace(0, 1, len(value_counts)))
            ax.pie(value_counts.values, labels=value_counts.index, autopct='%1.1f%%', colors=colors)
            auto_title = f'Distribution of {x_column}'
            
        elif chart_type == 'box':
            if y_column is None:
                numeric_cols = _get_numeric_columns(df)
                if not numeric_cols:
                    raise ValueError("No numeric columns found for box plot")
                y_column = numeric_cols[0]
            
            if group_by and group_by in df.columns:
                groups = df[group_by].unique()[:10]  # Limit to 10 groups
                data = [df[df[group_by] == g][y_column].dropna() for g in groups]
                ax.boxplot(data, labels=groups)
                ax.set_xlabel(group_by)
            else:
                ax.boxplot(df[y_column].dropna())
            ax.set_ylabel(y_column)
            auto_title = f'Box Plot of {y_column}'
            
        elif chart_type in ['bar', 'line', 'scatter']:
            if x_column is None or y_column is None:
                raise ValueError(f"Both x_column and y_column are required for {chart_type} chart")
            
            if x_column not in df.columns:
                raise ValueError(f"x_column '{x_column}' not found")
            if y_column not in df.columns:
                raise ValueError(f"y_column '{y_column}' not found")
            
            if group_by and group_by in df.columns:
                for name, group in df.groupby(group_by):
                    if chart_type == 'bar':
                        ax.bar(group[x_column].astype(str), group[y_column], label=str(name), alpha=0.7)
                    elif chart_type == 'line':
                        ax.plot(group[x_column], group[y_column], label=str(name), marker='o', markersize=4)
                    else:  # scatter
                        ax.scatter(group[x_column], group[y_column], label=str(name), alpha=0.7)
                ax.legend(title=group_by, loc='best')
            else:
                if chart_type == 'bar':
                    # For bar charts, aggregate if needed
                    if df[x_column].dtype == 'object':
                        agg = df.groupby(x_column)[y_column].mean()
                        ax.bar(agg.index.astype(str), agg.values, color='#4C72B0', alpha=0.7)
                    else:
                        ax.bar(df[x_column].astype(str).head(50), df[y_column].head(50), color='#4C72B0', alpha=0.7)
                elif chart_type == 'line':
                    ax.plot(df[x_column], df[y_column], marker='o', markersize=4, color='#4C72B0')
                else:  # scatter
                    ax.scatter(df[x_column], df[y_column], alpha=0.6, color='#4C72B0')
            
            ax.set_xlabel(x_column)
            ax.set_ylabel(y_column)
            auto_title = f'{y_column} by {x_column}'
            
            # Rotate x labels if needed
            if df[x_column].dtype == 'object' or chart_type == 'bar':
                plt.xticks(rotation=45, ha='right')
        
        # Set title
        ax.set_title(title or auto_title, fontsize=14, fontweight='bold')
        
        plt.tight_layout()
        
        # Save chart
        if output_format == 'file':
            charts_dir = _PROJECT_ROOT / 'data' / 'charts'
            charts_dir.mkdir(parents=True, exist_ok=True)
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            filename = f'{chart_type}_{timestamp}.png'
            filepath = charts_dir / filename
            plt.savefig(filepath, dpi=150, bbox_inches='tight')
            plt.close()
            
            return {
                "chart_type": chart_type,
                "file_path": str(filepath.relative_to(_PROJECT_ROOT)),
                "absolute_path": str(filepath),
            }
        else:
            # Return as base64
            buffer = io.BytesIO()
            plt.savefig(buffer, format='png', dpi=150, bbox_inches='tight')
            plt.close()
            buffer.seek(0)
            image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
            
            return {
                "chart_type": chart_type,
                "format": "png",
                "encoding": "base64",
                "image_data": image_base64,
                "display_note": "Use this base64 string to display the image",
            }

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/K02D/mcp-tabular'

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