Skip to main content
Glama

Annotation Overlay MCP

DOM-aware visual annotation overlay for web feedback. Draw arrows, boxes, text, freehand strokes, click elements, and annotate text selections — all in one toolbar. Submit structured annotations to Claude Code via MCP.

6 interaction modes. Zero JS dependencies. Chrome Extension + MCP Server.

[Browser Page]  ←→  [Chrome Extension]  ←→  [MCP Server]  ←→  [Claude Code]
   overlay.js         bridge.js              HTTP + stdio       read/clear tools

Quick Start

1. Install & Start MCP Server

git clone https://github.com/cc10143/annotation-overlay-mcp.git
cd annotation-overlay-mcp
npm install
npm start
# → HTTP server on port 3847 + MCP stdio transport connected

2. Load Chrome Extension

  1. Open chrome://extensions

  2. Enable Developer mode

  3. Click Load unpacked

  4. Select the extension/ directory

The overlay auto-injects on every page, but the toolbar stays hidden during normal browsing (it's a tool for agents, not a resident UI). To annotate, either press Ctrl+Shift+A, or — in the feedback flow — have the agent call set_annotation_mode(true), which shows the toolbar on the active tab.

3. Configure Claude Code MCP

// ~/.claude/settings.json
{
  "mcpServers": {
    "annotation-overlay-mcp": {
      "command": "node",
      "args": ["D:/KaiFa/annotation-overlay/server/index.js"]
    }
  }
}

Or use the CLI:

claude mcp add annotation-overlay-mcp -- node D:/KaiFa/annotation-overlay/server/index.js

Related MCP server: claude-annotate

Usage

Annotation Workflow

  1. Agent launches the annotation browser (primary) — Claude Code runs the annotation-browser-launch skill: Playwright opens chromium with the extension + a persistent profile, navigates to the target page, and shows the toolbar. (Fallback: the agent calls set_annotation_mode(true) on the user's own Chrome.)

  2. Annotate — use any of the 6 tools to mark issues

  3. Submit — click Submit to send structured JSON to the MCP server. The extension automatically captures a viewport screenshot with annotations overlaid and uploads it; the server saves it to ~/.annotation-overlay/screenshots/. Submit also hides the toolbar and deactivates the overlay.

  4. Agent reads — Claude Code calls read_annotations → gets annotations + screenshotPath → feeds the screenshot to a vision-capable tool → processes feedback

  5. Fix & verify — after the fix, the agent refreshes the annotation browser (via CDP reconnect), then compares a fresh screenshot against the annotated screenshotPath to confirm the fix actually landed

  6. Next round — the agent re-activates the overlay and repeats

Tools

Tool

Label

How

DOM Link

Arrow

Click & drag → arrow

Element under arrowhead

Box

Click & drag → rectangle

Region container + element at box center

Text

T

Click → type text label

Element under click point

Freehand

Click & drag → freeform drawing

Region container + bbox center element

Select

+

Hover highlights blue → click to pin numbered badge

Clicked element

TextSel

[ ]

Select page text → annotate instantly

Containing element

Drawing annotates immediately — no comment prompt interrupts your flow. To add a comment to an existing annotation, switch to the Select tool and double-click the drawing (or the numbered badge). Only the Text tool still prompts, because its text is the annotation itself.

Keyboard Shortcuts

Key

Action

Ctrl+Shift+A

Toggle overlay

1–6

Switch tool

Ctrl+Z

Undo last annotation

Ctrl+Shift+Z / Ctrl+Y

Redo

Double-click

Edit annotation comment (select tool)

?

Show shortcut panel

Escape

Cancel drawing / close overlay

Enter

Confirm comment

Shift+Enter

Newline in comment

Colors

5 preset colors: red #e94560, blue #4080f0, green #2ecc71, yellow #f1c40f, purple #9b59b6.

Screenshot

On Submit, the extension captures the current viewport with annotations overlaid — the canvas strokes and numbered badges are DOM, so they appear in the shot naturally; only the toolbar is hidden during capture. The screenshot is uploaded to the MCP server and saved to ~/.annotation-overlay/screenshots/anno-<timestamp>.png.

read_annotations returns the absolute screenshotPath in its response. Pass it to a vision-capable tool (e.g. a vision MCP) to see exactly what the user is pointing at — this is how a non-multimodal agent gets visual context for the annotations.

For before/after verification, the agent calls capture_page after the fix reloads the page. The extension captures the current viewport (clean — the overlay is inactive until toggled) and the server exposes it as afterScreenshotPath in read_annotations. Feeding both screenshotPath (annotated "before") and afterScreenshotPath (clean "after") to a vision-capable tool lets the agent confirm a fix landed instead of working blind.

Extension only. Without the extension bridge (standalone script injection), submit proceeds without a screenshot.

MCP Tools

read_annotations

Read all pending annotations. The response also includes screenshotPath — the absolute path to the last annotated viewport screenshot (saved on Submit) — and afterScreenshotPath — the absolute path to the last capture_page screenshot (clean post-fix viewport), both for vision-capable consumption.

Each annotation includes:

{
  "id": "uuid",
  "type": "circle",                       // arrow | circle | text | freehand | select | textsel
  "comment": "user feedback text",        // empty until double-clicked in select mode
  "selector": "div.card:nth-child(1) > button.btn-primary",
  "fallbackSelectors": [
    { "type": "id", "value": "#submit-btn" },
    { "type": "cssPath", "value": "div.card:nth-child(1) > ..." },
    { "type": "contentHash", "value": "Buy Now-a3f8b2c1" }
  ],
  "tagName": "BUTTON",
  "classes": ["btn-primary"],
  "elementText": "Buy Now",
  "contentHash": "Buy Now-a3f8b2c1",
  "region": {                             // box/freehand only: the container element
    "selector": "#card",                  //   covering the drawn area (deepest ancestor
    "fallbackSelectors": [                //   whose rect covers ≥60% of the drawing)
      { "type": "id", "value": "#card" },
      { "type": "cssPath", "value": "div.card" },
      { "type": "contentHash", "value": "Card A-..." }
    ],
    "tagName": "div",
    "classes": ["card"]
  },
  "position": { "x": 100, "y": 200, "w": 200, "h": 100 },
  "color": "#e94560"
}

screenshotPath — added to the response when a screenshot was captured on submit, e.g. "C:\Users\you\.annotation-overlay\screenshots\anno-2026-08-12T06-27-48.png". Pass it to a vision-capable tool to see the page with annotations overlaid.

afterScreenshotPath — added to the response after an agent-requested capture_page, e.g. "C:\Users\you\.annotation-overlay\screenshots\anno-2026-08-12T06-30-12.png". This is the clean post-fix viewport; compare it against screenshotPath to verify a fix landed. afterScreenshotTabUrl is the URL of the tab that was captured — confirm it matches the page you expected (the capture is of the active tab). All three are cleared by clear_annotations.

httpOwnedExternallytrue when another annotation-overlay instance holds the HTTP port (e.g. a stale npm start, or an orphaned MCP child from an abrupt Claude Code shutdown), with externalHttpVersion naming that instance. read_annotations/clear_annotations still work against the shared store, but capture_page/set_annotation_mode may fail until the stale process is killed (netstat -ano | grep :3847taskkill //PID <pid> //F). The server no longer crashes on a port conflict — it keeps the MCP tools alive and reports this flag instead.

position shape varies by type (viewport coordinates):

Type

position

arrow

{ "start": {"x","y"}, "end": {"x","y"} }

circle / select / textsel

{ "x", "y", "w", "h" }

text

{ "x", "y" }

freehand

{ "points": [{"x","y"}, ...] }

region (box/freehand only) is the container element that best represents the drawn area — e.g. the card <div> the user circled — with the same fallback chain as selector. Use it to locate the region when the box center happens to sit on a child element.

capture_page

Capture the current page viewport and save it as the "after" state for before/after verification. The server asks the extension (via its existing ~10s badge poll) to capture the active tab of the focused window; the shot is clean — the extension hides any annotation overlay chrome before capturing. Returns the absolute path to the saved PNG plus the URL of the captured tab, e.g.:

{ "ok": true, "path": "C:\\Users\\you\\.annotation-overlay\\screenshots\\anno-2026-08-12T06-30-12.png", "tabUrl": "https://example.com/" }

Verify tabUrl matches the page you expectcapture_page captures the active tab, so if the user switched tabs it would show the wrong page (the server can't tell). May take up to ~15s (extension poll + capture). Requires the extension to be loaded. Pair the result with screenshotPath from read_annotations to confirm a fix landed.

set_annotation_mode

Show or hide the annotation toolbar on the active tab, so the user can give visual feedback. The overlay is injected on every page but the toolbar is hidden by default — this is how the agent turns it on before asking the user to annotate, and off after processing:

{ "ok": true, "enabled": true, "tabUrl": "https://example.com/" }

The extension relays the change to the page's overlay via its ~10s badge poll. May take up to ~15s. Requires the extension to be loaded and the target page to be the active tab. The user can also toggle manually with Ctrl+Shift+A.

clear_annotations

Clear all stored annotations. Call after processing feedback.

Claude Code Skills

The repo ships two skills (in skills/) that encode the annotation feedback loop. Install them to ~/.claude/skills/ with:

npm run install-skills

Skill

Purpose

annotation-browser-launch

Primary flow. The agent launches its own annotation browser (Playwright chromium + extension + persistent profile + fixed CDP port), navigates the target page, and shows the toolbar. The agent natively controls navigation/refresh/screenshot afterward.

annotation-feedback-wait

Wait for the user's submission (background watcher polling the annotation count — the MCP server can't push), then read_annotations, process, and clear_annotations.

Edit the skills in skills/ (the repo is the source of truth), then re-run npm run install-skills to sync.

Selector Fallback Chain

When the agent regenerates the page, CSS selectors may break. Each annotation carries a fallback chain:

  1. id#element-id (most stable)

  2. cssPathdiv.card:nth-child(1) > button.btn-primary

  3. contentHashBuy Now-a3f8b2c1 (first 40 chars of text + djb2 hash)

The agent should try each fallback in order when resolving elements after page changes.

Standalone Use (Without Extension)

The overlay can be injected into any page via script tag or browser console:

<script src="annotation-overlay.js"></script>

Or via Tandem evaluate / Playwright:

// Tandem
tandem_devtools_evaluate({ function: "..." }) // paste annotation-overlay.js contents

// Playwright
await page.evaluate(fs.readFileSync('annotation-overlay.js', 'utf-8'));

Public API:

__annotationOverlay.activate()    // show toolbar
__annotationOverlay.deactivate()  // hide overlay
__annotationOverlay.serialize()   // → JSON string
__annotationOverlay.clear()       // remove all annotations
__annotationOverlay.submit()      // send to MCP server via direct fetch

Architecture

┌──────────────────────────────────────────────────────┐
│  annotation-overlay.js  (src/, ~1600 lines)          │
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐   │
│  │ Toolbar  │  │  Canvas  │  │  DOM Bridge      │   │
│  │  6 tools │  │  2D ctx  │  │  elementFromPoint │   │
│  │  5 colors│  │  DPR     │  │  CSS selector gen │   │
│  │  Submit  │  │  undo    │  │  contentHash      │   │
│  └────┬─────┘  └────┬─────┘  └────────┬─────────┘   │
│       │             │                │               │
│       └─────────────┴───────┬────────┘               │
│                             │                         │
│                    ┌────────▼────────┐                │
│                    │  Annotation Model│               │
│                    │  + Serializer   │               │
│                    │  + Fallback     │               │
│                    └────────┬────────┘               │
└─────────────────────────────┼────────────────────────┘
                              │ postMessage
┌─────────────────────────────┼────────────────────────┐
│  Chrome Extension           ▼                         │
│  ┌──────────────┐  ┌──────────────┐                  │
│  │  bridge.js   │  │  service-    │                  │
│  │  (ISOLATED)  │  │  worker.js   │                  │
│  │  inject +    │  │  badge +     │                  │
│  │  relay       │  │  relay       │                  │
│  └──────┬───────┘  └──────┬───────┘                  │
└─────────┼──────────────────┼──────────────────────────┘
          │                  │ HTTP (port 3847)
┌─────────▼──────────────────▼──────────────────────────┐
│  MCP Server (Node.js, single process)                  │
│  ┌──────────────┐  ┌──────────────┐                   │
│  │  Express API │  │  MCP stdio   │                   │
│  │  POST/GET/   │  │  read_       │                   │
│  │  DELETE ann  │  │  clear tools │                   │
│  └──────┬───────┘  └──────┬───────┘                   │
│         └────────┬─────────┘                           │
│         ┌────────▼────────┐                            │
│         │  File Store      │                            │
│         └─────────────────┘                            │
└────────────────────────────────────────────────────────┘

On Submit the extension captures the annotated viewport; the server saves it to ~/.annotation-overlay/screenshots/ and read_annotations surfaces the path via screenshotPath.

Comparison

Annotation Overlay MCP

Vibe Annotations

Dongke-X/redline

Drawing tools

4 (arrow/box/text/freehand)

None

Full HTML editor

Click-to-select

Yes (+ badge)

Yes

Full edit

Text selection annotation

Yes

No

No

MCP automation

Yes (stdio)

Yes (SSE/HTTP)

No (file-based)

Selector fallback

id→cssPath→contentHash

source maps

id→cssPath→contentHash

License

MIT

PolyForm Shield

Apache 2.0

Dependencies

3 (Express + cors + MCP SDK)

Many (WXT, etc.)

Many (React, etc.)

Configuration

Env Var

Default

Description

ANNO_PORT

3847

HTTP server port

License

MIT — Copyright (c) 2026 gaogao

Available Tools

2 tools
clear_annotationsA

Clear all stored annotations. Call after processing feedback to prepare for the next annotation round.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It discloses the destructive nature ('Clear all') and adds workflow context, but does not mention irreversibility, scope (e.g., global vs workspace), or permissions. For a zero-parameter tool this is adequate but not thorough.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with the action stated first. No filler. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (0 params, no output schema), the description sufficiently covers its purpose and usage timing. It could mention that clearing is irreversible or affects all annotations, but those are not necessary for basic invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema is trivially 100% covered. The description adds no parameter-specific details, but none are needed; baseline of 4 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Clear') and resource ('all stored annotations'), making the action unmistakable. It also distinguishes itself from the sibling tool 'read_annotations' by explicitly focusing on clearing rather than reading.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly states when to use the tool ('Call after processing feedback to prepare for the next annotation round'). While it doesn't discuss alternatives or when not to use it, the context is clear and the single sibling tool is distinct enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_annotationsA

Read all pending visual annotation feedback. Each annotation includes a CSS selector, fallback selector chain (id → cssPath → contentHash), comment, annotation type (arrow/box/text/freehand/select/textsel), color, viewport position, and element metadata. Use this to consume user feedback after they press Submit in the overlay.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must disclose behavioral traits on its own. It describes the data contents in detail, but uses the ambiguous phrase 'consume user feedback' without clarifying whether reading is destructive or marks annotations as no longer pending. Since 'clear_annotations' exists, reading is likely non-destructive, but the description fails to confirm this, creating uncertainty about side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no wasted words. The first sentence is dense, listing all the fields in a compact manner, while the second gives the usage timing. It could be structured with a list for readability, but it remains appropriately concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description thoroughly enumerates the return structure (CSS selector, fallback chain, comment, type, color, viewport position, metadata), which provides strong coverage. However, it leaves the lifecycle of 'pending' annotations ambiguous and doesn't clarify whether reading has side effects. Given the low tool complexity, this omission prevents a higher score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, making schema coverage 100%. The description adds context about what the read operation returns, which is the relevant semantic contribution when no parameters exist. Baseline for 0 params is 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Read all pending visual annotation feedback' – a specific verb+resource pair. It then enumerates the annotation fields (selector, fallback chain, comment, type, color, viewport position, element metadata), clearly distinguishing it from the sibling 'clear_annotations' which focuses on removal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The final sentence provides an explicit usage trigger: 'Use this to consume user feedback after they press Submit in the overlay.' It does not explicitly state when not to use it, but with only one sibling (clear_annotations) the contrast is implied, and the timing criterion is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv2.0.0
    • First observedclear_annotations
    • First observedread_annotations

TDQS

A4/5.0

Scored across 2 tools

Disambiguation5/5

read_annotations and clear_annotations have completely distinct purposes: one retrieves feedback, the other removes it. There is no overlap or ambiguity between the two operations.

Naming Consistency5/5

Both tools follow the same verb_noun pattern (read_annotations, clear_annotations), with clear and predictable naming. The convention is consistent across the entire server.

Tool Count3/5

With only 2 tools, the server feels minimal. While the read/clear pair covers the core workflow, the count is borderline for a standalone MCP server.

Completeness4/5

The server covers the essential operations for consuming and resetting annotation feedback. There is no per-annotation update or delete, but the batch read/clear model fits the described usage pattern well.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers