@useloupe/mcp-server
v0.9.2
Published
MCP server for Loupe. Exposes browser-captured annotations to Claude Code, Cursor, and other MCP clients.
Maintainers
Readme
@useloupe/mcp-server
The local MCP server for Loupe. Reads annotation payloads from ~/.loupe/annotations/ and exposes them to any MCP client (Claude Code, Cursor, etc.). Accepts new annotations + screenshots over HTTP so the extension's send path doesn't need a separate persistence layer.
Install + run
npx -y @useloupe/mcp-server --httpServer boots on http://127.0.0.1:19532. Use --port to override.
For stdio mode (the default Claude Code transport):
npx -y @useloupe/mcp-serverHook up Claude Code
claude mcp add loupe -- npx -y @useloupe/mcp-serverThen in any Claude Code session:
Show me the latest Loupe annotation.
Claude calls get_latest_annotation, receives the full payload + annotated full-page screenshot + per-pin crops, and gets to work.
CLI flags
| Flag | Default | Notes |
|---|---|---|
| --http | off (stdio) | Run as an HTTP server (required for the extension bridge + /stats + MCP-over-HTTP) |
| --port <n> | 19532 | HTTP port |
| --host <h> | 127.0.0.1 | Bind address. Don't expose this publicly — there's no auth |
| --annotations-dir <path> | ~/.loupe/annotations | Where payloads + screenshots + audio live |
Optional: server-side transcription
Drop API keys in ~/.loupe/.env:
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
# optional: force a specific provider
LOUPE_TRANSCRIPTION_PROVIDER=whisperchmod 600 ~/.loupe/.env. Restart the server; you'll see transcription enabled via whisper (or anthropic) on the startup banner. Any annotation that arrives with audio is auto-transcribed and the result is written back into the annotation's comment / transcript fields.
Whisper is preferred when both keys are set because it's purpose-built for ASR. Override with LOUPE_TRANSCRIPTION_PROVIDER.
Tools
| Tool | Returns |
|---|---|
| get_latest_annotation | Most recent payload + annotated full-page + per-pin crops |
| get_annotation { id } | Specific payload by id |
| list_annotations { limit?, url_filter?, since? } | Summaries, newest first |
| get_screenshot { annotation_id } | Viewport screenshot only |
| get_full_page_screenshot { annotation_id } | Annotated stitched full-page |
| get_annotation_crop { annotation_id, index } | Focused crop for a single pin (1-based index) |
| get_annotation_audio { annotation_id, index } | Raw audio for a voice-recorded pin |
| get_loupe_stats | Usage stats: total annotations, engaged-by-Claude count, resolved count, totals this week |
| clear_annotations { before? } | Delete payloads + companion files |
HTTP endpoints (when --http)
| Route | Notes |
|---|---|
| GET /health | Health probe (extension bridge uses this) |
| GET /stats | JSON snapshot of get_loupe_stats (popup uses this) |
| POST /annotations | Extension bridge — accepts { payload, screenshot, screenshotFullPage?, screenshotAnnotated?, annotationCrops?, audioFiles? } |
| POST /mcp | StreamableHTTP MCP transport for clients that don't speak stdio |
On-disk layout
~/.loupe/
├── .env # API keys (you create this)
├── stats.json # Auto-managed usage stats
├── logs/host.log # Native-host logs (if installed)
└── annotations/
├── lp-a1b2c3d4.json # Annotation payload
├── lp-a1b2c3d4.png # Viewport screenshot
├── lp-a1b2c3d4-full.jpg # Stitched full-page (when captured)
├── lp-a1b2c3d4-annotated.jpg # Same, with pins painted on
├── lp-a1b2c3d4-crop-a1.png # Per-annotation focused crop
├── lp-a1b2c3d4-a1.webm # Voice recording (if any)
└── ...Security
- Binds to
127.0.0.1only by default. Don't change--hostunless you know what you're doing — there's no auth. - CORS is scoped per endpoint. Read-only (
/health,/stats) allow any origin so the popup + diagnostic tools can probe. State-mutating endpoints (/annotations,/transcribe,/backup/export,/backup/import) only acceptchrome-extension://andmoz-extension://origins. A regular web page onhttp://evil.examplecannot POST annotations to your local server, even though the port is open. - API keys live in
~/.loupe/.envwith owner-only permissions. - All annotation data stays on local disk unless you opt into the async feedback flow.
Development
pnpm --filter @useloupe/mcp-server build
pnpm --filter @useloupe/mcp-server test
pnpm --filter @useloupe/mcp-server start # runs the bin entry against current build