Inspiration
Wikipedia does not use spoiler warnings. That is a deliberate policy, settled in 2007 after a long debate, and it is not going to change: the plot section of every film, series and novel article states the ending as plainly as it states the release date. So looking up one fact about a show you are halfway through means reading around the ending and hoping.
The obvious answer — ask a model to summarise the article carefully — throws away the article. You get one paraphrase, mediated and unverifiable, and you have lost the thing you came for. We wanted the real page, still navigable, with the parts you have not earned yet held back.
What it does
Unspoiled is a Wikipedia reader whose masking is driven by your agent. The agent reads the article on your behalf, decides which sentences would spoil it for you, and the page enforces that decision sentence by sentence — with every decision, and its reason, printed on your screen where you can overrule it.
On the Breaking Bad article, the page's own presets withhold 203 of 738 sentences before an agent says anything. Tell your agent you finished season three and it opens exactly those seasons — 49 sentences in one call — and leaves season four closed.
Why WebMCP is the right fit
The division of labour is the whole idea:
- The page knows the article. Which sentences exist, which section they sit in, which of them read like a giveaway.
- The agent knows the reader. Which seasons they finished, whether they read the source manga, how much they want to know before watching.
"What counts as a spoiler" is the intersection of those two, and neither party holds both halves. And because the agent can leave rules behind in the page, that intersection is not consumed by one answer: it keeps applying to the next article you open, with or without the agent present.
To be straight about the boundary: a bespoke backend plus a frontend could reproduce this behaviour. What WebMCP contributes is that the live page exposes semantic operations over its own local state — no server integration to build, no screen-coordinate automation to break, and the article never leaves the reader's machine except as the Wikipedia request itself.
What people and agents do together here
The agent decides; the page enforces and shows its work. apply_mask takes sections, paragraphs or individual sentence ids to show or hide, and one thing it cannot omit: a reason. Every call is announced the moment it happens — "Your agent showed 6 sentences and hid 20", with the reason on the line below — and kept in "Agent activity" at the foot of the article, newest first, timestamped. A call that matched nothing is still a row, reading "0 shown · 0 hidden": an agent cannot quietly decide nothing.
You can overrule any of it, on the page. Tap an opened sentence and it closes again. Click a withheld band and it opens. Your action writes the same shown/hidden sets the agent writes, and it is the last word — the reader's hand is above the agent's, which is above the page's own heuristics.
What the agent read is visible too. read_article_content is ungated on purpose: your agent reads the whole article, because that is what it takes to judge what would spoil it for you. So the page says so, in a banner across the top: "Your agent has read: Episodes, Season 1 (2008), Season 2 (2009) and 9 more — in this conversation your agent knows what those sections say, even where the page still withholds them." The honest shape of this app is not "the agent is kept ignorant" — for a famous work the model likely knows the ending from training anyway. It is that the asymmetry runs the other way: your agent knows the article, you do not yet, and what reaches your eyes is a decision you can inspect and reverse.
What the agent writes outlives the conversation. This is the part a chat cannot do at all. With add_rules the agent turns what it learned about you into standing rules for the page: literal phrases that come off the page wherever they appear, in this article and in every article you open after it. Each rule needs a label and a reason, and the label must not contain its own phrases — the call is rejected if it does, because the label is what you will see. The phrases themselves sit behind a "Show phrases" button you may never press, so a rule can be audited, and removed, without the audit spoiling you. In the demo one rule takes Breaking Bad from 154 withheld sentences to 184; following a link to Jesse Pinkman, with no agent involved, it is still withholding 54 there.
And it works with no agent at all. Five named presets — Show everything, Ending only, Major spoilers, Spoiler-safe, Maximum protection — run the page's own wording heuristics, grouped into categories (death, identity, outcome, relationship, ending, return). On Breaking Bad that is 38 / 161 / 203 / 508 of 738 sentences. The agent raises the ceiling; it is not the floor.
How we built it
A static Vite + React + TypeScript app on Vercel. No backend: article text is fetched straight from the MediaWiki action=parse API in the browser, which allows CORS.
The page parses an article into sections, paragraphs and sentences, then applies a two-level risk ladder: narrative and analysis sections (Plot, Episodes, Themes, Series overview, あらすじ) are withheld wholesale, while everything else is judged sentence by sentence against reveal wording. Withheld text is never in the DOM — a withheld paragraph is drawn as a band whose height scales with how much is hidden ("3 sentences withheld · 251 chars · reveal"), never as real text behind a blur. Opening sentences animates as a single stream of words, and tapping one closes it again.
Five tools are registered on document.modelContext:
open_article— the outline with per-section ids, risk and withheld countsread_article_content— full text with paragraph and sentence ids and ashownflagapply_mask— show/hide by section, paragraph or sentence id, with a required reasonadd_rules— labelled phrase rules that persist across articles and sessions, never echoing the phrasesget_masking_report— counts, decisions, rules by label, sections read — and no article text
English and Japanese Wikipedia are both supported, internal links open inside Unspoiled and the browser's back button walks the articles you came through, light/dark/system themes are respected, and the reader works on a phone. 508 tests cover the risk scoring, the tool surface and the reader's precedence over the agent.
Challenges we ran into
Everything returned to the agent is a leak surface. The first version happily put a withheld heading in a tool result's reason field — "hidden because 'Series finale' gives away the ending" — which hands over the spoiler while claiming to protect it. Reasons became fixed phrases that never interpolate article text, and no tool returns a withheld heading by default.
Calibrating the ladder took real articles. An early version withheld the entire lead and Plot sections of everything; a later one hid the Sales and Manga sections of Attack on Titan, which spoil nothing. Wording heuristics also miss what has no giveaway words — "his mother is eaten by a Titan" — which is exactly the gap apply_mask exists to fill.
Section headings are not enough on their own. Lost calls its plot "Series overview", so its per-season summaries scored as safe and stayed readable. The fix keys narrative sections off the whole heading path, so Series overview > Season 1 is plot while Reception > Critical reception > Season 1 stays readable.
WebMCP as shipped has sharp edges. The API is document.modelContext, not navigator.modelContext. Tool arguments arrive as a JSON string and results are JSON-stringified. There is no image content type — a tool surface is text in, text out. There is no unregisterTool, and re-registering a name rejects with InvalidStateError, so registration has to be idempotent to survive React's double mount in development. And a page cannot initiate anything: there is no way to prompt the agent or even detect that one is attached, so tool descriptions are the only steering a page gets, and each of ours names the call that should come next.
What we learned
The claim we started with — "the agent can never learn the ending" — was not true, and building the thing is what showed us. For a famous work the model knows the ending from pretraining, and any sentence the reader opens becomes visible to the next tool call. What a page can actually promise is narrower and more useful: it controls what it transmits, it shows every decision and its reason, and it puts the reader's hand above the agent's. Writing the honest version of the claim made the product better, because it moved the value from a guarantee we could not keep to an audit trail we could.
The second thing: the artifact matters. A summary is consumed once. A rule written into the page keeps working tomorrow, on the next article, with no agent in the room — and that is the part of this that a chat interface structurally cannot reach.
What's next
Reader-owned rule sets that sync across devices, a per-work progress model ("I am on episode 7") instead of per-section flags, and support for the other sites where spoilers live — episode wikis, review aggregators, storefront pages.
Attribution
Article text from Wikipedia, licensed CC BY-SA 4.0, with a link back to every source article. Unspoiled is not affiliated with Wikipedia or the Wikimedia Foundation.
Built With
- mediawiki-api
- react
- tailwindcss
- typescript
- vercel
- vite
- webmcp
Log in or sign up for Devpost to join the conversation.