Charted
The Charted server lets AI agents generate charts and visualizations from structured data or CSV input without writing code.
Create charts from structured data (
create_chart): Generate charts by passing data as lists or dicts, supporting 14 chart types (bar,column,line,scatter,bubble,pie,polar_area,radar,area,box,histogram,heatmap,gantt,combo) plus anautomode that picks the best type based on data shape.Create charts from raw CSV text (
chart_from_csv): Automatically parse CSV content, detect numeric columns as data series, and generate a chart — no pre-processing required.Control output format: Return charts as raw SVG markup, HTML-wrapped SVG, a data URL for use in
<img>tags, or a rasterized PNG image for inline display in chat UIs.Apply themes and palettes: Use built-in presets (
light,dark,high-contrast) or named color palettes (viridis,inferno,ocean,okabe-ito, etc.), or pass a custom theme config object.Customize chart appearance: Set title, width, height, axis labels, series names, and PNG resolution scale.
Save charts to disk: Optionally save the generated chart to a specified file path in addition to returning it in the response.
Discover chart types (
list_chart_types): Get a full list of supported chart types with descriptions to help choose the right one.Discover themes (
list_themes): Get all available theme presets and named palettes, plus a usage hint for combining them.
Allows charts to render inline automatically in Jupyter notebooks.

Zero-dependency charts for Python and AI agents. SVG and PNG, 15 chart types, a one-line MCP server, and a Claude skill.
Quickstart
uv add chartedOr with pip:
pip install chartedfrom charted import BarChart
chart = BarChart(
title="Sales by Quarter",
data=[120, 180, 210, 150],
labels=["Q1", "Q2", "Q3", "Q4"],
)
chart.save("chart.svg")
chart.save("chart.png") # PNG export (requires cairosvg)That is the whole loop: pass a list of numbers, get back an image. No numpy, no pandas, no config files.
Core principle: charted itself has zero runtime dependencies. PNG export and MCP server support are opt-in extras that pull in their own dependencies, and the base library stays pure Python.
Related MCP server: Table Charts
Gallery
Every chart type, rendered. Each image below was generated by charted itself from the example script; the Quick Tour shows the code behind each one.
Bar | Column | Line |
Scatter | Pie | Area |
Radar | Box Plot | Histogram |
Heatmap | Gantt | Bubble |
Combo | Polar Area |
Sankey diagrams are also supported; see the Quick Tour for the flow and funnel examples.
Integration
Run the MCP server with no install using uvx:
uvx --from charted[mcp] charted-mcpThis fetches charted with the MCP extra into a throwaway environment and starts the server over stdio, so an agent can generate charts without you adding charted to a project or virtualenv.
Client config
Client | Setup |
Claude Code |
|
Cursor, Cline, other clients | Add the JSON below to the client's MCP server config |
{
"mcpServers": {
"charted": {
"command": "uvx",
"args": ["--from", "charted[mcp]", "charted-mcp"]
}
}
}The server exposes create_chart, list_chart_types, list_themes, and
chart_from_csv. See the MCP Server section
below for tool details and the pip install path.
Claude Skill
Install the chart Skill in one line:
claude skill install chartedChart gallery
The Quick Tour renders every chart type with the code that
produced it. The same examples live in docs/examples/.
Why Charted?
Zero runtime dependencies: pure Python, no numpy/pandas required
15 chart types: Bar, Column, Line, Scatter, Pie, Area, Radar, Box Plot, Histogram, Heatmap, Gantt, Bubble, Combo, Polar Area, Sankey
Multi-series support: stacked, side-by-side, grouped layouts
Negative values handled: proper zero baseline calculations
SVG and PNG output: SVG natively, PNG via optional
cairosvg(pip install charted[png])Theme system: 3 built-in presets + custom theme composition
Per-series styling: granular control with SeriesStyle builders
Data loading: CSV/JSON parsers built-in
Markdown export: generate embed-ready markdown snippets
CLI included: create charts without writing Python code
Jupyter ready: charts render inline automatically
Base Chart class: unified API for dynamic chart type selection
Quick Tour
Every chart type shares the same simple interface: pass data, labels, dimensions, and a title:
from charted.charts import BarChart, LineChart, PieChart
# Bar: single series with negatives
BarChart(
title="Profit/Loss by Region ($M)",
data=[-12, 34, -8, 52, -5, 28, 41, -19, 15, 60],
labels=["North", "South", "East", "West", "Central", "Pacific", "Atlantic", "Mountain", "Plains", "Metro"],
width=700, height=500,
).save("bar.svg")# Bar: multi-series side-by-side
BarChart(
title="Revenue vs Expenses by Quarter ($K)",
data=[[120, -45, 180, -30, 210, -60], [-80, -20, -95, -15, -110, -25]],
labels=["Q1 Prod", "Q1 Ops", "Q2 Prod", "Q2 Ops", "Q3 Prod", "Q3 Ops"],
width=700, height=500,
).save("bar_multi.svg")# Bar: stacked
BarChart(
title="Budget by Department ($K)",
data=[[100, -50, 120], [80, 60, -40]],
labels=["Q1", "Q2", "Q3"],
series_names=["Revenue", "Expenses"],
x_stacked=True, width=700, height=400,
).save("bar_stacked.svg")# Bar: side-by-side with negatives
BarChart(
title="Revenue vs Expenses by Quarter ($K)",
data=[[120, 180, 210], [-80, -95, -110]],
labels=["Q1", "Q2", "Q3"],
series_names=["Revenue", "Expenses"],
width=700, height=400,
).save("bar_sidebyside.svg")# Column: multi-series with negatives
from charted.charts import ColumnChart
ColumnChart(
title="Year-over-Year Growth Rate (%) by Segment",
data=[[12, -8, 22, 18, -5, 30], [-3, -15, 5, -2, -20, 8], [9, -23, 17, 16, -25, 38]],
labels=["Q1", "Q2", "Q3", "Q4", "Q5", "Q6"],
width=700, height=500,
theme={"v_padding": 0.12, "h_padding": 0.10},
).save("column.svg")# Column: stacked (default for multi-series)
ColumnChart(
title="Year-over-Year Growth by Segment",
data=[[12, 22, 30], [-8, -15, -20], [4, 7, 10]],
labels=["Q1", "Q2", "Q3"],
series_names=["Revenue", "Costs", "Net"],
width=700, height=400,
).save("column_stacked.svg")# Column: side-by-side
ColumnChart(
title="Sales Performance by Region",
data=[[45, 52, 38, 61], [38, 46, 52, 49], [52, 39, 46, 51]],
labels=["Q1", "Q2", "Q3", "Q4"],
series_names=["North", "South", "East"],
width=700, height=400, y_stacked=False,
).save("column_sidebyside.svg")# Line: multi-series signal data
import math
from charted.charts import LineChart
n = 20
LineChart(
title="Signal Analysis: Raw vs Filtered vs Baseline",
data=[
[math.sin(i * 0.5) * 30 + (i % 7 - 3) * 5 for i in range(n)],
[math.sin(i * 0.5) * 25 for i in range(n)],
[math.sin(i * 0.5) * 10 - 5 for i in range(n)],
],
labels=[str(i) for i in range(n)],
width=700, height=400,
).save("line.svg")# Line: XY mode with temperature anomaly data
years = list(range(1990, 2010))
anomalies = [-15, -5, 10, 20, 5, 25, 15, 30, 10, 20, 40, 25, 45, 30, 50, 35, 60, 55, 45, 70]
baseline = [round(5 + 2 * math.sin(i * 0.4) + i * 0.5, 1) for i in range(len(years))]
LineChart(
title="Temperature Anomaly vs 5-Year Rolling Baseline (1990-2009)",
data=[anomalies, baseline],
x_data=years,
labels=[str(y) for y in years],
width=700, height=400,
).save("xy_line.svg")# Line: single series
LineChart(
title="Monthly Active Users (K)",
data=[[42, 48, 55, 61, 58, 70, 80, 78, 85, 92, 88, 100]],
labels=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
series_names=["MAU"], width=700, height=400,
).save("line_single.svg")# Line: log scale (opt-in) for data spanning orders of magnitude.
# Pass y_scale="log" (or x_scale="log"); a LogScale instance also works.
LineChart(
title="Requests/sec (log scale)",
data=[[1, 8, 60, 450, 3200, 25000]],
labels=["t0", "t1", "t2", "t3", "t4", "t5"],
series_names=["rps"],
y_scale="log",
width=700, height=400,
).save("line_log.svg")# Scatter: multi-series cluster analysis
import random
from charted.charts import ScatterChart
random.seed(42)
ca_x = [30 + random.gauss(0, 8) for _ in range(20)]
ca_y = [40 + random.gauss(0, 8) for _ in range(20)]
cb_x = [70 + random.gauss(0, 10) for _ in range(20)]
cb_y = [20 + random.gauss(0, 10) for _ in range(20)]
ScatterChart(
title="Cluster Analysis: Two Distinct Populations",
x_data=[ca_x, cb_x], y_data=[ca_y, cb_y],
series_names=["Cluster A", "Cluster B"],
width=700, height=400,
).save("scatter.svg")# Scatter: single series with quadratic curve
random.seed(1)
x_vals = [i for i in range(5, 95, 5)]
y_vals = [round(10 + (v - 50) ** 2 / 50 + random.gauss(0, 4), 1) for v in x_vals]
ScatterChart(
title="U-Shaped Response Curve: Signal vs Input",
x_data=x_vals, y_data=y_vals,
series_names=["Observations"],
width=700, height=400,
).save("scatter_single.svg")# Pie: basic
from charted.charts import PieChart
PieChart(
title="Market Share by Product Line",
data=[35, 28, 18, 12, 7],
labels=["Product A", "Product B", "Product C", "Product D", "Other"],
width=600, height=500,
).save("pie.svg")# Pie: doughnut mode
PieChart(
title="Operating System Market Share",
data=[72, 15, 8, 5],
labels=["Windows", "macOS", "Linux", "Other"],
inner_radius=0.5, width=600, height=500,
).save("pie_doughnut.svg")# Radar: multi-series
from charted.charts import RadarChart
RadarChart(
title="Player Skill Comparison",
data=[[85, 90, 75, 88, 92], [70, 85, 90, 75, 80]],
labels=["Speed", "Strength", "Defense", "Technique", "Stamina"],
width=600, height=500,
).save("radar.svg")# Radar: single series
RadarChart(
title="Character Stats",
data=[20, 35, 30, 45, 25],
labels=["Speed", "Power", "Endurance", "Defense", "Skill"],
width=600, height=500,
).save("radar_multi.svg")# Area: CPU temperature over 24 hours
from charted.charts import AreaChart
temps = [42 + 10 * math.sin(i * 0.6) + (hash(str(i)) % 5 - 2) * 1.5 for i in range(24)]
AreaChart(
title="CPU Temperature (°C): 24-hour Cycle",
data=[round(t, 1) for t in temps],
labels=[f"{h}:00" for h in range(24)],
width=700, height=400,
).save("area.svg")# Area: multi-series revenue by channel
AreaChart(
title="Multi-series Area: Revenue by Channel",
data=[[30, 50, 45, 60, 70, 80, 65, 55], [20, 35, 30, 45, 50, 55, 40, 35]],
labels=["Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "Q7", "Q8"],
series_names=["Online", "Retail"],
width=700, height=400,
).save("area_multi.svg")# Box Plot: distribution quartiles with outliers
import random
from charted.charts import BoxPlot
random.seed(42)
box_a = [round(random.gauss(50, 10), 1) for _ in range(50)] + [95, 5, 102]
box_b = [round(random.gauss(70, 15), 1) for _ in range(50)] + [120, 30, 130]
box_c = [round(random.gauss(30, 8), 1) for _ in range(50)] + [55, 8, 60]
BoxPlot(
title="Test Scores by Group: with Outliers",
data=[box_a, box_b, box_c],
labels=["Group A", "Group B", "Group C"],
width=700, height=400,
).save("boxplot.svg")# Histogram: normal distribution (bell curve)
import random
from charted.charts import Histogram
random.seed(42)
scores = [random.gauss(50, 15) for _ in range(500)]
Histogram(
title="Exam Scores: Normal Distribution (500 Students, 10 Bins)",
data=scores,
bins=10, width=700, height=400,
).save("histogram.svg")# Heatmap: monthly temperature matrix
from charted.charts import HeatmapChart
HeatmapChart(
title="Average Temperature (°C): Monthly by City",
data=[
[35, 36, 38, 40, 43, 45, 47, 46, 44, 41, 38, 36],
[22, 24, 28, 32, 36, 40, 42, 41, 38, 33, 27, 23],
[15, 18, 22, 27, 32, 37, 40, 39, 35, 29, 22, 17],
[5, 8, 14, 20, 26, 32, 35, 34, 29, 22, 14, 7],
[-2, 2, 10, 18, 25, 31, 34, 33, 27, 19, 10, 3],
],
x_labels=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
y_labels=["Dubai", "Sydney", "Tokyo", "Berlin", "Moscow"],
width=700, height=450,
low_color="#21639e", high_color="#f97316",
show_values=True, value_format=".0f",
).save("heatmap.svg")# Gantt: software project timeline
from charted.charts import GanttChart
GanttChart(
title="Software Project Timeline: Q1 2026",
data=[(0, 2), (1, 4), (3, 6), (5, 8), (6, 9)],
labels=["Design", "Frontend", "Backend", "Testing", "Deployment"],
width=700, height=400,
dependencies=[(0, 1), (0, 2), (2, 3), (3, 4)],
show_today_line=True,
x_position=4.5,
).save("gantt.svg")# Sankey: flow diagram (d3-sankey layout)
from charted.charts import SankeyChart
SankeyChart(
title="Electricity generation to consumption",
nodes=["Coal", "Gas", "Solar", "Grid", "Homes", "Industry", "Export"],
links=[
("Coal", "Grid", 45), ("Gas", "Grid", 30), ("Solar", "Grid", 15),
("Grid", "Homes", 40), ("Grid", "Industry", 35), ("Grid", "Export", 15),
],
width=800, height=500,
).save("sankey.svg")Nodes are placed in columns by alignment, then relaxed over several iterations so
connected nodes line up and ribbons cross as little as possible. Link thickness is
proportional to flow value; the widths stacked at each node sum to the node's height.
Pass links as (source, target, value) tuples or {"source", "target", "value"}
dicts; endpoints may be node names or indices. Node labels in a dense column are
nudged apart vertically so they stay readable instead of overlapping.
The alignment option controls how nodes are assigned to columns: justify
(default, like d3-sankey) pushes every sink to the final column; left keeps each
node at its own depth; right aligns by distance from the sink; center is left
with sources pinned to the first column.
For funnel / drop-off data, use alignment="left". Funnels have dropout sinks
at many different depths (failed-filter, didn't-respond, cancelled, and so on). Under
the default justify those are all yanked into one tall final column, which crushes
the funnel shape. left lets each dropout terminate at its natural stage, so the
chart reads as a true funnel staircase:
# Funnel: recruitment drop-off, left-aligned so dropouts sit at their depth
SankeyChart(
title="Recruitment funnel",
nodes=[
"Applied", "Passed screen", "Failed screen",
"Interviewed", "Rejected", "Offered", "Declined", "Hired",
],
links=[
("Applied", "Passed screen", 320), ("Applied", "Failed screen", 680),
("Passed screen", "Interviewed", 140), ("Passed screen", "Rejected", 180),
("Interviewed", "Offered", 60), ("Interviewed", "Rejected", 80),
("Offered", "Hired", 45), ("Offered", "Declined", 15),
],
alignment="left",
width=900, height=400,
).save("funnel.svg")Theming
Three built-in presets (light, dark, high-contrast) plus custom theme composition:
from charted import BarChart
# Built-in themes
chart = BarChart(data=[120, 180, 210], labels=["Q1", "Q2", "Q3"], theme="light")
chart = BarChart(data=[120, 180, 210], labels=["Q1", "Q2", "Q3"], theme="dark")
chart = BarChart(data=[120, 180, 210], labels=["Q1", "Q2", "Q3"], theme="high-contrast")Theme | Preview |
Light | |
Dark | |
High Contrast |
See the Theming docs for custom palettes, font overrides, and per-series styling.
CLI Usage
Generate charts without writing Python:
# From CSV
python -m charted create bar output.svg --data sales.csv
# From JSON
python -m charted create column chart.svg -d data.json
# Set the title and dimensions
python -m charted create bar output.svg --data sales.csv \
--title "Q3 Sales" --width 900 --height 400
# Batch from directory
python -m charted batch input_data/ output_svg/--title, --width, and --height override the same values in a --config
file when you pass both.
CSV format (default):
The first column is the x-axis labels. Every other column is a data series.
Quarter,Revenue,Expenses
Q1,120,80
Q2,180,95
Q3,210,110Wide CSV (--transpose):
If your CSV is laid out sideways, with one series per row and the x values
across the header, pass --transpose. The corner cell is ignored, the rest of
the header row becomes the x-axis labels, and each following row is a series
named by its first cell. Without --transpose this layout would plot with the
axes swapped, so the flag is explicit rather than guessed.
Series,Q1,Q2,Q3
Revenue,120,180,210
Expenses,80,95,110python -m charted create column out.svg --data wide.csv --transposeJSON format:
{
"labels": ["Q1", "Q2", "Q3"],
"data": [[120, 180, 210], [80, 95, 110]],
"series_names": ["Revenue", "Expenses"]
}Full CLI docs: python -m charted --help
Data Loading
Load CSV/JSON without pandas:
from charted import load_csv, load_json, BarChart
# From CSV
x, y, labels = load_csv("sales.csv", x_col="Quarter", y_col="Revenue")
chart = BarChart(data=y, labels=x, title=labels[0])
chart.save("sales.svg")
# From JSON
x, y, labels = load_json("data.json")
chart = ColumnChart(data=y, labels=x)Jupyter Notebook
Charts render inline automatically, no extra setup needed:
from charted.charts import BarChart
chart = BarChart(
title="Sales by Quarter",
data=[120, 180, 210, 150],
labels=["Q1", "Q2", "Q3", "Q4"],
)
# Renders inline in the notebook cellMarkdown Export
from charted import BarChart
chart = BarChart(data=[120, 180, 210], labels=["Q1", "Q2", "Q3"], title="Sales")
# With file path
chart.save("docs/sales.svg")
md = chart.to_markdown(path="docs/sales.svg") # 
# As inline data URL
md = chart.to_markdown() # Data URL embedded in markdownBase Chart Class
Dynamically select chart type at runtime:
from charted import Chart
chart = Chart(
data=[120, 180, 210],
labels=["Q1", "Q2", "Q3"],
title="Sales",
chart_type="bar", # or column, line, scatter, pie, area, boxplot, histogram, heatmap, gantt
)
chart.save("chart.svg")
# Access all chart methods
svg = chart.to_svg()
md = chart.to_markdown()Installation
Library (use in your code):
uv add chartedOr with pip:
pip install chartedCLI or MCP server (standalone tool, no project install needed):
uvx charted ...
# or
pipx install chartedNote: a
uvx/pipx-installed package runs in its own isolated environment and cannot be imported into your project code. Useuv addorpip installwhen you want toimport chartedin your scripts.
Optional extras (these add dependencies, the core library stays zero-dep):
uv add "charted[png]" # PNG export via cairosvg
uv add "charted[mcp]" # MCP server for AI agent integration
uv add "charted[duckdb]" # generate charts from SQL queries
uv add "charted[dev]" # dev tools including PNG visual testing
# pip equivalents
pip install 'charted[png]'
pip install 'charted[mcp]'
pip install 'charted[duckdb]'
pip install 'charted[dev]'PNG Export
Save charts directly as PNG by using the .png extension:
chart = BarChart(data=[10, 20, 30], labels=["A", "B", "C"])
chart.save("chart.svg") # SVG (no extra dependencies)
chart.save("chart.png") # PNG (requires cairosvg)
chart.save("chart.png", scale=3) # PNG at 3x resolutionPNG export requires cairosvg. If it's not installed, save() raises a helpful ImportError with install instructions.
MCP Server (AI Agent Integration)
Charted includes an MCP server so AI agents (Claude Code, Cursor, etc.) can generate charts without writing Python:
# Run standalone (no install step, recommended)
uvx --from 'charted[mcp]' charted-mcp
# Or install the extra and run directly
pip install 'charted[mcp]'
charted-mcp
# Register with Claude Code
claude mcp add charted -- uvx --from 'charted[mcp]' charted-mcpExposes tools: create_chart, list_chart_types, list_themes, chart_from_csv. The charted[mcp] extra is required.
create_chart and chart_from_csv take an output_format of svg, html, data_url, or png. With output_format="png" the tool returns a rasterized PNG image, so an agent can show the chart inline in a chat UI instead of relaying raw SVG markup. The mcp extra includes cairosvg, so PNG output works out of the box with the uvx command above. PNG rasterization needs cairo's system libraries; on Debian/Ubuntu install them with apt install libcairo2.
More features
A few extra parts of the public API that the examples above don't cover.
Auto chart selection. auto(data, **kwargs) picks a chart type from the shape of the data (1D becomes a bar or pie, a matrix becomes a heatmap, and so on) and returns a chart instance. auto_size(data, width, height) returns the (width, height) it would scale a dataset up to when you don't pass explicit dimensions.
from charted import auto
chart = auto([10, 20, 30], title="Sales")Build from a dict or DataFrame. from_dict({"data": ..., "chart_type": ...}) builds a chart from a config dict. from_dataframe(df) takes a pandas DataFrame (or a plain dict of column to list if pandas isn't installed) and uses the first numeric column as the data and the index or first string column as the labels.
from charted import from_dict, from_dataframe
chart = from_dict({"chart_type": "line", "data": [1, 2, 3]})
chart = from_dataframe(df) # falls back to a dict if pandas is missingInline and data-URL embedding. inline_svg(path) reads an SVG file back as a string for embedding in HTML or notebooks. chart_to_data_url(path) returns the same SVG URL-encoded as a data:image/svg+xml,... URI you can drop straight into an <img> tag or markdown image.
Save and restore a chart's config. chart.to_config() serializes a chart to a JSON-friendly dict (dimensions, data, labels, scales, reference lines, annotations, and so on). Chart.from_config(config, **overrides) rebuilds the chart from that dict, with keyword overrides merged on top so you can tweak one value without rebuilding the whole config.
config = chart.to_config()
chart2 = Chart.from_config(config, title="Updated title")Fluent styling. chart.style(**kwargs) applies theme overrides and returns the chart for chaining, so you can set things like background_color or legend_font_size after construction.
chart = BarChart(data=[1, 2, 3]).style(background_color="#fff", legend_font_size=12)Hover tooltips in HTML. chart.to_html(tooltips=True) attaches a native SVG <title> to each data mark so browsers show a built-in hover tooltip with no JavaScript. This only affects the HTML output; to_svg() and save() are unchanged.
Named palettes. resolve_palette(name) turns one of the built-in palette names into a list of hex colors you can pass as colors=. The names live in NAMED_PALETTES: default, viridis, ocean, categorical, rainbow, monochrome, pastel, sunset, forest, inferno, and the colourblind-safe okabe-ito.
from charted import resolve_palette
chart = BarChart(data=[1, 2, 3], colors=resolve_palette("viridis"))Reference lines. Pass reference_lines=[{"value": 50, "axis": "y", "label": "Target"}] to draw a horizontal or vertical line at a value with an optional label. axis is "y" for a horizontal line (the default) or "x" for a vertical one.
Annotations. Pass annotations=[...] using LineAnnotation, BoxAnnotation, or LabelAnnotation to mark up the plot with lines, shaded regions, or text.
from charted import BarChart, LineAnnotation, BoxAnnotation, LabelAnnotationLog and time scales. Pass x_scale= or y_scale= as "log" for a logarithmic axis or "time" for a time axis (which accepts dates, datetimes, or ISO date strings as x values). The default is "linear". Log and time scales are rejected on the value axis of a bar or column chart, since those fill from a zero baseline.
LineChart(data=[1, 10, 100, 1000], labels=["a", "b", "c", "d"], y_scale="log")Links
Font System
Charted avoids tkinter by using pre-defined font metrics in fonts/definitions/. Generate new font definitions:
uv run python charted/commands/create_font_definition.py HelveticaAvailable Tools
4 toolschart_from_csvA
Parse raw CSV text and generate a chart directly from it, without requiring the caller to pre-process the data. The tool auto-detects numeric columns for the y-axis and uses the first non-numeric column as x-axis labels. Use this when the user provides CSV content directly; for data already in a Python list or dict use create_chart instead.
| Name | Required | Description | Default |
|---|---|---|---|
| csv_data | Yes | Complete CSV text including a header row. Columns are auto-classified as labels (non-numeric) or data series (numeric). Newlines should be literal line breaks. Example header row: Month,Sales. | |
| chart_type | No | The type of chart to render. Defaults to 'auto', which picks the best type based on the data shape. See list_chart_types for descriptions of each option. | auto |
| x_column | No | Name of the CSV column to use as x-axis category labels. If omitted, the first non-numeric column is used automatically. Example: 'Month'. | |
| y_columns | No | Names of CSV columns to plot as data series. If omitted, all numeric columns (except x_column) are used. Example: ['Revenue', 'Costs']. | |
| title | No | Optional chart title rendered above the plot area. Example: 'Q1 Sales by Region'. | |
| theme | No | Visual theme. Pass a preset name ('light', 'dark', 'high-contrast') or a palette name ('viridis', 'inferno', etc.) as a string, or a config object. Use list_themes to see all options. | |
| output_format | No | Format for the returned chart: 'svg' (default), 'html', 'data_url' (SVG data URL), or 'png' (rasterized image, requires charted[png]). | svg |
| scale | No | Resolution multiplier for 'png' output. Default 2 gives high-DPI output. Has no effect for SVG formats. | |
| save_path | No | Optional file path to also save the chart on disk. Extension should match output_format, e.g. 'output/chart.svg'. The tool still returns the chart content in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses that the tool auto-detects numeric columns for y-axis and first non-numeric column as x-axis labels, which adds value beyond the schema. However, it does not mention error handling, CSV size limits, or the fact that it requires a header row (though schema covers this). No annotations are present, so the description carries the burden.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no fluff. First sentence states the core function and auto-detection; second provides usage guidelines and sibling tool reference. Every sentence serves a purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity (9 parameters, multiple output formats, themes), the description covers the essential use case and distinguishes from siblings. It does not mention output_format, theme, or save_path, but these are fully documented in the schema (100% coverage). The description completes the agent's decision-making context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description adds value by explaining the auto-detection logic for x_column and y_columns, which is not present in the schema param descriptions. This helps the agent understand the default behavior without reading the schema in detail.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool parses raw CSV text and generates a chart, with automatic column detection. It distinguishes itself from create_chart by specifying the input format (CSV text vs. Python list/dict).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells when to use this tool ('when the user provides CSV content directly') and when to use create_chart instead ('for data already in a Python list or dict'). Provides clear context and alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_chartA
Generate an SVG, HTML, data-URL, or rasterized PNG chart from structured data. Supports 14 chart types (bar, column, line, scatter, bubble, pie, polar_area, radar, area, box, histogram, heatmap, gantt, combo) plus 'auto' which picks the best type automatically. Use this when you have data already in memory as a list or dict; for CSV input prefer chart_from_csv instead.
| Name | Required | Description | Default |
|---|---|---|---|
| chart_type | Yes | The type of chart to render. 'auto' inspects the data shape and picks the most appropriate type. Examples: 'bar' for horizontal category comparison, 'line' for time-series trends, 'pie' for part-of-whole proportions, 'scatter' for XY correlation, 'heatmap' for a 2-D color matrix. | |
| data | Yes | Chart data. For a single series pass a 1-D list of numbers, e.g. [10, 20, 15]. For multiple series pass a 2-D list where each inner list is one series, e.g. [[10, 20], [5, 30]]. Scatter/bubble charts expect [[x_values], [y_values]]. | |
| labels | No | Category labels for the x-axis, one per data point. Example: ['Jan', 'Feb', 'Mar']. Omit for numeric or auto-indexed axes. | |
| title | No | Optional chart title rendered above the plot area. Example: 'Monthly Revenue 2024'. | |
| series_names | No | Display names for each data series, shown in the chart legend. Must match the number of series in data. Example: ['Revenue', 'Costs']. | |
| width | No | Chart canvas width in pixels. Default is 500. Increase for more detail or wider labels. | |
| height | No | Chart canvas height in pixels. Default is 500. Adjust to control aspect ratio. | |
| theme | No | Visual theme. Pass a preset name ('light', 'dark', 'high-contrast') or a palette name ('viridis', 'inferno', 'ocean', etc.) as a string, or a config object such as {'colors': 'viridis', 'background_color': '#1a1a2e'}. Use list_themes to see all available options. | |
| output_format | No | Format for the returned chart. 'svg' returns raw SVG markup (default, zero extra dependencies). 'html' wraps the SVG in a minimal HTML page. 'data_url' returns an SVG data URL suitable for use in an img src attribute. 'png' rasterizes the chart to a PNG image (requires charted[png]) and returns it as inline image content visible in chat UIs. | svg |
| scale | No | Resolution multiplier applied when output_format is 'png'. A value of 2 (default) doubles the pixel dimensions for high-DPI displays. Has no effect for SVG output. | |
| save_path | No | Optional absolute or relative file path where the chart should also be saved on disk. The file extension should match output_format (e.g. 'chart.svg' or 'chart.png'). The tool still returns the chart content regardless. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries the burden. It explains output formats and optional file saving, but does not disclose behavioral traits like idempotency, safety, or side effects beyond creation. Lacks explicit statement that this is a read-only operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Description is two sentences with a list of chart types. Front-loaded with main purpose, no wasted words. Efficient and structured well.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 11 parameters fully described in schema and no output schema, the description covers main purpose and usage context. Does not explain return values or error handling, but for a chart generation tool the output is self-evident from parameters. Reasonably complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers all 11 parameters with descriptions (100% coverage). Description adds value by explaining the 'auto' chart type behavior and the difference from chart_from_csv, but does not add meaning beyond schema for individual parameters. Baseline 3 + minor extra context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool generates charts from structured data and lists 14 supported chart types plus 'auto'. It distinguishes itself from sibling tool chart_from_csv by specifying when to use each.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit guidance: 'Use this when you have data already in memory as a list or dict; for CSV input prefer chart_from_csv instead.' This clearly tells the agent when to use and when not to use this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_chart_typesA
Return the full list of chart types supported by charted, each with its type identifier and a short description of what it is best suited for. Call this before create_chart or chart_from_csv when you are unsure which chart_type to choose.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully discloses the tool's behavior: it returns a list of chart types with identifiers and descriptions. No side effects or additional behaviors are expected for this read-only operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two well-structured sentences: one for purpose and one for usage guidance. No redundant or irrelevant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with no parameters and no output schema, the description is complete: it explains what is returned, the format, and when to use it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has no parameters, and schema coverage is 100%. The description adds no parameter-specific information, which is appropriate. Baseline score of 3 applies per guidelines.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns the full list of chart types with identifiers and descriptions. It distinguishes from sibling tools like create_chart and chart_from_csv, which are about creating charts, and list_themes, which lists themes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly recommends calling this tool before create_chart or chart_from_csv when unsure about chart_type, providing clear when-to-use guidance and alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_themesA
Return all available theme presets and named color palettes that can be passed to the theme parameter of create_chart or chart_from_csv. Also returns a usage hint showing how to combine a palette with custom background and foreground colors.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It indicates a read-only operation (returning presets) with no destructive hints. The extra mention of a usage hint adds behavioral context. A small gap: it doesn't state that it has no side effects, but that's obvious given the nature.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no wasted words. The first sentence states the primary function, the second adds a usage hint. Perfectly front-loaded and efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero parameters, no output schema, and no annotations, the description fully covers what the tool does and how its output should be used. It is complete and sufficient for an agent to decide when to invoke this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are no parameters, and schema description coverage is 100%. The description adds value by explaining the purpose of the returned data and how to use it with sibling tools, going beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns 'all available theme presets and named color palettes' for use with specific sibling tools (create_chart, chart_from_csv), distinguishing it from list_chart_types which lists chart types.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells when to use this tool: to obtain theme presets and palettes for the theme parameter of create_chart or chart_from_csv. It implies not for other purposes and provides a specific usage hint.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v1.0.1- Changed
chart_from_csv9 fields changed- changed
Input schema / properties / chart_type / descriptionPrevious value: -"Chart type."New value: +"The type of chart to render. Defaults to 'auto', which picks the best type based on the data shape. See list_chart_types for descriptions of each option." - changed
Input schema / properties / csv_data / descriptionPrevious value: -"Raw CSV text (with header row)."New value: +"Complete CSV text including a header row. Columns are auto-classified as labels (non-numeric) or data series (numeric). Newlines should be literal line breaks. Example header row: Month,Sales." - added
Input schema / properties / output_format / descriptionAdded value: +"Format for the returned chart: 'svg' (default), 'html', 'data_url' (SVG data URL), or 'png' (rasterized image, requires charted[png])." - added
Input schema / properties / save_path / descriptionAdded value: +"Optional file path to also save the chart on disk. Extension should match output_format, e.g. 'output/chart.svg'. The tool still returns the chart content in the response." - changed
Input schema / properties / scale / descriptionPrevious value: -"Resolution multiplier for 'png' output."New value: +"Resolution multiplier for 'png' output. Default 2 gives high-DPI output. Has no effect for SVG formats." - added
Input schema / properties / theme / descriptionAdded value: +"Visual theme. Pass a preset name ('light', 'dark', 'high-contrast') or a palette name ('viridis', 'inferno', etc.) as a string, or a config object. Use list_themes to see all options." - added
Input schema / properties / title / descriptionAdded value: +"Optional chart title rendered above the plot area. Example: 'Q1 Sales by Region'." - changed
Input schema / properties / x_column / descriptionPrevious value: -"Column name for x-axis labels."New value: +"Name of the CSV column to use as x-axis category labels. If omitted, the first non-numeric column is used automatically. Example: 'Month'." - changed
Input schema / properties / y_columns / descriptionPrevious value: -"Column names for y-axis data series."New value: +"Names of CSV columns to plot as data series. If omitted, all numeric columns (except x_column) are used. Example: ['Revenue', 'Costs']."
- Changed
create_chart11 fields changed- changed
Input schema / properties / chart_type / descriptionPrevious value: -"Chart type. 'auto' picks the best type from data shape."New value: +"The type of chart to render. 'auto' inspects the data shape and picks the most appropriate type. Examples: 'bar' for horizontal category comparison, 'line' for time-series trends, 'pie' for part-of-whole proportions, 'scatter' for XY correlation, 'heatmap' for a 2-D color matrix." - changed
Input schema / properties / data / descriptionPrevious value: -"Chart data. 1D array for single series, 2D for multi-series."New value: +"Chart data. For a single series pass a 1-D list of numbers, e.g. [10, 20, 15]. For multiple series pass a 2-D list where each inner list is one series, e.g. [[10, 20], [5, 30]]. Scatter/bubble charts expect [[x_values], [y_values]]." - changed
Input schema / properties / height / descriptionPrevious value: -"Chart height in pixels."New value: +"Chart canvas height in pixels. Default is 500. Adjust to control aspect ratio." - changed
Input schema / properties / labels / descriptionPrevious value: -"X-axis category labels."New value: +"Category labels for the x-axis, one per data point. Example: ['Jan', 'Feb', 'Mar']. Omit for numeric or auto-indexed axes." - changed
Input schema / properties / output_format / descriptionPrevious value: -"Output format. 'png' returns a rasterized PNG image (needs charted[png]); use it when the caller needs a picture rather than SVG markup."New value: +"Format for the returned chart. 'svg' returns raw SVG markup (default, zero extra dependencies). 'html' wraps the SVG in a minimal HTML page. 'data_url' returns an SVG data URL suitable for use in an img src attribute. 'png' rasterizes the chart to a PNG image (requires charted[png]) and returns it as inline image content visible in chat UIs." - changed
Input schema / properties / save_path / descriptionPrevious value: -"Optional file path to save the chart."New value: +"Optional absolute or relative file path where the chart should also be saved on disk. The file extension should match output_format (e.g. 'chart.svg' or 'chart.png'). The tool still returns the chart content regardless." - changed
Input schema / properties / scale / descriptionPrevious value: -"Resolution multiplier for 'png' output."New value: +"Resolution multiplier applied when output_format is 'png'. A value of 2 (default) doubles the pixel dimensions for high-DPI displays. Has no effect for SVG output." - changed
Input schema / properties / series_names / descriptionPrevious value: -"Names for each data series (used in legend)."New value: +"Display names for each data series, shown in the chart legend. Must match the number of series in data. Example: ['Revenue', 'Costs']." - changed
Input schema / properties / theme / descriptionPrevious value: -"Theme preset name or config object."New value: +"Visual theme. Pass a preset name ('light', 'dark', 'high-contrast') or a palette name ('viridis', 'inferno', 'ocean', etc.) as a string, or a config object such as {'colors': 'viridis', 'background_color': '#1a1a2e'}. Use list_themes to see all available options." - changed
Input schema / properties / title / descriptionPrevious value: -"Chart title."New value: +"Optional chart title rendered above the plot area. Example: 'Monthly Revenue 2024'." - changed
Input schema / properties / width / descriptionPrevious value: -"Chart width in pixels."New value: +"Chart canvas width in pixels. Default is 500. Increase for more detail or wider labels."
4 tool updates
v1.0.0- First observed
chart_from_csv - First observed
create_chart - First observed
list_chart_types - First observed
list_themes
TDQS
Scored across 4 tools
Each tool has a distinct and clearly defined purpose. chart_from_csv and create_chart are differentiated by input format (CSV vs structured data), and their descriptions explicitly guide the agent on when to use each. list_chart_types and list_themes serve separate informational roles with no ambiguity.
All tool names follow a consistent verb_noun pattern in snake_case: chart_from_csv, create_chart, list_chart_types, list_themes. The naming is predictable and matches the tool's action and subject.
With 4 tools, the server is well-scoped for its purpose: two creation tools for different input types and two informational tools for discovery. Each tool earns its place without unnecessary bloat or deficiency.
The tool set covers the core workflow of generating charts from data, including type and theme selection. Minor gaps exist, such as the absence of a tool to validate CSV or inspect default settings, but these are not critical for basic operation.
Maintenance
Related MCP Connectors
Renders interactive Chart.js charts and dashboards inline in AI conversations.
Renders interactive Chart.js charts and dashboards inline in AI conversations. Supports bar, line, area, pie, doughnut, scatter, and radar charts with multi-chart dashboard grids.
Charts and tables for AI agents with live-updating embed links. No account: signup returns a key.
251Create, inspect, manage, and render charts and data visualizations as SVG/PNG or interactive embeds.
Related MCP Servers
- FlicenseAqualityDmaintenanceGenerates and downloads charts using QuickChart.io through Claude Desktop and other MCP clients.11-
- AlicenseAqualityAmaintenanceTableCharts MCP Server gives any LLM assistant the ability to instantly convert tabular data into beautiful, hosted, interactive dashboards. Simply provide JSON rows, raw CSV text, or a public URL (Notion page, Google Sheet, or Salesforce report), and get back a shareable dashboard URL plus an embeddable iframe code — all in one tool call. Supports 6 chart types: bar, line, area, pie, scatter, a213 npmMIT
- AlicenseNot gradedqualityNot gradedmaintenanceEnables AI agents to generate beautiful, presentation-ready charts (SVG + PNG) with zero setup, supporting various chart types and styling options.25 npmMIT
- AlicenseNot gradedqualityCmaintenanceA minimal, dependency-free MCP server that renders interactive widgets (like bar charts) inline in Claude, ChatGPT, and Grok from a custom connector.1MIT