annotate-mcp
The annotate-mcp server provides a dedicated, persistent browser instance where a human can visually annotate any live web page with comment pins, freehand drawings, and eraser marks – with no integration needed on the target site. Annotations are injected into every page and cross‑origin frame, anchored to underlying DOM elements, and reflow with page changes. A waiting mechanism (annotate_wait) parks the agent until the human explicitly sends a batch of marks, enabling a seamless hand‑off. The agent can then retrieve, manage, and act on those annotations.
Open & Navigate Browser: Launch headed Chrome with a persistent profile, optionally navigate to a URL, and auto‑inject the annotation toolbar.
Create Annotations: Add comment pins, draw with a pencil, or erase directly on any page. Marks can be edited, moved, linked/unlinked, and are tracked as pending or sent.
Hand‑off via
annotate_wait: Park execution until the human presses “Send” – the agent receives the batch of annotations as the primary feedback mechanism.List Annotations: Retrieve a readable briefing of all marks grouped by page, filterable by status (open, sent, all) and URL path substring.
Re‑read Last Batch: Use
annotate_inboxto get the most recent sent batch without waiting.Screenshots: Capture the current page with annotations visible (toolbar hidden), optionally cropping around a specific mark or capturing the full scrollable page.
Resolve Marks: Mark annotations as done (
annotate_resolve) – they dim on the page to show what was handled.Clear Marks: Delete specific marks or clear all marks permanently.
Close Browser: Shut down the annotation browser while preserving all marks and profile on disk for persistence.
Click on "Install 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., "@annotate-mcpOpen the browser so I can annotate the staging site."
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.
annotate-mcp
Draw on a live page, and your coding agent reads what you drew.
An MCP server that opens a real browser and injects a comment/pencil overlay into every page it loads — your app, a staging deploy, someone else's site. You mark things up like a PDF annotation layer; the agent gets back your note plus the DOM elements it lands on.
It is project-agnostic by construction. Nothing is installed into the app under review: no component, no route, no dev-server dependency, no framework assumption.
Why it can be agnostic
Two Playwright primitives:
addInitScriptinjectssrc/overlay.jsinto every page and frame before any page script runs, for the life of the browser context. Navigation, reloads, and cross-origin hops all keep the overlay.exposeBindinggives that injected script a real function into Node, so marks round-trip to the server instead of being trapped in page memory.
State lives in Node, not the page. The overlay is a pure view: on load it asks the
server for the marks, on every change it pushes them back. That inversion is what makes
a mark left on /dashboard still exist after you navigate to /settings, reload, or
close the tab — none of which localStorage would survive cleanly across origins.
Related MCP server: ui-annotator-mcp
Quick start
Clone it anywhere — it does not have to live inside the project you are reviewing:
git clone <this-repo> annotate-mcp
cd annotate-mcp
npm install # builds automatically via `prepare`; pnpm and bun work tooThen point your MCP client at dist/index.js. For Claude Code, add it to the project
you want to annotate — .mcp.json in that project's root:
{
"mcpServers": {
"annotate": {
"command": "node",
"args": ["/absolute/path/to/annotate-mcp/dist/index.js"],
"timeout": 28800000
}
}
}Restart the session, accept the "new MCP server" prompt, and ask the agent to open the browser. Full per-client instructions, including one-line install and where marks are stored: docs/INSTALL.md.
Uses your installed Google Chrome by default (familiar browser, no 150MB download), falling back to Playwright's bundled Chromium. The profile is persistent, so logins survive between sessions — you need to be signed in to review the flows worth reviewing.
Using it
Ask the agent to open the browser, then mark things up. Toolbar starts bottom-right; drag it anywhere by the grip on its left edge and it stays there, per browser.
grip | move the toolbar — the panel opens away from whichever edge you park it on |
comment | click anywhere to pin a note |
pencil | drag to draw; the note box opens automatically, so the text is linked to that drawing with no tool swap |
erase | click a single pin or line to delete just that one |
clear all | press twice to confirm |
| review panel — unsent marks here + elsewhere; everything ticked by default, untick to exclude, then Send |
Esc backs out one level at a time: open note → unlinking → linking mode → active tool → panel.
Without it you can get stuck in comment mode unable to click the page underneath.
Working with existing marks
Click a pin or a drawing to reopen its note — edit the text, or delete it from there. Clicking never stacks a second comment on top of the first, and it works whichever tool is in hand, including the pencil you just drew with.
Drag a pin to move it. It re-resolves what it points at on drop, so a moved pin cites the element it landed on and not the one it left.
Link (chain icon in the note box) makes one comment cover several places: press it, then click another mark to pull it in, click empty page to drop a new linked pin, or draw to add a linked drawing. Comments and drawings link to each other freely — a group can be any mix. Linked marks share one text (editing any of them rewrites all of them) and are joined on screen by a dashed thread. Press Done or
Escto leave linking mode.Unlink — the eraser in the link banner. It appears once a group exists, and clicking a member takes it out while leaving the mark, its text, and the rest of the group alone. It erases links and nothing else; the toolbar eraser is the one that deletes marks. Take the second-to-last member out and the group dissolves on its own, since one mark is not a link.
Sent vs pending
The panel has two tabs. Send moves a mark from To send into Sent, where it is dimmed and cannot be sent again — a queue that never shrinks stops telling you what is outstanding. The ink stays on the page as a record; the eye toggle in the Sent tab takes it off the page entirely when the archive starts covering the app you are still reviewing.
Editing a sent mark's text is the one way back: rewriting it makes it new information, so it rejoins the queue.
Reflow
Marks are anchored to the element they land on, as a fraction of that element's box. Resize the window and the ink follows its component — pins are repositioned, drawings are translated without being stretched, and the stored coordinates are rewritten so the agent, the screenshot crop, and the screen never disagree.
If the element is not rendered at the new size (a nav that collapsed, a card dropped from the layout), its mark is deliberately left where it was rather than dragged somewhere arbitrary, and drawn hollow/dashed so it reads as "this is about something not on screen" instead of silently labelling whatever slid underneath.
Tools
tool | what it does |
| launch/focus the browser, optionally navigate |
| block until you press Send, then return that batch — the main hand-off |
| all marks as a briefing, filterable by status/path |
| re-read the most recent sent batch |
| screenshot with ink visible, toolbar hidden; |
| mark items done — they dim on the page so you can see what was handled |
| delete marks |
| close the browser (marks are kept on disk) |
The hand-off, and the three states of Send
An MCP server answers calls; it cannot start a turn in an agent's session. So a send only
reaches the agent on its own while something is parked in annotate_wait.
annotate_wait parks for an hour by default (max 8h). Parking is free — a blocked call
spends no tokens — and on clients that background long calls it doesn't block the agent
either: Claude Code moves any MCP call still running after two minutes into a background
task, so it keeps working and the batch arrives as a task notification the moment you press
Send. Long parks are kept alive with progress pings, because a client will abort an stdio
call that has been silent too long — 30 minutes, in Claude Code's case.
Two ceilings are worth knowing:
| hard wall-clock per call. Set to 8h here. Also floors the idle timeout |
| the same limit as an env var; defaults to ~28h when unset |
Nothing sent is ever lost. A send with nobody parked is held, and the next park collects it immediately instead of waiting for a fresh one — so the gap between an agent taking one batch and parking again costs a delay, not your feedback.
That leaves three states, and the panel says which one you are in — but only when it is not the obvious one:
state | what Send does | what the panel says |
live — an agent is parked | starts work immediately | nothing |
busy — it took a batch and hasn't come back | queued; collected when it parks again | "The agent is working on your last batch" |
asleep — nothing listening | held on disk | "The agent is asleep — send its session any message" |
No copy anywhere names a model or vendor — the panel says "the agent", so the tool reads the same whichever assistant is on the other end of the MCP connection. Falling asleep while you are working raises a one-off notice, since that is the moment the button quietly changes meaning. Loading a page with nothing listening does not — that is ordinary, and the line under Send already covers it.
What a mark carries
The note is what gets acted on; the DOM capture is how it gets found in source.
### 1. ✏️ drawing
> currency should come first
- where: stroke over 30,369 → 160×30 (document coords)
- viewport: 782×495 @1x — tablet/laptop
- elements under the drawing (most specific first):
- testid=`price-badge` · name="€25.00"
- selector: `[data-testid="price-badge"]`
- box: 40,379 140×38A pin records the element clicked plus three ancestors. A stroke hit-tests a grid across its bounding box, so "I circled these three cards" survives as structured data rather than only as pixels — that is what makes a drawing actionable instead of merely suggestive.
Deliberately not captured: a file:line source mapping. React 19 dropped _debugSource
from fibers and Next 16 is on SWC, so that needs a build-plugin dependency in every project
— which would destroy the agnosticism. testid plus visible text finds the component in one
grep.
Output
Written to <cwd>/.annotations/ (override with ANNOTATE_DIR):
store.json— canonical state, reloaded on restartannotations.md— every mark, rewritten on changeinbox.md— only the batch you last sent. The separation is the point: one file is everything ever marked, the other is what you're asking about right now.
Environment
var | default | |
|
| where marks are stored |
|
| persistent browser profile |
|
| Playwright channel; unset-able to force bundled Chromium |
|
|
|
Tests
pnpm build && pnpm testtest/smoke.mjs drives the real overlay in a real browser with real clicks and drags. It
covers what typechecking cannot: injection ordering, the Node↔page binding, stroke
hit-testing, mark survival across a full page load, selective send, the eraser, reopening
and editing a mark, pin dragging, linking and unlinking across kinds, linked notes staying
in step, ink surviving the note box, the sent/pending split, and marks following their
element through a reflow.
Known limits
One browser. You annotate in the window this server opens, not your everyday Chrome.
No interaction tools. The server navigates (
annotate_open) and screenshots, but exposes no click/type/hover — a human drives this window. If you want the agent to drive the app too, that is the Playwright MCP's job, or add the tools here; the Playwrightpageis already in hand.Iframes are skipped. The overlay mounts only in the top frame — otherwise a page with three iframes gets four toolbars.
Re-anchoring resolves a selector. Short CSS paths can match more than one element, so a mark on a generic
div > buttonmay re-anchor to the wrong sibling after a reflow. Marks on anything carrying atestidoridare exact.Only pins can be dragged. A drawing's position follows its element on reflow, but there is no handle to move the ink by hand — erase and redraw.
Contributing
Bug reports: yes, please. Especially "the overlay broke on this site" — the injected script has to survive whatever CSS and JS the host page throws at it, and the failures worth knowing about are on pages I will never think to test.
Pull requests: not by default. This is a small tool with a specific shape, and most of it is one injected script that cannot import anything, cannot ship a stylesheet, and has to work inside a stranger's cascade — constraints that are invisible until you have been bitten by them. Fork it and make it yours; that is what the licence is for. If you have something you think genuinely belongs upstream, open an issue first and we can talk.
License
MIT © Nikolaos Psycharis
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceAn MCP server that allows users to interact with their browser through natural language commands, enabling actions like getting page content as markdown, modifying page styles, and searching browser history.Last updated1
- AlicenseAqualityDmaintenanceMCP server that annotates any web page with hover labels — zero extensions, works in any browserLast updated510715MIT
- Alicense-qualityDmaintenanceMCP server for visual feedback, video direction, and QA assertions on web pages, enabling AI agents to read, reply, and resolve annotations in real time.Last updated4MIT
- Flicense-qualityAmaintenanceMCP server that exposes web page annotations to AI coding agents, enabling automated implementation of visual feedback and design tweaks.Last updated16120
Related MCP Connectors
A MCP server built for developers enabling Git based project management with project and personal…
MCP server for AgentDocs (agentdocs.eu): read, search, write, comment on & share Markdown docs.
A paid remote MCP for AI agent browser approval MCP, built to return verdicts, receipts, usage logs,
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ElaineMHr/annotate-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server