mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

Dev Tools

Flint: A Visualization DSL That Agents Can Actually Use

Microsoft's Flint compiles semantic chart specs into five backends. Here's how the DSL layer bridges LLM output and production rendering.

Source: github.com
Flint: A Visualization DSL That Agents Can Actually Use

Agents produce terrible charts. They hallucinate axis labels, guess at scales, and emit verbose JSON that breaks when data changes. Microsoft Research’s Flint solves this by introducing a semantic intermediate language that compiles to five different charting backends without forcing the agent to know about tick marks or padding.

The project hit 2,398 stars in days and includes a Model Context Protocol (MCP) server, which means you can wire it into Claude Desktop or any MCP-compatible agent runtime. The core insight: separate what the chart means from how it renders.

The Problem Flint Solves

Traditional charting libraries require explicit configuration of scales, axes, legends, spacing, and layout. When an LLM generates a Vega-Lite or ECharts spec, it must:

  • Infer data types from column names or sample values
  • Choose appropriate scales (linear, log, time)
  • Set axis domains and tick counts
  • Calculate layout dimensions based on label length
  • Handle edge cases like missing data or outliers

This bloats the context window and introduces failure modes. The agent either over-specifies (brittle) or under-specifies (broken).

Flint replaces this with a semantic layer. Instead of telling the backend “use a linear scale from 0 to 100,” you tell Flint “this column is a Temperature.” The compiler derives the rest.

Architecture: Semantic Types to Backend Configs

Flint’s compilation pipeline has three stages:

  1. Semantic inference: Parse the spec and data, assign semantic types
  2. Layout derivation: Calculate scales, axes, spacing from cardinality and types
  3. Backend emission: Generate native Vega-Lite, ECharts, Chart.js, Plotly, or Excel config

Semantic Type System

Flint ships with 70+ semantic types organized into categories:

CategoryExamplesInference Signals
QuantitativePrice, Temperature, PercentageUnit suffixes, value ranges
TemporalDate, Year, MonthISO strings, numeric year ranges
NominalCountry, City, CategoryString cardinality, known entity lists
OrdinalRank, Rating, PriorityOrdered discrete values

The compiler uses these types to:

  • Choose scale types (linear for Temperature, band for Country)
  • Format labels ($1,234 for Price, 23°C for Temperature)
  • Set default color palettes (diverging for Sentiment, sequential for Rank)
  • Derive axis titles from semantic names

Multi-Backend Compilation

A single Flint spec compiles to five backends. Here’s the flow:

import { compile } from 'flint-chart';

const spec = {
  data: [
    { country: 'USA', gdp: 21427 },
    { country: 'China', gdp: 14343 }
  ],
  mark: 'bar',
  encoding: {
    x: { field: 'country', type: 'Country' },
    y: { field: 'gdp', type: 'Price' }
  }
};

// Compile to different backends
const vegaLite = compile(spec, { backend: 'vega-lite' });
const echarts = compile(spec, { backend: 'echarts' });
const chartjs = compile(spec, { backend: 'chartjs' });
const plotly = compile(spec, { backend: 'plotly' });
const excel = compile(spec, { backend: 'excel' });

Each backend has a translation layer that maps Flint’s semantic primitives to native config objects. The compiler maintains a registry of backend-specific quirks:

  • Vega-Lite uses encoding.x.scale.domain for axis ranges
  • ECharts uses xAxis.min and xAxis.max
  • Chart.js uses scales.x.min and scales.x.max

Flint abstracts these into a single scale concept during the semantic phase, then emits the correct syntax per backend.

MCP Server Integration

The flint-chart-mcp package exposes three tools:

  • create_chart: Accept a Flint spec, return a rendered image or JSON
  • validate_spec: Check semantic types and data compatibility
  • list_semantic_types: Return available types for agent discovery

Server-Side Validation

The MCP server validates specs before compilation. This catches:

  • Mismatched semantic types (e.g., Country field with numeric data)
  • Invalid mark types for the encoding (e.g., line chart with two nominal fields)
  • Missing required fields

Validation happens server-side to avoid round-trip latency. The agent sends a spec, the server returns either a rendered chart or a structured error with suggestions.

