Engineering Journal
Schema Editor
Schema Editor

Postmortem: We Wired the MCP Protocol Before We Had a Serialization Contract

2026-06-04

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 PhaseInitial Approach (Protocol-First)Refactored Approach (Contract-First)
Response FormatRaw SVG innerHTML stringVersioned JSON domain schema (v2)
Setup Duration2 Hours2 Days (Contract design & testing)
AI ReliabilityHigh 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:

  1. Component Misclassification: Asking the AI to count resistors failed. It missed resistors nested inside unclassed SVG groups and misidentified junction dots as active components.
  2. Topology Hallucinations: Asking the AI to describe net connectivity produced false reports based on screen pixel proximity rather than true wire connections.
  3. UI Noise Pollution: Editor UI overlay elements (grid lines, selection handles) leaked into the payload, confusing the LLM context window.
SVG markup encodes display instructions rather than domain semantics. Returning SVG forced the LLM to reverse-engineer topology from visual geometry.


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 →