Recall Me Maybe

Recall Me Maybe tells you whether a second-hand child product has been recalled, when nobody knows what it is called. The buyer supplies one photograph of the label, the page turns it into typed evidence, and deterministic rules compiled from the official CPSC notice make the safety decision. Their assistant drives the whole thing, and can only ever do what the evidence allows.

A stroller is for sale for $30. No box, no manual, no model number, and the seller is not hiding anything: they bought it second hand too. Graco recalled 4.7 million strollers with that folding hinge in November 2014, after six fingertip amputations, four partial amputations and one laceration. The notice went to whoever registered the warranty. Nothing connects it to the person about to hand over the cash.

That gap is measurable in the official record. When CPSC re-announced the Maclaren umbrella stroller recall in May 2011, the reason was 37 reported injuries that occurred after the stroller had already been recalled, including five more fingertip amputations. The recall worked. The notice did not reach the people holding the product.

The problem is identity, and it is upstream of everything else

Every recall notice is written against identifiers: model names, model numbers, lot numbers, serial ranges, manufacture dates. Section 103 of the CPSIA, implemented as 16 CFR part 1130, requires durable infant and toddler products to carry the manufacturer, the model name and number and the date of manufacture permanently on the product itself, which is why the identity is nearly always on the object. It is just not in the listing, not in the buyer's head, and not in any photograph a camera can resolve into a model number from across a driveway.

So a recall checker that starts from a search box is asking for the one thing the person does not have. Recall Me Maybe starts from what they do have, and asks for the rest by name.

Why this is a strong fit for WebMCP

The tool surface is the state of the evidence. This is the whole design, and WebMCP is what makes it a mechanism rather than a promise. verify_recall does not exist while the identity is a similarity score. It is not disabled and it is not behind a permission check: it is unregistered, so it is not in the assistant's tool list, so it cannot be called. When the person photographs the label and the evidence becomes sufficient, it appears. get_official_remedy, register_owner_for_notices and report_listing_to_marketplace appear only if a recall was actually confirmed, and a negative verdict leaves all three unregistered, because there is no remedy to fetch and nothing to report. An assistant cannot take a step out of order, and never has to be told not to.

Three parties who will never share a database. The marketplace, the safety-notice registry and the checker are three separate websites on three separate origins. Each partner publishes its tools with exposedTo naming exactly one origin, the checker embeds them with allow="tools", discovers with getTools({ fromOrigins }) and calls with executeTool. There is no shared backend and no data-sharing agreement, which is the only version of this that could exist between a marketplace and a safety service in the real world.

What people and their agents can do together that was not possible before

An agent that knows exactly which photograph to ask for, and a person who can take it.

A visual search that says "this looks like a Graco, 74% confident" is not a safety decision and should never be presented as one. In the demo build the closest visual match to the demo stroller is a Britax at 0.774, and the right family is fourth at 0.671. Every candidate carries identity_status: UNVERIFIED, and that is enforced rather than labelled: visual evidence has an authority of 5, every compiled rule requires 70, and no code path turns a candidate into an identifier.

What the page does instead is compute the gap. It takes the recall rules still open, works out which identifier kinds would settle each of them, and returns the places those identifiers are printed, in words taken from the official notices themselves. Five candidates, five places a label can be on a stroller, and one photograph settles all five at once. Then a person turns the stroller over.

That loop is new. The agent knows which evidence matters and cannot obtain it. The person can obtain it and does not know what matters. Neither half works alone, and the page is where they meet.

A better experience

The person presses one button and points a camera.

What comes back is a proof rather than a verdict. Tesseract and ZXing run in the browser, from files served by the page's own origin, so no photograph of anybody's child's stroller reaches a server. Each identifier is drawn as a rectangle over the exact characters it was read from: model LiteRider, manufactured 2013-04-18, serial 130418 0044213, barcode 047406172009 at authority 95. Then the rule table shows the decision field by field, next to the sentence from the official notice it was compiled from.

And when something has to leave the page, the person sees it first. register_owner_for_notices returns a Promise that does not settle until they press a button. The panel names the recipient, the recipient's own origin, the five exact fields, and the recipient's own retention promise, and lets them change the email address before it goes. A host assistant can already ask for confirmation. What only a page can do is show the payload and let it be edited.

Safety, by construction

The prompt-injection surface here is unusually wide: an attacker can put text in a listing title, in a description, on a printed label, or into an OCR result. All four are treated as inert data.

Nothing a seller wrote crosses a tool boundary. The marketplace returns enums, integers, booleans and URLs on its own origin, and a seller's brand only if it matched the marketplace's own vocabulary, so it is an enum rather than a thirty-character string with a hopeful pattern. One listing in the demo carries an instruction aimed at whatever reads it; the page renders it, because a person browsing a marketplace should see what the seller wrote, and no tool result has a field it could occupy. OCR output is parsed, not passed on: a line becomes evidence only if it matches an identifier pattern, so IGNORE PREVIOUS INSTRUCTIONS on a label reaches no further than the "everything the reader saw" list. Partner payloads are validated against this page's schemas, and a rejected payload never reaches a return value, a log line, or an error message.

There is also no unsafe-eval in the deployed policy. The JSON Schema validator is an interpreter rather than a compiler, so the only relaxation the page needs is wasm-unsafe-eval for the OCR core it serves itself.

How I implemented WebMCP

Every direct document.modelContext call in the repository lives in one package.

  • Each partner site registers its tools with registerTool and exposedTo scoped to the checker's origin alone. Never a wildcard, in either direction.
  • The checker embeds each partner in an iframe that delegates the tools permission with allow="tools", discovers with getTools({ fromOrigins }) one origin at a time so the origin is known by construction, and calls with executeTool, treating a null return as a navigation rather than as an empty success. Arguments are tried as an object and as a JSON string, and whichever the browser accepted is latched and reported. Chrome 152 accepts the JSON string.
  • The ten agent-facing tools are registered and unregistered by a lifecycle manager holding one AbortController each, driven by state transitions and never from inside a handler. A transition that lands mid-execution is applied the moment the handler returns, so the surface never goes stale in the window an agent is calling through it.
  • ontoolchange is how the page learns a partner has withdrawn its tools.
  • /verify.html probes all four capabilities live, in whatever browser you opened, and prints what it found.

If a browser cannot do cross-origin WebMCP, the same partner sites on the same separate origins are reached over a typed postMessage protocol with the same schemas, the same firewall and the same consent gate, and the page states in words which path it used. The fallback is never presented as WebMCP federation.

The recall data is real, and the limits are stated

Fifteen CPSC recalls, fetched live from the CPSC Recall API and joined to affected-product rules compiled by hand from the text of each official notice: 330 explicit model names, model numbers and serials, plus date intervals, lot ranges and exclusion patterns. Each rule carries the excerpt it was compiled from, a rule hash, and the time the official record was retrieved. Thirty-two official CPSC product photographs make the visual index.

search_cpsc_recalls reaches the live API for everything else, and a record it returns that is not in the compiled pack cannot produce a verdict. The tool says so.

There is no SAFE. The negative verdict reads: No matching CPSC recall found for the supplied identifiers, against the 15 recalls compiled into this build, as of this date. That is what was proven, and nothing more.

Recall Me Maybe. You shouldn't need to know what it's called to know it was recalled.

Built With

Share this project:

Updates