Atlas / MCP servers / ticktockbent / Charlotte

CharlotteBLOCK

mcp/ticktockbent/charlotte

Token-efficient browser MCP server — structured web pages for AI agents, not raw accessibility dumps

Verdict
BLOCK
Grade
D
Trust score
69 /100
Exposed tools
46 35r · 9w · 2d
Transport
stdio · streamable-http
License
MIT
Stars
181
01

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

Read from source at commit 9a303c62666bOBSERVED · 2026-10-06
02

Connect

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-code (npm)
claude mcp add charlotte -- npx -y @ticktockbent/[email protected]
03

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.

ToolRiskDescription
Countryread
Testread
charlotte_backreadNavigate back in browser history. Returns page representation after navigation.
charlotte_clear_cookiesdestructiveClear cookies from the browser. Optionally filter by name(s) to remove specific cookies. Without a filter, clears all cookies for the current page.
charlotte_clickreadClick an interactive element on the page. Returns full page representation after the click.
charlotte_click_atreadClick 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_configurereadConfigure Charlotte runtime settings. Changes take effect immediately.
charlotte_consolereadRetrieve 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_auditwriteRun accessibility and quality audits on the current page. Returns findings with severity levels and actionable recommendations.
charlotte_dev_injectreadInject CSS or JavaScript into the current page for testing modifications without editing files. Returns the page representation with a delta showing changes.
charlotte_dev_servereadServe 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_dialogreadHandle a pending JavaScript dialog (alert, confirm, prompt, beforeunload). Accept or dismiss the dialog. Returns page representation after the dialog is resolved.
charlotte_diffreadCompare current page state to a previous snapshot. Returns structural diff showing added, removed, moved, and changed elements.
charlotte_dragreadDrag an element to another element. Uses mouse primitives to simulate drag-and-drop. Returns full page representation after the drag.
charlotte_evaluatewriteExecute JavaScript in page context. Supports single expressions and multi-statement code. Returns the completion value of the last expression-statement.
charlotte_forwardreadNavigate forward in browser history. Returns page representation after navigation.
charlotte_get_cookiesreadGet cookies for the active page. Optionally filter by URL(s). Returns cookie name, value, domain, path, and flags.
charlotte_hoverwriteHover over an element to trigger hover states. Returns full page representation after hover.
charlotte_keywriteSend 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_navigatereadLoad 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_networkwriteConfigure network conditions for the active page. Set throttling presets, block URL patterns, or enable request logging.
charlotte_observereadGet current page state without performing any action. Use detail levels to control verbosity:
charlotte_reloadreadReload the current page. Returns page representation after reload.
charlotte_requestsreadRetrieve 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_screenshotreadCapture a visual screenshot. Fallback for when structured representation isn
charlotte_screenshot_deletedestructiveDelete a saved screenshot artifact by its ID. Removes the file from disk.
charlotte_screenshot_getreadRetrieve a previously saved screenshot artifact by its ID. Returns the image data and metadata.
charlotte_screenshotsreadList all saved screenshot artifacts. Returns metadata for each saved screenshot including ID, filename, page URL, and timestamp.
charlotte_scrollreadScroll the page or a specific container. Returns full page representation after scrolling.
charlotte_selectreadSelect an option in a select/dropdown element. Returns full page representation after selection.
charlotte_set_cookieswriteSet cookies on the active page. Cookies persist for subsequent navigations within matching domains.
charlotte_set_headerswriteSet extra HTTP headers for subsequent requests. Headers persist for all navigations on the active page.
charlotte_submitwriteSubmit a form. Can submit by form ID or by clicking its submit button. Returns full page representation after submission.
charlotte_tab_closereadClose a browser tab by its ID. If the closed tab was active, switches to the first remaining tab.
charlotte_tab_openreadOpen a new browser tab. Optionally navigate to a URL. The new tab becomes the active tab.
charlotte_tab_switchreadSwitch to a different browser tab by its tab ID. Returns the page representation of the activated tab.
charlotte_tabsreadList all open browser tabs with their URLs, titles, and active status.
charlotte_togglereadToggle a checkbox or switch element. Returns full page representation after toggle.
charlotte_toolsread
charlotte_typereadType text into an input element. Returns full page representation after typing.
charlotte_uploadwriteSet 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_viewportreadChange the browser viewport. Use custom width/height, a generic preset (dimensions only), or a named device like
charlotte_wait_forreadWait for a condition to be met on the page. Returns page representation when the condition is satisfied, or a TIMEOUT error.
find-and-actreadNavigate to ${HN_URL}, locate the
form-fillreadNavigate to ${SELENIUM_FORM_URL}, fill the text/password/textarea fields, pick a dropdown option, submit.
orient-and-readreadNavigate to ${HN_URL} and obtain the page
04

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.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfaceWARN
L4Behavioural (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)

