Navvi
Navvi is an MCP server for managing browser personas and performing web automation through a controlled browser container.
Manage personas (create, get, update, list, delete) with config, writing style, and persistent Docker volume.
Manage linked accounts and credential references (add, list, update, delete).
Curate persona milestones (add, list, export, brief, delete) with evidence and screenshots.
Maintain a persona knowledge store (add, list, search, update, remove, digest, save_digest).
Start, stop, check status, and list browser containers (local Docker or remote Codespace).
Control browser via atomic OS-level actions: navigate, click, fill, press, drag, hold, mouse, scroll, find elements, tabs, screenshots, get URL.
Manage credentials in gopass: list, get non-secrets, generate passwords, import, autofill (never exposes passwords).
Record browser sessions as MP4, trim, and convert to GIF.
Manage reusable flow recipes per domain (list, show, save, delete) that auto-load during browsing.
Use high-level navvi_browse for natural-language web tasks, handling navigation, clicks, typing, CAPTCHAs, cookie banners, and login detection.
Use navvi_login to log in with stored credentials and handle 2FA via VNC.
Get a noVNC URL for human intervention on CAPTCHAs, OAuth, or 2FA.
Allows searching DuckDuckGo via browser automation, with persistent sessions and anti-detection.
Supports interacting with GitHub, including starring repositories, searching, and attaching screenshots to pull requests.
Enables logging into Tutanota (Tuta) with stored credentials, handling forms and CAPTCHAs automatically.
Your agent asks in plain words.
navvi "extract the title, price and availability" <url>.Navvi compiles a scraper, fast and accurately. Code finds the candidates on the page; a model only picks among them. With Jev those picks are typed, cheap and several times faster than Haiku.
Re-runs make zero LLM calls. The compiled scraper is a JSON file of selectors and fingerprints. Run it in cron, in CI, on Apify.
It heals itself when the site changes. A field that moved is found again and the scraper is updated; a redesign is reported, never guessed.

Why use Jev
A scraper compiler makes a lot of small decisions: which element is the price, which control to click next, is the search done. Navvi never asks a model to write a selector or code; it enumerates the options and asks for a pick. That is exactly the shape Jev is built for.

