Table of Contents

MCP server — charts for an AI agent

MatPlotLibNet.Mcp is a Model Context Protocol server: a .NET tool that an MCP host — Claude Code, Claude Desktop, VS Code — starts over stdio, so a model can render this library's charts and look at the result.

Install

Ships with 1.15.0. Until that release is published the server runs from this repository (dotnet run --project Src/MatPlotLibNet.Mcp), not from nuget.org.

The package is a .NET tool; the .NET 10 SDK resolves and runs it, so there is nothing to install by hand.

{
  "servers": {
    "MatPlotLibNet.Mcp": {
      "type": "stdio",
      "command": "dnx",
      "args": ["MatPlotLibNet.Mcp", "--yes"]
    }
  }
}

For Claude Code: claude mcp add matplotlibnet -- dnx MatPlotLibNet.Mcp --yes.

One optional setting, MATPLOTLIBNET_MCP_OUTPUT_ROOT: the only directory save_chart may write under. It defaults to the system temp directory.

The four tools

tool what it does
render_chart(spec, format) renders the spec and returns a PNG the model can see, with a text summary beside it
save_chart(spec, path, format, overwrite) writes PNG, SVG or PDF to a file and returns the path it wrote
list_chart_types() the chart types a spec may name
describe_chart_schema(seriesType) the fields of a spec, or of one chart type, with a worked example

SVG and PDF are not returned inline — a single SVG of a normal chart is tens of thousands of characters of the model's context. save_chart writes them to a file instead.

The spec

A spec is the library's own figure JSON: the same document figure.ToJson() writes and ChartSerializer.FromJson reads. There is one definition of a chart, so anything the library draws, an agent can ask for.

{
  "width": 800,
  "height": 600,
  "title": "Revenue",
  "subPlots": [
    {
      "xAxis": { "label": "Quarter" },
      "yAxis": { "label": "€M" },
      "series": [
        { "type": "line", "xData": [1, 2, 3, 4], "yData": [12, 18, 15, 22], "label": "2026", "color": "steelblue" }
      ]
    }
  ]
}

The same JSON in C#:

using MatPlotLibNet;
using MatPlotLibNet.Styling;

string spec = Plt.Create()
    .WithSize(800, 600)
    .WithTitle("Revenue")
    .SetXLabel("Quarter")
    .SetYLabel("€M")
    .Plot([1, 2, 3, 4], [12, 18, 15, 22], s => { s.Label = "2026"; s.Color = Color.FromName("steelblue"); })
    .Build()
    .ToJson(indented: true);

That is the fastest way to learn the format: build the chart you want with the fluent API, print its JSON, and hand that shape to the model.

It refuses before it renders

FromJson is a round-trip reader for the library's own writer: it drops an unknown series type, skips an unknown property and ignores a misspelled enum value, because a newer document must still load in an older build. Handed a document a model typed, that lenience turns every typo into a blank chart reported as a success. So the server validates first, and the message names the field:

what the model wrote what it gets back
"type": "lien" 'lien' at $.subPlots[0].series[0].type is not a chart type this server lists — did you mean 'line'?
"type": "Line" the same, with 'line' — the serializer's lookup is case-sensitive
"x": [...] Unknown field 'x' at $.subPlots[0].series[0]. describe_chart_schema lists the fields a spec accepts.
"scale": "logarithmic" 'logarithmic' … is not an accepted value; accepted: Linear, Log, SymLog, Logit, Date.
"color": "reddish" 'reddish' … is not a colour the library knows: use a CSS4 colour name or #RRGGBB.
xData of 3, yData of 1 The series at $.subPlots[0].series[0] has 3 'xData' values and 1 'yData' values; they must match.
no width rendered at the library default, 800×600
"width": 16000 'width' must be between 1 and 4000 … the picture goes into a context window.

Colour names go through the library's own CSS4 table, and #f00 is expanded, so a model may write "red", "CornflowerBlue" or #6495ED.

What comes back

render_chart returns two blocks: the PNG, and a summary of what was drawn.

"Revenue" — 800×600, 1 subplot, 1 series
  · line "2026", 4 points, x 1…4, y 12…22

The ranges come from the same ComputeDataRange call the renderer makes to place the axes, so the text and the picture cannot disagree. A count is omitted rather than guessed: a pie has slices, not points.

Two things worth knowing

The SVG this server writes carries glyph outlines, not <text>. The PNG backend is loaded in the same process, and it installs a glyph-path provider so text is drawn with the library's own embedded font everywhere. Self-contained, and identical to the PNG — but not machine-readable as text.

Seven chart types are not available through the spec. sankey, sunburst, treemap, polarbar, polarline, polarscatter and treegrid have a JSON reader that builds a fixed placeholder instead of reading the document, so the server refuses them by name rather than draw someone else's data. list_chart_types names them with the reason.