@hyzer-labs/annotate
v0.2.0
Published
Annotate any page in the browser and send the element, screenshot, and your comment to your local coding agent over MCP.
Downloads
395
Maintainers
Readme
hyzer-annotate
Click an element in your browser, type what's wrong with it, and your coding agent gets the comment, the element metadata, and a screenshot — anchored to the thing you actually pointed at.
Works with any agent that speaks MCP. Nothing leaves your machine.
Install from the Chrome Web Store

How it works
The MCP server binds a port on 127.0.0.1 (39280-39300) when your agent starts it. The extension scans that range to find running sessions, you connect a tab to one, and each annotation you submit is POSTed to that session's server. The agent drains the queue when you ask it to.
There's one server process per agent session, so the session picker lists one entry per running agent.
Setup
Two halves: the MCP server your agent talks to, and the browser extension. The server comes from npm, the extension from the Chrome Web Store.
The MCP server
For Claude Code, register it once at user scope so it's available in every project you annotate:
claude mcp add hyzer-annotate --scope user -- npx -y @hyzer-labs/annotateOr per-project, in that project's .mcp.json:
{
"mcpServers": {
"hyzer-annotate": {
"command": "npx",
"args": ["-y", "@hyzer-labs/annotate"]
}
}
}User scope is usually the better choice. Each agent process binds its own port and the extension discovers whichever ones are running, so registering it globally costs nothing and saves you repeating this per repo.
Restart the agent after adding it.
npx caches by version, so it will keep running whatever it first fetched. Pin with @hyzer-labs/annotate@latest if you'd rather it check each time, or install it globally (npm i -g @hyzer-labs/annotate) and register the hyzer-annotate binary directly.
The extension
Install it from the Chrome Web Store. Works in Chrome and other Chromium browsers — Brave, Edge, Arc.
Or build it from source:
git clone https://github.com/hyzerlabs/annotate.git
cd annotate
npm install
npm run build:extensionThen open brave://extensions (or chrome://extensions), enable Developer mode, click "Load unpacked", and select the extension/ directory.
Optional: a /fb shortcut
Asking your agent to "check annotations" works fine. A slash command is shorter — /fb, for feedback — and this one groups the queue into tasks and gets them agreed before it edits anything. Save it as ~/.claude/commands/fb.md:
---
description: Read the browser annotation queue and turn it into an agreed plan
---
Call `get_annotations` to read the browser annotation queue. It empties the
queue as it reads, so hold on to what comes back — a second call returns
nothing.
For each annotation: the comment is the ask, the selector and element metadata
say where, and the screenshot path is there when you need to see it — read it
if the comment is visual ("this looks wrong", "spacing is off"), skip it if the
comment already tells you what to change.
Then, before editing anything, propose a plan:
- Group the annotations by the area they touch (component, page, module), and
label each group by type — bugfix, nit, feature, question, unclear.
- Turn each group into one task. Annotations on the same component belong in
one task, not one per annotation.
- Order them: bugs before nits, and anything the user has to answer first.
- Show the list with a one-line summary per task and the annotations feeding
it. Ask the user to confirm, drop, or reorder, and to answer anything you
marked unclear.
Once they confirm, put the agreed tasks on the todo list and work them one at
a time. Finish a task, say what changed in a line or two, then start the next
— so the user can redirect between tasks instead of after all of them. If
their feedback changes the remaining plan, update the todos and say so.
$ARGUMENTSThen /fb reads the queue and proposes a plan, or /fb just summarize, don't edit to steer it.

