Schema Editor
Exposing a Canvas Tool to an AI Agent via MCP: The Serialization Contract Comes First
TLDR
Wiring Model Context Protocol (MCP) servers into canvas tools so AI agents can inspect and modify diagrams is mostly a serialization challenge, not a protocol issue. While protocol setup takes five lines of glue code, returning raw SVG strings forces LLMs to reverse-engineer semantic meaning from visual paint orders. Establishing a versioned JSON serialization contract (schema: 'ginexys-diagram-v2') separating component metadata and explicit port connection records provides a stable surface for AI reasoning.
| Integration Tier | Returned Data Format | AI Reasoning Quality | Contract Maintenance |
|---|---|---|---|
| Raw SVG Dump | Raw innerHTML string | Unreliable (Misses components, guesses links) | Zero protocol overhead |
| Versioned JSON Schema | Structured domain payload | Exact (Explicit types & connection pairs) | Requires schema builder methods |
Problem statement: the flaws of SVG protocol dumps
When connecting diagram tools to AI agents using MCP, developers often take an easy shortcut: returning raw SVG strings directly in response to read_diagram tool calls:
// NAIVE IMPLEMENTATION: Returning raw SVG string
function handleMcpReadDiagram() {
return { content: [{ type: 'text', text: document.getElementById('canvas-svg').innerHTML }] };
}
This returns internal DOM IDs, CSS style declarations, and SVG path coordinates. The AI receives a noisy document where semantic component relationships are buried under rendering implementation details.
Technical failure mode: inferring topology from visual layout
LLMs parsing raw SVG face three severe issues:
- Uncertain Component Types: A resistor symbol rendered as nested SVG
<path>elements within a<g>container requires the LLM to guess component classification based on unstandardized CSS class names. - Ambiguous Wire Topology: SVG paths contain visual curve segments (
M... C...), but no explicit connection records. The LLM must infer electrical connections from coordinate proximity, leading to incorrect netlist analysis.
The fix & architecture: versioned domain serialization contracts
Define a versioned JSON schema representing domain topology directly:
// Ground-Truth Serialization Contract (ginexys-diagram-v2)
function buildDiagramPayload(editor) {
return {
schema: 'ginexys-diagram-v2',
domain: 'electrical',
components: editor.getComponents().map(c => ({
id: c.id,
type: c.symbolType, // 'resistor', 'capacitor', 'op-amp'
label: c.label,
position: { x: c.bbox.cx, y: c.bbox.cy },
properties: c.properties, // { value: '10k', tolerance: '5%' }
ports: c.ports.map(p => ({ name: p.name, position: p.worldPos }))
})),
connections: editor.getWires().map(w => ({
id: w.id,
from: { componentId: w.startComponentId, portName: w.startPortName },
to: { componentId: w.endComponentId, portName: w.endPortName },
net: w.netName
})),
metadata: { exportedAt: new Date().toISOString(), componentCount: editor.getComponents().length }
};
}
Protocol handler (extension host)
The MCP server handler returns the structured JSON payload cleanly:// MCP Server Request Handler
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name === 'read_diagram') {
const payload = await webviewPanel.webview.postMessage({ type: 'mcp:read-schema' });
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
}
});
Rule of thumb: Always version your MCP serialization payload (schema: 'v2') and return structured domain entities rather than visual rendering markup like SVG or HTML.
Read this post in the full Engineering Journal →