annotate
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@annotateopen http://localhost:5173/ so I can mark up the checkout page"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Draw on the page. Claude fixes the code. A Claude Code plugin: mark up your running localhost site in the browser, hit Send to Claude, and every mark lands in the session that opened the page, with the element, its component and a screenshot under each one.

/plugin marketplace add FinalAngel/claude-annotate
/plugin install annotate@claude-annotateclaude --dangerously-load-development-channels plugin:annotate@claude-annotate # once per session, see "The channel flag"/annotate http://localhost:5180/The page opens in its own Chrome window with the tools already on it. Pen, arrow, box, circle, numbered notes. Scroll, click through to other pages, keep marking. One Send. Claude reads the crops, edits the code, and each pin on the page reports back: spinning while Claude works on it, green with a one-line result, grey if skipped. Clear wipes it all and you go again.
What Claude receives
One event per Send, pushed into the running session. This is the batch behind the screenshot above, as Claude saw it (paths shortened):
Browser annotations · batch 1 · 1 page · 3 notes · 4 marks
## Page 1 of 1 — http://localhost:5180/
viewport 1440×900 · document 1440×7980
overview (full page, tall): /tmp/claude-annotate/5e1f2a9c/batch-1/p1-page.png
### Note 1 — "monthly should be the default"
crop: /tmp/claude-annotate/5e1f2a9c/batch-1/p1-crop1.png (region 0,4167 → 1110×536)
pin at 666,4415 · pink
on: <button.ff-seg__tab[role=radio]> "MONTHLY" at 625,4397 size 82×36
in: div.ff-planpass__body > div.ff-planpass__top > div.ff-seg[role=radiogroup]
component: ToggleGroupItem ‹ ToggleGroup ‹ PlanPass ‹ Pricing ‹ SiteSection
source: src/screens/site/content.tsx:171
marks here: pink box around <div.ff-seg[role=radiogroup]> "MONTHLY YEARLY"; sun arrow → <div.ff-planpass__body> "KEEP THE LIGHTS ON MONTHLY YEARLY $…"; cyan stroke around <b.ff-num> "$5"
### Note 2 — "price feels too big, drop one size"
pin at 140,4519 · cyan
on: <b.ff-num> "$5" at 88,4485 size 95×77
component: PlanPass ‹ Pricing ‹ SiteSection ‹ SiteMain ‹ Landing
source: src/screens/site/content.tsx:135
### Note 3 — "arrow should sit in its own circle like the hero buttons"
crop: /tmp/claude-annotate/5e1f2a9c/batch-1/p1-crop2.png (region 0,4670 → 1178×440)
on: <svg> at 752,4883 size 14×14
in: div.ff-planpass__action > a.ff-btn.ff-btn--ghost > span.ff-btn__ic
component: Icon ‹ Link ‹ A ‹ Button ‹ ButtonLink
source: src/screens/site/content.tsx:45
marks here: lime box around <a.ff-btn.ff-btn--ghost> "BECOME A SUPPORTER"Per note that is: your text, the element under the pin with its visible text and box, where it sits in the DOM, the React components it is rendered by (only the ones that appear in your own code, library internals are dropped), the file and line where that element is written, and the marks drawn near it with what each one points at. Nearby marks are clustered into one crop, so a note and the arrow next to it arrive in the same picture. The full-page overview is there for layout questions. On a page without React you still get the element, its text, the DOM path and the crops.
source comes from React's own development metadata (_debugSource on React 18, the owner stack on React 19). Vite and Next.js dev builds have it. Production builds don't, and then only the component names and DOM path are reported.
Related MCP server: Browser Feedback MCP
On the page

