Exposing a Canvas Tool to an AI Agent via MCP: The Serialization Contract Comes First
TLDR
Wiring a canvas tool to an MCP server so an AI agent can read and manipulate the diagram is mostly a serialization problem. The protocol layer (receiving a tool call, returning a response) is trivial. The hard part is defining what the diagram's state looks like as a structured, stable, round-trippable document that an AI can reason about.
The Problem Class
Any developer adding AI assistant capabilities to an interactive tool faces this sequence: the AI needs to read the tool's current state, optionally modify it, and return. The MCP protocol handles the communication. The serialization contract defines what is communicated.
Most developers approach this as a protocol problem. They wire up the MCP server, receive the tool call, and then figure out what to return. The serialization is an afterthought. This produces brittle integrations where the AI receives either too much data (the full SVG with implementation details), too little (a summary that loses structural information), or an unstable format that breaks when the tool changes.
The Naive Approach
Return the SVG string when the AI requests the diagram:
function handleMcpReadDiagram() {
return {
content: [{ type: 'text', text: document.getElementById('svg-display').innerHTML }]
};
}
This returns everything. The SVG includes internal IDs, system elements, CSS classes, and rendering details that are not part of the diagram's semantic content. The AI receives a document that is hard to parse, includes noise, and may contain implementation details that should not be in the context.
More importantly: the SVG is an unstable format for AI interaction. When the editor is refactored, the SVG structure changes. Tool calls that worked before break.
Why It Breaks
The AI agent needs to understand the diagram's structure, not its rendering. The AI does not care that a resistor is rendered as a <g class="domain-symbol" data-symbol="resistor"> with specific SVG path children. The AI cares that there is a resistor at a specific position with specific connections.
Returning the raw SVG means the AI must parse SVG to extract this semantic information. SVG parsing in an LLM context is unreliable: the model may miss elements, misinterpret transform math, or confuse rendering attributes for semantic ones.
The serialization contract defines the semantic layer. It is the interface between the diagram editor (which knows about SVG rendering) and the AI agent (which needs to reason about circuit topology, connections, and components).
The Better Model
Define a serialization contract as a versioned JSON schema before writing any MCP wiring. The schema describes the diagram in domain terms:
// ginexys-diagram-v2 schema — serialization contract
const payload = {
schema: 'ginexys-diagram-v2',
domain: 'electrical',
components: editor.getComponents().map(c => ({
id: c.id,
type: c.symbolType, // 'resistor', 'capacitor', etc.
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: {
title: editor.getTitle(),
exportedAt: new Date().toISOString(),
componentCount: components.length,
}
};
The MCP handler returns this payload:
// in the webview: handle MCP read request
window.addEventListener('message', (e) => {
if (e.origin !== window.location.origin) return;
if (e.data.type !== 'mcp:read-schema') return;
const payload = editor._buildDiagramPayload(); Bridge.send('mcp:reply', { requestId: e.data.requestId, payload }); });
The extension host wires the MCP tool:
// in the extension: register the MCP tool
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) }]
};
}
});
The AI receives structured domain data: component types, positions, connections, net names. It can reason about the circuit without parsing SVG.
Tradeoffs
The serialization contract requires maintaining a _buildDiagramPayload() method that stays in sync with the editor's internal model. When the editor adds a new element type, the payload builder must be updated. This is a maintenance obligation.
The alternative (returning raw SVG) has no maintenance obligation but produces unreliable AI interactions. The structured payload is worth the maintenance cost.
The One Thing to Watch For
Version the schema immediately: schema: 'ginexys-diagram-v2'. When the payload format changes (and it will), the version lets consumers distinguish old from new. An AI agent receiving a v1 payload and a v2 payload with different field names should know they are different versions, not assume one is malformed.