nakkas について
nakkas は、宣言的な JSON 設定から AI アシスタントがアニメーション付き SVG を作成できる MCP サーバーです。ロゴ、アイコン、ローディング表示、GitHub README のバナーやバッジが対象で、JavaScript を使わず CSS @keyframes と SMIL のみで動作します。各レンダリングで PNG プレビューが返るため、モデルは自らの出力を確認して修正できます。
主な機能
- 01JSON 設定からアニメーション SVG を生成
- 02レンダリングごとに PNG プレビューを返却
- 03SVG をサーバー側で artifact id として保持
- 04CSS アニメーションのフレームサンプリング
- 05preview と save のツールを提供
活用シーン
- 01GitHub README 向けのアニメーション バナーやバッジの作成
- 02ローディング表示やアイコンのデザイン
- 03AI 自身のプレビューを使ったデザインの反復修正
- 04結果を SVG または PNG ファイルとして保存
MCP 対応
nakkas は render_svg、preview、save の各ツールを MCP クライアントに公開するサーバーです。クライアントはこれらを使って SVG のレンダリング、プレビュー、ファイル保存を行えます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
3掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| defs | object | いいえ | |
| defs.masks | array | いいえ | |
| defs.paths | array | いいえ | |
| defs.filters | array | いいえ | |
| defs.markers | array | いいえ | |
| defs.symbols | array | いいえ | |
| defs.patterns | array | いいえ | |
| defs.clipPaths | array | いいえ | |
| defs.gradients | array | いいえ | |
| canvas | object | はい | |
| canvas.width | number|string | はい | |
| canvas.height | number|string | はい | |
| canvas.viewBox | string | いいえ | |
| canvas.background | string | いいえ | |
| output | object | いいえ | |
| output.svg | boolean | いいえ | |
| output.frames | number | いいえ | |
| output.minify | boolean | いいえ | |
| output.preview | boolean | いいえ | |
| output.previewWidth | number | いいえ | |
| elements | array | はい | |
| animations | array | いいえ |
詳細
AI を SVG アーティストに変える MCP サーバー。
レンダリングエンジンはひとつ。すべては AI が決めます。
MCP Registry、Glama、LobeHub、PulseMCP に正式に掲載されています。
どちらも AI が nakkas を通じて描いたもので、それぞれ 1 つの JSON 設定から、自身の PNG プレビューに対して反復を重ねて作成されました。純粋な CSS アニメーションで、JavaScript は使用していません。
Nakkas は、Claude のような AI アシスタントが宣言的な JSON 設定からアニメーション SVG グラフィックを作成できるようにする MCP(Model Context Protocol)サーバーです。ロゴ、アイコン、ローディングスピナー、GitHub README のバナー、バッジ、ジェネラティブアートなどに対応します。JavaScript を使わずに CSS @keyframes と SMIL アニメーションをレンダリングするため、出力は GitHub の README 内や、<img> タグが SVG をレンダリングするあらゆる場所で動作します。レンダリングのたびに PNG プレビューとサーバー側のアーティファクト id が返されるので、AI は自身の成果をすぐに確認でき、SVG のテキストをコンテキストウィンドウに通すことなく反復できます。
nakkaş はトルコ語(古語)で画家/芸術家を意味します。
"make a neon terminal logo with animated binary digits"
→ AI constructs JSON config
→ nakkas renders to animated SVG
→ AI previews the PNG, critiques, revises
→ clean animated SVG output
なぜ
- ひとつのツールで、無限のデザイン。
render_svgは JSON 設定を受け取ります。すべては AI が埋めます。 - AI が自身の成果を確認できます。 レンダリングのたびに PNG プレビューが返るので、モデルは盲目的にデザインするのではなく、批評して修正できます。
- トークンを節約できる反復。 SVG はアーティファクトとしてサーバー上に残り、プレビューと保存は id でそれを参照するため、修正ループで SVG のテキスト分のコストがかかりません。
- 純粋な宣言的 SVG。 CSS @keyframes + SMIL アニメーションで、JavaScript は使いません。GitHub の camo プロキシを通過します。
- 外部依存ゼロ。 クラウド API も API キーも不要。ローカルで動作します。
インストール
Claude Desktop
設定ファイルに追加します:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"nakkas": {
"command": "npx",
"args": ["-y", "nakkas@latest"]
}
}
}
Claude Code (CLI)
claude mcp add nakkas npx nakkas@latest
Cursor / Zed / その他の MCP クライアント
{
"mcpServers": {
"nakkas": {
"command": "npx",
"args": ["-y", "nakkas@latest"]
}
}
}
ローカル開発
git clone https://github.com/arikusi/nakkas
cd nakkas
npm install && npm run build
# Use dist/index.js as the command
クイックスタート
AI に次のように依頼してください(Nakkas を接続した状態で):
「アニメーション SVG を作成してください:暗いターミナルフレーム(800×200)、光るシアンのテキスト 'NAKKAS'、ネオングローフィルター、読み込み時のフェードイン。」
「ローディングスピナーを作成してください:円に描画されるストロークアニメーションが 1.5 秒ごとにループするもの。」
「データ可視化:アニメーション付き棒グラフ、棒は 5 本、それぞれがずらした遅延でフェードインし、グラデーションで塗りつぶされるもの。」
「プロフィールバッジ(400×120):青から紫へのグラデーション、白いユーザー名テキスト、ドロップシャドウ、控えめなパルスアニメーション。」
ツール
Nakkas は 3 つのツールを提供します:
| ツール | 目的 |
|---|---|
render_svg | SVGConfig JSON を受け取り、PNG プレビュー + アーティファクト id(+ 設計分析の警告)を返します |
preview | 保存されたアーティファクト(または生の SVG)を任意の幅で PNG に再レンダリングします |
save | 保存されたアーティファクト(または生のコンテンツ)を SVG(テキスト)または PNG(ラスター)としてディスクに保存します |
想定されるワークフロー:レンダリング → 返されたプレビューを確認 → 設定を修正 → 再度レンダリング → 保存。レンダリングされた SVG はサーバー上に アーティファクト として保持されます:render_svg はプレビュー画像と art-1 のような id を返し、preview/save はその id を直接受け取ります。SVG テキストがモデルのコンテキストウィンドウを通って戻る必要は一切なくなり、反復ループのトークンコストを SVG を貼り付けて回す場合のほんの一部に削減できます。アーティファクトはサーバープロセスの生存期間中有効です(上限 32 個、最も古いものから破棄されます)。
save ツール
{ "artifact": "art-1", "outputPath": "./design.svg", "format": "auto" }
artifact(render_svg からの id、推奨)または content(生の文字列)のいずれかを渡してください。フォーマット:auto(拡張子から推測)、svg(テキストファイル)、png(先にラスターにレンダリング)。ファイルが存在する場合、上書きを防ぐために数値カウンターが付加されます。実際に保存されたパスが返されます。
render_svg ツール
入力: SVGConfig JSON オブジェクト
出力: PNG プレビュー画像 + アーティファクト id を示すサマリー、および任意の設計分析メモ
レスポンスの形式は、設定内の任意の output ブロックで制御されます:
{ "output": { "svg": false, "preview": true, "previewWidth": 800, "minify": false } }
svg: trueはレスポンスに完全な SVG テキストを含めます(デフォルトはオフ。プレビューと保存はアーティファクト id で対応できます)preview: falseは PNG 画像をスキップしますpreviewWidthはプレビューを拡大縮小しますminify: trueは保存および保存済み SVG 内の空白を圧縮しますframes: N(2 から 10)は、静的プレビューを、CSS アニメーションを N 時点でサンプリングした 1 枚のフィルムストリップ画像に置き換えます — 単一のプレビューでは開始状態しか表示されないため、モーションを検証する手段となります
frames を使用すると、nakkas は @keyframes の計算(各セグメントの duration、delay、iteration count、direction、fill mode、easing)を自ら評価し、サンプリングした各状態を静的なフレームに焼き込みます。transform-box: fill-box として宣言された transform origin は、要素のジオメトリから数値的に解決されます。SMIL アニメーションはサンプリングされません。
レンダリング後、レスポンスには、同時アニメーションが多すぎる、transformBox の欠落、グループレベルのスケール変換、コンテンツがビューポートを超えて広がっている(実際にレンダリングされたバウンディングボックスから測定され、オーバーフローはピクセル単位)、キャンバス背景に対するテキストの低コントラスト(WCAG 比)といった一般的な問題に関する設計警告が含まれる場合があります。テキストには独自のレイアウト監査があります:すべてのテキスト要素のインクバウンディングボックスが分離レンダリングを通じて測定されるため、ビューポートからはみ出したテキストは正確なオーバーフローとともに特定され、重なって印刷された 2 つのテキストは両方を特定するオーバーラップ警告として返されます。
SVGConfig の構造
{
canvas: {
width: number | string, // e.g. 800 or "100%"
height: number | string,
viewBox?: string, // "0 0 800 400"
background?: string // hex "#111111" or "transparent"
},
defs?: {
gradients?: Gradient[], // linearGradient | radialGradient
filters?: Filter[], // preset or raw primitives
clipPaths?: ClipPath[],
masks?: Mask[],
symbols?: Symbol[],
paths?: { id, d }[], // for textPath elements
patterns?: Pattern[], // repeating tile fills
markers?: Marker[] // arrowheads: triangle | arrow | circle | square | diamond | bar
},
elements: Element[], // shapes, text, groups, use instances
animations?: CSSAnimation[] // CSS @keyframes definitions
}
要素タイプ
| タイプ | 必須フィールド | 備考 |
|---|---|---|
rect | width、height | x、y のデフォルトは 0。角丸には rx/ry |
circle | r | cx、cy のデフォルトは 0 |
ellipse | rx、ry | 水平方向と垂直方向の半径を個別に指定 |
line | x1、y1、x2、y2 | markerStart/markerEnd は defs.markers のマーカー id を取ります |
polyline | points | 開いたパス:"10,20 50,80 90,20"。markerStart/markerMid/markerEnd をサポート |
polygon | points | 自動的に閉じられる図形 |
path | d | 完全な SVG パスコマンド |
image | href、width、height | 埋め込み画像用の URL または data:image/... URI |
text | content | 文字列または (string | Tspan)[] 配列 |
textPath | pathId、text | 曲線に沿うテキスト。パスは defs.paths で定義します |
group | children | すべての子要素に適用される共有属性(グループのネストは不可) |
use | href | シンボルをインスタンス化、または #id で要素を複製 |
radial-group | cx、cy、count、radius、child | 完全な円の周囲に N 個のコピーを配置 |
arc-group | cx、cy、radius、count、startAngle、endAngle、child | 円弧に沿って N 個のコピーを配置 |
grid-group | cols、rows、colSpacing、rowSpacing、child | M×N のグリッドにコピーを配置 |
scatter-group | width、height、count、seed、child | シード付きのランダムな位置に N 個のコピーを散らばめて配置 |
path-group | waypoints、count、child | ポリラインに沿って N 個のコピーを均等に分布 |
parametric | fn | 数学的曲線:rose、heart、star、lissajous、spiral、superformula、epitrochoid、hypotrochoid、wave |
2 つのフィールド名は意図的に生の SVG と異なっています。text 要素の文字列は content に入り(textPath では text)、取り違えるとバリデーションエラーが正確なフィールドを指し示します。パターングループはデフォルトで各コピーを外側に向けて回転させます。子要素がテキストや、直立したままであるべき図形の場合は rotateChildren: false を設定してください。
すべての視覚要素(共有フィールド)
{
id?: string, // required for filter/gradient/clip references
cssClass?: string, // matches CSS animation names
fill?: string, // "#rrggbb" | "none" | "url(#gradId)"
stroke?: string,
strokeWidth?: number,
strokeDasharray?: string, // "10 5", use for draw-on animation
strokeDashoffset?: number,
opacity?: number, // 0–1
filter?: string, // "url(#filterId)"
clipPath?: string, // "url(#clipId)"
transform?: string, // "rotate(45)" "translate(100, 50)"
transformBox?: "fill-box" | "view-box" | "stroke-box", // set "fill-box" for CSS rotation
transformOrigin?: string, // "center", works with fill-box
smilAnimations?: SMILAnimation[]
}
フィルタープリセット
defs.filters で定義した後、任意の要素で filter: "url(#myId)" として参照します:
{ "type": "preset", "id": "myGlow", "preset": "glow", "stdDeviation": 8, "color": "#ff00ff" }
| プリセット | 主なパラメータ | 効果 |
|---|---|---|
glow | stdDeviation、color | 柔らかなハロー |
neon | stdDeviation、color | 強烈で明るいグロー |
blur | stdDeviation | ガウスぼかし |
drop-shadow | stdDeviation、offsetX、offsetY、color | ドロップシャドウ |
glitch | stdDeviation | タービュランス変位(アニメーション) |
grayscale | value(0~1) | 彩度を下げる |
sepia | — | 温かみのあるセピア調 |
invert | — | 色を反転 |
saturate | value | 彩度を上げる/下げる |
hue-rotate | value(度) | 色相をシフト |
chromatic-aberration | value(px オフセット、デフォルト 3) | レンズ歪み風の RGB チャンネル分離 |
noise | value(不透明度 0~1、デフォルト 0.25) | フィルムグレインとテクスチャのオーバーレイ |
outline | color、value(太さ、デフォルト 2) | 要素の周囲に色付きのアウトライン |
inner-shadow | color、stdDeviation、value(不透明度、デフォルト 0.5) | 要素の内側の影 |
emboss | stdDeviation、value(強度、デフォルト 1.5) | 3D レリーフシェーディング効果 |
CSS アニメーション
{
"animations": [{
"name": "pulse",
"duration": "2s",
"iterationCount": "infinite",
"direction": "alternate",
"keyframes": [
{ "offset": "from", "properties": { "opacity": "0.3", "transform": "scale(0.9)" } },
{ "offset": "to", "properties": { "opacity": "1", "transform": "scale(1.1)" } }
]
}],
"elements": [{
"type": "circle",
"cx": 100, "cy": 100, "r": 40,
"cssClass": "pulse",
"transformBox": "fill-box",
"transformOrigin": "center"
}]
}
CSS プロパティキー:キャメルケース(strokeDashoffset)またはケバブケース(stroke-dashoffset)。どちらも動作します。
アニメーション可能な CSS プロパティ:opacity、fill、stroke、transform、filter、clip-path、stroke-dasharray、stroke-dashoffset、font-size、letter-spacing など。
SMIL アニメーション
3 つの SMIL タイプがあり、各要素で smilAnimations: [] を介してインラインで定義します:
{ "kind": "animate", "attributeName": "d", "from": "...", "to": "...", "dur": "2s" }
{ "kind": "animateTransform", "type": "rotate", "from": "0 100 100", "to": "360 100 100", "dur": "3s" }
{ "kind": "animateMotion", "path": "M 0 0 C ...", "dur": "4s", "rotate": "auto" }
パスモーフィング(attributeName: "d"):from/to のパスは同一のコマンドタイプと数を持つ必要があります。異なってよいのは座標のみです。
フォント
CSS の汎用ファミリーである sans-serif、serif、monospace を優先してください。これらはブラウザでも nakkas のプレビューでも、あらゆるプラットフォームで実際のフォントに解決されます。Arial や Helvetica のような名前付きフォントは一部のシステムにしか存在しないため(ほとんどの Linux マシンにはありません)、これらに依存したデザインは別の環境では異なって表示されます。安全なパターンは、名前付きフォントと汎用フォールバックの組み合わせです: "Georgia, serif"。
preview および PNG の save では、汎用ファミリーはオペレーティングシステム自身のフォントマッピング(Linux では fontconfig)を通じて解決されるため、AI が見るものはそのマシン上のブラウザが表示するものと一致します。カスタムフォントファミリーも受け付けられ、レンダリング環境でそのフォントが利用可能であれば動作します。
ユースケースと互換性
| コンテキスト | CSS @keyframes | SMIL | 外部フォント | インタラクティブ (onclick) |
|---|---|---|---|---|
GitHub README <img> | ✅ | ✅ | ❌ | ❌ |
Web ページ <img> | ✅ | ✅ | ❌ | ❌ |
| Web ページのインライン SVG | ✅ | ✅ | ✅ | ✅ |
| デザインツールのエクスポート | ✅ | ✅ | ✅ | — |
| 静的ファイルビューアー | ✅ | ✅ | 依存する | 依存する |
トラブルシューティング
「MCP error -32602: Input validation error」
これは、入力がハンドラーに到達する前に MCP SDK がそれを拒否したことを意味します。通常は最初の試行で発生し、再試行すると成功します。最も一般的な原因は次のとおりです:
- グラデーションタイプのタイプミス。
"linear"や"radial"ではなく、"linearGradient"または"radialGradient"を使用してください。これは最も頻繁に発生する単一のミスです。 - キーフレームオフセットの文字列指定。
0や100(数値)、または"from"/"to"と記述してください。"0%"や"100%"と記述すると失敗します。 - グラデーションとフィルターの色。 グラデーションストップとフィルターの色は 16 進数である必要があります:
"red"やrgb()ではなく"#ff0000"です。要素のfill/strokeは任意のペイント文字列("#ff0000"、"none"、"url(#id)")を受け付けますが、レンダラー間で最も安全なのは 16 進数です。 - 要素の
typeの欠落。 すべての要素オブジェクトにはtypeフィールドが必要です。
ハンドラーに到達する検証エラーは、失敗した正確なフィールドを明示し(たとえば elements.1.content: Required)、失敗した要素タイプのフィールド参照を付加するため、通常は最初の修正で再試行が成功します。数値フィールドへの数値文字列(letterSpacing: "6")は、失敗する代わりに自動的に変換されます。
ハンドラーはレンダリング前に参照の整合性もチェックします: fill/stroke/filter/clipPath/mask 内の存在しない url(#id)、どこも指していない use.href、defs.paths に存在しない textPath.pathId、または重複した ID は、壊れた出力を黙ってレンダリングする代わりに、正確なフィールドパスと定義済み ID のリストを含むエラーとして返されます。
MCP クライアント統合を構築していてこれが一貫して発生する場合、問題はクライアントが引数をシリアライズする方法にある可能性があります。既知のシリアライズの癖については、anthropics/claude-code#29104 を参照してください。
プレビューに空白または予期しない画像が表示される
単一のプレビューは、アニメーションが始まる前の t=0 における静的スナップショットをレンダリングします。動きを確認するには、output: { frames: 4 }(最大 10 まで)でレンダリングしてください。nakkas は CSS アニメーションを N 個の時点でサンプリングし、ラベル付きのフィルムストリップ画像を 1 枚返します。SMIL アニメーションは例外で、サンプリングされず常に基本状態を表示します。
画像が完全に空白の場合:
- 要素に
fillまたはstrokeが設定されているか確認してください。透明なキャンバス上で塗りのない図形は見えません。 - 座標を確認してください。幅
800pxのキャンバス上でx: 2000にある要素は、単に画面外にあります。 filter: "url(#myFilter)"を使用している場合は、myFilterが実際にdefs.filtersで定義されていることを確認してください。
GitHub でアニメーションが動作しない
GitHub の README は SVG を <img> タグ経由でレンダリングするため、JavaScript は取り除かれますが、CSS と SMIL は保持されます。ローカルではアニメーションが動作するのに GitHub では動作しない場合:
<script>やイベントハンドラー(onclick、onmouseover)は避けてください。これらは削除されます。- 外部フォントは読み込まれません。汎用ファミリー(
monospace、sans-serif、serif)か、汎用フォールバックを伴う名前付きフォントを使用してください。 - フォント用の CSS
@importはブロックされます。特定のフォントが必要な場合は、システムフォールバックを伴うインライン<text>を使用してください。
大きな SVG 出力
render_svg がファイルサイズ(50kb 超)に関する警告を返す場合、パラメトリック曲線がサンプリングする点が多すぎる可能性があります。steps を減らしてください。パターングループは低コストです。子は一度定義され、<use> でインスタンス化されるため、cols: 50, rows: 50 のグリッドグループは、子の定義 1 つと 2500 個の 1 行 <use> タグで済みます。可能な限り最小のファイルにするには、output: { minify: true } を追加してください。
テスト方法
すべてのリリースは、ドッグフーディング実行の後にのみ出荷されます。実際の MCP stdio レイヤーを通じて、ビルド直後のサーバーに対してデザインを生成し、レンダリング → プレビュー → 批評 → 修正を、納得がいくまで繰り返します。テストデザインはテスト対象の変更に合わせて調整されるため、実行によって新機能が現実的な使用条件下で動作することが証明され、単に何も壊れていないことだけが確認されるわけではありません。プロンプト、反復回数、および生成されたアセットを含む完全なログは dogfooding.md にあります。
この儀式は実際のバグを捕捉します。v0.3.0 のイージングのドッグフーディングでは、translateX のような CSS 軸ショートハンドが SVG の transform 属性にそのまま埋め込まれ、そこでは無効であるため要素がサイレントにフリーズすることが判明しました。修正は同じリリースで出荷されました。output.frames の背後にあるアニメーションフレームサンプラーは、実際のブラウザに対して検証されます。ランダム化されたアニメーション(シード付き、ランダムな cubic-bezier 曲線を含む)は、負の animation-delay トリックを用いてヘッドレス Chromium でフリーズされ、nakkas のサンプリング位置と 1 ピクセル以内で一致する必要があります。このクロスチェックは CI で tests/browser-truth.test.ts として実行されます。元の手動ハーネスは scripts/easing-browser-truth.sh に残っています。
ドッグフーディング実行に加えて、MCP stdio のエンドツーエンドカバレッジを含む 397 個のユニットテストと統合テストがあります。
技術スタック
- TypeScript + Node.js 18+
@modelcontextprotocol/sdk(MCP サーバー)zod(スキーマ検証と AI 型ガイダンス)- 外部 SVG ライブラリなし、純粋な XML 構築
- Vitest(397 テスト)
ライセンス
MIT。arikusi によって構築されました。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。