The Screen That Has No Opinions
Every WebMCP example so far makes the page the actor: it books, buys, submits. Sepiola is the complement — a page that exposes tools which show rather than do, so an agent whose intelligence lives in an MCP server can draw that read onto a persistent screen a human is also looking at. Three roles kept apart: the analyst decides, the pen draws, the screen has no opinions. The last one is a test, not a slogan — every text node on screen must come from the analyst's read or the interface copy — and the forty lines in the original mockup that sorted, multiplied, and composed verdicts did not survive it. Also: what Chromium's real WebMCP, the NHL's rate limiter, and Safari's SVG renderer each had to say.
Forty Lines That Knew Better
The mockup arrived as a single HTML file, and it was beautiful: a warm desktop, a rink drawn in SVG, jerseys in formation, a spotlight that swept onto a skater when you clicked him. It also had opinions. Its read_ice sorted the lineup by a schedule value and called the top two “Start.” Its replay multiplied games by points-per-game and printed the product as a projection. Its verdict line compared two counts and wrote “Gridin skates 2 more. Start him.”
const start = [...active].sort((a, b) => b.value - a.value).slice(0, 2);
const pts = +(gp(q) * q.ppg).toFixed(1);
v.textContent = diff > 0 ? `${p.name} skates ${diff} more. Start him.` : `Even on games.`;None of those three lines survived. Not because they were wrong — for the mock data they were fine — but because a screen that can sort, multiply, and compose a sentence is a second analyst, and the whole point of the thing was that there should be exactly one.
Every number on the page now comes from somewhere else. So does every sentence.
The Page Is the Actor
WebMCP, a W3C Community Group draft, lets a web page register tools that an agent running in the browser can call. Nearly every published example uses those tools the way the site’s own buttons are used: book the table, complete the checkout, submit the form. The page performs the task and the agent is a hands-free operator of it. The page is the actor.
That assumes the page owns the capability. Increasingly it does not. The capability is an MCP server, an API, a model the agent already has. What the agent lacks is a way to present a result that outlives a chat turn and that a human can touch. The read arrives as a paragraph. It is correct, and it is gone with the next message.
Television solved this half of the problem sixty years ago. During the intermission the analyst has a pen. The game is frozen on the big screen. A circle appears around a defender; the play runs back; two clips sit side by side. Three things happen at once and nobody confuses them: the game is the game, the read is the analyst’s, and the telestrator is where the read gets drawn onto the game so that everyone watching sees what the analyst sees.
Sepiola is a telestrator for an MCP server. The analyst is ChirpIQX — the same fantasy-hockey server described in One Grammar, Two Readers — which reads a week’s schedule and says who plays, who sits on a back-to-back, who to stream. The pen is any WebMCP agent, or a person typing at a console. The screen is a page that draws, remembers, and decides nothing.
Seven Verbs, None of Them Think
The screen registers a vocabulary borrowed from the broadcast booth rather than from forms.
Each is one thing an analyst would do with a pen in hand, each changes what is visible, and each returns a structured acknowledgment of what it drew. The test for adding an eighth: if an analyst would not do it with a telestrator, it belongs on the analyst’s side. There is no rank, no recommend, no score.
The grammar is one array. Each entry is a complete description of a move — its name, the broadcast phrase, the input shape, which views it touches, which motion sequence it plays, and a pure handler from state to state. WebMCP registration, the producer console, and the documentation table are all derived from that array. A tool exists in exactly one place, which is the same discipline as a validator that runs the real rule rather than a second description of it.
{
name: 'circle',
move: 'Circle him',
description: 'Spotlight one skater with the reason pinned above.',
input: { ids: 'id[]', reason: 'string?' },
touches: ['spot'],
sequence: 'circle',
handler(state, { ids, reason }) {
const id = ids[0];
if (!skater(state, id)) refuse(copy.errors.unknownSkater, { id });
return open({ ...state, circle: { id, reason: reason || null } }, 'rink');
},
ack: (s) => ({ circled: s.circle.id }),
}Every Sentence Travels in the Contract
If the screen may not compose a sentence, the analyst must write every one the screen will ever show. That turned out to be most of the engineering. The read contract carries, per skater, the game bits for the window, a back-to-back flag, a schedule value from zero to a hundred, a flag, a one-line reason, points per game, and projected points already multiplied. Per read it carries the start, sit, stream, and injured-reserve calls in the analyst’s order, games in hand for both sides, a closing line for every replay the pen might ask for, and the take.
The three offending lines from the mockup moved into ChirpIQX, where an analyst’s opinions belong, and the contract grew the three fields those views had been computing. Lines from a pasted lineup that the analyst cannot resolve come back as notes rather than vanishing, so the panel can say “Not resolved: Bob Nobody LW” instead of silently drawing thirteen players out of fourteen.
The invariant is not a comment. It is a test.
for (const text of textNodes(String(view(state)))) {
const ok = allowed.has(text) || templates.some((re) => re.test(text));
expect(ok, `"${text}" is not in the read or in copy.js`).toBe(true);
}allowed is every scalar in the analyst’s read. templates is every string in the interface copy. A view that produces any other text fails the build. Layout arithmetic — a value scaled to a bar width — is allowed. A comparison, a sum, a sort across skaters, or a sentence is not. This is the same move as the diagram that refuses coordinates, applied one layer up: there, geometry is derived from meaning; here, presentation is derived from a read, and the presenter is forbidden from adding to it.
What the Real Host Said
The first fourteen browser scenarios drove the seven moves against a fake modelContext, because no browser on the machine had a real one. Then Playwright’s Chromium 153, launched with --enable-features=WebMCP, did. It exposed the API on document.modelContext, nothing on navigator, and accepted all seven descriptors. The flag name most secondary sources quote did nothing at all. The adapter now looks at document first, and that scenario runs on every push as its own Playwright project.
The analyst’s first deploy to a Cloudflare Worker failed for five clubs, then twenty-five, then all thirty-two. Making the unavailable-reason name each club’s failure took a minute and said the same thing thirty-two times: HTTP 429. The NHL’s edge throttles a shared egress address that fires ninety-six requests at once — a failure that never once appeared on a laptop. The fix is a rule rather than a retry: never load on a viewer’s clock. Pace, warm the cache on a schedule, and when cold, say “warming” and start it in the background.
Fifteen Chromium scenarios passed while the spotlight shipped grey in Safari.
WebKit does not repaint animated changes inside an SVG mask. The hole never opened; the viewer saw a shade and a callout and a click that appeared to do nothing. The spotlight is now a wide-stroked ring under a static clip, and every visual mechanism gets a WebKit pass before it deploys.
A fourth finding was smaller and sharper. A refactor removed one import; the paste button’s handler referenced it and died; every scenario stayed green because they called the move through the dispatch API instead of pressing the button. The scenarios now press the buttons a viewer would press. Verification proves what it exercises and nothing else — the argument What the Gauntlet Can’t See made about specifications, made here about a click.
A Skin, Not a Brain
The name is the bobtail squid, and it was chosen after the honest one turned out to be taken. Telestrator is a generic device name, and a Vancouver company has spent twenty-seven years being the number one telestrator; the pattern keeps that word, the product could not. So the product is named for an animal that does the pattern for a living. A cuttlefish’s skin is a display of millions of pigment cells driven directly by its brain: a neuron fires and a pixel shows, the neuron stops and the skin clears. The skin decides nothing. And the passing cloud — a dark band that travels across the body to hold prey still — is a spotlight done by an animal. The Name Is the First Spec argued that a natural metaphor arrives carrying constraints; this one arrived carrying the invariant.
Sepiola is live, one file of 264 kilobytes on Cloudflare Pages, reading from a ChirpIQX Worker, seven moves registered against a real WebMCP host, 542 unit tests and 17 browser scenarios green. The forty lines that knew better are gone, and the page has never once said anything the analyst did not.
The general shape is the one this whole body of work keeps returning to: declare meaning once, derive everything visible from it, and forbid the deriving layer from adding to the meaning. A diagram that refuses coordinates. A validator that runs the real rule. A screen that has no opinions. The analyst decides. The pen draws. The skin shows.
Explore the artifact
Related
The Diagram That Refuses Coordinates
Geometry derived from declared meaning; the same boundary one layer down.
One Grammar, Two Readers
ChirpIQX, the analyst behind the screen, and the Semantic Intent block it runs on.
The Name Is the First Spec
Why a natural metaphor arrives carrying architectural constraints.