CharlotteBLOCK
Token-efficient browser MCP server — structured web pages for AI agents, not raw accessibility dumps
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
The Web, Readable.
Your AI agent burns ~50,000 characters of accessibility tree just to look at the Hacker News front page. Charlotte does it in 364.
Charlotte is an MCP server that gives AI agents structured, token-efficient access to the web. Instead of dumping the full accessibility tree on every call, Charlotte returns only what the agent needs: a compact page summary on arrival, targeted queries for specific elements, and full detail only when explicitly requested. On content-heavy pages that orientation is up to ~140x smaller than a full accessibility-tree snapshot from Playwright MCP; on trivially small pages the two are roughly the same size.
Why Charlotte?
Most browser MCP servers dump the entire accessibility tree on every call — a flat text blob that can exceed a million characters on content-heavy pages. Agents pay for all of it whether they need it or not.
Charlotte decomposes each page into a typed, structured representation — landmarks, headings, interactive elements, forms, content summaries — and lets agents control how much they receive with three detail levels. When an agent navigates to a new page, it gets a compact orientation (364 characters for Hacker News) instead of the full element dump (~50,000 characters). When it needs specifics, it asks for them.
Benchmarks
Measured on Charlotte v0.8.0 against Playwright MCP v0.0.79, by characters returned per tool call on real websites (npx tsx benchmarks/run-benchmarks.ts --suite comparison), 2026-08-08. This section is a summary — the canonical benchmarks page (including per-task cost and release drift) is charlotte.mintlify.site/benchmarks; methodology, instruments, and raw results: benchmarks/.
Orientation cost (what an agent pays to "see" a page on arrival):
A Charlotte navigate returns a usabl
9a303c62666bOBSERVED · 2026-10-06Connect
Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control.
claude mcp add charlotte -- npx -y @ticktockbent/[email protected]
Exposed tools (46)
35 read · 9 write · 2 destructive. Blast radius: 2 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.
| Tool | Risk | Description |
|---|---|---|
Country | read | |
Test | read | |
charlotte_back | read | Navigate back in browser history. Returns page representation after navigation. |
charlotte_clear_cookies | destructive | Clear cookies from the browser. Optionally filter by name(s) to remove specific cookies. Without a filter, clears all cookies for the current page. |
charlotte_click | read | Click an interactive element on the page. Returns full page representation after the click. |
charlotte_click_at | read | Click at specific page coordinates. Use when target elements are not in the accessibility tree (custom widgets, canvas, non-semantic interactive divs). Dispatches real CDP-level mouse events. Returns full page representation after the click. |
charlotte_configure | read | Configure Charlotte runtime settings. Changes take effect immediately. |
charlotte_console | read | Retrieve console messages from the active page. Returns messages at all severity levels (log, info, warning, error, debug, etc.) with timestamps. Useful for debugging JavaScript behavior. |
charlotte_dev_audit | write | Run accessibility and quality audits on the current page. Returns findings with severity levels and actionable recommendations. |
charlotte_dev_inject | read | Inject CSS or JavaScript into the current page for testing modifications without editing files. Returns the page representation with a delta showing changes. |
charlotte_dev_serve | read | Serve a local directory as a static website and optionally watch for file changes. Navigates to the served URL and returns the page representation. File changes trigger automatic reloads and surface as reload_event on the next tool response. |
charlotte_dialog | read | Handle a pending JavaScript dialog (alert, confirm, prompt, beforeunload). Accept or dismiss the dialog. Returns page representation after the dialog is resolved. |
charlotte_diff | read | Compare current page state to a previous snapshot. Returns structural diff showing added, removed, moved, and changed elements. |
charlotte_drag | read | Drag an element to another element. Uses mouse primitives to simulate drag-and-drop. Returns full page representation after the drag. |
charlotte_evaluate | write | Execute JavaScript in page context. Supports single expressions and multi-statement code. Returns the completion value of the last expression-statement. |
charlotte_forward | read | Navigate forward in browser history. Returns page representation after navigation. |
charlotte_get_cookies | read | Get cookies for the active page. Optionally filter by URL(s). Returns cookie name, value, domain, path, and flags. |
charlotte_hover | write | Hover over an element to trigger hover states. Returns full page representation after hover. |
charlotte_key | write | Send keyboard input to the page or a specific element. Supports single key with modifiers, or a sequence of keys. Use for keyboard-driven UIs (games, terminals, code editors) and non-input elements with keydown listeners. |
charlotte_navigate | read | Load a URL in the active page. Returns page representation after navigation. Default minimal detail includes landmarks, headings, and interactive element counts — use charlotte_find to locate specific elements, or pass detail: |
charlotte_network | write | Configure network conditions for the active page. Set throttling presets, block URL patterns, or enable request logging. |
charlotte_observe | read | Get current page state without performing any action. Use detail levels to control verbosity: |
charlotte_reload | read | Reload the current page. Returns page representation after reload. |
charlotte_requests | read | Retrieve network request history from the active page. Returns all HTTP requests with method, status, resource type, and timestamps. Useful for debugging API calls and resource loading. |
charlotte_screenshot | read | Capture a visual screenshot. Fallback for when structured representation isn |
charlotte_screenshot_delete | destructive | Delete a saved screenshot artifact by its ID. Removes the file from disk. |
charlotte_screenshot_get | read | Retrieve a previously saved screenshot artifact by its ID. Returns the image data and metadata. |
charlotte_screenshots | read | List all saved screenshot artifacts. Returns metadata for each saved screenshot including ID, filename, page URL, and timestamp. |
charlotte_scroll | read | Scroll the page or a specific container. Returns full page representation after scrolling. |
charlotte_select | read | Select an option in a select/dropdown element. Returns full page representation after selection. |
charlotte_set_cookies | write | Set cookies on the active page. Cookies persist for subsequent navigations within matching domains. |
charlotte_set_headers | write | Set extra HTTP headers for subsequent requests. Headers persist for all navigations on the active page. |
charlotte_submit | write | Submit a form. Can submit by form ID or by clicking its submit button. Returns full page representation after submission. |
charlotte_tab_close | read | Close a browser tab by its ID. If the closed tab was active, switches to the first remaining tab. |
charlotte_tab_open | read | Open a new browser tab. Optionally navigate to a URL. The new tab becomes the active tab. |
charlotte_tab_switch | read | Switch to a different browser tab by its tab ID. Returns the page representation of the activated tab. |
charlotte_tabs | read | List all open browser tabs with their URLs, titles, and active status. |
charlotte_toggle | read | Toggle a checkbox or switch element. Returns full page representation after toggle. |
charlotte_tools | read | |
charlotte_type | read | Type text into an input element. Returns full page representation after typing. |
charlotte_upload | write | Set files on a file input element. Validates that files exist and that the target is a file input. Returns full page representation after upload. |
charlotte_viewport | read | Change the browser viewport. Use custom width/height, a generic preset (dimensions only), or a named device like |
charlotte_wait_for | read | Wait for a condition to be met on the page. Returns page representation when the condition is satisfied, or a TIMEOUT error. |
find-and-act | read | Navigate to ${HN_URL}, locate the |
form-fill | read | Navigate to ${SELENIUM_FORM_URL}, fill the text/password/textarea fields, pick a dropdown option, submit. |
orient-and-read | read | Navigate to ${HN_URL} and obtain the page |
Trust audit
BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | FAIL |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | WARN |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- declared (3 observation(s))
- Network
- declared (10 observation(s))
- Shell
- none-observed
- Dependencies
- not all pinned
- Secrets in source
- found
Findings (25)
"169.254.0.0/16", // link-local, incl. 169.254.169.254 cloud metadata
CMD node -e "fetch('http://127.0.0.1:3737/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"readonly LOCAL_BASE_URL="http://127.0.0.1:${CHARLOTTE_PORT}"`--proxy-server=http://127.0.0.1:${this.guardProxy.port}`,url: `http://127.0.0.1:${assignedPort}`,{ token: "definitely-not-the-token" },{ token: "valid-shape-wrong-value" },charlotte_clear_cookies, charlotte_screenshot_delete
.prettierignore
const hash = createHash("md5").update(input).digest("hex");import Footer from "../../components/Footer";
import Footer from "../../components/Footer";
import ReleaseDrift from "../../components/ReleaseDrift";
import CostPerTask from "../../components/CostPerTask";
import QuickStart from "../../components/QuickStart";
Every navigation Charlotte's browser makes is checked against a deny-by-default list of private address ranges — loopback, RFC1918 (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), link-local (`169.2
{ ip: "169.254.169.254", range: "169.254.0.0/16", note: "cloud metadata" },expect(isPrivateOrInternalIp("169.254.169.254")).toBe(true);expect(await isInternalUrl("http://169.254.169.254/latest/meta-data")).toBe(true);curl http://127.0.0.1:3737/healthz
@modelcontextprotocol/node, @modelcontextprotocol/server, chokidar, express, ipaddr.js, puppeteer, zod, @eslint/js
@vercel/analytics, next, @tailwindcss/postcss, @types/node, @types/react, @types/react-dom, eslint, tailwindcss
Navigate to https://www.selenium.dev/selenium/web/web-form.html, fill the text/password/textarea fields, pick a dropdown option, submit.
- `POST /mcp` — the MCP endpoint. Requires `Authorization: Bearer <token>`;
5. **Token exchange.** On success, Charlotte redirects back to claude.ai with an authorization code; claude.ai's backend exchanges it (`POST /oauth/token`) for a bearer token derived from your operato
Gates applied: no_behavioural_pass.
9a303c62666bfull audit observations/trust-audit/mcp-server/ticktockbent__charlotte.json · Report an issue / request a re-scanAudit history
Every audit this server has had. A grade with a past is a grade somebody is still checking.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-06 | 9a303c62666b | BLOCK | D | 69 | first audit |
Questions
What is the Charlotte MCP server?
Token-efficient browser MCP server — structured web pages for AI agents, not raw accessibility dumps
What tools does Charlotte expose?
46 in total: 35 read-only, 9 that write, and 2 that can delete or overwrite (charlotte_clear_cookies, charlotte_screenshot_delete). Every one is listed on this page with its risk.
Is Charlotte safe to connect to an agent?
No — not without reading the findings first. The audit graded it D (69/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 2 of its tools can destroy data, so scope the token you give it to what you actually need.
What credentials does Charlotte need?
No credential environment variables were found in its source, so it appears to need none.
How does Charlotte run?
It speaks stdio and streamable-http, so it runs as a local process your client starts. It is published on npm as site at 0.1.0.
How current is this page?
The grade is for one exact copy of the source (9a303c62666b), read on 2026-10-06. The repository is watched and re-audited when it changes.