Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, get in touch and we’ll put it right.

nodriver — undetected browser automation

PYPI · NODRIVER-MCP · SCANNED SEP 21

Undetected anti-bot Chrome automation. 65 tools, parallel isolated browsers.

Available components

0 this week 65 Trust /100
Trust breakdown (7 categories)

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →

Supply Chain Security50
  • Malware scan not yet available for this package.Unverified
  • No known CVEs affecting this package version or its production dependencies.Pass
  • Runs setuptools.build_meta at install time, a recognised native-build step with no shell scripting around it. View diagnostics → Pass
  • 1 of 37 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency32
Schema Quality & AI Usability67
  • AI-judged instruction clarity (excellent).Pass
  • Context-footprint check failed: tool/resource definitions use about 17117 tokens (~263/item across 65 items; 65 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
  • Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management97
  • Stability observed for 29 of 30 days with no destabilising changes; credit accrues until the full window elapses.Partial
Tool Coverage100
  • 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
  • 100% of tool parameters carry a description.Pass
  • Structured output schemas are declared (97% of tools); any adoption earns full credit.Pass
Tool Safety100
  • No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
  • All 5 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.Pass
  • An AI judge read all 66 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
  • Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
Install

How do I install the nodriver — undetected browser automation MCP server?

nodriver — undetected browser automation runs locally as a PyPI package, launched with uvx nodriver-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

pypi · nodriver-mcp

# add to Claude Code
claude mcp add andresolbach-nodriver-mcp-server -- uvx nodriver-mcp
// .cursor/mcp.json
{
  "mcpServers": {
    "andresolbach-nodriver-mcp-server": {
      "command": "uvx",
      "args": [
        "nodriver-mcp"
      ]
    }
  }
}
// .vscode/mcp.json
{
  "servers": {
    "andresolbach-nodriver-mcp-server": {
      "command": "uvx",
      "args": [
        "nodriver-mcp"
      ]
    }
  }
}
# add to Codex CLI
codex mcp add andresolbach-nodriver-mcp-server -- uvx nodriver-mcp
// opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "andresolbach-nodriver-mcp-server": {
      "type": "local",
      "command": [
        "uvx",
        "nodriver-mcp"
      ],
      "enabled": true
    }
  }
}
# add to OpenClaw
openclaw mcp add andresolbach-nodriver-mcp-server --command uvx --arg nodriver-mcp
# ~/.hermes/config.yaml
mcp_servers:
  andresolbach-nodriver-mcp-server:
    command: "uvx"
    args: ["nodriver-mcp"]
// ~/.netclaw/config/netclaw.json
{
  "McpServers": {
    "andresolbach-nodriver-mcp-server": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "nodriver-mcp"
      ]
    }
  }
}
# add to Vellum
assistant mcp add andresolbach-nodriver-mcp-server -t stdio -c uvx -a nodriver-mcp
// mcp.json
{
  "mcpServers": {
    "andresolbach-nodriver-mcp-server": {
      "command": "uvx",
      "args": [
        "nodriver-mcp"
      ]
    }
  }
}
Changelog

Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.

  • 21 Sept 26 +1

    No change was recorded against any check on this day. Stability & Change Management went from 93 to 97. That category is still filling its 30-day observation window: 28 days of observed history at the previous scan, 29 at this one. The score rises as the window fills, whether or not the server changes.

  • 18 Sept 26 +1

    No change was recorded against any check on this day. Stability & Change Management went from 83 to 87. That category is still filling its 30-day observation window: 25 days of observed history at the previous scan, 26 at this one. The score rises as the window fills, whether or not the server changes.

  • 16 Sept 26 −2
    • Stability: pass → 0.80 functional
  • 15 Sept 26 0
    • Stability: 0.97 → pass security
  • 14 Sept 26 −14
    • Malware scan: pass → unverified security
  • 13 Sept 26 +15
    • Malware scan: unverified → pass security
  • 11 Sept 26 −14
    • Malware scan: pass → unverified security
  • 9 Sept 26 +1
    • Security disclosure: unverified → fail functional
Diagnostics

Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.

Captured 21 Sept 2026 · Analysed pypi/nodriver-mcp@2.4.0

Provenance No attestation

The registry publishes no build provenance for this version, so there is nothing to verify.

Result No attestation
Ecosystem pypi

Background: How many MCP packages publish verified provenance →

Install scripts 1 script
Hook Tier Command
build_backend allowlisted setuptools.build_meta

Background: Why install scripts are a supply-chain risk →

Dependencies 37 packages
Packages resolved 37
Stale 1
Tree resolution Complete

Background: SBOMs and build attestations, explained →

MCP tools · 65 exposed · ~16,297 tokens

