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.
liha-preview deploy .builds the project and publishes it. One URL, and every later version arrives at the same one.- 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.
- 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.
- 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_commentwithreplyTo— 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 ondocument.modelContext. Details we went out of our way to get right:- Only the annotations WebMCP actually defines. The spec's
ToolAnnotationsdictionary has exactly two members,readOnlyHintanduntrustedContentHint.destructiveHint,idempotentHintandopenWorldHintare base-MCP server-side hints, dropped before an agent sees them. We set only the real two —readOnlyHintexplicitly, includingfalseon 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()andunregisterTool()were removed during 2026, unregistration becameAbortSignal-based, and the getter moved fromnavigatortodocument. We probedocument, thennavigator, 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.
- Only the annotations WebMCP actually defines. The spec's
API — Hono on Cloudflare Workers, D1 for metadata, R2 for artifacts. Route code talks to
Database/ObjectStoreports that the real bindings satisfy structurally, so integration tests run the shipped SQL and migrations againstnode:sqlitein 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--jsonon 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
- Live app: https://livepreview.liha.dev — press "See a sample". It mints a real preview of a landing page already carrying anchored feedback, and hands you the owner token, so there is nothing to upload and nothing to sign up for.
- Repository: https://github.com/liha-app/live-preview (MIT)
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
- cloudflare
- hono
- javascript
- node.js
- playwright
- react
- typescript
- vite
- webmcp
- zod
Log in or sign up for Devpost to join the conversation.