HIGHNetwork egress · net.metadata_ip · CWE-200, CWE-319
src/browser/navigation-guard.ts:36
"169.254.0.0/16", // link-local, incl. 169.254.169.254 cloud metadata
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
Dockerfile:123
CMD node -e "fetch('http://127.0.0.1:3737/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
docker/entrypoint.sh:28
readonly LOCAL_BASE_URL="http://127.0.0.1:${CHARLOTTE_PORT}"
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
src/browser/browser-manager.ts:178
`--proxy-server=http://127.0.0.1:${this.guardProxy.port}`,
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
src/dev/static-server.ts:106
url: `http://127.0.0.1:${assignedPort}`,
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
tests/integration/remote-auth.test.ts:103
{ token: "definitely-not-the-token" },
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
tests/integration/remote-auth.test.ts:173
{ token: "valid-shape-wrong-value" },
MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
charlotte_clear_cookies, charlotte_screenshot_delete
Why it matters. 2 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.hidden_file · CWE-1104
.prettierignore
.prettierignore
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
src/utils/hash.ts:15
const hash = createHash("md5").update(input).digest("hex");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
site/app/privacy/page.tsx:3
import Footer from "../../components/Footer";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
site/app/vs-playwright/page.tsx:3
import Footer from "../../components/Footer";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
site/app/vs-playwright/page.tsx:4
import ReleaseDrift from "../../components/ReleaseDrift";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
site/app/vs-playwright/page.tsx:5
import CostPerTask from "../../components/CostPerTask";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
site/app/vs-playwright/page.tsx:6
import QuickStart from "../../components/QuickStart";
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
docs/security.mdx:19
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
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/unit/browser/navigation-guard.test.ts:26
{ ip: "169.254.169.254", range: "169.254.0.0/16", note: "cloud metadata" },
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/unit/dev/auditor.test.ts:113
expect(isPrivateOrInternalIp("169.254.169.254")).toBe(true);
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/unit/dev/auditor.test.ts:141
expect(await isInternalUrl("http://169.254.169.254/latest/meta-data")).toBe(true);
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
docs/docker.mdx:51
curl http://127.0.0.1:3737/healthz
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
package.json
@modelcontextprotocol/node, @modelcontextprotocol/server, chokidar, express, ipaddr.js, puppeteer, zod, @eslint/js
Why it matters. 19 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
site/package.json
@vercel/analytics, next, @tailwindcss/postcss, @types/node, @types/react, @types/react-dom, eslint, tailwindcss
Why it matters. 9 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
benchmarks/results/tasks/2026-08-09/tasks.md:76
Navigate to https://www.selenium.dev/selenium/web/web-form.html, fill the text/password/textarea fields, pick a dropdown option, submit.
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
docs/configuration.md:120
- `POST /mcp` — the MCP endpoint. Requires `Authorization: Bearer <token>`;
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
docs/security.mdx:33
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
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine

Gates applied: no_behavioural_pass.

Audited 2026-10-06 · audit v0.4.1 · source sha 9a303c62666bfull audit observations/trust-audit/mcp-server/ticktockbent__charlotte.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-069a303c62666bBLOCKD69first audit
06

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.

Advertisement