Use
- Click the extension icon and connect the tab to your agent session.
- In the in-page pill, choose either:
- Annotate (
Alt+Shift+A) — click the element you want to talk about, then comment on it. - Capture (
Alt+Shift+C) — comment on the page as a whole, no element picking.
- Annotate (
- Submit. Repeat as many times as you like.
- Tell your agent to check the annotations, or run
/fbif you added the command above. Either way it callsget_annotations, which hands over everything you've queued and empties the queue.
There's no "send" step to remember. Every annotation POSTs the moment you submit it, and the agent collects the whole queue in one call.
The rest of the pill: click Connected: name to switch the tab to a different agent session, the wrench for settings, and the × to disconnect. It also disconnects on its own when you close the tab, navigate to a different origin, or stop the agent.
The pill is draggable — drop it wherever it's out of your way and it stays there across navigation.
Screenshots
Every annotation carries one unless you clear Include screenshot in the composer. Annotate crops to the element you picked, with a little padding for context. Capture stitches the whole scrollable page together from viewport captures. Either way the image is written to a temp directory and handed to the agent as a file path, so it only loads the image when it needs to.
The pill and the settings popup hide themselves during capture so they stay out of the shot — but only when they'd actually be in frame, and never when the thing you're annotating is one of them.
Two things worth knowing about full-page capture:
captureVisibleTabis rate-limited to 2 calls/sec, so a tall page takes a beat, and stitching stops at 12 viewport-heights. The agent is told when a shot was truncated.- Fixed and sticky elements are hidden after the first band. Otherwise a sticky header gets stamped into every strip. A sticky sidebar will leave a gap where it was.
Images are swept once they're more than six hours old, rather than when the queue drains. The agent reads those paths after get_annotations returns, so deleting on drain would hand it dead paths. Anything still queued is never swept, however old it gets.
Settings
The wrench in the pill opens a popup beside it, holding three things:
- Keep queue across restarts — see below.
- Annotation outline — the colour of the box that follows your cursor while picking an element. Left alone it follows the light/dark theme; Default puts it back after you've picked a colour.
- Keyboard shortcuts — the current bindings for annotate and capture. Chrome owns extension shortcuts, so Change shortcuts hands you to
chrome://extensions/shortcuts, the only place they can be rebound. If another extension already claimedAlt+Shift+AorAlt+Shift+C, yours will show as Not set until you assign one there.
The outline colour is stored in the browser. The queue setting belongs to the agent's server, so it follows the session rather than the browser.
Keeping the queue across restarts
The queue lives in memory, so restarting your agent discards anything you haven't asked it to read yet. Tick Keep queue across restarts in settings to write it to the runtime directory instead. The setting is stored there too, since a toggle that reset on restart would forget at exactly the wrong moment.
It's off by default. It applies to every project rather than per-repo, being a preference about your machine rather than about any one codebase, and the saved copy is discarded as soon as the agent drains the queue.
Set HYZER_ANNOTATE_DIR to move the runtime directory somewhere else.
Development
npm run check # typecheck, tests, extension buildnpm test runs four suites:
geometry.mjs— capture and placement arithmetic: crop clamping, zoom scaling, band planning, stitch height, and the composer's flip-above-when-there's-no-room-below.token-scope.mjs— runs the:rootto:hostrewrite over the real installed@hyzer-labs/uistylesheet, so an upgrade that adds a:rootselector fails here instead of silently shipping unstyled overlays.smoke.mjs— boots the server and exercises the HTTP contract the extension depends on: discovery, claiming, screenshot decoding, queueing, input rejection. Then drains over MCP to check every annotation shape formats correctly.persistence.mjs— restarts the server for real, which is the only way to prove the queue survives one.
The server tests run against HYZER_ANNOTATE_DIR. Settings and the saved queue are shared by every server for a user, so without an override the suite would rewrite your real state.
Packaging
npm run icons # regenerate icons/*.png from icon.svg (macOS only)
npm run package:extension # build and zip into release/ for the Chrome Web StoreThe extension version comes from package.json and is stamped into the manifest at build time. extension-src/manifest.json has no version field at all, so there's no second copy to forget on a release.
Icons are committed under icons/ rather than generated during the build. Rasterizing needs sips, which is macOS-only, so generating them at build time would quietly ship an iconless extension on Linux and in CI. npm run build:extension fails outright if they're missing.
License
GPL-3.0-only. This is a modified derivative of opencode-chrome-annotation by Benjamin Shafii. See NOTICE for what changed.