Agent Workflow

  1. Agent receives user request: “Show me GDP by country”
  2. Agent calls list_semantic_types to discover Country and Price types
  3. Agent constructs minimal Flint spec with those types
  4. Agent calls create_chart with spec and data
  5. Server validates, compiles to requested backend, returns image or embeddable JSON

The agent never sees Vega-Lite syntax or ECharts options. It only manipulates semantic types and mark names.

Layout Derivation Logic

Flint’s compiler calculates layout from data cardinality and semantic types. Key heuristics:

  • Axis label rotation: If x-axis has >8 categories or max label length >12 characters, rotate 45 degrees
  • Chart dimensions: Base width on category count (min 400px, +40px per category up to 1200px)
  • Legend placement: Right side if <6 categories, bottom if >6
  • Tick density: Quantitative axes get 5-10 ticks depending on range and precision

These rules are encoded in a layout engine that runs after semantic inference but before backend emission. You can override them in the spec:

const spec = {
  // ... data and encoding
  config: {
    layout: {
      width: 800,
      height: 400,
      axisTitleFontSize: 14
    }
  }
};

Overrides merge with derived values, so you only specify what differs from the defaults.

Failure Modes and Observability

Flint’s compilation can fail at three points:

  1. Semantic inference failure: Data doesn’t match declared type (e.g., strings in a Temperature field)
  2. Layout constraint violation: Derived dimensions exceed backend limits (e.g., ECharts max 10,000px width)
  3. Backend emission error: Semantic construct has no equivalent in target backend

The compiler returns structured errors with:

  • Failure stage (inference, layout, emission)
  • Affected field or encoding
  • Suggested fix (e.g., “Use Nominal instead of Country for non-geographic categories”)

For production deployments, instrument these error paths:

  • Log inference failures to catch data quality issues
  • Alert on layout violations (indicates unusual data cardinality)
  • Track backend emission errors by target (some backends support fewer mark types)

Deployment Shape

Flint runs in three modes:

  • Client-side: Compile in the browser, render with Vega-Lite or Chart.js
  • Server-side: Compile in Node.js, return static images or JSON
  • MCP server: Long-running process that agents call via stdio or HTTP

For agent workflows, the MCP server is the natural choice. It maintains a warm compiler instance and caches semantic type metadata. Startup time is ~50ms, compilation is 5-20ms depending on data size.

If you’re embedding Flint in a web app, client-side compilation keeps the server stateless. The tradeoff: you ship the compiler (~120KB gzipped) and one backend library (~200KB for Vega-Lite, ~800KB for ECharts).

Security Boundaries

Flint specs are JSON, so the usual injection risks apply:

  • Data injection: User-supplied data can contain script tags if rendered in HTML without escaping
  • Spec injection: Malicious specs can specify extremely large dimensions or nested data structures

The MCP server mitigates this by:

  • Validating data types before compilation
  • Capping chart dimensions (max 2000x2000px)
  • Rejecting specs with >100 fields or >10,000 data points

If you expose Flint compilation to untrusted input, add:

  • Content Security Policy headers to block inline scripts
  • Resource limits on data size and compilation time
  • Output sanitization if embedding in HTML

When to Use Flint

Use Flint when:

  • Agents generate charts and you want consistent output without verbose specs
  • You need to support multiple charting backends from a single spec
  • You want semantic types to drive formatting and layout automatically
  • You’re building a data analysis agent that needs reliable visualization

Avoid Flint when:

  • You need pixel-perfect control over every visual element
  • Your charts require custom interactions or animations not covered by semantic types
  • You’re working with domain-specific visualizations (network graphs, geographic projections) that don’t map to Flint’s mark types
  • Your backend is not one of the five supported libraries

Technical Verdict

Flint is infrastructure for agent-generated visualizations. It solves the context bloat problem by moving rendering decisions into a compiler that understands semantic types. The MCP server makes it trivial to wire into any agent runtime.

The multi-backend compilation is the real win. You can switch from Vega-Lite to ECharts without rewriting agent prompts or specs. The tradeoff is less control: if you need custom scales or interactions, you’ll drop down to native backend config.

For production agent systems that generate charts, Flint is the most practical option available. It’s narrow, well-scoped, and solves a real problem without introducing orchestration complexity.