关于 nakkas
nakkas 是一个 MCP 服务,让 AI 助手根据声明式 JSON 配置生成动画 SVG,适用于 logo、图标、加载动画、README 横幅和徽章等场景。它只使用 CSS @keyframes 与 SMIL,不含 JavaScript,并在每次渲染后返回 PNG 预览,便于 AI 迭代修改。
主要功能
- 01由 JSON 配置渲染动画 SVG
- 02每次渲染返回 PNG 预览
- 03SVG 以 artifact id 保存在服务端
- 04支持帧采样以检查 CSS 动画
- 05提供 preview 与 save 工具
适用场景
- 01生成可用于 GitHub README 的动画横幅或徽章
- 02制作加载动画与图标
- 03让 AI 通过预览反复修改设计
- 04将生成结果保存为 SVG 或 PNG 文件
MCP 相关
nakkas 是一个 MCP 服务,向 MCP 客户端暴露 render_svg、preview 和 save 三个工具,客户端可借此渲染、预览并保存 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 绘制,每幅图使用一份 JSON 配置,并根据自身的 PNG 预览反复迭代。纯 CSS 动画,无 JavaScript。
Nakkas 是一个 MCP(Model Context Protocol)服务,可让 Claude 等 AI 助手通过声明式 JSON 配置创建动画 SVG 图形:徽标、图标、加载指示器、GitHub README 横幅、徽章和生成艺术。它使用 CSS @keyframes 和 SMIL 动画进行渲染,不需要 JavaScript,因此输出可用于 GitHub README,以及任何能渲染 SVG 的 <img> 标签。每次渲染都会返回 PNG 预览和一个服务端 artifact 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 预览,让模型进行评估和修改,而不是盲目设计。
- 迭代省 token。 SVG 会作为 artifact 保存在服务端;预览和保存时通过 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 提供三个工具:
| 工具 | 用途 |
|---|---|
render_svg | 接收 SVGConfig JSON,返回 PNG 预览图 + 工件 ID(以及设计分析警告) |
preview | 将已存储的工件(或原始 SVG)重新渲染为任意宽度的 PNG |
save | 将已存储的工件(或原始内容)保存到磁盘,格式为 SVG(文本)或 PNG(栅格图像) |
预期工作流:渲染 → 查看返回的预览 → 修改配置 → 再次渲染 → 保存。渲染后的 SVG 会作为工件保留在服务端:render_svg 会返回预览图像和类似 art-1 的 ID,preview/save 可直接接收该 ID。SVG 文本无需再经由模型的上下文窗口传回,因此迭代循环的 token 成本仅为反复粘贴 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)会将静态预览替换为一张胶片条图像,在 N 个时间点采样 CSS 动画——这是验证动画效果的方式,因为单张预览只显示起始状态
使用 frames 时,nakkas 会自行计算 @keyframes 数值(时长、延迟、迭代次数、方向、填充模式、每个动画片段的缓动),并将每个采样状态烘焙为静态帧。根据元素几何形状,以数值方式解析通过 transform-box: fill-box 声明的变换原点。不会对 SMIL 动画进行采样。
渲染后,响应可能会包含常见问题的设计警告,例如并发动画过多、缺少 transformBox、组级缩放变换、内容超出视口(根据实际渲染边界框测量,并以像素表示溢出量),或文字与画布背景的对比度较低(WCAG 比率)。文本还会进行单独的布局审查:系统通过隔离渲染测量每个文本元素的墨迹边界框,因此会指出超出视口的文本及其精确溢出量;若两个文本相互重叠,则会返回一条列出两者名称的重叠警告。
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 |
两个字段名与原始 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(像素偏移,默认值为 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 动画
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"):起始路径和目标路径必须具有相同类型和数量的命令,只能改变坐标。
字体
优先使用 CSS 通用字体族:sans-serif、serif、monospace。它们在每个平台上都能解析为实际字体,浏览器和 nakkas 预览中均如此。Arial 或 Helvetica 之类的具名字体只存在于部分系统上(大多数 Linux 机器上并没有),因此依赖这些字体的设计在其他环境中会呈现不同效果。稳妥的写法是具名字体加通用字体回退:"Georgia, serif"。
在 preview 和 PNG save 中,通用字体族会通过操作系统自身的字体映射进行解析(Linux 上为 fontconfig),因此 AI 看到的效果与该机器上的浏览器呈现效果一致。自定义字体族也受支持,只要渲染环境中有相应字体即可使用。
用例与兼容性
| 场景 | CSS @keyframes | SMIL | 外部字体 | 可交互(onclick) |
|---|---|---|---|---|
GitHub README <img> | ✅ | ✅ | ❌ | ❌ |
网页 <img> | ✅ | ✅ | ❌ | ❌ |
| 网页内联 SVG | ✅ | ✅ | ✅ | ✅ |
| 设计工具导出 | ✅ | ✅ | ✅ | — |
| 静态文件查看器 | ✅ | ✅ | 视情况而定 | 视情况而定 |
故障排查
“MCP error -32602: Input validation error”
这表示 MCP SDK 在输入到达处理程序之前拒绝了它。通常发生在首次尝试时,重试后即可成功。最常见的原因:
- 渐变类型拼写错误。 使用
"linearGradient"或"radialGradient",不要使用"linear"或"radial"。这是最常见的错误。 - 将关键帧偏移量写成字符串。 使用
0或100(数字),或"from"/"to"。写成"0%"或"100%"会导致失败。 - 渐变和滤镜中的颜色。 渐变色标和滤镜颜色必须使用十六进制值:
"#ff0000",不能用"red"或rgb()。元素的fill/stroke接受任意绘制字符串("#ff0000"、"none"、"url(#id)"),但使用十六进制值在不同渲染器中最为稳妥。 - 元素缺少
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 会在 N 个时间点采样 CSS 动画,并返回一张带标签的胶片条图像。SMIL 动画是例外;它们不会被采样,始终显示其基础状态。
如果图像完全空白:
- 检查元素是否设置了
fill或stroke。在透明画布上,没有填充的形状不可见。 - 检查坐标。在宽度为
800px的画布上,位于x: 2000的元素会在屏幕外。 - 如果使用
filter: "url(#myFilter)",请确保defs.filters中确实定义了myFilter。
GitHub 上的动画无法运行
GitHub README 会通过 <img> 标签渲染 SVG,这会移除 JavaScript,但保留 CSS 和 SMIL。如果动画在本地可以运行,但在 GitHub 上无法运行:
- 避免使用
<script>或事件处理程序(onclick、onmouseover)。这些内容会被移除。 - 外部字体无法加载。请使用通用字体系列(
monospace、sans-serif、serif),或指定字体并提供通用字体作为回退。 - 字体的 CSS
@import会被阻止。如果需要特定字体,请在<text>中直接设置,并提供系统字体作为回退。
SVG 输出过大
如果 render_svg 返回文件大小警告(超过 50kb),参数曲线可能采样了过多点;请减小 steps。图案组开销很小:子元素只定义一次,并通过 <use> 实例化,因此 cols: 50, rows: 50 的网格组只需一个子元素定义和 2500 个单行 <use> 标签。要生成尽可能小的文件,请添加 output: { minify: true }。
测试方式
每次发布前都会进行一次真实环境自测:通过真实的 MCP stdio 层,使用刚构建的服务生成设计,并反复执行渲染 → 预览 → 评审 → 修改,直到效果达标。测试设计会针对待测更改量身定制,因此该流程能验证新功能在真实使用场景中的表现,而不只是确认没有破坏现有功能。完整日志(包含提示词、迭代次数和生成的资源)见 dogfooding.md。
这一流程能发现实际漏洞。v0.3.0 的缓动自测发现,像 translateX 这样的 CSS 轴向简写会原样写入 SVG 的 transform 属性中,而这类写法无效,会导致元素静默冻结;修复随即在同一版本中发布。output.frames 背后的动画帧采样器会通过真实浏览器验证:使用随机种子(包括随机三次贝塞尔曲线)生成的动画,会通过负动画延迟技巧在无头 Chromium 中冻结,其采样位置必须与 nakkas 的采样结果相差不超过一个像素。这项交叉验证会在 CI 中作为 tests/browser-truth.test.ts 运行;原始手动测试脚本保留在 scripts/easing-browser-truth.sh。
除了自测流程外,还有 397 项单元测试和集成测试,其中包括 MCP stdio 端到端覆盖。
技术栈
- TypeScript + Node.js 18+
@modelcontextprotocol/sdk(MCP 服务)zod(架构验证和 AI 类型指导)- 不依赖外部 SVG 库,直接构造 XML
- Vitest(397 项测试)
许可证
MIT。由 arikusi 开发。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。