What does annotate do?
The user clicks an element on a page, writes a note, and Claude gets the note with a selector, nearby text, bounding box and URL, so it can find the element in the code and fix it.
Built on Agentation: the agentation toolbar (React 18+, desktop only) and the agentation-mcp notes server (HTTP API on SQLite, plus optional MCP tools).
Pick the mode first
Ask the user if it isn't obvious:
- Local: just the user and Claude, on this machine. The notes server runs on
localhost:4747. Nothing gets deployed. This is the default. - Shared: colleagues comment on a hosted site, and everyone sees everyone's notes. It needs a notes server on a public HTTPS host. See references/shared.md. That page describes the architecture and requirements; the user chooses the hosting.
Where the toolbar goes
Choose by what the page is. The same options work in both modes; only endpoint and session change.
- Inside the user's React app.
npm i -D agentation, then render it next to the app, gated so it never reaches production:Add{import.meta.env.DEV && <Agentation endpoint="http://localhost:4747" />}useHashLocationfor hash routers andappNameto label the feedback. - Any HTML page they control, React or not. Add one script tag. The loader mounts the toolbar in its own container and its own React root, so it does not touch the host page:
Query params:<script type="module" src="https://cdn.jsdelivr.net/gh/Wahajolla/[email protected]/embed/annotator.js?endpoint=http://localhost:4747"></script>endpoint,session,app,hash=1. Alternatively, setwindow.ANNOTATOR_CONFIG = {endpoint, session, app, hash}before the script loads. - Any site at all, without touching its code. Use a bookmarklet. Give the user this line to save as a bookmark; clicking it toggles the toolbar on and off:
Limits to tell the user about:javascript:(()=>{const s=document.createElement('script');s.type='module';s.src='https://cdn.jsdelivr.net/gh/Wahajolla/[email protected]/embed/annotator.js?endpoint=http://localhost:4747&t='+Date.now();document.head.appendChild(s)})()- Sites with a strict Content-Security-Policy (GitHub, many banks and big apps) block the injected scripts, so the toolbar won't appear there.
- On a public
https://site that talks tolocalhost, the browser may ask for local network access: allow it. - The notes server must allow that site's origin (see CORS below).
- Notes are tied to the site's URL, and the toolbar's local drafts live in that site's localStorage.
- Chrome extension. This is the most robust way to annotate any site. It toggles with one click or Alt+Shift+A, works on strict-CSP sites (GitHub, Notion, etc.), and needs no CORS setup: requests go through the extension. Install: download
annotator-extension.zipfrom https://github.com/Wahajolla/annotator-skill/releases/latest, thenchrome://extensions→ Developer mode → Load unpacked. The notes server is set in the extension options. It defaults tohttp://localhost:4747; for shared mode, use the hosted URL plus the session id. Live sync is polling every 5 seconds. If the notes server is unreachable, the extension shows a "Notes server is offline" card with the start command, and#annotator-ext-rootgetsdata-annotator-server="offline"("online"once it answers): if you're driving the browser, check it before telling the user their notes were saved. In the options, "Sign in with GitHub" signs every note with the user's login, so colleagues' notes show who left them.
Without endpoint the toolbar runs copy-only. Notes stay in the browser, and the user presses Copy and pastes the markdown into the chat. This needs no server, so use it when the user only wants it once.
Local mode
-
Run the notes server. Pick one:
- MCP (best for repeated use). With the user's OK:
claude mcp add agentation -- npx -y agentation-mcp server. Claude Code then starts it with each session, and theagentation_*tools appear after a restart:get_all_pending,resolve,reply, andwatch_annotationsfor hands-free loops. - Plain HTTP (no restart). Run
npx -y agentation-mcp serveras a background command, and read notes over HTTP (see below).
Check it with
curl http://localhost:4747/health, which should return{"status":"ok"}. - MCP (best for repeated use). With the user's OK:
-
CORS. By default only dev origins are allowed (
localhost,*.localhost,127.0.0.1, private IPs,.local,.test,.internal). For a bookmarklet on a public site, start the server withAGENTATION_CORS_ORIGINS=https://that-site.com(exact origins, comma-separated).*also works, but it lets any open tab write notes, so prefer exact origins. -
Put the toolbar on the page using one of the options above, with
endpoint=http://localhost:4747. -
Verify. Add one test note through the toolbar, check that
curl localhost:4747/pendingshows it, then delete it.
Notes persist in ~/.agentation/store.db.
Reading and acting on notes (both modes)
- MCP:
agentation_get_all_pending, then fix the code, thenagentation_resolvewith a short summary. - HTTP:
GET /pending(all open notes),GET /sessions,GET /sessions/:id. After fixing a note, sendPATCH /annotations/:idwith{"status":"resolved"}. - Who wrote it: notes from the Chrome extension with GitHub sign-in carry
authorId: "github:<login>", and by default the comment starts with@<login>:. A note with noauthorIdis anonymous (any other way of loading the toolbar). Mention the author when you report back. - Useful fields are
comment,url,element,elementPath,fullPath,nearbyText,selectedText,boundingBoxandreactComponents(dev builds only). - Let the user decide before deleting anything.
Showing notes for screenshots
Agentation shows a note's text only while you hover its marker. To show all of them at once, use the notes layer: an outline, a number and a full-text card for each note. When it's done drawing, <html> gets data-annotator-notes="<count>".
- The user, in Chrome: Alt+Shift+N in the extension (or right-click the icon → Show all notes on this page).
- You, on a page you're driving (Playwright, Chrome DevTools MCP, etc.): set your own notes and load the layer, then wait for the attribute and screenshot:
Each note takeswindow.ANNOTATOR_NOTES = [ { selector: "h1", comment: "Too long. Make it “Simple pricing”", author: "claude" }, { selector: "#cta", comment: "Use the brand blue" }, ] const s = document.createElement("script"); s.type = "module" s.src = "https://cdn.jsdelivr.net/gh/Wahajolla/[email protected]/embed/notes.js?t=" + Date.now() document.head.appendChild(s)selector(orboundingBox: {x, y, width, height}in page coordinates),comment, and an optionalauthor. Notes whose selector matches nothing are skipped with a console warning, so check that the count matches. Instead ofANNOTATOR_NOTES, pass?endpoint=http://localhost:4747to show the server's notes for that page; add&all=1to include resolved ones. Loading it again with a newthides the layer. - Strict-CSP sites (GitHub and others) block that script. In Playwright, create the context with
bypassCSP: true; otherwise use the extension. - To make your notes real (the user sees them in their toolbar too, and they can resolve them):
POST /sessionswith{"url": "<page url>"}and thenPOST /sessions/:id/annotationswithcomment,element,elementPath(a CSS selector),url,boundingBoxandx/y(page coordinates). Then show them with?endpoint=…. Ask the user first: these become shared notes.
Pitfalls
- The toolbar is desktop only.
- The notes server has no auth, only CORS. Anyone whose browser origin is allowed can write and delete notes.
useHashLocationkeeps notes per#route. Turning it on later doesn't move existing notes.- Agentation is licensed PolyForm Shield, which is fine for internal feedback use.