JSON Canvas MCP Server

Un servidor para crear, modificar, validar y exportar archivos JSON Canvas, un formato para datos de lienzo infinito.

Documentación

Servidor MCP de JSON Canvas

Un servidor de Protocolo de Contexto de Modelo (MCP) para trabajar con archivos de JSON Canvas — el formato de lienzo infinito abierto utilizado por Obsidian. Permite que un cliente MCP cree, valide, lea y liste archivos .canvas.

Construido sobre el SDK oficial de Python mcp (>=1.27), que negocia la revisión del protocolo MCP 2025-11-25. Se ejecuta sobre stdio de forma predeterminada y opcionalmente sobre el transporte HTTP Streamable.

Los hosts que admiten la extensión de interfaz de usuario MCP Apps renderizan un visor de lienzo interactivo en línea al leer o crear un lienzo — una vista de paneo/zoom, al estilo de Obsidian, de los nodos y bordes. Los clientes de solo texto no se ven afectados y continúan recibiendo el lienzo como salida de texto/estructurada.

Componentes

Herramientas

  • create_canvas — Crea un lienzo a partir de nodes (y edges opcional) y lo escribe en un archivo .canvas con prefijo de fecha bajo OUTPUT_PATH.
    • Entrada: nodes (matriz de objetos de nodo JSON Canvas), filename (cadena, sin extensión), edges (matriz opcional de objetos de borde).
    • Devuelve (estructurado): { path, node_count, edge_count }.
  • validate_canvas — Valida los datos del lienzo contra la especificación JSON Canvas 1.0.
    • Entrada: canvas (objeto con nodes y edges opcionales).
    • Devuelve (estructurado): { valid, error }.
  • read_canvas — Lee un archivo .canvas almacenado y devuelve sus nodos y bordes.
    • Entrada: filename (cadena, con o sin la extensión .canvas).
    • Devuelve (estructurado): { nodes, edges } (también renderizado por el visor de lienzo; el texto de respaldo es el JSON del lienzo).
  • list_canvases — Lista los archivos .canvas disponibles en OUTPUT_PATH.
    • Devuelve: matriz de nombres de archivo.
  • edit_canvas — Agrega, actualiza y/o elimina nodos y bordes en un lienzo almacenado en una sola escritura atómica (una operación fallida deja el archivo sin cambios).
    • Entrada: filename, más add_nodes opcional, update_nodes (parcial, debe incluir id), remove_node_ids (cascada de bordes conectados), add_edges, update_edges, remove_edge_ids.
    • Devuelve (estructurado): { path, node_count, edge_count, canvas } — el lienzo actualizado, de modo que los hosts con capacidad de interfaz de usuario lo vuelvan a renderizar en línea.
  • export_canvas — Exporta un lienzo almacenado a otro formato.
    • Entrada: filename, format (markdown | svg).
    • Devuelve (estructurado): { format, mime_type, content }. Markdown es un esquema ordenado por bordes; SVG es una imagen vectorial independiente (solo líneas de título de nodo — el SVG simple no puede renderizar Markdown).
  • search_canvases — Búsqueda de subcadenas sin distinción de mayúsculas y minúsculas en los lienzos almacenados.
    • Entrada: query, filename opcional para limitar el alcance a un lienzo.
    • Devuelve (estructurado): { matches: [{ filename, kind, id, field, snippet }] }.

create_canvas, read_canvas y edit_canvas están vinculados al visor de lienzo mediante _meta.ui.resourceUri, de modo que los hosts con capacidad de interfaz de usuario renderizan el resultado en línea.

Los objetos de nodo utilizan la forma JSON Canvas: id, type (text | file | link | group), x, y, width, height, color opcional, más campos específicos del tipo (text, file/subpath, url, label/background/backgroundStyle). Los objetos de borde utilizan id, fromNode, toNode y fromSide/toSide/fromEnd/toEnd/color/label opcionales.

Recursos

  • canvas://schema — Esquema JSON para validar archivos de lienzo.
  • canvas://examples/basic — Un lienzo de ejemplo simple (dos nodos de texto unidos por un borde).
  • ui://canvas/viewer.html — El visor de lienzo interactivo (interfaz de usuario de MCP Apps), servido con tipo MIME text/html;profile=mcp-app. Referenciado por create_canvas y read_canvas.

Visor de lienzo interactivo (interfaz de usuario de MCP Apps)

El visor es un paquete HTML autónomo único construido a partir del código fuente ui/ con Vite y el cliente oficial @modelcontextprotocol/ext-apps. Renderiza nodos (con markdown, colores y grupos) y bordes (lados, flechas, etiquetas) en una vista de paneo/zoom, con temas mediante las variables CSS del host.