The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →

Tool Tokens
set_checked ~343

Tick or untick a checkbox, or select a radio button, and verify it took. Use this instead of fill for anything with a checked state. fill types over a field's text, which a checkbox does not have — and its read-back compares `value`, which a checkbox never changes, so a fill could report success while nothing was selected. Idempotent: asking for the state it is already in is reported and does not click. Otherwise it delivers a real CDP click, on the element or, when the input is visually hidden behind a styled label (the usual custom-control pattern), on the label that controls it. The state is read back afterwards, so a failure is an error rather than a success you cannot trust.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
checkedbooleanTrue to tick the box or select the radio, False to untick it. Radio buttons cannot be unticked by the browser — select a different one in the group instead.
include_snapshotbooleanAppend a fresh page snapshot to the response. Worth it when this action changes the page and take_snapshot would be your next call anyway — it saves a round trip, at the cost of a much larger respons…
uidstringyesElement uid from the most recent take_snapshot, e.g. "4_12". uids are invalidated whenever the page changes — if you get "unknown uid", take a fresh snapshot and retry with the new uid.
NameTypeReqDescription
resultstringyes

No examples provided.

set_cookie ~215

Set a single browser cookie. Useful for injecting a known session token, consent flag or A/B bucket without walking through a login flow. This creates a session cookie: no expiry, gone when the browser closes. To restore a full cookie set including expiry and SameSite, use load_session. Cookies are browser-wide, not per tab.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
domainstringyesDomain the cookie belongs to, e.g. "example.com". A leading dot (".example.com") makes it apply to subdomains as well.
namestringyesCookie name.
pathstringURL path the cookie is scoped to. "/" covers the whole site.
securebooleanSend the cookie over HTTPS only. Browsers require this for any cookie with SameSite=None.
valuestringyesCookie value.
NameTypeReqDescription
resultstringyes

No examples provided.

set_local_storage ~170

Write entries into the current page's localStorage. Merges into what is already there rather than clearing it. Handy for setting feature flags, consent state or auth tokens before the page's own scripts read them. localStorage is scoped per origin, so navigate to the site first — writing on about:blank goes nowhere. Most pages read these values only at startup, so reload after setting them.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
itemsobjectyesKey-value pairs to write, e.g. {"token": "abc", "locale": "de"}. Values must be strings — JSON-encode anything structured yourself.
NameTypeReqDescription
resultstringyes

No examples provided.

set_proxy ~395

Send this browser's traffic through a proxy, with credentials if it wants them. The proxy itself is a launch flag, so it needs a browser restart — which on an ephemeral profile also drops its cookies. Authentication is handled live: an authenticating proxy stops Chrome at a native dialog that no page and no other CDP command can dismiss, so the page just never loads and nothing says why. Answering it means enabling the Fetch domain, which pauses every request for a round trip, so that only happens when a username is actually set. A proxy that does not ask for credentials costs nothing extra. Call with no arguments to see what is configured. The password is never echoed back. Values set here override the NODRIVER_PROXY environment variable.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
bypassstringHosts to reach directly, Chrome's own syntax, e.g. "localhost;127.0.0.1;*.internal". Empty string sends everything through the proxy.
passwordstringPassword that goes with `username`.
restartbooleanRestart Chrome now so the proxy takes effect, closing all open pages. Default true; false defers it to the next browser start.
serverProxy to route through, e.g. "http://198.51.100.7:8080" or "socks5://198.51.100.7:1080". A bare "host:port" is treated as http. Pass "" to remove the proxy; omit it to leave the current one alone.
usernamestringUsername, for a proxy that demands authentication. Chrome ignores credentials embedded in the proxy URL, so they have to be given here.
NameTypeReqDescription
resultstringyes

No examples provided.

shutdown_browser ~151

Quit one browser's Chrome and free its name, releasing all memory behind it. Use it when an agent is finished with its browser. The difference to close_browser: close_browser quits Chrome but keeps the browser, so its selected profile and runtime flags survive and the next call relaunches Chrome with them. shutdown_browser discards that too, and the name starts fresh if it is ever used again. The "default" browser cannot be retired, since it is the server itself; shutting it down quits its Chrome and resets it. A browser you attached to with use_running_browser is only detached from, never closed: it belongs to the user.

NameTypeReqDescription
browserstringName of the browser to shut down.

No output schema declared.

No examples provided.

take_memory_snapshot ~159

Capture a V8 heap snapshot of the page, for memory-leak debugging. Writes the raw .heapsnapshot file for Chrome DevTools -> Memory -> Load. The usual workflow is two snapshots taken around a suspect interaction, then comparing retained objects between them. On a heavy page a snapshot can be hundreds of megabytes and take several seconds; the response reports the resulting file size.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
file_pathstringyesLocal path to write the .heapsnapshot file to. Open it in DevTools -> Memory -> Load profile.
NameTypeReqDescription
resultstringyes

