earmark
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., "@earmarkList pending annotations from the browser."
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.
earmark
Click an element in your running app, say what should change, and your coding agent gets the CSS selector, the source file and line, the component path, the computed styles and the box geometry — instead of "the button on the right looks wrong".
Works in any framework. No build step required for the overlay.
┌─ browser ──────────────┐ ┌─ broker ────────┐ ┌─ agent ─────────┐
│ click → annotate │ POST │ store + SSE │ MCP │ list / watch │
│ pins, panel, markdown │───────▶│ long-poll │◀──────▶│ ask / resolve │
│ │◀───────│ .earmark/*.json │ │ dismiss │
└────────────────────────┘ SSE └─────────────────┘ └─────────────────┘Try it in 30 seconds
npm install && npm run exampleOpen http://127.0.0.1:5173/examples/vanilla/, click the arrow in the toolbar
(bottom right) or press alt+a, then click anything on the page.
The landing page and full guide is served alongside it at http://127.0.0.1:5173/site/ — source in site/index.html, a single self-contained file with no dependencies.
For live agent sync, run the broker in a second terminal:
npm run serverRelated MCP server: vibe-annotations
Install
npm install -D earmarkimport { createEarmark } from 'earmark';
if (import.meta.env.DEV) {
createEarmark();
}No bundler:
<script type="module" src="/node_modules/earmark/src/index.js" data-earmark-auto></script>Options
createEarmark({
endpoint: 'http://127.0.0.1:7331', // or false for copy-paste only
hotkey: 'alt+a',
theme: 'auto', // 'auto' | 'light' | 'dark'
persist: true, // keep annotations across reloads
onAnnotate: (annotation) => {},
});The endpoint defaults to the local broker and degrades silently when nothing is listening — the overlay still works, the sync dot just goes grey.
Using it
Tool | What it does |
➤ | Click an element. Shift-click to add more, then click to finish. |
T | Select text — the exact string is the most greppable thing you can hand an agent. |
⛶ | Drag a region. Reports every element inside, or flags an empty area. |
❊ | Freeze everything moving — CSS animations, |
☰ | Panel: review, delete, answer the agent, copy markdown. |
⌘↵ saves an annotation, esc cancels, alt+a toggles picking. Each
annotation can be marked high, normal or low priority; high sorts
first for the agent.
Copy-paste mode
Click Copy markdown in the panel and paste into your agent:
## UI feedback — 1 annotation
- **Page:** http://localhost:5173/dashboard
- **Viewport:** 1440×900 @2x, dark mode
- **Framework:** react
### 1. Export button padding is too tight — needs 10px 16px
- **Element:** `<button>` <ExportButton>
- **Selector:** `[data-testid="export-btn"]`
- **Source:** `src/components/Card.tsx:42:7`
- **Component path:** App › Dashboard › Card › ExportButton
- **Text:** "Export"
- **Box:** 66×37 at (194, 376)
- **Computed:** padding: 7px 13px; border-radius: 8px; font-size: 13px
- **Ancestors:** div.row ← section.card ← mainAgent sync mode (MCP)
claude mcp add earmark -- npx -y earmark-mcpOr, to write it into the project's .mcp.json:
npx earmark-mcp initThat one process runs the MCP server and the broker the browser talks to. When something is not working, ask it why:
npx earmark-mcp doctor✓ Node version: v24.12.0
✓ sqlite backend: available
✓ MCP registration: earmark is registered in .mcp.json
✓ Broker: responding on http://127.0.0.1:7331 — 1 annotations, 2 sessions
✓ Browser overlay: http://localhost:5173/ (1 annotations)Each failing check prints the command that fixes it, and doctor exits non-zero
so CI can use it.
Tools
Tool | Purpose |
| Outstanding work, as markdown (or |
| Blocks until the human annotates something |
| One annotation with its full reply thread |
| Which browser tabs are open, and which routes were annotated |
| One tab with every annotation it produced |
| "I've read it, I'm on it" — the pin turns blue |
| Ask a clarifying question — the pin turns amber |
| Mark done with a summary — the pin turns green |
| Decline with a reason the human sees |
| Delete everything |
| Is the overlay connected? What endpoint should it use? |
The fix loop this enables:
watch → acknowledge → read the source path → edit the file → resolve → watchacknowledge matters on anything slow: without it, an agent halfway through a
refactor looks exactly like an agent that ignored you. Blue pin means picked up,
green means actually done.
When the feedback is ambiguous, ask instead of guessing. The question appears
on the pin; the human's answer wakes the next watch.
Statuses
open → acknowledged → resolved, with needs-input when the agent is
waiting on a human and dismissed when it declines. Pins are colour-coded:
orange, blue, green, amber, grey.
Sessions
A session is one browser tab, not one page load — the id lives in
sessionStorage, so it survives reloads. Annotations carry their own
page.url, so a session that wandered across three routes gives an agent one
group with three differently-routed items.
SPA navigation is tracked too: pushState, replaceState, popstate and
hashchange all update the session's route list. A tab counts as connected for
exactly as long as its SSE stream is open.
curl http://127.0.0.1:7331/sessionsSource file paths
Selectors tell an agent what to grep for. Source paths tell it exactly where to look, which is the difference between one edit and three greps.
React 19 removed the runtime _debugSource fiber field, so this is done at build
time:
// vite.config.js
import earmark from 'vite-plugin-earmark';
export default {
plugins: [react(), earmark()],
};Every intrinsic JSX element gets data-earmark-src="src/Card.tsx:42:7" during
vite dev. The plugin also injects the overlay, so createEarmark() in your app
code becomes optional.
earmark({
inject: false, // do not auto-mount the overlay
endpoint: '…', // passed through to createEarmark
applyInBuild: true, // also stamp production builds (off by default)
})Without the plugin everything still works — you get selectors, component names
and text, just not file:line. You can also add data-earmark-src by hand.
Plain HTML and CSS — no build step
A static site has no build to stamp, so earmark resolves the source at annotation time instead:
HTML — the document is re-fetched and parsed with position tracking, then the element's child-index path is walked in the source. Every step is checked against the live tag name, so a framework-rendered page (where the served HTML is just a shell) reports nothing rather than inventing a line.
CSS — every rule that matches the element, mapped back to the file and line that declares it. This one works everywhere, framework or not.
- **Source:** `index.html:101:11` _(resolved from the served HTML)_
- **CSS rules that style it:**
- `button` → `index.html (inline <style>):49`
- padding: 7px 13px; border-radius: 8px; border: 1px solid var(--line);
- `button.primary` → `index.html (inline <style>):59`
- background: var(--accent); color: rgb(255, 255, 255);The agent now knows the padding it has to change lives at line 49 in the generic
button rule, not in .primary. Inline <style> blocks are offset into their
host document; external stylesheets report their own path; cross-origin
stylesheets are skipped because their contents are unreadable.
Standalone broker
npx earmark-server --port 7331
curl http://127.0.0.1:7331/markdownRoute | |
| liveness + counts |
| list |
| create (batch) |
| long-poll |
| update status |
| append to the thread |
| remove · clear |
| register a tab / record a route change |
| tabs, with counts and annotations |
| SSE stream; also the tab's liveness signal |
| the agent-facing document |
Flags: --host --store --file --no-persist --webhook --token --quiet.
Storage
--store json (default) writes a readable .earmark/annotations.json on a
250 ms debounce. --store sqlite writes each change immediately to
.earmark/annotations.db through node:sqlite, so a crash loses at most the
statement in flight — no dependency, Node 22.5+, and it falls back to JSON if
unavailable. --store memory keeps nothing.
Webhooks
npx earmark-server --webhook https://hooks.example/earmarkAlso EARMARK_WEBHOOK_URL and EARMARK_WEBHOOKS (comma-separated). Every
annotation event is POSTed with an x-earmark-event header. Delivery is
fire-and-forget with a 5 s timeout and one retry, so a dead endpoint cannot
stall the annotation loop.
Security
This is a development tool.
The broker binds
127.0.0.1only. Do not bind it to0.0.0.0.CORS is open by design — your dev server is on an arbitrary origin.
Any page open in your browser can reach a loopback port. Pass
--token SECRETif that matters on your machine.Webhooks send annotation content off your machine — page URLs, element text, and whatever you typed. Only configure endpoints you control.
Source resolution re-fetches your own page and stylesheets from the same origin. Nothing is sent anywhere.
Do not run it on a shared or public host.
Tests
npm testSeven suites, 88 tests: store and HTTP behaviour, the MCP surface driven by a
real stdio client, the overlay's sync client, both persistence backends, webhook
delivery, the init/doctor CLI, and the source resolvers.
Not supported
Desktop browsers only. No iframes, no canvas/WebGL internals, no screenshots. See plan.md for the full open list and the reasoning behind every design decision.
License
MIT. Clean-room implementation — not derived from any other tool's source.
This server cannot be installed
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
- 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.4MIT
- Flicense-qualityAmaintenanceMCP server that exposes web page annotations to AI coding agents, enabling automated implementation of visual feedback and design tweaks.1132
- Alicense-qualityCmaintenanceA MCP server that enables AI coding agents to consume structured UI feedback via click-to-annotate, supporting issue types and severity levels.11MIT
- AlicenseAqualityBmaintenanceMCP server that opens a browser and adds a comment/pencil overlay to every page, letting you annotate live sites and send the marks to your coding agent.8MIT
Related MCP Connectors
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,
A paid remote MCP for AI agent browser DevTools 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/nahar-strativ/Agentic'
If you have feedback or need assistance with the MCP directory API, please join our Discord server