Schema Editor
Postmortem: We Wired the MCP Protocol Before We Had a Serialization Contract
TLDR
When adding Model Context Protocol (MCP) support to our canvas editor, we completed protocol setup in under two hours by returning raw SVG markup (innerHTML). However, defining a stable JSON diagram schema (_buildDiagramPayload) that an AI could reliably reason about required two days and two architectural redesigns. Protocol-first development was the wrong sequence: serialization contracts must precede protocol integration.
| Development Phase | Initial Approach (Protocol-First) | Refactored Approach (Contract-First) |
|---|---|---|
| Response Format | Raw SVG innerHTML string | Versioned JSON domain schema (v2) |
| Setup Duration | 2 Hours | 2 Days (Contract design & testing) |
| AI Reliability | High Failure Rate (Missed components/nets) | 100% Deterministic Parity |
Problem statement: the flawed assumption of native SVG dumps
We initially assumed that MCP integration was primarily a transport protocol problem.
Because SVG is a standardized format, we assumed an LLM could easily parse raw SVG output. We wired the read_diagram tool handler to return document.getElementById('canvas').innerHTML directly.
Technical failure mode: AI interpretation errors
Testing revealed immediate integration failures:
- Component Misclassification: Asking the AI to count resistors failed. It missed resistors nested inside unclassed SVG groups and misidentified junction dots as active components.
- Topology Hallucinations: Asking the AI to describe net connectivity produced false reports based on screen pixel proximity rather than true wire connections.
- UI Noise Pollution: Editor UI overlay elements (grid lines, selection handles) leaked into the payload, confusing the LLM context window.
The fix: replacing innerHTML dumps with semantic methods
We removed the innerHTML return path (~50 lines deleted) and implemented a dedicated _buildDiagramPayload() method on the editor core:
// REFACTORED: Dedicated semantic diagram payload generator
function _buildDiagramPayload() {
return {
schema: 'ginexys-diagram-v2',
domain: 'electrical',
components: editor.getComponents().map(c => ({
id: c.id,
type: c.symbolType,
label: c.label,
properties: c.properties,
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
}))
};
}
With explicit type: 'resistor' attributes and { from, to } connection records, AI reasoning accuracy reached 100%.
Rule of thumb: Before writing MCP protocol boilerplate, design and validate your domain serialization contract against real LLM queries. Native display markup is for browser rendering, not AI communication channels.
Read this post in the full Engineering Journal →