No examples provided.

take_screenshot ~337

Capture the page, the viewport or a single element as an image. Do NOT use this to read a page. take_snapshot gives you the same content as searchable text, is dramatically smaller, and yields the uids every interaction tool needs. Use a screenshot only when you genuinely need pixels: layout and styling checks, visual regression, or text that exists only inside an image. Element capture needs a uid from the current snapshot. Without file_path the image is returned inline as a base64 data URL.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
file_pathstringWrite the image to this local path. Omit to get a base64 data URL inline in the response — that is expensive, so prefer a file for full-page or high-DPR captures.
formatstringImage format. "png" is lossless and the default; "jpeg" or "webp" with a quality setting are far smaller, which matters for full-page captures.
full_pagebooleanCapture the whole scrollable page rather than just the visible viewport. Cannot be combined with `uid`.
qualityintegerCompression quality from 1 to 100, for jpeg and webp only. Ignored for png. 0 leaves Chrome's default.
uidstringCapture just this element, using a uid from the most recent take_snapshot. Cannot be combined with full_page. Empty string captures the page.
NameTypeReqDescription
resultstringyes

No examples provided.

take_snapshot ~457

Read the page as compact text, with a uid for every element. This is the primary way to see a page, and the source of the uids that click, fill, hover, drag and upload_file all take. Prefer it over take_screenshot for anything but a genuine visual check: it is searchable, far smaller, and it is what makes interaction possible at all. The output is the accessibility tree — roles, names, values and states, indented by nesting — with Chrome-internal and purely presentational nodes filtered out unless `verbose` is set. A URL on the same origin as the page is printed relative to it — the root node carries the absolute one, so nothing is lost and a link-dense page costs noticeably less to read. uids stay stable across snapshots for elements that did not change, but any page change can invalidate them. Whenever a tool reports "unknown uid", take a fresh snapshot and use the new uid. Output is capped at 200 000 characters. Frames are included and marked, so an element inside an iframe gets a uid like any other and click, fill and evaluate_script work on it unchanged. For plain page text without uids, get_page_content is cheaper; to find elements by CSS selector, use query_selector.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
file_pathstringWrite the snapshot to this local path instead of returning it — useful for very large pages you intend to search rather than read in full.
include_framesbooleanAlso read the page's iframes and splice each one in under the element that hosts it. On by default, because Chrome's accessibility tree stops at a frame boundary and anything inside an iframe would o…
verbosebooleanReturn the complete accessibility tree, including nodes normally filtered out as noise and containers that are otherwise collapsed. Much larger — use it only when an element you need is missing from…
NameTypeReqDescription
resultstringyes

No examples provided.

type_text ~196

Type text into whatever element currently has focus. Appends at the caret instead of replacing, and needs something focused already — click the field first, or use fill, which takes a uid, clears the field and needs no separate focus step. Prefer fill for ordinary form filling. Use type_text when you must add to existing content, or for widgets that only react to raw key events such as contenteditable, rich-text and canvas editors.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
submit_keystringKey to press once typing finishes, e.g. "Enter" to submit a search box or "Tab" to move to the next field. Empty string presses nothing.
textstringyesThe text to type, one character at a time.
NameTypeReqDescription
resultstringyes

No examples provided.

upload_file ~318

Attach a local file to a file input on the page. Sets the input's files directly over CDP, so no OS file-picker dialog ever opens — clicking an upload button normally would open one, and that blocks every further tool call until a human dismisses it. Chrome renders a file input with an internal shadow button, and that is what the accessibility tree exposes, so the uid you can see is usually not the input itself. This resolves to the real input: the element, one inside it, the input a label controls, or one just above in the tree. The result is read back. If the page's own uploader consumed the file straight away, the response says so, which is a successful upload rather than an empty input.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
file_pathstringyesAbsolute path to a local file on the machine running this server.
include_snapshotbooleanAppend a fresh page snapshot to the response. Worth it when this action changes the page and take_snapshot would be your next call anyway — it saves a round trip, at the cost of a much larger respons…
uidstringyesuid of the file input, or of the button, label or drop zone in front of it — the real <input type="file"> is resolved from there. Comes from the most recent take_snapshot.
NameTypeReqDescription
resultstringyes

No examples provided.

use_profile ~172

