Pi Svg Mcp
БесплатноПоддерживаетсяPi extension: run the svg-mcp MCP server via sharedserver and inject the diagram-authoring directive. Pairs with pi-mcp-adapter, which makes svg-mcp's tools rea
Описание
Pi extension: run the svg-mcp MCP server via sharedserver and inject the diagram-authoring directive. Pairs with pi-mcp-adapter, which makes svg-mcp's tools reachable from Pi.
README
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.Alternative: install from a clone (for development)
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, use start_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.json - Windows:
%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.- macOS:
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.
Status
The full inkex catalog is mapped through to 181 MCP tools (see INKEX_PRIMITIVES.md), plus the theme system and the declarative facade families (diagrams, charts, annotations), 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). - Themes — a document-wide design language authored as real CSS (
.svg-mcp/themes/): tokens as custom properties (resolved server-side; emitted CSS is var-free), namespaced classes, per-theme variants (dark mode in ten lines), prose guidance returned to the agent on load. Themes are a role-routed set (boxes from one theme, text from another); nodes hook in automatically by category/primitive/role, and the precedence rule is one sentence: if you typed it, it sticks; if the theme supplied it, it stays linked. See docs/themes.md. - Box-arrow diagrams — declare, don't draw:
add_diagram_node(kind, label)(theme picks the shape, label sizes the box), id-parametric edges with side auto-selection, port fan-out and rounded orthogonal routing, containers drawn behind their members, explicitreflow()after moves, andlayout_diagram(layered/tree/grid) for zero-coordinate authoring. See docs/diagrams.md. - Graph ingest (
add_diagram_graph) — hand it a whole{nodes, edges}graph, in the shape code-graph/dependency exports already emit (from/to, extra keys ignored), and get boxes, arrows and a layout in one call: self-edges dropped and counted, parallel edges merged with their weights summed, unknown endpoints refused rather than invented. You say what the picture is about —collapsegroups,include/excludeglobs,size_fieldscaling a box by area — because no centrality score knows which nodes deserve one. - Edge router —
layout_diagramreserves a lane per rank-spanning edge (dummy nodes in the ranking) so long edges travel between the boxes instead of over them, routes sharing a corridor fan apart instead of drawing on top of each other, and a label is scored onto the cheapest segment long enough to hold it — charged for the boxes, routes and labels it would land on. When the router's choice isn't yours,waypointspins an edge's middle through every later reflow and layout, andpinnedkeeps a node where you put it while the layout packs the drawing around it. - Charts — seven data-parametric kinds (
bar,line,donut,scatter,histogram,sparkline,radar) with nice-number ticks and margins measured from the actual tick labels; anaxes=spec for pinned (and clipped) limits, tick counts or explicit ticks, percent/currency/SI labels, gridlines, minor ticks, tick direction, rotated/inverted axes, a zero spine, reference lines and log scales; plus waterfalls with a total bar, normalized stacks, marker glyphs (open,markevery, bubbles by area), series bands, and step-mid lines.edit_chartre-derives the picture from new data. Deliberately not a plotting library (no density/KDE, secondary axes or colormaps —import_svgmatplotlib output for that). - Annotations —
add_legend()generated from what the document uses (swatches wear the real theme classes),add_calloutcards whose leader lines point at node ids and survivereflow,add_tablewith measured columns,add_callout_cardwith kind-colored accents. - 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.svg is 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.
Design language & declarative facades
State what things are — kinds, roles, connections, data — and geometry and paint are
derived. Everything below was authored with zero coordinates inside the facades, styled by
the bundled default theme, and stays editable (edit_chart swaps the data and re-derives;
translate_node + reflow() re-routes; set_theme_variant("dark") reskins in one call).
And the same idea applied to a graph you already have: the self-portrait below is svg-mcp's own
module graph handed to add_diagram_graph in one call — a code-index export, seven collapse
groups, boxes scaled by symbol count, laid out and routed with no coordinates anywhere.
Guides: docs/themes.md (authoring a design language as CSS — tokens, variants, residency, precedence) and docs/diagrams.md (diagram, chart, and annotation facades). Regenerate the images with examples/generate.py.
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 — it owns the warm
server's lifecycle, refcounted so the process stops once the last session leaves.
You don't need to install it: the plugin fetches a prebuilt release on first use
when none is present, so no Rust toolchain is required. An
sharedserveralready onPATH, or$SHAREDSERVER_BIN, is used as-is and never silently replaced. - 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-mcp
The first session pays a one-time resolve/build cost; later sessions attach to the warm server and start immediately.
Install as an OpenCode plugin
The OpenCode counterpart lives in plugins/opencode
and behaves identically (same warm sharedserver-managed server, same
uvx svg-mcp@<version> pin, same combiner stand-down). Add it to your opencode.json:
{ "plugin": ["@geohar/opencode-svg-mcp@latest"] }
It registers svg-mcp as a type: "remote" MCP endpoint (:7731) and keeps one warm
server behind it. Full options (port, gracePeriod, dev, manage, …) are in the
plugin's README. The same MCP_COMBINER /
MCP_COMBINER_SERVES_SVG_MCP switch below applies — the OpenCode plugin honours it too.
Install as a Pi extension
Pi has no MCP of its own, so it needs pi-mcp-adapter
(pi install npm:pi-mcp-adapter) plus the @geohar/pi-svg-mcp extension in
plugins/pi. The extension runs the same warm
sharedserver-managed server (same uvx svg-mcp@<version> pin) and injects the
diagram-authoring directive on before_agent_start; point the adapter at svg-mcp with a
one-line mcp.json (plugins/pi/mcp.json.example).
Load the extension by symlink into ~/.pi/agent/extensions/, a local path under
settings.json extensions, or the published package under packages. Full options
(SVG_MCP_PORT / _VERSION / _DEV, PI_SVG_MCP_*) are in the plugin's
README. The same MCP_COMBINER /
MCP_COMBINER_SERVES_SVG_MCP stand-down applies — when combiner-served the extension
injects the directive only (no mcp.json needed; you register the combiner instead).
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 global
It 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 copy
SVG_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, pkg-config |
build-essential · python3-dev · pkg-config |
| lxml | the XML/XSLT C libs it binds | libxml2-dev · libxslt1-dev |
PyGObject + pycairo (both pulled in by inkex on Linux) |
GObject-introspection (girepository) and the cairo C lib (PyGObject depends on pycairo) |
libgirepository-2.0-dev · gir1.2-girepository-2.0 · libcairo2-dev (drag in libglib2.0-dev) |
pangocffi / pangocairocffi (cairo extra only) |
the pango C lib (cairo is already covered above) | libpango1.0-dev · libffi-dev |
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-dev
On 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-dev
Equivalents 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 extra
If 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
Settings come from a TOML config file and from env vars prefixed SVG_MCP_. Highest wins:
- CLI flags (
--transport,--host,--port) - env vars (
SVG_MCP_PORT=7731), or a.envfile in the working directory - the config file
- built-in defaults
The config file is ~/.config/svg-mcp/config.toml ($XDG_CONFIG_HOME/svg-mcp/config.toml), or
the path in $SVG_MCP_CONFIG. It's optional. A named $SVG_MCP_CONFIG that doesn't exist, an
unknown key, or a misspelt table ([webauth]) stops svg-mcp at startup rather than being
ignored. Keys are the setting names below; web_auth is a table.
Every launch reads the default file, including stdio servers that MCP clients (Claude Desktop, the plugins) start on their own. Keep it to settings they should all share:
# ~/.config/svg-mcp/config.toml
renderer = "resvg"
default_background = "white"
Settings for one server, such as a shared HTTP server with the web login, go in a file of their
own, selected by $SVG_MCP_CONFIG in that server's launch script. Otherwise a stdio client starts
an HTTP server, or refuses to start for want of the web password:
# ~/.config/svg-mcp/web.toml — launched with SVG_MCP_CONFIG=~/.config/svg-mcp/web.toml
transport = "streamable-http"
port = 7731
[web_auth] # the live preview's browser login, see Web login
mode = "password"
session_days = 30
$SVG_MCP_CONFIG replaces the default file rather than adding to it, so repeat any shared
settings there.
Each key's env var is SVG_MCP_ plus the name in capitals. web_auth takes JSON in the env
var, merged over the file's table:
export SVG_MCP_WEB_AUTH='{"mode": "password", "session_days": 7}'
Setting (env var: SVG_MCP_ + name) |
Default | Meaning |
|---|---|---|
renderer |
resvg |
Render backend: resvg (CLI if present, else in-process) · resvg-py · resvg-cli · cairo · inkscape |
resvg_binary |
auto | Path to the resvg CLI |
inkscape_binary |
auto | Path to the Inkscape CLI |
feedback_max_edge |
unset | Optional long-edge cap (px); unset = raw image handed back directly as base64 |
default_background |
transparent | Default render background (CSS color) |
render_timeout_s |
30 |
Per-render subprocess timeout |
transport |
stdio |
Server transport: stdio · http · streamable-http · sse |
host / port |
127.0.0.1 / 8000 |
Bind address for the http transports |
shutdown_grace |
0 |
Seconds to let in-flight http connections drain on shutdown |
preview |
false |
Auto-start the live preview web server on boot |
preview_host / preview_port |
127.0.0.1 / 8808 |
Bind address for the live preview |
web_auth |
off | Browser login for the live preview (see Web login) |
web_password_hash |
unset | The login's password hash (svg-mcp hash-password); overrides web_auth.password_hash |
web_password |
unset | The login password, RAW — hashed with a fresh salt at startup, in memory only (the explicit hash wins when both are set) |
Two settings are env-only: SVG_MCP_AUTH_TOKEN, the optional bearer token for the http /mcp
endpoint (see Authentication), and SVG_MCP_CONFIG.
Keep secrets out of a config file you share: put the password in SVG_MCP_WEB_PASSWORD (e.g. a
launch script that reads it from 1Password), not in the file.
Authentication
The http /mcp endpoint is unauthenticated by default — fine on loopback, but open the
moment you bind beyond 127.0.0.1. Set SVG_MCP_AUTH_TOKEN and every request to /mcp
must present Authorization: Bearer <token>; a missing/wrong token gets a plain 401 (no
WWW-Authenticate, so standards clients don't drift into OAuth). Unset ⇒ open (unchanged);
/health stays open. When svg-mcp runs behind the
mcp-combiner, give the combiner the token in
this server's servers.json entry: {"auth": {"bearer": "${SVG_MCP_AUTH_TOKEN}"}}. (The gate
is inbound_auth.py, vendored byte-identical from the combiner.)
When svg-mcp runs standalone — its plugins register it directly with the client, no
combiner — setting SVG_MCP_AUTH_TOKEN in the environment you launch the client from is all
you need: each plugin presents the bearer automatically (since v0.7.1), gated on the token
(unset ⇒ no header, exactly as before):
- Claude Code — the SessionStart hook registers with
claude mcp add … -H "Authorization: Bearer $SVG_MCP_AUTH_TOKEN". The hook runs with the full environment (unlike aheadersHelper, which Claude Code strips*TOKEN*-named vars from), so it bakes a static header — no per-connection helper. It reconciles url+header on every start, so rotating or unsetting the token converges without re-adding when nothing changed. - OpenCode — the plugin's config hook adds the
Authorizationheader to itsmcpentry. - Pi — run
/svg-mcp install-configto write a pi-mcp-adaptermcp.jsonentry wired withauth:"bearer", bearerTokenEnv:"SVG_MCP_AUTH_TOKEN"(pi-mcp-adapter has no runtime hook).
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
# or set transport/port in ~/.config/svg-mcp/config.toml, or SVG_MCP_TRANSPORT=http SVG_MCP_PORT=7731
A 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: the model is instructed to open a preview and hand you the URL up front on any
non-trivial drawing, so you can watch (and redirect) as it builds. You can also ask for it
outright — "show me", "open a preview" — or set
preview = true(SVG_MCP_PREVIEW=1) to 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.
Web login
The preview binds loopback by default. Bind it wider (SVG_MCP_PREVIEW_HOST, or a tailnet behind
tailscale serve) and anyone who can reach the address can watch your documents. A browser
can't send the /mcp bearer, so the preview has its own optional login. It's off by default.
Turn it on in the web server's config file (the one its launch script names in
$SVG_MCP_CONFIG, see Configuration):
[web_auth]
mode = "password"
or in the environment: export SVG_MCP_WEB_AUTH='{"mode": "password"}'. Then give it a
password, either as a hash:
svg-mcp hash-password # prompts twice, prints scrypt$16384$8$1$...
export SVG_MCP_WEB_PASSWORD_HASH='scrypt$16384$8$1$...' # or password_hash in [web_auth]
or RAW, and let svg-mcp hash it at startup (e.g. a launch script that reads it from 1Password
with op read):
export SVG_MCP_WEB_PASSWORD='<the password>' # hashed at load, fresh salt, never persisted
When the config file turns the login on, an env var can't silently turn it off: a resolved
mode of "off" stops svg-mcp at startup. Switch it off in the file instead.
An explicit SVG_MCP_WEB_PASSWORD_HASH wins over this. mode: "password" with neither
credential stops svg-mcp at startup; mode password with neither is refused, and web login
doesn't engage with no credential at all.
The password, session cookie and ticket formats are the same as
mcp-combiner's web_auth, so a hash from
mcp-combiner hash-password works here unchanged. mode: "password" without a hash stops svg-mcp
at startup.
| Who | Asked for the password? |
|---|---|
| A browser on this machine (loopback, this machine's interface or tailnet address, opening a URL that names this machine) | No, with exempt_local (the default) |
Opening the URL start_preview returned (it carries a 5-minute ?t= ticket for that chat's preview) |
No: the ticket signs the browser in, then redirects to the clean URL |
A browser holding a valid session cookie (svgmcp_session) |
No, until it expires (session_days) |
Anyone else (another device, or a browser behind a local proxy such as tailscale serve) |
Yes: pages redirect to /auth/login; the event stream, renders and JSON get 401 |
"Names this machine" means the Host header is localhost, one of its addresses, its tailnet
(MagicDNS) name, or the preview's bind host. That stops DNS rebinding: a web page that points
its own domain at 127.0.0.1 connects from loopback, but its requests still say
Host: attacker.example, so they get the login.
A proxy on this machine is believed only from 127.0.0.1 / ::1, and only when it names the
real browser in X-Forwarded-For.
Tunnel caveat: loopback peers are exempt even WITHOUT that header — an ssh -L / socat
tunnel, tailscale serve --tcp, or any forwarder that strips X-Forwarded-For makes every
browser arriving through it look like this machine and skip the login. If you run one, set
exempt_local: false (then only the ticket on start_preview URLs or a password admit
tunnels too). After 5 failed logins in a minute, an
address gets 429, even with the right password. /auth/logout signs the browser out.
Deleting the signing key ($XDG_STATE_HOME/svg-mcp/web-auth.key, by default
~/.local/state/svg-mcp/web-auth.key) signs every browser out. The login covers only the
preview. /mcp keeps its bearer (Authentication).
web_auth option |
Default | Meaning |
|---|---|---|
mode |
"off" |
"off" or "password" |
password_hash |
unset | svg-mcp hash-password output (SVG_MCP_WEB_PASSWORD_HASH wins) |
password |
unset | RAW password, hashed at startup (SVG_MCP_WEB_PASSWORD wins; prefer the env var) |
session_days |
30 |
How long a browser stays signed in |
tickets |
true |
start_preview URLs carry a login ticket |
ticket_uses |
1 |
How many times one ticket can sign a browser in |
exempt_local |
true |
Requests from this machine (its own address, and a Host naming it) skip the login |
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.png
Claude Code — install it as a plugin, or add the server explicitly:
claude mcp add svg-mcp -- /Users/geohar/Development/svg-mcp/.venv/bin/svg-mcp
Claude 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:mcp
The 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 string
Conventions
- 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. A bare handle is an id, else an exact name — never a hierarchy query, so a name containing/(graph imports name nodessrc/ops/x.py) just works. Be explicit when you must:id:x,name:x(verbatim, slashes and all), orpath:ancestor/namefor the ancestor-chain query (each segment an id or name, gaps allowed) that disambiguates a duplicated name; the prefix is stripped once, so any literal name is expressible (name:name:foo). Names should be unique — a name matching several nodes is rejected (no silent guess), and the error lists a handle per candidate. 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.
Установить Pi Svg Mcp в Claude Desktop, Claude Code, Cursor
unyly install pi-svg-mcpСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add pi-svg-mcp -- npx -y @geohar/pi-svg-mcpПошаговые гайды: как установить Pi Svg Mcp
FAQ
Pi Svg Mcp MCP бесплатный?
Да, Pi Svg Mcp MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Pi Svg Mcp?
Нет, Pi Svg Mcp работает без API-ключей и переменных окружения.
Pi Svg Mcp — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Pi Svg Mcp в Claude Desktop, Claude Code или Cursor?
Открой Pi Svg Mcp на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectServer Sequential Thinking
MCP server for sequential thinking and problem solving
автор: GitHub ActionsUnyly Deploy
Deploy sites and MCP servers to Unyly straight from your AI agent
автор: UnylyAmap Maps Mcp Server
MCP server for using the AMap Maps API
автор: duxiaohuiPayMCP
Lightweight payments layer for MCP servers: turn tools into paid endpoints with a two-line decorator. [PyPI](https://pypi.org/project/paymcp/) · [npm](https://w
автор: blustAIServer For Power BI
Enables exploring Microsoft Fabric / Power BI workspaces and semantic models, and executing ad-hoc DAX queries.
автор: Mitsubishi-FusoGpt Image 2 Mcp
RunAPI GPT Image 2 MCP server for image generation: create tasks, poll results, and check pricing across 1 model from Claude Code, Codex, Cursor, and VS Code.
автор: runapi-aiCompare Pi Svg Mcp with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
