Skip to main content
Glama

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 mcp SDK (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-mcp

Optional: 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

open_svg(svg_path, source_script="", title="")

Opens the SVG in the browser. Pass the figure's generating script path (e.g. matplotlib's fig_*.py) as source_script; every subsequent batch of annotations carries it back verbatim. If a page is already open, the tab is reused (a session event is pushed to swap the figure).

wait_for_annotations(timeout_s=120)

Blocks until the user clicks "Submit to Claude". A timeout returning status:"timeout" is not an error — just call it again immediately to keep waiting (loop protocol; also documented in the tool's description).

get_annotations()

Non-blocking fallback: retrieves the most recently submitted batch (to recover the scene after a broken timeout chain).

set_status(message)

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", a target field: the clicked element's tag/id/text (verbatim)/ancestors/d_prefix/bbox_svgthis is the strongest locator anchor; use it first (grep the id or text directly in the generating script or SVG);

  • geometry_norm (0-1) and geometry_svg (in viewBox coordinates, already converted server-side);

  • hits[]: the SVG elements the selection hit; each includes tag / id (semantic groups such as text_N/line2d_N in 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's d — 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.jsonl

Page 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 open swaps the figure; multiple Claude sessions = independent server instances, no mutual interference);

  • No annotations.json export (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_svg will warn; matplotlib output is all embedded, so this does not trigger);

  • Browser opens only via macOS open;

  • No auth on localhost.

Available Tools

4 tools
get_annotationsA

非阻塞地取最近一批已提交的批注(wait_for_annotations 的兜底)。

用于超时链中断后恢复现场;没有批注时返回 status="empty"。
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It reveals that the operation is non-blocking and mentions the empty status, but does not explain what it returns on a success path (e.g., does it return a list of annotations or a status?), nor whether the fetch consumes the annotations. This is a significant gap since there is no output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, two sentences, with the core purpose stated first. It fronts the key information (non-blocking fetch, fallback for wait_for_annotations, purpose) and includes an edge-case note. No fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple (no parameters, no output schema), so the description needs to explain the return value. It only mentions the empty case (status='empty') and leaves the non-empty case unspecified. Without an output schema, this is a notable gap for an agent that needs to interpret the result. The purpose and context are clear, but return details are incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and the schema coverage is 100% (vacuous). Since there are no parameters, the description is not required to add any parameter semantics. Baseline of 4 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies a clear verb (non-blocking fetch), a specific resource (recent committed annotations), and explicitly frames it as a fallback to wait_for_annotations, which distinguishes it from that sibling. However, the terms 'recent batch' and 'committed' are not defined, leaving some ambiguity about scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear usage hint: it is for recovering context after an interrupted timeout chain. It also states the edge case of returning status='empty' when no annotations exist. It does not explicitly rule out other tools like open_svg or set_status, but the context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

open_svgA

在浏览器中打开一个 SVG 图,供用户圈选区域写批注。

打开后应调用 wait_for_annotations 等待用户提交批注。
svg_path 必须是绝对路径。source_script 可选:该 SVG 的生成脚本路径
(如 matplotlib 的 fig_*.py);之后每批批注都会原样带回该路径,
便于判断是改 SVG 文件还是改脚本重跑。title 可选,显示在页面顶栏。
ParametersJSON Schema
NameRequiredDescriptionDefault
titleNo
svg_pathYes
source_scriptNo

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It discloses the browser-opening side effect, the requirement to await user annotations, and the behavior that source_script is echoed back with each annotation batch. However, it does not clarify whether the tool mutates the SVG file, what the immediate return value is, or what happens if the user cancels without annotating. The disclosed behaviors are useful but incomplete for a tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded, leading with the core purpose before elaborating on parameters. Each sentence adds distinct information: the workflow step, the path requirement, the source_script semantics, and the title behavior. Minor redundancy exists between the opening line and the operational details, but overall it is efficient with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description is reasonably complete: it covers purpose, the required follow-up action, parameter semantics, and the round-trip behavior of source_script. Gaps remain around the return value format and error/cancel handling, but these are not critical for an agent determining whether and how to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must fully compensate. It does: svg_path must be absolute (syntax constraint), source_script is contextualized as the generation script (e.g., matplotlib fig_*.py) with its return behavior explained, and title is described as appearing in the top bar. This adds real meaning beyond the bare property names and types for all three parameters, nearly closing the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource ('打开一个 SVG 图') with clear intent (供用户圈选区域写批注). It clearly differentiates from sibling tools: wait_for_annotations and get_annotations are about retrieving annotation data, whereas this tool handles the opening/display action. The purpose is unambiguous and contains no tautology.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs the agent to call wait_for_annotations after opening, establishing the correct sequential workflow. It also clarifies the conditional choice between editing the SVG vs. re-running the source script via the source_script parameter. It lacks explicit when-not-to-use guidance but provides solid operational direction; the alternating tool routing is implied through sequential instructions rather than named exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_statusA

向批注页面顶栏推送一条状态文字,如「正在修改 fig_xxx.py 并重跑…」。

修改开始前调用一次,能让用户知道 Claude 正在干活;改完文件后
页面会因文件变化自动刷新,无需再调用本工具通知。
ParametersJSON Schema
NameRequiredDescriptionDefault
messageYes

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

无任何注释,描述须独立承担行为披露责任。它说明了推送状态文字的行为,并解释了自动刷新机制导致无需再次调用,提供了基本行为上下文。但未说明消息的显示时长、是否覆盖旧消息、失败处理等细节,行为透明度有限。

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

描述仅两句话:第一句明确动作并给出示例,第二句补充使用时机和自动刷新逻辑。内容紧凑、无冗余,关键信息前置,结构合理,是一份高效规范。

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

工具简单(单参数,无输出模式),描述涵盖了用途、时机和副作用(自动刷新的自解释)。虽然没有详细的行为细节(如消息清除方式),但鉴于工具的简单性,描述已足够让代理正确调用。

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

输入模式中message参数仅有标题“Message”,无描述,schema描述覆盖率为0%。描述通过示例「正在修改 fig_xxx.py 并重跑…」暗示了参数应包含的文本内容,这为代理提供了有用的语义补充,但未明确参数格式、长度限制或是否必须为字符串,补偿不完整。

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

描述明确说明工具是“向批注页面顶栏推送一条状态文字”,并给出具体示例「正在修改 fig_xxx.py 并重跑…」。动词和资源明确,与兄弟工具(open_svg、wait_for_annotations、get_annotations)功能截然不同,区分度高。

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

描述给出了明确的使用时机:“修改开始前调用一次”,以及何时不使用:“改完文件后页面会因文件变化自动刷新,无需再调用本工具通知”。这提供了清晰的when和when-not,但没有提及替代工具(尽管兄弟工具功能不同,不需要排除)。

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

wait_for_annotationsA

阻塞等待用户在浏览器里提交批注,提交后立即返回批注内容。

重要协议:返回 status="timeout" 表示用户还没提交,这不是错误——
若仍在等待用户批注,应立即再次调用本工具继续等待,直到拿到
status="submitted" 或用户在对话里明确说不批注了。
返回的每条批注含:kind(rect/arrow/freehand/text)、note(用户文字)、
geometry_svg(viewBox 坐标)、hits(命中的 SVG 元素:id/文字/祖先链,
用于在 SVG 源文件或生成脚本中定位)、texts_in_region(选区内的文字)。
ParametersJSON Schema
NameRequiredDescriptionDefault
timeout_sNo

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full responsibility for behavioral disclosure. It meticulously explains the blocking behavior, the timeout status as non-error, the retry protocol, and the detailed structure of each returned annotation (kind, note, geometry_svg, hits, texts_in_region). It also clarifies that timeout is not an error and how to proceed. This is exceptionally transparent for a wait operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-organized: it opens with the core purpose and immediate return, then clearly explains the timeout protocol, and finally lists the annotation fields. Every sentence serves a purpose, with no redundant phrasing. It is concise yet comprehensive, front-loading the most important behavioral information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is thorough for a wait tool: it covers the blocking behavior, timeout handling, retry instructions, and the full return payload structure. It does not describe error scenarios (e.g., if the session expires) or what happens if the user submits invalid annotations, but given that there is no output schema and the tool is relatively simple, the provided details are largely sufficient. A small gap prevents a perfect score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% description coverage for the single parameter timeout_s (default 120), and the description does not explicitly explain this parameter. However, the description mentions status='timeout' and the retry protocol, which implies that the tool waits for a specified period. This indirect inference is not as clear as explicitly stating 'timeout_s is the maximum wait time in seconds.' The description adds some context but doesn't fully compensate for the lack of schema detail, so a 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: '阻塞等待用户在浏览器里提交批注' (blocks waiting for user annotations) and that it returns the annotation content immediately upon submission. It identifies the specific verb 'wait' and the resource 'annotations', making it distinct from siblings like get_annotations (which likely retrieves existing annotations) and open_svg. However, it doesn't explicitly name a sibling alternative, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit instructions on when to use this tool: it should be called when waiting for user annotations, and if status='timeout', it should be called again to continue waiting. It also defines a clear stopping condition: until status='submitted' or the user explicitly declines to annotate. It doesn't discuss when not to use it relative to siblings, but the protocol is sufficient for correct usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedget_annotations
    • First observedopen_svg
    • First observedset_status
    • First observedwait_for_annotations

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

每个工具职责清晰唯一:打开、等待、获取、状态推送,互不重叠,边界明确。open_svg 负责启动,wait_for_annotations 阻塞等待,get_annotations 非阻塞兜底,set_status 用于反馈,无混淆可能。

Naming Consistency5/5

所有工具均采用 snake_case 的动词_名词结构(open_svg, wait_for_annotations, get_annotations, set_status),动词清晰且规律一致,无混用风格。

Tool Count5/5

4 个工具覆盖了 SVG 批注交互的完整核心流程,每个工具都有不可替代的作用,没有冗余或缺失,数量恰到好处。

Completeness5/5

工具集覆盖了打开、等待提交、非阻塞获取和状态通知,生命周期闭环。没有明显的缺失操作,且描述了超时续等和兜底恢复机制,足以支持用户与 agent 的完整交互。

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    1
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Chrome extension + MCP bridge that gives Claude control over your real browser via CDP, enabling navigation, clicking, typing, scrolling, screenshots, and JS execution with a visible cursor and tab-bring-to-front.
    1
    MIT