Microsoft Researchが公開した Flint(microsoft/flint-chart・⭐3,636)は、「AIエージェントが確実に、見栄えのするチャートを作れるようにする」ことを目的とした可視化中間言語です。LLMに軸の範囲や配色や余白を数百項目書かせるのをやめ、データの意味だけを書かせて残りをコンパイラに導出させる——設計思想はこの一点に尽きます。
ただし「1つの入力を5つのバックエンドへ」というREADMEの見出しは、実際に動かすと3段階で狭くなります。この記事ではnpm版 flint-chart 0.5.0 と flint-chart-mcp 0.5.0 を実際にインストールし、その境界線を実測で示します。
- ・正体:Microsoft Research × IDEAS Lab(中国人民大学)による可視化中間言語。MIT、TypeScript製。⭐3,636 / fork 198
- ・何ができる:
data/semantic_types/chart_specの3点だけ書けば、軸・目盛り・配色・ラベル配置を自動導出してVega-Lite / ECharts / Chart.js / Plotly / Excel の仕様を吐く - ・描画はしない:npm版の実行時依存はゼロ。描画ライブラリはすべて optional な peerDependencies
- ・移植できる範囲:対応チャートは全体で48種だが、5バックエンド共通は11種(実測)
- ・テーマ:10プリセットあるが、効くのはVega-Liteのみ。他バックエンドでは無警告で無視される
- ・注意:未対応の
chartTypeは例外で止まるが、存在しない意味型は黙って通る
この記事ではFlintを「MCPサーバーとしてエージェントに接続するツール」として実測します。MCPサーバーそのものの作り方は MCPサーバーの作り方2026年完全ガイド:TypeScript・Python両対応チュートリアル をご覧ください。
Flintとは——AIエージェントのための可視化中間言語
Flintが解こうとしている問題は、「LLMにチャートを描かせると壊れる」という現象です。Vega-LiteやECharts の仕様をLLMに直接書かせると、軸のスケール、目盛りの刻み、凡例の位置、ラベルの回転角、系列の配色といった項目を一つひとつ指定させることになります。項目数が多いほど破綻の確率は上がり、しかも「文法的には正しいが見た目が壊れている」出力はバリデーションでは弾けません。
Flintの答えは、指定させる項目を減らすことです。エージェントが書くのは次の3つだけです。
・data:データそのもの(行の配列、またはJSON / CSV / TSV へのパス)
・semantic_types:各フィールドが「何を意味するか」(Price、Country、Rank など)
・chart_spec:チャート種別とエンコーディング(どのフィールドをx軸・y軸・色に割り当てるか)
軸の範囲やゼロ基線を含めるかどうか、目盛りの書式、配色、余白、ラベルの間引きは、この3点とデータの実際の分布からコンパイラが導出します。「どう描くか」を書かせず「何を意味するか」を書かせる——これがFlintの中核です。
data / semantic_types / chart_spec"] --> B["意味の解決
resolveFieldSemantics"] B --> C["決定の導出
スケール・ゼロ基線・書式・配色"] C --> D["レイアウト計算
余白・ラベル・凡例"] D --> E{"backend"} E --> F["Vega-Lite 仕様"] E --> G["ECharts option"] E --> H["Chart.js config"] E --> I["Plotly figure"] E --> J["Excel ネイティブ図"]
開発体制はMicrosoft ResearchとIDEAS Lab(中国人民大学)の共同で、設計を記述した論文 Flint: A Semantics-Driven Data Visualization Intermediate Language(arXiv:2607.20775)が2026年7月22日に投稿されています。リポジトリは2026年5月13日作成、最初のリリース0.1.1が6月28日、最新の0.5.0が8月6日(CHANGELOGとREADMEの表記は8月5日)と、6週間で5リリースという速いペースです。この記事の数値がすぐ古くなる可能性は高く、以下はすべて flint-chart 0.5.0 / flint-chart-mcp 0.5.0 / Node v22.13.1(2026-08-11 JST) での実測値として読んでください。
① 何ができる:データの意味だけを書いて、5種類の描画ライブラリ向けの仕様を出し分けられる。② 何を解決する:LLMに低レベル設定を書かせたときの「文法は通るが見た目が壊れる」問題。③ 何を代替できる:Vega-LiteやEChartsの仕様を手書き/LLMに直書きさせる工程。描画ライブラリ自体は代替しない。
インストールと最小の使い方——同じ入力を5バックエンドへ
ライブラリとMCPサーバーは別パッケージです。ライブラリだけならインストールは一瞬で、依存パッケージは1つも入りません。
# ライブラリとして使う(実行時依存ゼロ)
npm install flint-chart
# エージェント / MCPクライアントから使う
npx -y flint-chart-mcp
npm install flint-chart は added 1 package(=flint-chart自身のみ)で完了しました。package.json を見ると dependencies は空で、vega / vega-lite / echarts / chart.js / plotly.js はすべて peerDependenciesMeta で optional: true が付いています。Flintは描画しません。出力は各ライブラリ向けの仕様オブジェクトであり、実際に絵にするのは受け取った側の責任です。この分離のおかげで、Vega-Lite仕様だけ欲しい場合にEChartsを入れずに済みます。
最小の呼び出しは次のとおりです。バックエンドを差し替えても入力の形は変わりません。
import { assembleVegaLite, assembleECharts, assembleExcel } from 'flint-chart';
const input = {
data: { values: [{ cat: 'A', val: 30 }, { cat: 'B', val: 55 }, { cat: 'C', val: 20 }] },
semantic_types: { cat: 'Category', val: 'Quantity' },
chart_spec: {
chartType: 'Bar Chart',
encodings: { x: { field: 'cat' }, y: { field: 'val' } },
baseSize: { width: 400, height: 300 },
},
};
const vl = assembleVegaLite(input); // → Vega-Lite 仕様
const ec = assembleECharts(input); // → ECharts option
const xl = assembleExcel(input); // → Excel ネイティブ図の中間表現
この入力を5つの関数すべてに通した結果が次の表です。同じ入力から、それぞれのライブラリが直接受け取れる形の仕様が返ってきます(以下の数値はすべて上記3行のデータでの実測)。
| バックエンド | 出力サイズ | 返ってくるオブジェクトの主キー |
|---|---|---|
| Vega-Lite | 1,811バイト | mark / encoding / config / width / data |
| ECharts | 1,275バイト | tooltip / xAxis / yAxis / series / grid |
| Chart.js | 1,148バイト | type / data / options |
| Plotly | 1,117バイト | data / layout |
| Excel | 442バイト | schema / kind / chartType / seriesBy / series |
インストール規模とPython版の現状
ライブラリとMCPサーバーでは、ディスク上の規模が大きく違います。空のプロジェクトへ入れて実測したところ、flint-chart 単体は 38MB(追加パッケージ0個)だったのに対し、flint-chart-mcp は 193MB・183パッケージでした。MCPサーバーはサーバー内で実際に描画してPNG / SVGを返すため、vega / vega-lite / echarts / chart.js に加えて @napi-rs/canvas や @resvg/resvg-js といったネイティブのレンダラを同梱するからです。「仕様を作るだけ」ならライブラリ、「画像まで欲しい」ならMCPサーバー、と規模がそのまま役割の違いを表しています。
Python版については注意が必要です。READMEは「Pythonパッケージは公開予定、現時点ではリポジトリ内のソースのみのプレビュー」と書いており、実際に PyPIに flint-chart は存在しません(2026年8月11日時点で404)。さらにリポジトリ内の packages/flint-py/ を見ると、バージョンは 0.1.0(JS版は0.5.0)で、実装されているバックエンドは vegalite のみでした。意味型レジストリの44種はJS版と一致していますが、Pythonから使えるのは実質Vega-Lite出力だけであり、複数バックエンドへのチャート生成を前提にするならJS/TS版を使う必要があります。
対応チャート48種のうち、5バックエンド共通は11種
ここからが本題です。「同じ入力をどのバックエンドにも出せる」という説明は、チャート種別を固定した場合にのみ成り立ちます。
各バックエンドが持つテンプレート定義(vlAllTemplateDefs などの公開配列)を実行して集計したところ、対応チャート種は次のようにバックエンドごとに大きく違いました。
*AllTemplateDefs を実行して集計。全体(和集合)は48種だが、5バックエンド共通(積集合)は11種にとどまる和集合は48種、5バックエンドすべてで使える積集合は11種でした。内訳は Bar Chart / Grouped Bar Chart / Stacked Bar Chart / Line Chart / Area Chart / Scatter Plot / Connected Scatter Plot / Pie Chart / Histogram / Radar Chart / Waterfall Chart です。つまり「あとからバックエンドを載せ替える」前提で設計するなら、この11種の範囲内に収める必要があります。
バックエンド固有のチャートも実測できました。ECharts だけが持つのは Sankey Diagram / Network Graph / Tree / Parallel Coordinates / Calendar Heatmap の5種、Chart.js だけが持つのは Bubble Chart / Combo Chart / Doughnut Chart の3種、Plotly だけが持つのは Density Contour の1種です。Vega-LiteとExcelには「そこにしか無いチャート」はありませんでした。
同じチャートなのに名前が違う罠
移植性の落とし穴として、ドーナツチャートだけバックエンド間で名前が割れています。
| バックエンド | chartType に渡す文字列 |
|---|---|
| Vega-Lite / Plotly / Excel | Donut Chart |
| Chart.js | Doughnut Chart |
Donut Chart を Chart.js に渡すと例外になり、Doughnut Chart を Vega-Lite に渡しても例外になります。仕様上は同じ図なのに、バックエンドを変えると文字列を書き換えなければなりません。前掲の「共通11種」にドーナツが入っていないのは、この表記ゆれが理由です。
バックエンドを切り替え可能にしておきたい場合、
chartType をアプリ側で定数管理し、少なくとも Donut / Doughnut は変換テーブルを持つ。共通11種を超える図を使うなら、その時点でバックエンドは実質固定されると考えたほうが安全。
MCPサーバーとして使う——エージェントに見える範囲は3バックエンド
flint-chart-mcp は、エージェントが会話の流れの中でチャートを作れるようにするMCPサーバーです。Claude CodeなどMCP対応クライアントの設定に次を追加すれば使えます。
{
"mcpServers": {
"flint-chart": {
"command": "npx",
"args": ["-y", "flint-chart-mcp"]
}
}
}
stdio経由で tools/list を投げたところ、公開ツールは6つでした。
| ツール | 役割 | スキーマ込みのサイズ |
|---|---|---|
render_chart |
チャートを描画しPNG / SVGを返す | 4,209バイト |
create_chart_view |
インタラクティブなチャートビューを開く | 4,010バイト |
compile_chart |
バックエンドネイティブ仕様へコンパイル | 3,722バイト |
validate_chart |
描画前に入力を検証 | 3,682バイト |
list_chart_types |
対応チャート種の一覧を返す | 491バイト |
list_themes |
テーマプリセットの一覧を返す | 479バイト |
tools/list 全体は 16,600バイトでした。MCPサーバーは接続している間ずっとこの定義がコンテキストに載るため、常時接続するサーバーとしては軽くはありません。加えて list_chart_types を引数なしで呼ぶと16,682バイトが返ってきます(全チャート種とそのエンコーディングチャネルの一覧)。必要なときだけ呼ぶツールとして扱うのが無難です。
そして重要なのが、MCP経由で選べるバックエンドは3つしかないという点です。compile_chart の入力スキーマを読むと、backend は次のenumに限定されていました。
"enum": ["vegalite", "echarts", "chartjs"]
サーバー起動時のバナーも backends: vegalite, echarts, chartjs と表示します。PlotlyとExcelはライブラリ専用で、エージェントからは選べません。0.4.0でPlotly 38種とExcel 18種が追加されたのはライブラリ側の話であり、MCP側はarXiv論文が挙げる3ターゲットのままです。
MCPを使わない構成向けの逃げ道も用意されています。リポジトリの agent-skills/ に、チャート作成用の flint-chart-author とテーマ作成用の flint-theme-author という2つのスキル定義(SKILL.md)が同梱されており、MCPサーバーを常駐させずにライブラリだけを使う場合でも、エージェントに書き方を教える指示書として流用できます。前述のとおり tools/list だけで16,600バイトを消費するため、チャート生成の頻度が低いプロジェクトではこちらの構成のほうが軽く済みます。
MCPというプロトコル自体の仕組みから確認したい場合は MCPとは何か?AIに手足を与えるプロトコルの仕組みと実践ガイド2026 を、ツール定義をどこまでサーバー側で作り込むべきかという設計論は 「MCP is all you need」Pydantic作者が語るMCPの正しい使い方とsampling完全解説 が参考になります。
止まる指定と、黙って通る指定——信頼性の実測
「AIエージェントが確実にチャートを作れる」という主張の実体は何か。ここを確かめるため、validate_chart に6パターンの入力を投げました。結果は構造には厳格、意味には無検査という明確な非対称でした。
validate_chart に6ケースを投入した結果止まる側の挙動は良質です。
・未対応の chartType(Banana Chart)→ valid: false、Unknown chart type: Banana Chart
・データに存在しないフィールド名 → valid: false、chart_spec.encodings.x.field "nope" does not exist in data
・存在しないテーマ名 → valid: false、しかも Flint ships: nyt, economist, swiss... と候補名を列挙して返す
とりわけ重要なのは、未対応のチャート種を黙って別の図にすり替えないことです。ECharts専用の Sankey Diagram を Chart.js に渡すと、勝手に棒グラフになったりせず例外で止まります。「棒グラフを頼んだのに折れ線が返ってきた」という種類の事故は起きません。エージェント向けの設計として、これは正しい方向です。
一方で、黙って通る側が2つあります。
・存在しない意味型:semantic_types に NotARealType を指定しても valid: true / warnings: [] / errors: [] が返る
・効かないバックエンドへのテーマ指定:theme_spec: 'economist' と backend: 'echarts' を同時に渡しても valid: true / warnings: []
つまり検証をきちんと挟んでも、この2つは素通りします。
意味型は44種しか登録されていない
READMEは「70種類以上の意味型」と書いていますが、これは数え方の違いです。設計ドキュメント docs/design-semantics.md は意味型を3階層で定義しており、T0(族)6種 + T1(カテゴリ)17種 + T2(具体型)46種と明記しています。合計すると約70になり、READMEの数字と整合します。誤りではなく、階層の合計を数えた表現です。
ただし実行時に具体名として解決できるのは、単一の真実として定義された TYPE_REGISTRY の44種でした(TypeScript版・Python移植版とも同数)。そして問題は、ここに設計ドキュメント自身が例に使う型名が入っていないことです。
getRegistryEntry() に投げて確認した結果が次です。
| 指定した型名 | 解決結果(族 / カテゴリ / 書式) |
|---|---|
Price |
Measure / Amount / currency |
Amount |
Measure / Amount / currency |
Quantity |
Measure / Physical / unit-suffix |
Revenue |
Categorical / Entity / plain |
Rating |
Categorical / Entity / plain |
NotARealType |
Categorical / Entity / plain |
Revenue は設計ドキュメントが「§6.1 Revenue bar chart」という節を立てて使っている型名ですが、デタラメな文字列 NotARealType とまったく同じ扱いになります。Rating(§6.4の例)も同様です。
これが実際の描画にどう響くかを、ゼロ基線が意味を持つ折れ線グラフで確かめました。デタラメ文字列 ZZZGARBAGE を対照群に置いた結果です。
semantic_types の値 |
y軸にゼロを含むか | 対照群と出力が同一か |
|---|---|---|
Amount / Price / Quantity |
zero: true |
異なる |
Revenue / Rating |
zero: false |
完全に一致 |
ZZZGARBAGE(対照群) |
zero: false |
— |
Amount と書けばy軸がゼロから始まり、Revenue と書くとゼロ基線が外れます。同じ「売上」を意味する語でも、登録済みかどうかで軸が変わるわけです。しかも警告は一切出ません。
LLMに
semantic_types を書かせる場合、自由に命名させると Revenue や Sales のような「それらしいが未登録」の語が混ざる。プロンプトかスキーマ側で登録済み44種の列挙リストに制約するのが確実。棒グラフではゼロ基線が常に強制されるためこの差は表面化せず、折れ線に切り替えた瞬間に顕在化する点にも注意。
なお、semantic_types を丸ごと省略してもコンパイルは通ります。数値列は自動的に quantitative として扱われるため、意味型は「無いと動かない必須項目」ではなく「あると決定が変わる補助情報」という位置づけです。
ThemeSpecと類似ツールとの比較
0.5.0の目玉が theme_spec です。New York Times / Economist / Swiss / Nature / McKinsey / Datawrapper / Power BI / Power BI Light / Pop / Cartoon の10プリセットが同梱され、extends で継承して一部だけ上書きすることもできます。
READMEも「ThemeSpecは現状Vega-Lite出力に影響する」と一行だけ触れていますが、実測すると影響はもっとはっきりしています。同じ入力に theme_spec: 'economist' を付けた前後で出力を比較しました。
| バックエンド | テーマ無し | economist 適用後 |
出力の変化 |
|---|---|---|---|
| Vega-Lite | 1,811バイト | 11,019バイト | 約6倍に増加 |
| ECharts | 1,275バイト | 1,275バイト | バイト単位で一致 |
| Chart.js | 1,148バイト | 1,148バイト | バイト単位で一致 |
| Plotly | 1,117バイト | 1,117バイト | バイト単位で一致 |
Vega-Lite以外では、テーマ指定はまったく効かず、警告も出ません。前節のとおり validate_chart も valid: true を返します。エージェントに「Economist風のグラフをEChartsで作って」と頼むと、素っ気ない既定デザインのチャートが何事もなかったかのように返ってきます。テーマを使うならバックエンドはVega-Lite一択、というのが0.5.0時点の実態です。
類似ツールとの違い
Flintの立ち位置は、描画ライブラリとも「自然言語からグラフを作るツール」とも異なります。
| ツール | 種別 | 入力 | 出力 | LLM / エージェント前提 |
|---|---|---|---|---|
| Flint | 中間言語・コンパイラ | 意味型+チャート仕様 | 5種の仕様(描画はしない) | あり(MCPサーバー同梱) |
| Vega-Lite | 可視化文法 | 宣言的な仕様(詳細指定) | 描画 | なし |
| ECharts / Chart.js | 描画ライブラリ | 設定オブジェクト | 描画 | なし |
| Microsoft LIDA | 自動生成ツール | 自然言語+データ | 可視化コード(Python等) | あり |
Vega-LiteやEChartsが「どう描くか」を書く言語であるのに対し、Flintはその手前に置く層です。LIDAのように自然言語からコードを生成するのではなく、エージェントが構造化された仕様を書き、決定論的にコンパイルする点が違います。生成のたびに結果が揺れないことが、Flintが「reliably(確実に)」と言う根拠です。
したがって競合というより併用対象で、Flintを入れてもVega-LiteやEChartsが不要になるわけではありません。置き換わるのは「Vega-Lite仕様をLLMに直書きさせていた工程」だけです。
向く:エージェントに定型のレポート図を量産させたい/複数の描画ライブラリを使い分けているアプリで入力を1本化したい/出力の再現性が要る。
向かない:Sankeyやツリーマップなど特定バックエンド固有の図が主役/Vega-Lite以外でブランドテーマを効かせたい/描画まで一括で欲しい。
まとめ——Flintをどう使い分けるか
Flintの設計思想「意味を書かせて、描き方は導出する」は、LLMにチャートを作らせるうえで筋が通っています。未対応のチャート種を黙って別の図にすり替えず例外で止める挙動、存在しないフィールド名やテーマ名をエラーとして候補付きで返す挙動は、エージェント向けの部品として信頼できる作りです。
一方で0.5.0時点の実測では、「1つの入力で5バックエンド」は3段階で狭くなります。
・ライブラリは5バックエンド、MCPサーバーは3バックエンド、テーマが効くのは1バックエンド
・対応チャート48種のうち、5バックエンド共通で使えるのは11種
・ドーナツだけ Donut / Doughnut と名前が割れている
・存在しない意味型と、効かないバックエンドへのテーマ指定は、検証をかけても黙って通る
実務上の要点は2つです。第一に、バックエンドを載せ替える前提なら共通11種に収めること。第二に、LLMに semantic_types を自由記述させないこと——Revenue のような「それらしいが未登録」の語はデタラメ文字列と同じ扱いになり、折れ線グラフではゼロ基線が外れる形で表面化します。登録済み44種の列挙に制約するだけで防げます。
6週間で5リリースという開発速度を踏まえると、ここで挙げた境界線(特にテーマのVega-Lite限定とMCPの3バックエンド)は今後動く可能性が高い部分です。導入を検討する際は、この記事の数値をそのまま前提にせず、自分が入れたバージョンでの範囲を確認することをおすすめします。
自分の環境で範囲を確認するコマンド
次のスクリプトを実行すれば、この記事で示した「対応チャート種」と「共通集合」を自分のバージョンで再計算できます。バックエンドを載せ替える設計にする前に、一度は自分の目で確認しておく価値があります。
// npm install flint-chart した場所で node で実行する
import * as F from 'flint-chart';
const sets = {
'Vega-Lite': F.vlAllTemplateDefs, 'ECharts': F.ecAllTemplateDefs,
'Chart.js': F.cjsAllTemplateDefs, 'Plotly': F.plAllTemplateDefs,
'Excel': F.excelAllTemplateDefs,
};
const names = Object.keys(sets);
const asSet = (a) => new Set(a.map((d) => d.chart));
const S = Object.fromEntries(names.map((n) => [n, asSet(sets[n])]));
names.forEach((n) => console.log(n, S[n].size));
const union = new Set(names.flatMap((n) => [...S[n]]));
const common = [...union].filter((c) => names.every((n) => S[n].has(c)));
console.log('union:', union.size, '/ 全バックエンド共通:', common.length);
console.log(common.sort().join(', '));
// 登録済みの意味型44種を列挙する(LLMに渡す制約リストの元ネタになる)
console.log(Object.values(F.SemanticTypes).join(', '));
MCPサーバー側の範囲は、クライアントから list_chart_types と list_themes を呼べば同じことが確認できます。compile_chart の入力スキーマに並ぶ backend のenumを見れば、そのバージョンでエージェントが選べるバックエンドがそのまま分かります。
参照ソース
・microsoft/flint-chart(公式リポジトリ・README) — 設計思想・インストール手順・テーマプリセット一覧・リポジトリ構成
・Flint: A Semantics-Driven Data Visualization Intermediate Language(arXiv:2607.20775) — 階層的意味モデルと、対象とする3つの可視化文法(2026年7月22日投稿)
・Flint 公式プロジェクトサイト(テーマエクスプローラー・ライブエディタ) — 10プリセットの実物比較とチャートギャラリー
・flint-chart(npm) — 0.5.0のパッケージ構成・peerDependenciesの定義