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

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

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",
            }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It does disclose key behavioral traits: the tool returns charts as base64-encoded PNGs or file paths, and it auto-generates titles if not provided. However, it lacks important details like error handling, performance characteristics, file format limitations (beyond CSV/SQLite), or whether the operation is read-only/destructive. The description adds some value but doesn't fully compensate for the absence of annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and appropriately sized. It starts with the core purpose, then covers the return format, followed by a clear parameter section with brief explanations. Each sentence earns its place, though the 'Returns' section could be slightly more concise. The information is front-loaded with the most important details first.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (7 parameters, data visualization functionality) with no annotations but an output schema, the description provides good coverage. It explains what the tool does, parameter meanings, and return formats. The output schema handles return value documentation, so the description appropriately focuses on usage. However, it could better address error cases, performance considerations, or integration with sibling tools to be fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description must compensate, and it does so effectively. It provides clear semantic explanations for all 7 parameters: what each parameter represents, optional vs. required status, valid values for chart_type, when x_column is not needed, default behaviors, and output format options. The description adds substantial meaning beyond the bare schema, though it could provide more detail about file path requirements or column name constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Generate a chart/visualization from tabular data.' It specifies the verb (generate) and resource (chart/visualization) with the data source (tabular data). However, it doesn't explicitly differentiate from sibling tools like 'analyze_time_series' or 'auto_insights' that might also involve visualization, leaving some ambiguity about when to choose this specific chart generation tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. With sibling tools like 'analyze_time_series' and 'auto_insights' that might overlap in visualization capabilities, there's no indication of when this specific chart generation tool is preferred. The description mentions basic usage context (tabular data) but lacks explicit when/when-not instructions or named alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

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

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