Switch the browser to a named persistent profile. Use it to pick up the cookies, logins and extensions stored in an earlier session, so a run starts out already authenticated. Restarts the browser and closes every open page. The profile has to exist already — create it first with create_profile. Only one browser may use a profile at a time; a second instance pointed at the same one fails to start.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
namestringyesName of an existing persistent profile, as shown by list_profiles. Pass "", "temp", "ephemeral" or "none" to switch back to an ephemeral profile.
NameTypeReqDescription
resultstringyes

No examples provided.

use_running_browser ~439

Drive a Chrome that is already running, instead of launching a new one. This is how you use a browser you are already signed into. Chrome locks its user-data-dir, so the profile holding your real logins cannot be opened a second time — attaching to the running instance is the only way in, and it saves rebuilding every login through the automation. The user starts Chrome themselves, once: chrome --remote-debugging-port=9222 --user-data-dir=/path/to/profile then this tool attaches to it. Every tool then acts on that browser and its real tabs. `--user-data-dir` is not optional, and it must not be Chrome's own default directory. Since Chrome 136 the port is refused there — Chrome starts normally, says nothing, and nothing ever listens, which is a confusing way to fail. So the everyday profile cannot be reached by adding the flag to it; it has to be a directory of its own, and a copy of an existing profile is accepted (`--profile-directory` then picks which profile inside it). SECURITY: that profile becomes part of the agent's reach. Whatever it is signed into — mail, bank, company systems — is reachable from here, because a cookie jar is all or nothing. Point this at a profile you are willing to expose, not your everyday one. While attached, close_browser and every profile switch only detach; they never close a browser they did not start. Return to a self-launched browser with use_temp_profile or use_profile.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
hoststringHost the browser is listening on. Leave at 127.0.0.1 unless you are attaching to a browser on another machine or in a container.
portintegerThe remote debugging port Chrome was started with, e.g. 9222 for --remote-debugging-port=9222.
NameTypeReqDescription
resultstringyes

No examples provided.

use_temp_profile ~133

Switch back to a fresh ephemeral profile, created and deleted per session. This is the default. It leaves nothing behind on disk and lets many nodriver instances run concurrently without colliding on a profile directory. Restarts the browser and closes all open pages. The profile you are leaving is not deleted — a persistent one keeps its cookies for the next use_profile. Anything held in the outgoing temp profile is gone.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
NameTypeReqDescription
resultstringyes

No examples provided.

wait_for ~223

Wait until one of several texts appears on the page, then snapshot it. The right way to wait after an action that starts loading — far more reliable than guessing a delay, and it returns the moment the text shows up instead of always burning the whole timeout. On success the page snapshot is included in the response, so no separate take_snapshot call is needed. Polls the visible text twice a second. To wait for an element rather than for wording, use wait_for_selector.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
textarrayyesTexts to wait for — the wait ends as soon as any one of them appears. Passing both outcomes, e.g. ["Welcome back", "Login failed"], lets one call tell you which happened. Matching is a case-sensitive…
timeoutintegerMaximum wait in milliseconds before giving up.
NameTypeReqDescription
resultstringyes

No examples provided.

wait_for_selector ~207

Wait until an element matching a CSS selector appears on the page. The structural counterpart to wait_for: use it when you know the markup but not the wording, or when the wording is localised. Polls about three times a second and returns as soon as the element shows up. Unlike wait_for it does not return a snapshot, so follow with take_snapshot when you intend to interact.

NameTypeReqDescription
browserstringWhich browser to act on. Each name is an independent Chrome, created on first use. Omit for the shared default; parallel agents each need their own name.
selectorstringyesCSS selector to wait for, e.g. "#login" or ".results .item".
timeoutintegerMaximum wait in milliseconds before giving up.
visiblebooleanAlso require the element to have a non-zero size. Use this with frameworks that insert an element into the DOM before rendering it — presence alone would otherwise resolve too early.
NameTypeReqDescription
resultstringyes

No examples provided.

Common questions

What is the nodriver — undetected browser automation MCP server?

nodriver — undetected browser automation is an MCP server listed in the public MCP registry as io.github.andresolbach/nodriver-mcp-server. Undetected anti-bot Chrome automation. 65 tools, parallel isolated browsers. This page covers its PyPI package (nodriver-mcp).

Is the nodriver — undetected browser automation MCP server safe to use?

nodriver — undetected browser automation scores 65 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 21 September 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

What tools does the nodriver — undetected browser automation MCP server expose?

nodriver — undetected browser automation exposes 65 tools: list_frames, bypass_insecure_warning, cf_verify, click, click_at, and 60 more. Their descriptions and schemas cost roughly 16,297 tokens of context every time the server is loaded.

Is the nodriver — undetected browser automation MCP server still maintained?

nodriver — undetected browser automation is still listed as active in the MCP registry. We last reached this channel on 21 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.