svg-annotate-mcp
svg-annotate-mcp
En el navegador puedes anotar figuras de artículos científicos en SVG: haz clic en un elemento (al pasar el cursor se resalta el elemento semántico, estilo Claude Design) o marca un área de selección. La anotación —con la información de los elementos SVG alcanzados— se devuelve a Claude a través de MCP; Claude modifica el archivo de origen (el SVG o el script generador), y la página vigila los cambios del archivo para refrescarse automáticamente. Así se cierra el ciclo «anotar → modificar → refrescar → volver a anotar».
Backend: Python + el SDK oficial
mcp(MCPServer, stdio); se levanta dentro del proceso un hilo HTTP de la librería estándar (127.0.0.1, puerto temporal).Frontend: un único archivo
src/svg_annotate_mcp/web/index.html. El SVG se inyecta mediantefetch+DOMParseren un Shadow DOM (con aislamiento en ambos sentidos de estilos e ids respecto a la página); las anotaciones se dibujan en un overlay independiente. Al enviar, la detección de elementos alcanzados se hace en el lado del navegador.
Instalación y registro
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-mcpOpcional: puedes ampliar el tiempo de espera del cliente MCP para que una sola llamada a wait_for_annotations pueda esperar más (si no lo haces también funciona, pero el bucle será más frecuente). Define MCP_TOOL_TIMEOUT=600000 en el perfil del shell o en la sección env de la configuración de Claude Code.
Related MCP server: Browser Feedback MCP
Herramientas (4)
Herramienta | Función |
| Abre el SVG en el navegador. En |
| Espera en bloqueo a que el usuario pulse «Enviar a Claude». Si devuelve |
| Recuperación no bloqueante: obtiene el lote más reciente de anotaciones enviadas (para restablecer el estado cuando la cadena de timeouts se ha roto). |
| Envía un estado a la barra superior de la página (por ejemplo, «modificando fig_xxx.py y reejecutando…»). Después de cambiar el archivo no hace falta llamarla; la página se actualiza sola. |
Selección por clic en elementos (v2)
Con la herramienta «Selección» por defecto, al pasar el cursor se resalta en tiempo real el elemento semántico que está debajo (los grupos g[id] de matplotlib, como text_N / line2d_N / legend_N; se toma el de menor área y la etiqueta muestra id + texto). Al hacer clic se abre el inspector de elementos: la barra lateral muestra id, el texto literal y las migas de pan de los ancestros; al pulsar una miga puedes cambiar la selección al grupo padre (por ejemplo, de text_54 a legend_1). Solo se genera una anotación si escribes contenido en el campo «Instrucciones de modificación» (un clic simple = inspector, y no crea anotaciones basura). Con Esc o «Cancelar selección» se sale del modo. Las anotaciones de tipo elemento aparecen en el dibujo como un recuadro discontinuo + número.
Estructura devuelta con las anotaciones (pensada para que Claude localice los cambios)
Cada anotación contiene:
kind(element/rect/arrow/freehand/text),note(la instrucción de modificación del usuario) ynumber(el número en el lienzo);cuando
kind:"element", además se incluyetarget: eltag/id/text(literal)/ancestors/d_prefix/bbox_svgdel elemento seleccionado —es el ancla más fuerte para localizarlo; úsala en primer lugar (grep delido deltextdirectamente en el script generador o en el SVG);geometry_norm(0–1) ygeometry_svg(coordenadas delviewBox, ya convertidas por el servidor);hits[]: elementos SVG alcanzados en la selección; cada uno incluyetag/id(grupos semánticos de matplotlib, comotext_N/line2d_N) /text(texto literal del elemento) /ancestors(cadena de ids ancestros, p. ej.["figure_1","legend_1","text_54"]) /bbox_svg/coverage/d_prefix(primeros 30 caracteres delddepath, ancla para hacer grep cuando se edita el SVG directamente). Ya está eliminado el ruido: se descartan los contenedores de fondo, los grupos semánticos tienen prioridad y no se repiten las hojas, y hay un máximo de 10 por lote;texts_in_region: todos los textos del área (orden de lectura, sin duplicados): primera pista para localizar con grep en el script generador.
Dónde modificar lo decide Claude: si la anotación trae source_script, conviene modificar el script y reejecutarlo (si tocas el SVG de salida, la próxima ejecución lo sobrescribe). Si no hay script, se edita directamente el archivo SVG. La página no asume nada sobre la forma de modificar; solo observa el mtime del archivo (sondeo de 500 ms, doble comprobación de estabilidad y validación de cierre </svg> para no leer un archivo a medias).
Ciclo de vida de las anotaciones
Los borradores no enviados se conservan cuando la página se refresca. Al pulsar «Enviar a Claude», esa anotación queda en el lienzo con un 35 % de opacidad (manteniendo su número para el comparado). El siguiente refresco que provoque el cambio de archivo por parte de Claude elimina esas anotaciones semitransparentes.
Bucle típico
用户: 帮我改 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() # 等下一轮Pruebas
uv run python tests/smoke_test.py # 端到端:握手/HTTP/阻塞等待/坐标换算/SSE reload/复用 tab
uv run python tests/manual_driver.py <svg> [script] # 起 server 供手动/浏览器自动化测试,批次落盘 tests/out/batches.jsonlParámetros de depuración en la página: ?nosse=1 omite el SSE (para capturas de pantalla headless); ?autotest=x,y,w,h dibuja automáticamente un rectángulo de anotación y lo envía (coordenadas 0–1 normalizadas); ?autotest_click=x,y hace clic en el elemento de ese punto y lo convierte en anotación de envío (con &autotest_stage=pick solo selecciona y resalta, para las capturas del inspector).
Variable de entorno: SVG_ANNOTATE_NO_OPEN=1 hace que open_svg no abra el navegador automáticamente (para pruebas).
Limitaciones conocidas (primera versión)
Sesión única (una sola figura a la vez; volver a abrir elimina la anterior; varias sesiones de Claude = varias instancias de servidor independientes que no interfieren). No se exporta annotations.json (las anotaciones se devuelven solo en memoria; se pierdebog al reiniciar el server, así que el contexto de Claude guarda la última copia). Los enlaces SVG externos no se proxyan (open_svg dará un aviso; los SVG de matplotlib van incrustados, no lo activan). La apertura del navegador solo funciona con open en macOS. localhost no tiene autenticación.
Available Tools
4 toolsget_annotationsA
非阻塞地取最近一批已提交的批注(wait_for_annotations 的兜底)。
用于超时链中断后恢复现场;没有批注时返回 status="empty"。
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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 可选,显示在页面顶栏。
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | ||
| svg_path | Yes | ||
| source_script | No |
TDQS
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.
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.
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.
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.
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.
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 正在干活;改完文件后
页面会因文件变化自动刷新,无需再调用本工具通知。
| Name | Required | Description | Default |
|---|---|---|---|
| message | Yes |
TDQS
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.
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.
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.
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.
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.
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(选区内的文字)。
| Name | Required | Description | Default |
|---|---|---|---|
| timeout_s | No |
TDQS
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.
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.
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.
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.
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.
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.
4 tool updates
v0.1.0- First observed
get_annotations - First observed
open_svg - First observed
set_status - First observed
wait_for_annotations
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 的完整交互。
Related MCP Connectors
Client feedback pinned on live sites, Figma frames, PDFs, images and video, for Claude and agents.
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.
Comment on AI-generated webpages; feedback flows back to your coding agent. Free, MIT, local-first.
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.64 npm3MIT