svg-annotate-mcp
This server lets Claude open SVG figures in a browser, collect user annotations (clicks, region selections, screenshots), and iteratively refine the underlying SVG or generation script in a closed loop.
open_svg: Opens an SVG file in the browser; optionally associates a source generation script and a display title. Reuses an existing browser tab and clears pending annotation queues when switching figures.wait_for_annotations: Blocks for user-submitted annotations and returns them with notes, element targets, geometry, hit SVG elements, text in region, and any pasted screenshot file paths. Timeout is an expected protocol, not an error—just keep calling.get_annotations: Non-blocking fallback to retrieve the latest submitted batch after a timeout chain breaks; returns empty if no annotations exist.set_status: Pushes a status message to the page header so the user knows Claude is working on modifications.Element-level clicking: hovering highlights semantic SVG elements; clicking inspects them (id, text, breadcrumb ancestry). Only typing a modification note turns an inspection into an annotation.
Annotation types include element, rectangle, arrow, freehand, and text; each carries normalized and SVG coordinates, hit element details, and text in the selected region.
Screenshot pasting: users can paste images into annotation notes; images are saved locally and returned as file paths for Claude to Read—no base64.
FIFO batch queue: rapid submissions are queued without loss; remaining pending count is returned for loop continuation.
Auto-refresh: the page watches the SVG file for mtime changes and reloads, completing the annotate → modify → refresh → re-annotate workflow.
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 "Deploy 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.
Related MCP server: Browser Feedback MCP
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.
Available Tools
4 toolsget_annotationsA
非阻塞地取最近一批已提交的批注(wait_for_annotations 的兜底)。
用于超时链中断后恢复现场;没有批注时返回 status="empty"。
Inputs: no named parameters.
open_svgA
在浏览器中打开一个 SVG 图,供用户圈选区域写批注。
打开后应调用 wait_for_annotations 等待用户提交批注。
svg_path 必须是绝对路径。source_script 可选:该 SVG 的生成脚本路径
(如 matplotlib 的 fig_*.py);之后每批批注都会原样带回该路径,
便于判断是改 SVG 文件还是改脚本重跑。title 可选,显示在页面顶栏。
Inputs: svg_path (required), title, source_script.
set_statusA
向批注页面顶栏推送一条状态文字,如「正在修改 fig_xxx.py 并重跑…」。
修改开始前调用一次,能让用户知道 Claude 正在干活;改完文件后
页面会因文件变化自动刷新,无需再调用本工具通知。
Inputs: message (required).
wait_for_annotationsA
阻塞等待用户在浏览器里提交批注,提交后立即返回批注内容。
重要协议:返回 status="timeout" 表示用户还没提交,这不是错误——
若仍在等待用户批注,应立即再次调用本工具继续等待,直到拿到
status="submitted" 或用户在对话里明确说不批注了。
返回的每条批注含:kind(rect/arrow/freehand/text)、note(用户文字)、
geometry_svg(viewBox 坐标)、hits(命中的 SVG 元素:id/文字/祖先链,
用于在 SVG 源文件或生成脚本中定位)、texts_in_region(选区内的文字)。
Inputs: timeout_s.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
每个工具职责清晰唯一:打开、等待、获取、状态推送,互不重叠,边界明确。open_svg 负责启动,wait_for_annotations 阻塞等待,get_annotations 非阻塞兜底,set_status 用于反馈,无混淆可能。
所有工具均采用 snake_case 的动词_名词结构(open_svg, wait_for_annotations, get_annotations, set_status),动词清晰且规律一致,无混用风格。
4 个工具覆盖了 SVG 批注交互的完整核心流程,每个工具都有不可替代的作用,没有冗余或缺失,数量恰到好处。
工具集覆盖了打开、等待提交、非阻塞获取和状态通知,生命周期闭环。没有明显的缺失操作,且描述了超时续等和兜底恢复机制,足以支持用户与 agent 的完整交互。
Maintenance
Related MCP Connectors
Create and edit architecture diagrams from your AI agent; get an SVG and a live editable canvas.
Give Claude a whiteboard: notes, links, images and diagrams on a board you both see and edit.
Generate images from Claude, ChatGPT, Cursor and Codex with 12 top AI image models and 10 tools: up to 4 images per call, SVG vectorization, editing and more. Remote MCP with OAuth, no API keys. Pay per image, no subscription.
Real-time collaborative whiteboard — AI agents and humans edit the same board live over MCP.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables visual annotation on web pages for Claude Code, allowing element selection, comment addition, screenshot capture, and structured UI feedback for code fixes via an MCP server.MIT
- AlicenseAqualityCmaintenanceEnables visual browser feedback collection directly into Claude Code. Users can point at elements in their browser and send annotated feedback that Claude can act on immediately.121MIT
- AlicenseAqualityDmaintenanceEnables DOM-aware visual annotation of web pages with drawing tools and element selection, submitting structured annotations to Claude Code via MCP for automated feedback processing.2MIT
- AlicenseNot gradedqualityDmaintenanceEnables drawing annotations directly on local web pages and sending screenshots, DOM data, and annotation context to your IDE agent for precise code edits.49 npm3MIT