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,
.jsonexport, 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.
renderreturns 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), 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'seaseshapes the segment that starts at it:linear,ease*,smooth,snap,back,back-in,anticipate,holdor 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. renderdraws 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.pyscans the package forprint()/sys.stdout, andtests/test_stdio.pyruns 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.
alignuses 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.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pac_vector-0.3.0.tar.gz | 82.0 kB | Without provenance | Details for pac_vector-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pac_vector-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for pac_vector-0.3.0-py3-none-any.whl |
Total release size: 158.7 kB
Release files / pac_vector-0.3.0.tar.gz
| Download URL | pac_vector-0.3.0.tar.gz |
|---|---|
| Size | 82.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5f30d7ae5afe4ed29c7abab05ae71b627c70c5b9f3f3cbbe938b503e52d038c0
|
|
BLAKE2b-256 checksum How to use checksums |
3d5eb639e7d5a5f7628f7bdf78ea5796aadaba98968b33d95e04bc89b976d6ff
|
| 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.3.0-py3-none-any.whl
| Download URL | pac_vector-0.3.0-py3-none-any.whl |
|---|---|
| Size | 76.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
18a91062e07118249dd38bfef6a857395c10d6385a7e3acb5a130168335390a8
|
|
BLAKE2b-256 checksum How to use checksums |
44dc9222411fb3cdbc99861ff69bcc7dc11404298b1ee49fb3c68df98526bb96
|
| 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.3.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.