Skip to main content
Glama

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 mediante fetch + DOMParser en 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-mcp

Opcional: 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.

Herramientas (4)

Herramienta

Función

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

Abre el SVG en el navegador. En source_script se pasa la ruta del script que genera la figura (por ejemplo, fig_*.py de matplotlib); a partir de entonces, cada lote de anotaciones se devuelve tal cual de vuelta. Si la página ya está abierta, se reutiliza la pestaña (enviando un evento de sesión para cambiar de figura).

wait_for_annotations(timeout_s=120)

Espera en bloqueo a que el usuario pulse «Enviar a Claude». Si devuelve status:"timeout", no es un error: para seguir esperando, vuelve a llamar (protocolo de bucle, también indicado en la description de la herramienta).

get_annotations()

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).

set_status(message)

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) y number (el número en el lienzo);

  • cuando kind:"element", además se incluye target: el tag/id/text (literal)/ancestors/d_prefix/bbox_svg del elemento seleccionado —es el ancla más fuerte para localizarlo; úsala en primer lugar (grep del id o del text directamente en el script generador o en el SVG);

  • geometry_norm (0–1) y geometry_svg (coordenadas del viewBox, ya convertidas por el servidor);

  • hits[]: elementos SVG alcanzados en la selección; cada uno incluye tag / id (grupos semánticos de matplotlib, como text_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 del d de path, 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.jsonl

Pará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.

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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.

View all MCP Connectors

Latest Blog Posts

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