Inspiration

Design feedback usually reaches a developer as a sentence and a screenshot, and the first ten minutes go on translating "the hero button is too big" into a file, a line and a selector.

Plenty of tools now capture that target for you. BugHerd has recorded a CSS selector for years. Vercel's vercel comments CLI prints one, alongside the React component tree, and exposes the thread over a remote MCP server. That part of the problem is solved, and we are not going to pretend otherwise.

What none of them can do is put the agent in the room. Every one of those integrations is out of band — a clipboard buffer, a local server on the developer's machine, or a remote API the agent calls from somewhere else. The reviewer is on one screen and the agent is somewhere else entirely, and a person carries messages between them.

WebMCP changes that specific thing: the page the reviewer is looking at can publish tools to the agent sitting beside them. So we built the review tool for when both of you are in the same place.

What it does

Before: a screenshot and "make this smaller" arrive in chat. Someone reopens the build, finds the element, guesses the breakpoint, and writes a prompt.

With Live Preview: the reviewer clicks the button. The click records the selector, the DOM snippet, the page, the viewport and the version. An agent in the same tab reads exactly that, scrolls the reviewer to it, checks it at 390px, reads the CSS behind it, and answers in the thread. A coding agent fixes the source and the same URL becomes version 2. Nobody translated anything.

Liha Live Preview gives any build artifact a stable review URL — a static site, an image, a PDF, or a page that is already deployed.

  1. liha-preview deploy . builds the project and publishes it. One URL, and every later version arrives at the same one.
  2. A reviewer opens it and clicks the thing they mean. Liha records the CSS selector, tag name, text content, an HTML snippet, the page path, the viewport size and the exact version — plus a pin, box, freehand or arrow in normalized coordinates.
  3. An agent reads that feedback through WebMCP, in the same page the human is looking at, and can reply inside the thread under its own name.
  4. The agent edits the source, rebuilds, and publishes a new immutable version to the same URL, then resolves the thread.

Nobody copy-pastes anything, and the reviewer never needs a new link.

What humans and agents do together

Two of the thirteen browser tools move the reviewer's own screen, and two more read whatever version is on it. Together with writes that land in the sidebar the reviewer is watching, these are the reasons this is a WebMCP entry and not a REST client:

  • focus_comment — the agent scrolls the human's screen to a comment, selects it, and outlines the element inside the preview. You say "show me what Sam meant" and their view moves.
  • set_viewport — the agent resizes the preview to 390px, looks at what breaks, and comments on it. Checking a responsive bug becomes something it does, not something it asks you to do.
  • read_artifact_file — the artifact runs in a sandboxed cross-origin iframe, so an agent cannot see inside it. This reads the HTML and CSS of the version the human currently has on screen — not necessarily the latest — so it can name the rule that needs changing without asking which build.
  • add_comment with replyTo — the agent answers in the thread under its own name, and the reply appears in the sidebar with no reload, because tool calls post through the same API client as a human action and refresh the same sidebar.

Running the other way, a click records the selector, tag, text, HTML snippet, page path, viewport and immutable version — captured at the moment of the gesture, not re-inferred later against a page that has since changed. That provenance is what keeps the feedback trustworthy three versions on.

What the browser cannot do, we say plainly: an agent in the page can triage, read the source and reply, but it cannot edit your repository — a web page never gets a filesystem. That is what the local MCP server and the CLI are for: eight tools confined to the project root, the same review and the same threads, from the terminal. WebMCP connects the agent to the human's browser context; local MCP connects the coding agent to the developer's workspace; the product is the loop between them. All three surfaces can reply to a reviewer.

How we built it

