svg-annotate-mcp
Allows opening SVG figures in a browser so users can annotate them (by clicking semantic elements like text/line/legend groups or drawing regions), then returns the annotations with element metadata (IDs, ancestors, text, bounding boxes, geometry) to the agent for editing the SVG file or its generation script.
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-annotate-mcp打开 figure11.svg 并关联它的生成脚本,我来圈选要改的地方"
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-annotate-mcp
Annotate SVG paper figures in the browser — click an element (hovering live-highlights the semantic element under the cursor, Claude Design style) or lasso a region. The annotation (including the hit SVG element info) is sent back to Claude over MCP; Claude edits the source file (the SVG or the generating script), and the page watches for file changes and auto-refreshes — forming a closed "annotate → modify → refresh → re-annotate" loop.
Backend: Python + official
mcpSDK (MCPServer, stdio), with an in-process stdlib HTTP thread (127.0.0.1 ephemeral port).Frontend: single file
src/svg_annotate_mcp/web/index.html. The SVG is fetched and injected via DOMParser into a Shadow DOM (bidirectionally isolated from the page's styles and IDs); annotations are drawn on a separate overlay, and hit testing happens browser-side at submission.
Installation and registration
cd ~/Projects/svg-annotate-mcp && uv sync
claude mcp add --scope user svg-annotate -- \
uv run --directory /Users/boryant/Projects/svg-annotate-mcp svg-annotate-mcpOptional: loosen the MCP client timeout so a single wait_for_annotations call can wait longer (it works without this too; the loop just runs more often): set MCP_TOOL_TIMEOUT=600000 in your shell profile or in the env block of the Claude Code settings.
Tool surface (4 tools)
Tool | Purpose |
| Opens the SVG in the browser. Pass the figure's generating script path (e.g. matplotlib's |
| Blocks until the user clicks "Submit to Claude". A timeout returning |
| Non-blocking fallback: retrieves the most recently submitted batch (to recover the scene after a broken timeout chain). |
| Pushes a status to the page's top bar (e.g. "Editing fig_fig_xxx.py and rerunning…"). No need to call it after an edit; the page refreshes automatically. |
Element-level click selection (v2)
Under the default "Select" tool, hovering immediately highlights the semantic element under the cursor (the g[id] groups from matplotlib such as text_N / line2d_N / legend_N, taking the minimum-area hit, with a small tag showing id + text). Clicking enters the element inspector (the side panel shows the id, verbatim text, and an ancestor breadcrumb — click the breadcrumb to reselect a parent group, e.g. text_54 → legend_1); an annotation is only produced when you type the "modification notes" (a bare click is just the inspector — no junk annotations). Esc or "Cancel selection" exits. Element annotations appear as dashed boxes + numbers on the figure.
Annotation payload structure (designed for Claude to locate)
Each annotation contains:
kind(element/rect/arrow/freehand/text),note(the user's modification instruction),number(the number on the canvas);For
kind:"element", atargetfield: the clicked element'stag/id/text(verbatim)/ancestors/d_prefix/bbox_svg— this is the strongest locator anchor; use it first (grep the id or text directly in the generating script or SVG);geometry_norm(0-1) andgeometry_svg(in viewBox coordinates, already converted server-side);hits[]: the SVG elements the selection hit; each includestag/id(semantic groups such astext_N/line2d_Nin matplotlib) /text(element text, verbatim) /ancestors(ancestor id chain, e.g.["figure_1","legend_1","text_54"]) /bbox_svg/coverage/d_prefix(first 30 chars of the path'sd— a grep anchor when editing the SVG directly). Already denoised: background containers removed, semantic groups preferred (leaves inside groups not repeated), max 10 per annotation;texts_in_region: all the text inside the selected region (reading order, deduplicated) — the first lead for grep-based locating in the generating script.
What gets changed is Claude's call: when an annotation carries source_script, prefer editing the script and rerunning (edits to the SVG artifact would be overwritten by the next rerun); for SVGs with no script, edit the SVG file directly. The page makes no assumption about the editing method — it only watches for file mtime changes (500 ms polling, double-tick stabilization, and a </svg> tail check to avoid acting on half-written files).
Annotation lifecycle
Unsubmitted drafts survive figure refreshes. After clicking "Submit to Claude", the batch stays on the figure at 35% transparency (numbers retained, for easy cross-reference); the next refresh triggered by Claude's file edit clears the translucent annotations.
Typical closed loop
用户: 帮我改 figure11,我来圈
Claude: open_svg("/path/figures/figure11.svg", source_script="/path/fig_tri_complement.py")
wait_for_annotations() # 挂起
用户: (浏览器里圈图例写「图例移到右上」,点提交)
Claude: 收到批注 → set_status("正在改 fig_tri_complement.py…")
→ 改脚本 → 重跑出图 → 页面自动刷新
→ wait_for_annotations() # 等下一轮Testing
uv run python tests/smoke_test.py # 端到端:握手/HTTP/阻塞等待/坐标换算/SSE reload/复用 tab
uv run python tests/manual_driver.py <svg> [script] # 起 server 供手动/浏览器自动化测试,批次落盘 tests/out/batches.jsonlPage debug parameters: ?nosse=1 skips SSE (for headless screenshots); ?autotest=x,y,w,h auto-draws a rectangle annotation and submits it after load (0-1 normalized coordinates); ?autotest_click=x,y click-selects the element at that point and submits it as a real annotation (add &autotest_stage=pick to just do the click-highlight, for inspector screenshots).
Environment variables: SVG_ANNOTATE_NO_OPEN=1 prevents open_svg from auto-opening the browser (for testing).
Known limitations and (first version)
Single-session instance (one figure at a time; another
openswaps the figure; multiple Claude sessions = independent server instances, no mutual interference);No
annotations.jsonexport (annotations are only returned through memory and are lost on server restart — Claude's context is the fallback copy);External resources in an SVG are not proxied (
open_svgwill warn; matplotlib output is all embedded, so this does not trigger);Browser opens only via macOS
open;No auth on localhost.
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.
Related MCP Connectors
Real-time collaborative whiteboard — AI agents and humans edit the same board live over MCP.
Live browser debugging for AI assistants — DOM, console, network via MCP.
Cross-agent artifact workspace with provenance across Claude Code, Codex, Cursor, LangGraph.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- 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/sunjianbo-123/svg-annotate-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server