使用说明
MCP server that turns AI into an SVG artist.
One rendering engine. AI decides everything.
Officially listed on the MCP Registry, Glama, LobeHub, and PulseMCP.
Both drawn by an AI through nakkas, one JSON config each, iterated against its own PNG previews. Pure CSS animation, no JavaScript.
Nakkas是一个MCP(模型上下文协议)服务器,它允许像Claude这样的AI助手从声明性JSON配置中创建动画SVG图形:徽标、图标、加载微调器、GitHub README横幅、徽章和生成艺术。它无需JavaScript即可渲染CSS@关键帧和SMIL动画,因此输出可以在GitHub README和任何 <img> 标签渲染SVG。每个渲染都以PNG预览和服务器端工件id的形式返回,因此AI可以立即看到自己的工作,并在SVG文本从未通过其上下文窗口的情况下进行迭代。
纳克卡斯 土耳其语中的“画家/艺术家”(旧)。
"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@关键帧+SMIL动画,无需JavaScript。GitHub的camo代理幸存下来。
- 零外部存款。 没有云API,没有API密钥。在当地运行。
安装
克劳德桌面
添加到您的配置文件中:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json - 窗户:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"nakkas": {
"command": "npx",
"args": ["-y", "nakkas@latest"]
}
}
}
克劳德代码(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(光栅)到磁盘 |
预期的工作流程:渲染→ 查看返回的预览→ 修改配置→ 再次渲染→ save.渲染的SVG作为 人工制品: render_svg 使用预览图像和类似id的答案 art-1,以及 preview/save 直接接受该id。SVG文本永远不必通过模型的上下文窗口返回,这将迭代循环的令牌成本降低到粘贴SVG的一小部分。工件在服务器进程生命周期内有效(上限为32,最旧的被驱逐)。
这 save 工具
{ "artifact": "art-1", "outputPath": "./design.svg", "format": "auto" }
要么通过 artifact (id来自 render_svg,首选)或 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 从以下位置获取标记id defs.markers |
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 (on 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[]
}
过滤器预设
参考如下 filter: "url(#myId)" 在定义后的任何元素上 defs.filters:
{ "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属性键:camelCase(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"):from/to路径必须具有相同的命令类型和计数。只有坐标可以不同。
字体
更喜欢CSS泛型族: sans-serif, serif, monospace。他们决心在浏览器和nakkas预览版的每个平台上使用真正的字体。命名字体如 Arial 或 Helvetica 仅存在于某些系统上(不存在于大多数Linux机器上),因此依赖于它们的设计在其他地方会呈现不同的效果。安全模式是一种具有通用回退的命名字体: "Georgia, serif".
进入 preview 和PNG save,泛型家族是通过操作系统自己的字体映射(Linux上的fontconfig)解决的,因此AI看到的内容与该机器上的浏览器显示的内容相匹配。当字体在渲染环境中可用时,可以接受自定义字体族并使其工作。
用例和兼容性
| 上下文 | CSS@关键帧 | SMIL | 外部字体 | 交互式(onclick) |
|---|---|---|---|---|
GitHub自述 <img> |
✅ | ✅ | ❌ | ❌ |
网页 <img> |
✅ | ✅ | ❌ | ❌ |
| 网页内联SVG | ✅ | ✅ | ✅ | ✅ |
| 设计工具导出 | ✅ | ✅ | ✅ | — |
| 静态文件查看器 | ✅ | ✅ | 取决于 | 取决于 |
故障排除
“MCP错误-32602:输入验证错误”
这意味着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")被自动强制而不是失败。
处理程序在呈现之前还会检查引用完整性: url(#id) 在 fill/stroke/filter/clipPath/mask一 use.href 指向任何地方,a textPath.pathId 缺少 defs.paths,或者重复的ID都会以错误的形式返回,并带有确切的字段路径和已定义的ID列表,而不是呈现静默中断的输出。
如果您正在构建MCP客户端集成,并且一直看到这一点,那么问题可能在于您的客户端如何序列化参数。看 人类学/克劳德编码#29104 了解已知序列化怪癖的上下文。
预览显示空白或意外图像
在任何动画开始之前,单个预览在t=0时渲染静态快照。要查看运动,请使用渲染 output: { frames: 4 } (或最多10个):nakkas在N个时间点对CSS动画进行采样,并返回一个标记的幻灯片图像。SMIL动画是个例外;它们不被采样,总是显示它们的基本状态。
如果图像完全空白:
- 检查你的元素是否
fill或stroke集。透明画布上没有填充的形状是不可见的。 - 检查坐标。一个元素在
x: 2000在A800px宽画布只是屏幕外的。 - 如果使用
filter: "url(#myFilter)",确保myFilter实际上是在defs.filters.
动画无法在GitHub上运行
GitHub README通过以下方式呈现SVG <img> 标签,它剥离了JavaScript,但保留了CSS和SMIL。如果你的动画在本地运行,但不在GitHub上运行:
- 避免
<script>或事件处理程序(onclick,onmouseover).这些已被删除。 - 外部字体无法加载。坚持使用普通家族(
monospace,sans-serif,serif)或具有通用回退的命名字体。 - 层叠样式表
@import因为字体被阻止。如果需要特定字体,请使用内联字体<text>系统回退。
大SVG输出
如果 render_svg 返回关于文件大小(超过50kb)的警告,参数曲线可能采样的点太多;减少 steps.模式组很便宜:子对象定义一次,并用实例化 <use>,因此网格组具有 cols: 50, rows: 50 一个孩子定义加2500行 <use> 标签。对于尽可能小的文件,添加 output: { minify: true }.
如何测试
每个版本都只在狗粮运行后发布:一种通过真实的MCP stdio层对新构建的服务器进行迭代渲染的设计→ 预览→ 批评→ 修改,直到它成立。测试设计是针对测试中的变化量身定制的,因此运行证明新功能在实际使用中是有效的,而不仅仅是没有损坏。包含提示、迭代计数和结果资产的完整日志位于 狗粮.md.
这种仪式会抓到真正的虫子。v0.3.0版本的easing dogfood发现CSS轴的简写如下 translateX 被逐字写入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。由...建造 礼仪.