Marks | Pen, arrow, box, circle. Four inks with a dark halo, so they read on light and dark pages. Hold |
Notes |
|
Browse |
|
Across pages | Marks are stored per URL. Navigate away and back and they are still there. One Send covers every page. |
Select |
|
Send |
|
Progress | Pin spins: Claude is on that note. Green with a line: done. Grey: skipped, with why. The toolbar shows which file Claude is editing. |
Clear | Two clicks (the second one says Sure?). Removes every mark on every page and the temporary screenshots. |
Keys: P pen · A arrow · R box · E circle · N note · S select · 1 – 4 inks · V / esc browse · ⌘Z · ⇧⌘Z · ⌫ · ⌘↵ send. Drag the toolbar by its grip, it remembers where you put it per site.
Why not paste a screenshot
Pasting a screenshot and describing it works, and it is what this replaces. Each round costs a screenshot, a paste, and prose like "the second toggle in the pricing card, no, the other card". Claude then guesses which file that is. Here the prose is the note, the position is the pin, and the file and line come along for free. Browser tools in the same space, as of October 2026:
Tool | Where you annotate | How it reaches Claude Code |
annotate (this) | Its own Chrome window, any local site, drawing and notes | Pushed into the running session on Send; progress and results come back onto the page |
A Playwright window, drawing only | Pushed into the session; a toast comes back | |
A Chrome DevTools panel, pick an element, write feedback | Pushed into the session | |
A toolbar you mount in your React app | Claude polls an MCP server, or you paste | |
A Chrome extension on localhost pages | Claude polls an MCP server | |
| Clipboard, you paste |
Everything that pushes into a running session uses Claude Code channels. Everything else waits to be asked. The first of those projects is where this one started, see Credits.
How it works
One Node process per session, spawned by Claude Code as an MCP server. It does three jobs:
Browser. On
/annotate <url>it launches the Chrome you already have throughplaywright-core, with its own profile under~/.cache/claude-annotate/, and injectsserver/overlay.jsinto every page before the page's own scripts run. The overlay is a Shadow DOM on a host appended to<html>: the page's CSS never reaches it, and its CSS never reaches the page. Marks live in document coordinates, so they stay put while you scroll. No change to your app, no extension, no build step.Bridge. The overlay talks to the process over a local HTTP port with a per-session token. State is kept per URL on the server, which is why navigation keeps your marks and a reload restores them. A
PostToolUsehook posts the file Claude just edited, and tool results, toasts and the done summary stream back to the page over server-sent events.Channel. On Send, the process screenshots every annotated page (pages that aren't open right now get rendered in a temporary tab behind yours, so focus never moves), clusters nearby marks into crops, writes the PNGs to a temp dir, and pushes one
notifications/claude/channelevent into the session. Claude reads the files, edits the code, and callsannotate_progressper note andannotate_doneat the end, which deletes the PNGs.
The overlay mounts on local development hosts only (localhost, 127.0.0.1, private IPs, *.localhost, *.test, *.local, *.internal) and takes the bridge token off the page before any page script runs, so a third-party site opened in that window never sees it. annotate_open refuses other hosts.

Tools the server exposes: annotate_open, annotate_progress, annotate_done, annotate_reply, annotate_screenshot, annotate_pull, annotate_wait, annotate_clear, annotate_close. The /annotate command tells Claude how to use them; the server's MCP instructions repeat the protocol so a channel event is handled even if you never typed the command in this session.
The channel flag
Channels are a research preview in Claude Code. A plugin from your own marketplace can push into a session only when you start the session with
claude --dangerously-load-development-channels plugin:annotate@claude-annotateClaude Code shows a full-screen confirmation once, then a dim line under the banner says messages from this plugin inject into the session. Alias the command. On Team and Enterprise plans an admin has to enable channels for the organization first, and the flag does not get around that.
Without the flag everything still works except the push:
/annotate <url> --pollmakes Claude wait inside a tool call until you hit Send. Nothing is pushed, nothing is lost./annotate pullfetches the last batch you sent, if Claude didn't react.
What is written where
Path | What | When it goes away |
| Crops and the full-page overview for one batch | When Claude calls done, on Clear, when the session ends. macOS purges what a crash leaves after three days |
| The Chrome profile the annotation window uses (cookies, logins for your localhost apps) | Stays. Delete it to start fresh. A second concurrent session gets a throwaway profile that is removed on exit |
| The bridge port and token, so the hook can find its session | When the session ends; stale ones are pruned at startup |
| Where you dragged the toolbar | Never, it's one key |
The bridge listens on 127.0.0.1 only and every request needs the session token. Neither the server nor the overlay makes an outbound connection.
FAQ
Does it change my app? No. The overlay is injected by the browser automation layer, not by your bundler. Your source, your index.html and your build are untouched. Open the same URL in your normal Chrome and nothing is there.
Does it work on a site that isn't React? Yes. You get the element, its visible text, the DOM path and the screenshots. Component names and source lines are React-only, and only in development builds.
What about a page behind a login? The annotation window has its own Chrome profile that persists, so log in once. Claude's temporary tabs for other pages share the same profile.
Claude didn't react to Send. Type /annotate pull. If a batch comes back, the session was started without the channel flag: restart with it, or use --poll next time. The server cannot tell whether the flag was given, so the page can't warn you; the pull is the check.
Two Claude Code sessions, two annotate windows? Yes. Each session has its own server, port, token and batch directory. The second window gets a throwaway Chrome profile because Chrome allows one process per profile.
Edge instead of Chrome? ANNOTATE_BROWSER=msedge. With ANNOTATE_BROWSER=chromium it uses Playwright's bundled build after npx playwright install chromium. Edge is untested.
Can Claude look at the page after fixing it? annotate_screenshot returns the current state of the annotation window, so after the dev server hot-reloads Claude can check its own work. The command asks it to when that helps.
Why Playwright and not the Claude in Chrome extension? The extension gives Claude a browser; this gives you a surface to draw on and needs the overlay on every page, including the ones Claude opens in the background for screenshots. Driving Chrome directly makes that one line of code instead of a protocol.
Development
npm install # only for a checkout; an installed plugin gets its packages from Claude Code
npm test # syntax, MCP handshake, bridge, hook, cleanup. No browser.
claude --plugin-dir . --dangerously-load-development-channels plugin:annotate@claude-annotateANNOTATE_DEBUG=1 adds an annotate_debug tool that drives the browser (mouse, keys, eval, viewport), which is how the screenshots in this README were staged against a real app.
Layout: the plugin is the repository root. server/index.mjs is the whole server, server/overlay.js the whole overlay, commands/annotate.md what Claude does, hooks/hooks.json and scripts/ticker.mjs the file ticker. Two runtime dependencies, @modelcontextprotocol/sdk and playwright-core, which Claude Code installs from the lockfile when it installs the plugin.
Not there yet: marks inside iframes, pages that scroll a wrapper instead of the window, attaching an image to a note, a mobile viewport preset, and a channel allowlisting so the flag can go.
To have an agent do the setup, point it at INSTALL.md.
Credits
Inspired by tomreinert/claude-annotate, which showed that a Claude Code channel can carry a drawing from a live page into the running session. This one grew out of wanting the same thing with notes, element context, several pages and progress back on the page.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Comment on AI-generated webpages; feedback flows back to your coding agent. Free, MIT, local-first.
Persistent memory for Claude Code and Cursor. Stop re-explaining your project every session.
Share context and questions between Claude instances — VS Code, claude.ai web, and mobile.
Human feedback for AI agents: share HTML, get a live review link, read anchored notes as markdown.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables visual annotation on web pages for Claude Code, allowing element selection, comment addition, screenshot capture, and structured UI feedback for code fixes via an MCP server.MIT
- FlicenseAqualityCmaintenanceEnables visual browser feedback collection directly into Claude Code. Users can point at elements in their browser and send annotated feedback that Claude can act on immediately.121-
- AlicenseAqualityBmaintenanceEnables DOM-aware visual annotation of web pages with drawing tools and element selection, submitting structured annotations to Claude Code via MCP for automated feedback processing.2MIT
- AlicenseNot gradedqualityCmaintenanceEnables drawing annotations directly on local web pages and sending screenshots, DOM data, and annotation context to your IDE agent for precise code edits.34 npm3MIT