A pnpm monorepo, TypeScript throughout, six packages:

  • @liha-cli/webmcp — thirteen tools on document.modelContext. Details we went out of our way to get right:

    • Only the annotations WebMCP actually defines. The spec's ToolAnnotations dictionary has exactly two members, readOnlyHint and untrustedContentHint. destructiveHint, idempotentHint and openWorldHint are base-MCP server-side hints, dropped before an agent sees them. We set only the real two — readOnlyHint explicitly, including false on the writers — and a test fails the build if anything else appears.
    • Chrome's metadata budgets enforced by test: 30 characters per name, 500 per description, 150 per parameter description. It caught a 246-character parameter description we had written.
    • Registration that survives the spec moving. provideContext() and unregisterTool() were removed during 2026, unregistration became AbortSignal-based, and the getter moved from navigator to document. We probe document, then navigator, then the global object, and support all five teardown shapes, each with a test, because guessing wrong means zero tools and no error message anywhere.
    • Untrusted content is fenced. Reviewer text and artifact source both come back inside delimiters, with an explicit note that they describe requested changes and are not instructions addressed to the agent.
    • Registered in the top-level document, artifact sandboxed in the iframe — ChatGPT's browser does not discover tools inside iframes.
  • API — Hono on Cloudflare Workers, D1 for metadata, R2 for artifacts. Route code talks to Database/ObjectStore ports that the real bindings satisfy structurally, so integration tests run the shipped SQL and migrations against node:sqlite in milliseconds.

  • Web — React, Vite, TanStack Router/Query. Light/dark/system themes, English and Japanese, fully keyboard-driven.

  • @liha-cli/mcp — a local stdio MCP server, so a coding agent outside the browser can read the same review and publish the fix. It only ever touches files under the project root it was given.

  • @liha-cli/live-preview — a CLI with --json on every command; stdout carries exactly one JSON document and nothing else.

Uploaded HTML is untrusted code, so every preview version is served from its own origin (<slug>--<version>.preview.example.com) inside an iframe sandboxed without allow-same-origin, with a matching CSP header. There is an end-to-end test that loads hostile script into a preview and asserts it can read neither the app's storage nor its parent frame.

Challenges we ran into

Coordinates that survive. A comment left on a 1280px desktop has to land in the right place on a phone. Everything is stored normalized 0–1 — against the rendered box for images and PDF pages, against the full scroll size for HTML, with the injected bridge reporting scroll offsets so the overlay re-projects on every scroll.

Talking to an opaque origin. The preview iframe has no origin we can check, so event.origin is useless for authentication. Identity comes from comparing event.source against the exact contentWindow we created — a reference no other document can forge.

Root-absolute asset paths. Bundlers emit /assets/app.js. Under a path mount those 404, which is why each version gets its own hostname rather than a path prefix — and it means a stale service worker from v1 cannot affect v2.

Counting. An early bug had the agent reporting "2 open comments" while the sidebar said "1" — the agent was counting messages and the UI was counting threads. Counts are per thread everywhere now.

Accomplishments we're proud of

  • 354 unit and integration tests, 86 end-to-end tests in real Chromium, on CI
  • Zero axe-core WCAG 2.1 AA violations on every screen, in both themes and both languages, enforced in the test suite
  • The whole loop — deploy, comment, agent fix, new version, resolve — works with no copy-paste, and there is a test that proves it

What we learned

Writing good tool descriptions mattered more than writing more tools. The difference between an agent using get_share_info correctly and going looking for an owner token was one sentence in the description saying what the tool is for and what it deliberately does not return.

untrustedContentHint also turned out to be a design constraint, not a flag: once you take seriously that comment text is written by whoever has the link, you start fencing it, labelling it, and never letting it read as an instruction.

What's next

The near-term work is unglamorous and written down honestly in docs/enterprise-readiness.md: SSO, an audit log and a data-retention policy are what separate this from something a company could buy.

The interesting problem is anchor durability. Every tool in this category, ours included, stores a selector that breaks the moment an agent edits the DOM it pointed at — BugHerd's selector_info carries a version field, which suggests they have already had to re-cut theirs once. We already record the ancestor path, the text content and an HTML snippet alongside the selector; the next step is to use them as a fallback chain, so a comment can re-find its target in a page that changed underneath it. Feedback that survives the fix is what makes this a review tool rather than a message queue.

Try it

Then open that same URL in a WebMCP-capable agentic browser — ChatGPT's in-app browser today — and ask:

What review feedback is open on this preview, and what does it point at?

Switch the preview to mobile width and tell me what breaks.

Show me the comment about the button, then read the CSS behind it.

The Agent tools button in the top bar lists what the page is publishing, and says so plainly when the browser has no WebMCP support.

Built with

TypeScript · WebMCP · Model Context Protocol · Cloudflare Workers · D1 · R2 · Hono · React · Vite · TanStack Router · TanStack Query · Zod · pdf.js · Vitest · Playwright · axe-core

Built With

Share this project:

Updates

Submission history