Skip to main content
Charlotte resolves its settings from four sources. When the same setting is provided by more than one source, the higher-precedence source wins:
  1. CLI arguments (highest)
  2. Environment variables
  3. Config file (JSON)
  4. Built-in defaults (lowest)

Config file

Pass a config file explicitly:
If --config is omitted, Charlotte looks for charlotte.config.json in the current working directory and loads it automatically when present. A missing default file is not an error; a missing explicit --config path is. The file is validated with zod. Unknown keys, wrong types, and invalid enum values produce a clear startup error on stderr (stdout is reserved for the MCP stdio transport) and Charlotte exits non-zero.

Schema

Every section is optional. An empty {} is valid.

HTTP mode (http)

charlotte --http [--port N] serves the MCP streamable HTTP endpoint instead of stdio. The two modes are mutually exclusive — one process serves one transport.
  • POST /mcp — the MCP endpoint. Requires Authorization: Bearer <token>; anything else is answered 401 {"error":"unauthorized"} before any browser or session activity. The server refuses to start without a token.
  • GET /healthz — unauthenticated liveness: {version, uptime_s, browser_connected}. No page data, no config echo.
The tool set is fixed at startup from http.profile (--profile overrides; --tools is ignored with a warning), because a stateless HTTP transport has no per-connection registry to mutate — over HTTP, charlotte_tools is a read-only reporter: it reports the active profile and group status, and enable/disable requests are refused with a pointer at http.profile. Default browse excludes the dev-mode, evaluate, and monitoring groups. Any path that is neither /mcp nor /healthz answers 404 {"error":"not_found"}. Keys marked Reserved above are validated and documented now so a config written today keeps working when their consumers land; they have no effect yet.

Request observation (http.debugRequests / CHARLOTTE_DEBUG_HTTP)

Either switch turns on a diagnostic mode that logs, to stderr, every inbound request — method, path with query string, and headers — plus the response status once the request finishes. Unmatched paths are logged explicitly, which is the point: it exists to capture which discovery endpoints a connector client probes before that support is designed. authorization, proxy-authorization, cookie, set-cookie, x-api-key, and x-auth-token values are never logged. They are replaced by a marker recording that the header was present and, for auth headers, its scheme (<redacted: present, scheme=Bearer>). Leave it off in normal operation: it is noisy, it writes request metadata (paths, query strings, user agents) into your logs, and nothing depends on it.

Output-size limits (limits)

These caps (issue #188) bound how much a single tool response can return so a pathological page — 100k links, an infinite-scroll feed, a giant document body — cannot blow the MCP client’s context window. All are optional; omitted keys fall through to the built-in defaults above. When a page response exceeds maxResponseBytes it degrades to a compact summary and suggests writing the full result to a file via output_file; charlotte_evaluate results are capped independently by maxEvaluateBytes. Truncated responses carry a truncation marker so agents can tell the output was clipped.

Environment variables

Init scripts

browser.initScripts, --init-script, and CHARLOTTE_INIT_SCRIPT register JavaScript that runs on every new document — every navigation, reload, and new tab — before any of the page’s own scripts. Typical uses: stub an analytics SDK, pre-seed localStorage, or polyfill an API a site expects. Precedence is the usual CLI > env > config file, and the winning source’s list replaces the others outright rather than merging.
Each file is read once at startup; editing it later has no effect until Charlotte restarts. A path that cannot be read fails startup with the path in the error (charlotte doctor reports the resolved sources). Init scripts run with full page privileges on every origin Charlotte visits, so treat the files as trusted code. Agents can register the same kind of script at runtime with charlotte_dev_inject { persist: true }; those last until the process exits. One limitation: a popup the page opens itself (target="_blank", window.open) is a new browser target, and its very first document has usually started running by the time Charlotte can attach the scripts. Every document the popup loads after that is covered.

The Chromium sandbox (--no-sandbox)

The Chromium sandbox is the primary defense between a malicious web page and the account Charlotte runs as. Because Charlotte navigates agents to arbitrary, often untrusted, URLs, the sandbox is enabled by default. Disable it only when you must (most commonly inside containers, where the kernel sandbox cannot be set up). The opt-out is exposed three ways, in precedence order:
The provided Dockerfiles set CHARLOTTE_NO_SANDBOX=1 because the container cannot use the kernel sandbox; docker-compose.yml keeps Docker’s default seccomp filter in place (it no longer uses seccomp=unconfined) so the container is not left without any syscall filtering.