nakkas is an MCP server that turns AI into an SVG artist, enabling you to generate, preview, and save animated SVG graphics through a declarative JSON-based configuration. It runs fully locally with no external dependencies, API keys, or cloud services required.
Render SVGs (
render_svg): Generate complete, animated SVG XML from a JSON config — AI controls all design parameters including shapes, colors, gradients, filters, and animations.Supports a rich set of element types:
rect,circle,ellipse,line,polyline,polygon,path,image,text,textPath,group,use, and pattern groups (radial-group,arc-group,grid-group,scatter-group,path-group)Create parametric mathematical curves: rose, heart, lissajous, spiral, star, superformula, hypotrochoid, wave
Define reusable
defs: linear/radial gradients (with animated stops), filter presets (glow, neon, blur, drop-shadow, glitch, chromatic-aberration, noise, outline, inner-shadow, emboss), clip paths, masks, symbols, and tile patternsAnimate with CSS @keyframes (linked via
cssClass) and SMIL animations (animate,animateTransform,animateMotion) — pure declarative SVG, no JavaScriptReceive design analysis warnings about common issues after rendering
Preview SVGs (
preview): Render an SVG string to a PNG image (returned as base64) for visual inspection, with optional width scaling. Renders a static snapshot (t=0). Supports an iterative render → preview → critique → revise workflow (at least 3 iterations recommended).Save to disk (
save): Save the final design as an SVG text file or a PNG raster image. Auto-detects format from file extension, supports explicit format specification, allows custom width for raster output, and auto-increments filenames to prevent overwrites.
Enables the creation of animated SVG assets specifically optimized for display in GitHub README files and other markdown environments.
Turns the AI into a vector artist capable of rendering complex, animated SVG graphics including shapes, path morphing, and advanced filter presets from declarative configurations.
nakkaş means painter/artist in Turkish (old).
"make a neon terminal logo with animated binary digits"
→ AI constructs JSON config
→ nakkas renders to animated SVG
→ clean animated SVG outputWhy
One tool, infinite designs.
render_svgtakes a JSON config. AI fills in everything.AI-native schema. Every field has
.describe()annotations so the model knows what to do.Pure declarative SVG. CSS @keyframes + SMIL animations, no JavaScript.
Zero external deps. No cloud API, no API keys. Runs locally.
Install
Claude Desktop
Add to your config file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"nakkas": {
"command": "npx",
"args": ["-y", "nakkas@latest"]
}
}
}Claude Code (CLI)
claude mcp add nakkas npx nakkas@latestCursor / Zed / Other MCP clients
{
"mcpServers": {
"nakkas": {
"command": "npx",
"args": ["-y", "nakkas@latest"]
}
}
}Local Development
git clone https://github.com/arikusi/nakkas
cd nakkas
npm install && npm run build
# Use dist/index.js as the commandQuick Start
Ask your AI (with Nakkas connected):
"Make an animated SVG: dark terminal frame (800×200), glowing cyan text 'NAKKAS', neon glow filter, fade-in on load."
"Create a loading spinner: a circle with a draw-on stroke animation that loops every 1.5 seconds."
"Data visualization: animated bar chart, 5 bars, each fading in with a staggered delay, gradient fills."
"Profile badge (400×120): blue-to-purple gradient, white username text, drop shadow, subtle pulse animation."
Tools
Nakkas provides three tools:
Tool | Purpose |
| Takes SVGConfig JSON, returns SVG string + design analysis warnings |
| Takes rendered content, returns a PNG image for visual inspection |
| Takes rendered content, saves to disk as SVG (text) or PNG (raster) |
The intended workflow: render → preview → iterate → save. The save tool is separate from render_svg to encourage previewing and refining before saving.
The save Tool
{ "content": "<svg ...>...</svg>", "outputPath": "./design.svg", "format": "auto" }Formats: auto (infers from extension), svg (text file), png (renders to raster first). If the file exists, a numeric counter is appended to prevent overwriting. The actual saved path is returned.
The render_svg Tool
Input: SVGConfig JSON object
Output: Complete SVG XML string plus optional design analysis notes
After rendering, the response may include design warnings about common issues such as too many concurrent animations, missing transformBox, or group-level scale transforms.
SVGConfig Structure
{
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
},
elements: Element[], // shapes, text, groups, use instances
animations?: CSSAnimation[] // CSS @keyframes definitions
}Element Types
Type | Required fields | Notes |
|
|
|
|
|
|
|
| Independent horizontal/vertical radii |
|
| |
|
| Open path: |
|
| Auto-closed shape |
|
| Full SVG path commands |
|
| URL or |
|
| String or |
|
| Text following a curve; path defined in |
|
| Shared attrs applied to all children (no nested groups) |
|
| Instance a symbol or clone an element by |
|
| Place N copies around a full circle |
|
| Place N copies along a circular arc |
|
| Place copies in an M by N grid |
|
| Scatter N copies at seeded random positions |
|
| Distribute N copies evenly along a polyline |
|
| Mathematical curve: |
All Visual Elements (Shared Fields)
{
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 Presets
Reference as filter: "url(#myId)" on any element after defining in defs.filters:
{ "type": "preset", "id": "myGlow", "preset": "glow", "stdDeviation": 8, "color": "#ff00ff" }Preset | Key params | Effect |
|
| Soft halo |
|
| Intense bright glow |
|
| Gaussian blur |
|
| Drop shadow |
|
| Turbulence displacement (animated) |
|
| Desaturate |
| — | Warm sepia tone |
| — | Invert colors |
|
| Boost/reduce saturation |
|
| Shift hues |
|
| RGB channel split for lens distortion look |
|
| Film grain and texture overlay |
|
| Colored outline around the element |
|
| Shadow inside the element |
|
| 3D relief shading effect |
CSS Animations
{
"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 property keys: camelCase (strokeDashoffset) or kebab-case (stroke-dashoffset). Both work.
Animatable CSS properties: opacity, fill, stroke, transform, filter, clip-path, stroke-dasharray, stroke-dashoffset, font-size, letter-spacing and more.
SMIL Animations
Three SMIL types, defined inline on each element via 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" }Path morphing (attributeName: "d"): from/to paths must have identical command types and counts. Only coordinates can differ.
Fonts
System fonts work everywhere without any loading: Arial, Helvetica, Courier New, Georgia, Verdana, monospace, sans-serif, serif.
Custom font families are also accepted. They work when the font is available in the rendering environment (web page with loaded fonts, design tool, etc.).
Use Cases & Compatibility
Context | CSS @keyframes | SMIL | External fonts | Interactive (onclick) |
GitHub README | ✅ | ✅ | ❌ | ❌ |
Web page | ✅ | ✅ | ❌ | ❌ |
Web page inline SVG | ✅ | ✅ | ✅ | ✅ |
Design tool export | ✅ | ✅ | ✅ | — |
Static file viewer | ✅ | ✅ | depends | depends |
Tech Stack
TypeScript + Node.js 18+
@modelcontextprotocol/sdk(MCP server)zod(schema validation and AI type guidance)No external SVG libraries, pure XML construction
Vitest (280 tests)
License
MIT. Built by arikusi.