svg-mcp
Provides tools for creating and manipulating SVG graphics, including shapes, paths, text, gradients, patterns, and more, allowing AI agents to author complex SVG documents.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@svg-mcpcreate a simple red circle with radius 50"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
svg-mcp
π Rendered documentation: docs.georgeharker.com/svg-mcp β with a gallery and cookbook.
A FastMCP server that exposes structured, hierarchical SVG authoring to an LLM β all primitives, gradients/patterns, paths, text-on-path, embedded raster, reusable styles β built around an inkex-backed document model the AI can navigate, query, and manipulate as groups, layers, transforms, and masks, with a fast construct β render β see β iterate loop.
The logo above was authored entirely through these tools β see Usage.
See DESIGN.md for the full architecture and the
INKEX_PRIMITIVES.md catalog for the inkex β tool mapping.
Quickstart
Prerequisites β Python β₯ 3.12 and uv. Every path below runs svg-mcp through uv β standalone and the Claude Code plugin β so install it first if you don't have it:
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux # alternatives: brew install uv Β· pipx install uv Β· winget install astral-sh.uv (Windows)Rendering happens in-process (the resvg engine ships as the
resvg-pydependency), so there's no separate renderer to install. Optionally, install the resvg CLI for a small per-render speedup β used automatically when onPATH:brew install resvg # OPTIONAL (macOS; or: cargo install resvg)Linux build headers. On macOS the install is pure wheels. On Linux, two dependencies β lxml and PyGObject (the latter pulled in by
inkex) β sometimes have no matching wheel for a recent interpreter and are compiled from source by pip, which needs C headers. Install them up front (see System packages (Linux) for the full per-library mapping and other distros). On Debian 13 (trixie) / Ubuntu:sudo apt-get install build-essential python3-dev pkg-config \ libxml2-dev libxslt1-dev libglib2.0-dev \ libgirepository-2.0-dev gir1.2-girepository-2.0 libcairo2-devInstall.
Recommended β no clone. Install svg-mcp into an environment you manage, so the entrypoint is stable and launches fast (no per-invocation dependency resolution):
uv tool install svg-mcp # isolated tool; `svg-mcp` on PATH # into your own venv instead: uv pip install svg-mcp # (with that venv active) # track main (pre-release): uv tool install "git+https://github.com/georgeharker/svg-mcp"β entrypoint:
svg-mcp(absolute:which svg-mcp, usually~/.local/bin/svg-mcp), or/path/to/your/venv/bin/svg-mcpfor theuv pip installcase.π‘ Avoid
uvx svg-mcpas the MCP server command:uvxre-resolves dependencies on every launch β needless latency for a long-lived server. Install once (above) and point your client at the resulting entrypoint.Pick one of two install layouts β this choice decides the path you point Claude at:
a) Project-local venv (self-contained). Creates
./.venvinside the repo and installs svg-mcp editable into it β nothing touches your other environments:uv sync # β ./.venv with svg-mcp + deps (from pyproject/uv.lock) # equivalently, without uv.lock: uv venv && uv pip install -e .β entrypoint:
./.venv/bin/svg-mcp(absolute:$(pwd)/.venv/bin/svg-mcp).b) Into your own existing venv. Activate the venv you want first, then install editable:
source /path/to/your/venv/bin/activate # or otherwise have VIRTUAL_ENV set uv pip install -e . # or: pip install -e .β entrypoint:
/path/to/your/venv/bin/svg-mcp.β οΈ
uv syncalways targets the project's./.venv, butuv pip install/pip installtarget whichever venv is currently active. If you already have one active, svg-mcp and its dependencies (fastmcp, inkex, fontTools, numpy, Pillow, β¦) get installed into it. Use option (a) β or an explicit fresh venv β unless you deliberately want it in an existing environment.Connect a client. svg-mcp is a standard stdio MCP server β point your client at the absolute entrypoint from step 2 (Claude can't resolve
PATH). Which Claude surface you use decides what you get: the inline PNG render works everywhere, but the interactiveshow_widgetwidget (zoom Β· pan Β· backdrop Β· save) only renders on a chat surface β not in Claude Code.Surface
Wire it up
inline PNG
interactive
show_widgetClaude Code (CLI Β· IDE Β· desktop app)
plugin Β·
claude mcp addΒ·.mcp.jsonβ
β (falls back to the PNG)
Claude Desktop β chat
claude_desktop_config.jsonβ
β
claude.ai / remote
HTTP transport + a connector
β
β
In Claude Code,
show_widgetshows the same PNG; for an interactive view there, usestart_previewβ a loopback browser page.Claude Code β the plugin is easiest (no wiring). Or add the entrypoint explicitly, or commit a project-scoped
.mcp.json:claude mcp add svg-mcp -- "$(which svg-mcp)" # user scope{ "mcpServers": { "svg-mcp": { "command": "/ABSOLUTE/PATH/TO/svg-mcp" } } } // .mcp.json at repo rootClaude Desktop (chat) β the surface that renders the interactive widget. Add svg-mcp to
claude_desktop_config.json(create it if missing) using the absolute entrypoint:macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{ "mcpServers": { "svg-mcp": { "command": "/ABSOLUTE/PATH/TO/svg-mcp" } } }Then fully quit Claude Desktop (βQ on macOS β not just the window) and reopen so it launches the server; confirm it under Settings β Developer. In a chat, ask Claude to draw something and call
show_widgetβ the render appears as an interactive card you can zoom, pan, re-backdrop, refresh (β»), and open in the full browser preview (β). To save a file, ask Claude toexport_render/export_svg(a sandboxed widget can't trigger downloads). (Claude Desktop's "cowork"/agent mode is a Code-style surface and won't render the widget β use plain chat.)claude.ai / remote β run svg-mcp over HTTP and add it as a connector:
svg-mcp --transport streamable-http --host 127.0.0.1 --port 7731 # serves MCP at /mcpclaude.ai can't reach
localhost, so expose it with a tunnel β e.g.cloudflared tunnel --url http://127.0.0.1:7731β and add the resulting publichttps://β¦/mcpURL under Settings β Connectors.Try it β ask Claude:
Use svg-mcp to make a 320Γ120 badge that says "hello" on a blue gradient, then show me the render.
Claude creates a document, adds the shapes and text, and calls
render_documentto show you the image inline β then you iterate in plain language ("bigger text", "add a drop shadow", "outline the text to paths"). See Usage for the conventions and a worked example.
Related MCP server: PlotMCP Server
Status
The full inkex catalog is mapped through to 117 MCP tools (see
INKEX_PRIMITIVES.md), with ruff/mypy clean and the test suite green.
Document model (inkex-backed, multi-document with an active-document default): shapes, paths (+ arc/star factories and path-data ops), text + tspan runs + text-on-path + flowed text, images (base64/file embed), groups (+ ungroup, z-order, duplicate, world-preserving reparent), layers (+ visible/locked/opacity), symbols/use, hyperlinks.
Text β paths (
text_to_path): pure-Python glyph outlining (fontTools) β flattens tspans, honors bold/italic/per-run fill, and walks text along a curve (<textPath>).list_fontsenumerates installed families;measure_textreturns a run's width/height from font metrics so you can fit and center text without a render round-trip.Variable-width strokes (
add_variable_width_path): SVG has no native variable stroke-width, so swelling/tapering lines β calligraphy, engraving, brushes, tapered arrows β are expanded into a filled ribbon, with butt/round caps and optional cubic (Catmull-Rom) smoothing of both the path and the width.Squircles & smoothed shapes β a parametric family with iOS/Figma corner smoothing, each
add_/edit_with a stored spec for re-editing:add_squircle(Apple's continuous-corner app icon β rounded rect whose edges ease into the corner arc with cubic BΓ©ziers;smoothness0β1),add_rounded_polygon(the same idea generalized to N sides β soft-cornered triangle/pentagon/ hexagon/β¦),add_pill(stadium with fully rounded ends), andadd_superellipse(the LamΓ© curve|x/rx|^n+|y/ry|^n=1β one continuous edge-less curve theexponentmorphs from diamond β ellipse β squircle β rectangle).Boolean ops (
booleanβ union/difference/intersection/exclusion): combine shapes with no new dependency, realized via native clip/mask/compound-path constructs (e.g. an even-width icon bezel isdifferenceof an outer and inner squircle). Operands may be composite groups. Note: the result is a render-time construct, not a re-editable merged path β true geometry-level booleans await an engine (lib2geom).Path offset (
offset_path): parallel-curve / inset by a signed distance β concentric rings, even-width bezels, glow outlines, stroke-outlining. A squircle/pill/rounded-polygon is offset EXACTLY by regenerating its params (and stays a re-editable parametric shape); anything else uses an analytic cubic-BΓ©zier offset (adaptive Tiller-Hanson + round/miter/bevel joins) into a new path β approximate, no self-intersection trimming (a large inward offset can fold over itself).Bulk constructors (
add_rects/add_circles/add_lines/add_paths/add_variable_width_paths): add many shapes in one call (one round-trip) β for procedural art, hatching/engraving fields, and data viz.Import existing SVG (
import_svg, inline or from a file) into a session document to render, inspect, or edit.Reusable resources: named styles (CSS classes +
@namepaint refs), linear/radial/mesh gradients, patterns, markers, clip + mask, and filters (blur, drop-shadow, color-matrix/overlay, blend, morphology, component-transfer, turbulence, displacement, plus a raw filter-graph builder).Composable effect stack β Photoshop-style layer effects that stack on one node (each
apply_*appends;replace=truestarts fresh), each bounded by asize/falloff so the interior fill stays intact:apply_drop_shadow,apply_inner_shadow,apply_outer_glow,apply_inner_glow,apply_outline,apply_bevel,apply_gloss(glassy sheen on the lit edge),apply_front_light(its inverse β lights the front body),apply_grain.get_filterdescribes the ordered stack andedit_filtertweaks one effect's params by index β round-tripping through the SVG (data-fx).
Architecture
Authored entirely through the svg-mcp tools and rendered via the server's resvg backend (
docs/architecture.svgis the live serialized source).
Three layers: an interface & contract tier (the FastMCP server, pydantic input schemas, and
per-session document stores), a document-operations tier (inkex-facing construction/edit,
read-only introspection, and the document model), and a rendering & output tier (pure-Python
typesetting, the render/export backends, and SVG serialization). See DESIGN.md
for the full layering rationale.
Install as a Claude Code plugin
The repo doubles as a Claude Code plugin marketplace. The plugin
runs the published release with uvx, behind one warm server shared by every session
(via sharedserver) rather than a cold start per session. Your documents stay isolated
regardless: svg-mcp partitions state per MCP session, so each chat gets its own document
store exactly as separate processes did.
Prerequisites:
uv (for
uvx) onPATH. If it's missing the plugin still loads, but the server won't start β aSessionStarthook reports this with an install link.sharedserver on
PATH(cargo install sharedserver) β it owns the warm server's lifecycle, refcounted so the process stops once the last session leaves. Missing it is likewise reported, not fatal.Linux only: install the system packages (Linux) before the first launch, so
lxml/PyGObjectcan build. macOS installs entirely from wheels.
/plugin marketplace add georgeharker/svg-mcp
/plugin install svg-mcp@svg-mcpThe first session pays a one-time resolve/build cost; later sessions attach to the warm server and start immediately.
If a combiner already serves svg-mcp
The plugin writes to your user-scope MCP config (claude mcp add|remove) rather than
declaring a server in its manifest β that is what lets one plugin serve both deployments.
If an aggregator such as mcp-companion
already proxies svg-mcp, registering a second copy would mount every tool twice. Set the
global switch once (e.g. in ~/.zshenv) and the plugin will not register:
export MCP_COMBINER=1 # a combiner serves my MCPs
export MCP_COMBINER_SERVES_SVG_MCP=0 # β¦except svg-mcp β override, wins over the globalIt is a machine-wide toggle, not per-session: it must be set before Claude Code starts, and the registry it drives is global. Both directions converge β setting it removes our entry, unsetting it re-adds β but a change lands in the next session.
Running a local checkout (development)
By default the plugin serves uvx svg-mcp@<plugin version> β the published release, pinned so
the marketplace version and the served code agree. To serve your own working tree instead:
export SVG_MCP_DEV=~/Development/svg-mcp # a checkout to run via `uv run --project`
export SVG_MCP_DEV=1 # or: the plugin's own bundled copySVG_MCP_PORT (default 7731) moves both the registered URL and the server's bind port.
Install (manual / development)
pip install -e ".[dev]"System packages (Linux)
macOS installs entirely from wheels β nothing extra. On Linux, pip falls back to building a couple of dependencies from source whenever no wheel matches your interpreter (common on Debian 13 βtrixieβ and other recent/edge distros), and those builds need C headers and build tooling. Which library needs what:
Python dependency | Why | Debian/Ubuntu packages |
(all C-extension builds) | compiler, headers, |
|
lxml | the XML/XSLT C libs it binds |
|
PyGObject + pycairo (both pulled in by | GObject-introspection ( |
|
pangocffi / pangocairocffi ( | the pango C lib (cairo is already covered above) |
|
Core install (everything except the optional cairo extra) on Debian/Ubuntu β verified on
Debian 13 (trixie):
sudo apt-get install build-essential python3-dev pkg-config \
libxml2-dev libxslt1-dev \
libglib2.0-dev libgirepository-2.0-dev gir1.2-girepository-2.0 \
libcairo2-devOn trixie the GObject-introspection dev package is the new
libgirepository-2.0-dev(it pulls inlibglib2.0-dev); on older releases it islibgirepository1.0-dev. Likewise the XSLT headers arelibxslt1-dev(the barelibxslt-devname is only a virtual alias).
If you also install the cairo extra (pip install -e ".[cairo]"), add pango (cairo is
already in the core set above):
sudo apt-get install libpango1.0-dev libffi-devEquivalents on other distros (same libraries, distro-specific names):
# Fedora / RHEL
sudo dnf install gcc python3-devel pkgconf-pkg-config \
libxml2-devel libxslt-devel \
gobject-introspection-devel glib2-devel cairo-devel \
pango-devel libffi-devel # last line = cairo extra
# Arch
sudo pacman -S base-devel libxml2 libxslt gobject-introspection cairo \
pango libffi # pango/libffi = cairo extraIf pip instead finds wheels for every dependency on your platform/interpreter, none of the above is needed β the build-from-source fallback is the only thing that pulls these in.
inkex (the SVG DOM)
Depends on the released PyPI inkex>=1.4.1. Note its API shape (verified against the
install): inkex.colors is a flat module, and there is no Image.embed_image() β raster
embedding is done manually (base64 data URI). All element classes plus bounding_box,
composed_transform, specified_style, and Transform @ composition are present.
resvg (the default renderer)
resvg is a deterministic, cross-platform renderer with native text-on-path and broad filter
support and no system libs. It renders in-process via the bundled resvg-py binding
(same engine β verified pixel-identical to the CLI), so a bare install is self-contained with no
external binary.
If the resvg CLI is on PATH it's used automatically instead β marginally faster, entirely
optional:
brew install resvg # OPTIONAL (macOS; or: cargo install resvg)Override the CLI path with SVG_MCP_RESVG_BINARY=/path/to/resvg, or force a backend with
SVG_MCP_RENDERER=resvg-py (in-process) / resvg-cli. Vector export (pdf/ps/eps) still needs
the librsvg rsvg-convert binary; raster output never does.
Optional backends
cairoextra β secondary vector/raster backend (PDF/PS/SVG out) via cairocffi + pango; needs the cairo + pango system libs (macOS: Homebrewcairo+pango; Linux: see System packages (Linux)). Stub today.Headless Inkscape β reference renderer + heavy ops, driven via
--shell(no D-Bus on macOS). Stub today.
Configuration
All settings are env vars prefixed SVG_MCP_ (or a .env file):
Var | Default | Meaning |
|
| Render backend: |
| auto | Path to the resvg CLI |
| auto | Path to the Inkscape CLI |
| unset | Optional long-edge cap (px); unset = raw image handed back directly as base64 |
| transparent | Default render background (CSS color) |
|
| Per-render subprocess timeout |
|
| Server transport: |
|
| Bind address for the http transports |
| unset | Auto-start the live preview web server on boot ( |
|
| Bind address for the live preview |
Transport, host, and port can also be set with CLI flags (which take precedence over the env
vars): svg-mcp --transport streamable-http --host 127.0.0.1 --port 7731.
Develop
pytest # tests (resvg smoke test auto-skips if the binary is absent)
ruff check . # lint
mypy src # types (no Any / object β precise types only)Run
svg-mcp # FastMCP server over stdio (default)
svg-mcp --transport streamable-http --port 7731 # or streamable HTTP at 127.0.0.1:7731/mcp
# env vars work too: SVG_MCP_TRANSPORT=http SVG_MCP_PORT=7731 svg-mcpA long-running HTTP server is handy for a shared/persistent endpoint a bridge can connect to (the server runs as a single process; each client connection gets its own isolated documents).
Live preview
Rendering each step back into the chat costs tokens and, in a terminal, the model sees the image but you don't. The live preview solves both: a tiny loopback web page that mirrors the active document and auto-refreshes on every edit over Server-Sent Events β so you watch the drawing build in a browser while the model keeps working, with no render bytes spent on the conversation.
Start it: ask the model to "show me" (or "open a preview") and it calls the
start_previewtool, then hands you the URL. Or setSVG_MCP_PREVIEW=1to auto-start on boot.Watch it: open the URL once and leave it β it repaints on each change. Toggle PNG (faithful resvg output) vs SVG (crisp vector), zoom, drag to pan, and Save the current frame as PNG/SVG/WebP/JPEG/PDF straight from the page.
Per-chat: the URL carries a session token (
/<token>/), so each chat gets its own isolated preview even though they share one server and port β one chat's edits never appear in another's.
The page just mirrors the read-only svg:// resources: GET /<token>/active/render
is the same render as the svg://{id}/render resource, refreshed by the same change signal.
Inline widget (show_widget)
show_widget is the in-chat companion to the live preview: instead of a browser page, it renders
the active document inside the conversation as an interactive card β zoom, pan, backdrop,
refresh, and a jump to the full browser preview β on Claude surfaces that render MCP-Apps widgets
(Claude Desktop chat, claude.ai). The rendered PNG also rides in the tool result, so on surfaces
that don't render widgets (Claude Code, and the model's own render-and-see loop) you still get
the image inline β nothing is lost. To save a file, ask the model to export_render / export_svg
(a sandboxed widget can't trigger a browser download).
Reach for show_widget when you want a quick look in the chat with no browser, and for
start_preview when you want the always-on, auto-refreshing browser view (which
works on every surface). The widget is self-contained β the ext-apps
runtime is vendored inline (scripts/vendor_ext_apps.py regenerates it), so it needs no server and
no network.
Experiment with an LLM
Quickest sanity check (no LLM): render a sample poster to PNG.
.venv/bin/python scripts/demo.py # writes demo_output.pngClaude Code β install it as a plugin, or add the server explicitly:
claude mcp add svg-mcp -- /Users/geohar/Development/svg-mcp/.venv/bin/svg-mcpClaude Desktop β add to claude_desktop_config.json:
{
"mcpServers": {
"svg-mcp": { "command": "/Users/geohar/Development/svg-mcp/.venv/bin/svg-mcp" }
}
}MCP Inspector β interactively call tools and view rendered images in a browser:
uv run fastmcp dev src/svg_mcp/server.py:mcpThe model's loop is: create_document β add nodes / resources β render_document to see the
result inline β iterate β export_svg. The server's instructions describe the full workflow
and conventions; each tool carries its own description.
Usage
The tools are called by an LLM over MCP. The core loop is create β build β render-and-see β iterate β export. A minimal session (arguments shown as the JSON each tool receives):
create_document(width=320, height=120) # β {document_id:"doc1", active:true}
# define a reusable gradient, then paint with it by name (@name) or url(#id)
define_linear_gradient(x1=0, y1=0, x2=1, y2=0,
stops=[{offset:0, color:"#7dd3fc"}, {offset:1, color:"#1e3a8a"}], name="brand")
add_rect(x=0, y=0, width=320, height=120, rx=16, style={fill:"@brand"})
add_text(x=160, y=72, content="svg-mcp", name="title",
style={font_family:"Helvetica", font_size:"40px", font_weight:"bold",
text_anchor:"middle", fill:"#ffffff"})
apply_drop_shadow(target="title", dx=0, dy=2, blur=3, color="#000", opacity=0.4)
render_document(scale=2) # returns the rendered PNG inline β look, then adjust
export_svg() # final SVG source stringConventions
Active document.
create_documentreturns adocument_idand makes it active; you may omitdocument_idon later calls to target it. Pass it explicitly to switch, or useset_active_document. Callcurrent_context()to re-anchor (active id, open docs, outline).Targets by id or name. Every
target/parent/contentarg takes a node's returned id or the friendlynameyou gave it. Name things you'll revisit; reason viafind(name=β¦)andoutline. Names should be unique β a name matching several nodes is rejected (no silent guess); disambiguate with a hierarchy pathancestor/name(each segment an id or name) or the id. Each chat/connection has its own isolated set of documents (current_contextreports thesession_id).Stacking. Later siblings paint on top. Restack relative to a sibling with
reparent(target, above=<node>)/below=<node>instead of counting child indices.Coordinates. User units, origin top-left, y increases downward.
Style. A structured object β
fill,stroke,stroke_width,opacity, plus typography (font_family,font_size,font_weight,font_style,text_anchor). Colors accept hex /rgb()/ CSS names /none, or a paint referenceurl(#id)/@nameto a defined gradient or pattern.Resources follow create β define β reference/apply:
define_*returns an id you use as a fill (url(#id)/@name) or attach viaapply_*(clip/mask/marker/filter). Clip/mask/ symbol/pattern definitions move the listed content nodes into the resource, so build those shapes first.list_resources()shows what's defined.Transforms compose:
translate_node,rotate_node(optional center),scale_node(optional anchor),skew_node, orapply_transform("rotate(45 100 100)").Text.
add_text+add_text_runfor multi-line/styled spans;add_text_on_pathto flow along a path. Judge text size withrender_document(geometry queries are empty for live text).text_to_pathoutlines text to glyph paths β font-independent, flattens tspans, and walks<textPath>along its curve;list_fontslists installable families.
Resources
Open documents are also exposed as readable MCP resources, so a host can surface live state:
svg://documents (index + which is active), svg://{id}/svg (source), svg://{id}/render
(PNG). Mutations emit resources/updated notifications.
Example: the logo
The header logo (logo.svg) was built with these tools β <mcp>svg</mcp> as a blue gradient
wordmark, an orangeβwhite starfield clipped into the glyphs, the inner svg word under a
translucent white veil (so the <mcp> tags read as starry space and svg as frosted white), all
under one drop shadow β then text_to_path outlined the glyphs so the final file is
font-independent. scripts/demo.py shows a smaller end-to-end build you can run directly.
License
GPL-2.0-or-later. svg-mcp's document model is built on inkex
(the Inkscape extensions library), which is GPL-2.0-or-later β a strong copyleft license that
extends to works that import it. svg-mcp is therefore licensed under the GNU General Public License
v2 or later; see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/georgeharker/svg-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server