The same 19 questions navvi asked while compiling a Hacker News search, asked again of each backend, one after another, median of three runs:
Who decides | Time for 19 decisions | Jev is | Matched the reference |
Jev (TypeSafe API) | 3.5 s | — | 19/19 |
Claude Haiku 4.5 over the API (AI Gateway) | 12.6 s | 3.6× faster | 19/19 |
Claude Haiku through Claude Code — navvi's default without a key | 31–38 s | 8–11× faster | 19/19 |
Same answers, a fraction of the wait. The Claude Code row is already the fast version: navvi now runs it without extended thinking, which took it from 91.6 s to 31–38 s with the same 19/19 (two sets of runs the same day; Claude Code's time varies).
Method, every batch's
latency and the captured questions:
decisions-race-provenance.json (the video, an earlier set of runs: 3.2 s against 11.0 s)
and decisions-race-claude-code.json (the
table). One task and one network location: a measurement, not a benchmark.
Turning it on is one environment variable. With TYPESAFE_API_KEY (or
AI_GATEWAY_API_KEY) set, navvi picks Jev by itself and says so:
chooser: jev (TYPESAFE_API_KEY found — fast typed decisions; text questions go to claude)Jev picks; it does not write. The one or two free-text questions in a run (reading
your prompt, the words to type into a search box) go to a signed-in Claude Code or
Codex on your subscription, or to ANTHROPIC_API_KEY. Get a key at
typesafe.ai.
Related MCP server: puppeteer-real-browser-mcp-server
Install
Node 22+.
npm install -g navvi
npx playwright install chromium
navvi "Extract the book title, price and availability" \
https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html --browser chromium --out books.jsonRun the same command again, or point it at another page of the same kind
(…/sharp-objects_997/index.html): the saved scraper answers with zero model calls.
No key and no signed-in CLI? --decider agent makes your coding agent answer the
questions itself — see Give it to your agent.
From source: git clone https://github.com/fellowship-dev/navvi.git && cd navvi && NAVVI_SKIP_BROWSER_DOWNLOAD=1 npm ci && npx playwright install chromium && npm run build, then node dist/bin/cli.js ….
What you get
Records on stdout or
--out data.json|data.csv, one object per item with a_sourceURL (the page it was read on) and a_startUrl(the start URL that produced it).A scraper you can commit:
storage/key_value_stores/scraper-cache/<key>.json. The rest ofstorage/holds browser profiles with live sessions; keep it private.A summary on stderr saying who answered what:
navvi: status succeeded items 2 pages 2 templates 1 cache hit no decider jev: 3 decisions, 10115 input tokens, 7.3s waiting, $0.0004 writer claude: 1 text question, 18709 input tokens, 7.7s waiting, $0.0000 healing events 0 unmapped candidates 0 unhealed 0 data: 2 records on stdoutA run report that accounts for every page. The run summary (the
SUMMARYrecord on Apify) counts the pages that gave no row instead of healing them, each with the first 50 URLs, and a key appears only when it is non-zero:{ "status": "succeeded", "items": 918, "pages": 1000, "unhealed": 0, "blockedPages": 3, "deadPages": { "count": 41, "urls": ["https://example.com/p/retired-item"] }, "transientPages": { "count": 2, "urls": ["…"] }, "unsettledPages": { "count": 1, "urls": ["…"] }, "noPayloadPages": { "count": 4, "urls": ["…"] }, "emptyListings": { "count": 12, "urls": ["https://example.com/search?q=…"] }, "offTemplate": { "count": 5, "urls": ["https://example.com/category/…"] }, "optionalDrift": [{ "field": "listPrice", "pages": 610, "filled": 308 }] }blockedPagesis a bot challenge (the first is kept asBLOCKED_PAGE, with a screenshot);deadPagesanswered 404/410 or redirected off the template;transientPagesstill answered 5xx after a retry;unsettledPagesnever settled past a weak challenge reading;noPayloadPagesnever received the data the page loads for itself;emptyListingsare searches that found nothing;offTemplateare start URLs a pinned scraper does not match, never compiled;optionalDriftis an optional field filled on some pages and empty on others.Typed values when you ask:
--fields name,price:money,stock:booleanreads$ 6.990as6990andAgotadoasfalse; a value that does not coerce isnull.A URL list as input:
--from-url <url>fetches the pages to scrape from an endpoint (newline text or JSON), so a backend can feed the daily list.Optional fields (Apify actor input): a field declared
"optional": truemay be empty on a healthy page, such as a list price that exists only during a discount; replay never heals its nulls.
How it works
One compiler, cheapest evidence first. For each kind of page (a URL template):
flowchart LR
A[declared data<br/>JSON-LD, meta] --> B[the page's own<br/>JSON payloads]
B --> C[DOM candidates<br/>a model picks]
C --> D[gate + determinism]
D --> E[scraper.json]Tier 1 — what the page declares. JSON-LD and meta tags, read with one plain request. No browser, no model.
Tier 2 — what the page fetches. The JSON payloads the page loads for itself, bound only when a payload is provably about this page, and never two fields to one path.
Tier 3 — the DOM. Code enumerates candidate elements; the chooser picks one per field. Only the fields tiers 1–2 could not cover are asked.
Close calls are questions, not guesses. When two readings compete, the chooser is asked once, with your rules (
--rubric) in the question, and the answer is recorded with who gave it.Replay makes no model calls. Each field carries fingerprints; when one stops matching, only that field is re-decided and the scraper is updated.
Audit a compile: --work
Add --work <dir> and the compile writes every step as a file you can read, edit
and re-run from — the spec, the sample, what each tier found, why each field was
bound (rationale.md), and a scorecard:
navvi "Extract the book title, price and availability" <url> <url> --work work/booksnavvi make "<brief>" <url...> --work <dir> is the same pipeline as a resumable
driver: each stage re-runs only when the bytes it read changed, a hand-edited
artifact is never overwritten without --force, and open questions stop the run
with exit 3 until you --answer them. Both write the same scraper.json for a
record page.
Give it to your agent
Copy SKILL.md into your agent's skills (for Claude Code:
.claude/skills/navvi/SKILL.md). It tells the agent when to reach for navvi
instead of writing Playwright, the one command, how to answer questions itself
with no key (--decider agent, over stdio or with --agent-mode file and
--resume), and what each exit code means. llms.txt indexes the rest.
Choosers
Navvi asks two kinds of question. The decider answers the picks (which candidate, yes/no, a score); the writer answers free text.
| Who answers | Needs | Writes text |
| Jev by TypeSafe |
| no — hands text to the writer |
| Claude Code on your subscription |
| yes |
| Codex on your subscription |
| yes |
| Any AI SDK model |
| yes |
| Your coding agent, over stdio | nothing | yes |
Default order: a Jev key → Jev; else ANTHROPIC_API_KEY → model; else a
signed-in Claude Code, then Codex; else agent. The choice and its reason print
on stderr.
Jev's writer is, in order: a signed-in claude, then codex, then a metered
key (ANTHROPIC_API_KEY, then the Gateway). A CLI that is signed out or times out
hands over to the next CLI; it never quietly starts spending on an API.
--writer pins it.
Jev's transport: with both keys set it goes over the Gateway and falls back to
the TypeSafe API if the Gateway is unavailable, saying so in the summary.
--decider-transport gateway|typesafe pins it and turns the fallback off.
--chooser <name> still works and means --decider <name>.
NAVVI_CLAUDE_MODEL (default haiku) and NAVVI_CODEX_MODEL pick the CLI model;
NAVVI_CLI_TIMEOUT_MS raises the CLI wait on a slow machine.
Exit codes
Exit | Status | What to do |
0 |
| Use the data |
1 |
| Read stderr; a redesign needs a new prompt, a login needs |
1 |
| Rows were written, but a requested field was never bound and is null on every row; the status line names it ( |
2 | configuration error | Fix the flag; the message names what is missing |
3 |
| Answer the parked questions and |
4 |
| Retry later, raise the cap, or switch chooser |
Limits
Heals a moved field or a renamed button; reports a redesign as
driftrather than guessing.No captcha solving.
--headedhands a challenge to the person at the keyboard.Logins use
--profile local; secrets come fromNAVVI_SECRET_<NAME>,--secrets-file, a TTY prompt or a sealed bundle — never the command line, a question, a log or the scraper JSON.The run stays on the start URLs' domains (
--allow-domainwidens it); private hosts need--allow-private-host; destructive-looking clicks need--allow-mutation.Defaults: 10 pages, 1000 items, or the limit the prompt states ("up to 10", "the first 3 pages");
--max-pagesand--max-itemswin over both. In list mode every start URL's first page is read;maxPagesbounds the pages after them, and on the actormaxPagesPerStartcaps one listing's pagination andmaxConcurrency/minConcurrencyset how many replay pages run at once.Validate what you extract, not just that it is non-empty: a filled form is not always a finished search.
Secrets
navvi secrets seal turns gopass entries, env vars and a secrets file into one
encrypted bundle: set it as NAVVI_SECRETS plus NAVVI_SECRETS_PASSPHRASE on
Apify or any server and logins replay unattended, no gpg needed. See
docs/secrets.md.
More
Running on Apify — the actor, residential proxies, pay-per-event.
navvi specandnavvi heuristics— the brief as a spec with its open questions, and the rules that decide what a model is asked.Architecture — every module and import, generated from the code and checked in CI.
Development
npm ci && npx playwright install chromium
npm run typecheck && npm test && npm run buildCI runs the offline suite with Chromium and recorded answers; it needs no key. Report a failing URL with a redacted summary in an issue — never include browser profiles, cookies or API keys.
License
MIT.
Available Tools
31 toolsTool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
31 tool updates
v3.21.0- First observed
navvi_account - First observed
navvi_atomic - First observed
navvi_browse - First observed
navvi_click - First observed
navvi_context - First observed
navvi_creds - First observed
navvi_drag - First observed
navvi_fill - First observed
navvi_find - First observed
navvi_flow - First observed
navvi_hold - First observed
navvi_list - First observed
navvi_login - First observed
navvi_milestone - First observed
navvi_mousedown - First observed
navvi_mousemove - First observed
navvi_mouseup - First observed
navvi_open - First observed
navvi_persona - First observed
navvi_press - First observed
navvi_record_gif - First observed
navvi_record_start - First observed
navvi_record_stop - First observed
navvi_screenshot - First observed
navvi_scroll - First observed
navvi_start - First observed
navvi_status - First observed
navvi_stop - First observed
navvi_tab - First observed
navvi_url - First observed
navvi_vnc
TDQS
Scored across 31 tools
Each tool has a clearly distinct purpose: high-level browsing (navvi_browse), low-level actions (navvi_click, navvi_fill), credential management (navvi_creds), persona lifecycle (navvi_persona, navvi_account, navvi_milestone), etc. Even related tools like mouse actions are differentiated by action type. No overlapping ambiguity.
All tools follow the consistent snake_case pattern 'navvi_<verb_or_noun>'. The naming is predictable: verbs for actions (browse, click, find) and nouns for entities (context, flow, persona). No mixed conventions or irregular patterns.
31 tools is higher than typical (3-15) but well-justified given the comprehensive scope: browser automation (atomic & high-level), credential management, persona profiles, knowledge context, recording, and infrastructure. Each tool has a defined role without redundancy.
The tool set covers the full lifecycle of persona-driven web automation: browser control (open, find, click, type, scroll), credential handling, knowledge storage, flow recipes, and recording. Minor gaps exist (e.g., file uploads, extension management) but do not hinder primary workflows.
Maintenance
Related MCP Connectors
Cloud browsers for AI agents: get past bot detection, sign in with MFA, run web tasks at scale.
Undetectable cloud browser sessions for AI agents and scrapers. Navigate, extract, click, captcha.
Stealth web automation for AI agents. Login, signup, navigate, screenshot.
Stealth web automation for AI agents. Login, signup, navigate, screenshot.
Related MCP Servers
- FlicenseNot gradedqualityNot gradedmaintenanceEnables AI agents to authenticate with websites using a real Chromium browser with anti-detection measures and human-in-the-loop support for captchas and 2FA. Features stealth browsing, human-like interactions, and persistent session storage to automate and resume login workflows.-
- AlicenseNot gradedqualityDmaintenanceProvides AI assistants with powerful, detection-resistant browser automation capabilities, allowing them to control a real web browser to navigate, extract content, and perform tasks while avoiding bot detection.11 npmMIT
- AlicenseAqualityCmaintenanceEnables AI agents to fully control a browser for web automation, including navigation, clicking, typing, scrolling, screenshots, and DOM inspection, with session persistence and anti-bot bypass.4025 npmMIT
- AlicenseNot gradedqualityDmaintenanceProvides a persistent browser profile for AI agents, enabling them to log in once and maintain sessions across restarts. Supports 20 tools for browsing, navigation, text extraction, and screenshot.1MIT