Skip to main content

pac-vector

Local stdio MCP server: an SVG editor and Lottie animator over in-memory virtual documents (one per "window", like Inkscape), with a live browser preview of every edit.

  • Content goes in as SVG markup; structure (layers, groups, masks, z-order) has typed tools.
  • Animation documents turn finished SVG artwork into Lottie: keyframes or presets per layer, frame renders for the agent, .json export, and a player with a timeline.
  • Layers are real Inkscape layers, so exported files open in Inkscape with layers intact.
  • Path booleans (union, difference, intersection, exclusion, division) via Skia PathOps.
  • Every edit is atomic (a failed call changes nothing), one undo/redo step, and autosaved: windows of a crashed session reopen in the next one.
  • render returns a PNG the agent can look at; rendering is resvg (no browser, no Cairo).

Install

Same shape as pac-pincers: a pip package, run as a module by whatever Python it was installed into.

python -m pip install --upgrade pac-vector
claude mcp add pac-vector --scope user -- python -m pac_vector

As a plugin .mcp.json entry (mirrors the datatopia plugin's pac-pincers entry):

"pac-vector": {
  "type": "stdio",
  "command": "${PAC_VECTOR_PYTHON:-python}",
  "args": ["-m", "pac_vector"],
  "env": {}
}

Development:

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
.\.venv\Scripts\python.exe run_tests.py -q
.\.venv\Scripts\python.exe -m build          # dist/pac_vector-<ver>.tar.gz + .whl

Tools (22)

Group Tools
Documents doc_create (svg: blank, file, markup, duplicate · anim: from SVG artwork), doc_list, doc_set (size, background; svg: units, viewBox, fit; anim: fps, duration), doc_export (svg/png; anim: Lottie .json or one frame), doc_close
Reading inspect (svg outline or xml; anim layers + keyframes), render (svg PNG, crop to ids + bbox; anim: a frame or a strip of frames)
Editing add (markup → layer/group, defs auto-routed; SMIL animations into any element), update (svg: attrs/style/text/transform/label/hidden/locked/text-on-path · anim: layer in/out, static transform, name, hidden), replace, remove, arrange (z-order, move, duplicate, offset), import_svg (file / other window / markup → editable group or image; anim: new layers)
Structure group (also new layers), ungroup, mask (mask or clip from id or markup; anim: track matte from a layer, inverted too), unmask
Geometry boolean (union, difference, intersection, exclusion, division), align (align edges/centers, distribute evenly or by gap; relative to selection, page, first, last or an element)
Animation animate (keyframes for position, anchor, scale, rotation, opacity, trim start/end/offset, fill/stroke colour, stroke width — or presets: fade, pop, slide, draw on/off, spin, pulse, shake — with per-layer stagger)
History undo, redo

Animation documents (Lottie)

doc_create(kind="anim", source=<svg doc id | .svg path | markup>, duration="3s", fps=30, split="elements")
animate(doc, layers=["disc"], preset={"name": "pop_in", "start": 0, "duration": "0.8s"})
animate(doc, layers=["ring", "check"], preset={"name": "draw_on", "start": "0.3s"}, stagger="0.4s")
animate(doc, layers=["disc"], property="rotation", keyframes=[{"t": 30, "value": 0}, {"t": "2s", "value": 90, "ease": "back"}])
render(doc, frames=[0, 15, 45, 89])          # look at frames
doc_export(doc, path="intro.json")            # Lottie
  • Each element of the artwork becomes a layer (split: elements / layers / single). Shapes are baked to paths in composition pixels with their paint resolved; anchor and position start at the artwork's centre, so scale and rotation pivot there.
  • Times are frames (12, "12f") or seconds ("0.4s"). A keyframe's ease shapes the segment that starts at it: linear, ease*, smooth, snap, back, back-in, anticipate, hold or a [x1, y1, x2, y2] cubic-bezier.
  • Not carried over (reported on import): text (convert to paths first), images, <use>, clip paths and masks. Gradients become their average colour.
  • Track mattes: mask(doc, targets=["bones"], source="screen", kind="clip") — the source layer's artwork, following its own keyframes, decides where the targets show (a moving layer is a moving window). kind="clip" / mode="alpha" = alpha matte, mode="luminance" = luma matte, invert=true hides the targets inside the source instead. One source can matte several layers (exported as one Lottie matte copy per target); hide the source with update(hidden=true) if it should only act as the window. unmask removes a matte.
  • render draws frames with resvg from the same model, an approximation of lottie-web (trim paths trim strokes; Lottie also trims fills). The browser preview plays the real exported JSON with lottie-web.

Preview

doc_create / doc_list / inspect print the preview URL (/#<doc> for SVG, /anim#<doc> for animations). The SVG page has a tab per document, outlines the elements each edit touched, and shows (and copies) the id of anything you click. The animation page plays the Lottie with lottie-web (loaded from cdnjs): play/pause, frame step, loop, speed, a range bar to loop a section (handles, drag, I/O keys), and one track per layer with its animated properties and clickable keyframes. The server lives inside the MCP process: it starts on the first doc_create and dies with the session.

Environment

Variable Default Meaning
PAC_VECTOR_PORT 0 (random) Pin the preview port (falls back to random if busy)
PAC_VECTOR_OPEN_BROWSER off 1 opens the preview in the default browser on first start
PAC_VECTOR_ROOT server cwd Base directory for relative export/import paths
PAC_VECTOR_LOG WARNING Log level (stderr only)
PAC_VECTOR_AUTOSAVE on 0 disables the crash-recovery mirror
PAC_VECTOR_AUTOSAVE_DIR <tmp>/pac-vector-autosave Where the mirror lives (<pid>/<doc>.svg + .json)

Notes

  • stdout is the JSON-RPC pipe: nothing may print to it. tests/test_stdout_safety.py scans the package for print() / sys.stdout, and tests/test_stdio.py runs a real session.
  • Native modules are loaded before the stdio loop starts (render.warm): on Windows a first native load from inside the FastMCP loop can hang (see pac-pincers' _excel.py).
  • Bounding boxes are measured from pixels (two-pass render), so they include strokes and filters and work for text and curves; content further than two page-widths off-canvas isn't found. align uses them, so it aligns what you see.
  • Booleans use fill geometry only (strokes, text and images don't take part); the result is a <path> in the bottom operand's place, styled like it, keeping its id.
  • Animations are SMIL: the browser preview plays them; render / PNG export show the static first frame.
  • Version lives only in pyproject.toml; a release is a commit named after the version.

Metadata

Release files for pac-vector 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pac-vector 0.4.0
File Size Uploaded Provenance Actions
pac_vector-0.4.0.tar.gz 85.9 kB Without provenance Details for pac_vector-0.4.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pac-vector 0.4.0
File Interpreter ABI Platform Actions
pac_vector-0.4.0-py3-none-any.whl Python 3 none any Details for pac_vector-0.4.0-py3-none-any.whl

Total release size: 165.2 kB

Release files / pac_vector-0.4.0.tar.gz

Download URL pac_vector-0.4.0.tar.gz
Size 85.9 kB
Tags Source
SHA-256 checksum
How to use checksums
25f3fab37393d3403512faa0ef8930043196ffca2941f0e6e6205014d7879f08
BLAKE2b-256 checksum
How to use checksums
a2a090ec74b09f2f29ec9c378bc39fe297d851689a0df6c812500afebffa4d89
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.0

Release files / pac_vector-0.4.0-py3-none-any.whl

Download URL pac_vector-0.4.0-py3-none-any.whl
Size 79.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
589c161e93caf44fb0eec6fa8082cdeff285debaa891a2f8368a43791f784a53
BLAKE2b-256 checksum
How to use checksums
2233c69c19c62143655a03d148dba32c933bf414c025163bb1dc8f10c1c0d8bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.0

Security for pac-vector 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page