El paquete construido está confirmado en jsoncanvas/_ui/viewer.html y se incluye en el paquete, por lo que ejecutar el servidor solo necesita Python. Reconstruyelo después de cambiar ui/:

make build-ui     # cd ui && npm install && npm run build  (requires Node.js)

Para previsualizar el renderizador de forma independiente (sin host MCP), ejecuta cd ui && npm run dev y abre /preview.html.

Uso con Claude Desktop

Docker (stdio)

docker build -t mcp/jsoncanvas .

Agrega a tu claude_desktop_config.json:

{
  "mcpServers": {
    "jsoncanvas": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-v", "canvas-data:/data", "mcp/jsoncanvas"],
      "env": { "OUTPUT_PATH": "/data/output" }
    }
  }
}

uv (stdio)

{
  "mcpServers": {
    "jsoncanvas": {
      "command": "uv",
      "args": ["--directory", "/path/to/jsoncanvas", "run", "mcp-server-jsoncanvas"],
      "env": { "OUTPUT_PATH": "./output" }
    }
  }
}

Transporte HTTP Streamable

Para servir sobre HTTP Streamable en lugar de stdio:

mcp-server-jsoncanvas --transport streamable-http --host 127.0.0.1 --port 8000

El endpoint MCP es entonces http://127.0.0.1:8000/mcp. El transporte se vincula a localhost y, según la especificación 2025-11-25, valida el encabezado Origin con protección contra rebinding de DNS habilitada (solo orígenes localhost de forma predeterminada). Para aceptar conexiones desde fuera del host (por ejemplo, al ejecutar el contenedor con HTTP), vincula --host 0.0.0.0 y configura tus orígenes permitidos en consecuencia.

Los hosts MCP basados en navegador (el tipo que renderiza el visor de lienzo) se conectan entre orígenes y deben leer el encabezado de respuesta mcp-session-id, por lo que el transporte HTTP Streamable sirve encabezados CORS permisivos. Restringe los orígenes permitidos con MCP_CORS_ORIGINS (separados por comas; predeterminado *).

Nota de seguridad. El transporte HTTP no está autenticado — cualquiera que pueda alcanzar el puerto puede leer y escribir archivos .canvas bajo OUTPUT_PATH. El servidor está destinado para uso local; mantenlo vinculado a 127.0.0.1 (el predeterminado). La protección contra rebinding de DNS/Origin está fijada a orígenes y hosts de localhost al inicio, por lo que vincular --host 0.0.0.0 expone el puerto en la red pero aún rechaza encabezados Host/Origin que no sean de localhost — para exponerlo de forma segura de forma remota, colócalo detrás de un proxy inverso con autenticación en lugar de publicarlo directamente, y establece MCP_CORS_ORIGINS a los orígenes específicos que confíes (nunca *).

Configuración

Variables de entorno:

  • OUTPUT_PATH — Directorio donde se escriben/leen los archivos .canvas (predeterminado ./output).
  • MCP_TRANSPORT — stdio (predeterminado) o streamable-http.
  • MCP_HOST / MCP_PORT — Host/puerto para el transporte HTTP Streamable (predeterminado 127.0.0.1:8000).
  • MCP_CORS_ORIGINS — Orígenes CORS permitidos separados por comas para el transporte HTTP (predeterminado *).

Desarrollo

# Install uv: https://docs.astral.sh/uv/getting-started/installation/
make setup        # uv venv && uv sync --extra dev
make build-ui     # rebuild the canvas viewer bundle (requires Node.js)
make test         # run the test suite
make lint         # ruff check + format check
make audit        # scan dependencies for known vulnerabilities (pip-audit)
make run          # run the server over stdio

Ejecuta el ejemplo de biblioteca incluido:

make example      # writes example.canvas to OUTPUT_PATH (default ./output)

Ejemplo de biblioteca

El paquete jsoncanvas también se puede usar directamente:

from jsoncanvas import Canvas, TextNode, Edge

title = TextNode(id="title", x=100, y=100, width=400, height=100,
                 text="# Hello Canvas", color="#4285F4")
info = TextNode(id="info", x=600, y=100, width=300, height=100,
                text="More information here", color="2")  # preset color

canvas = Canvas()
canvas.add_node(title)
canvas.add_node(info)
canvas.add_edge(Edge(id="edge1", from_node="title", to_node="info",
                     from_side="right", to_side="left", label="Connection"))

import json
print(json.dumps(canvas.to_dict(), indent=2))

Licencia

MIT. Consulta LICENCIA.