Skip to main content
Glama
andresolbach

nodriver-mcp-server

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
NODRIVER_PROXYNoProxy server address. Default: None
NODRIVER_HEADLESSNoHeadless mode (true/false)false
NODRIVER_BROWSER_PATHNoChrome executable path. Auto-detected if not set.
NODRIVER_USER_DATA_DIRNoExplicit persistent Chrome profile dir (overrides the default). By default an ephemeral temp profile is used and auto-deleted per session.
NODRIVER_ENABLE_TRANSLATENoSet 'true' to re-enable Chrome's Google Translate popup. Default: disabled.false
NODRIVER_ENABLE_EXTENSIONSNoSet 'true' to allow externally-installed Chrome extensions (and their prompts). Default: disabled.false

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
bypass_insecure_warningA

Click through Chrome's "Your connection is not private" interstitial.

Use this when a navigation lands on an SSL/certificate warning page (expired or self-signed certificate, hostname mismatch) instead of the site itself — the snapshot then shows a warning page rather than the expected content. This performs the Advanced -> Proceed click for you.

Has no effect on any other kind of page.

cf_verifyA

Attempt to solve a Cloudflare "Verify you are human" challenge.

Use when a page is stuck on a Cloudflare interstitial — a checkbox widget, or "Checking your browser before accessing". Drives nodriver's built-in verification bypass, which locates the checkbox visually and clicks it.

Requires opencv-python to be installed; without it this returns an error. Many challenges also clear by themselves after a few seconds, so wait_for(["some text from the real page"]) is worth trying first.

clickA

Click an element addressed by its snapshot uid.

This is the click you want in almost all cases — prefer it over click_at, because a uid survives layout shifts and raw coordinates do not. The element is scrolled into view automatically, so it need not be visible beforehand.

Sends real CDP input events, so the page sees isTrusted=true, which is the entire point of an undetected driver. Two situations force a scripted fallback (element.click() plus synthetic events, isTrusted=false): a touch-emulated target, where CDP mouse input can crash the renderer, and a CDP click that times out or errors. The response says so explicitly whenever that happens, so a detectable click is never silent. Every step is bounded at 10s, so a wedged page cannot hang the call.

On "unknown uid", take a fresh take_snapshot and retry with the new uid.

click_atA

Click a raw viewport coordinate instead of an element.

Use click with a uid whenever the target shows up in a snapshot — it is robust against layout shifts, this is not. Coordinates are for surfaces with no addressable element: canvases, maps, video players, image hotspots.

The point must lie inside the current viewport; scroll_page or scroll_to_selector first if it does not. Coordinates are in CSS pixels and ignore the device pixel ratio, so they match what emulate/resize_page report.

Same input path as click: real CDP events, the same reported scripted fallback, the same 10s bound per step.

close_pageA

Close a single browser tab.

The last remaining tab cannot be closed — use close_browser to shut Chrome down entirely. Closing the selected page clears the selection, so subsequent tools fall back to the most recently opened tab.

Returns the remaining open pages, because indices shift when a tab closes: re-read them from this response rather than reusing older ones.

close_browserA

Quit Chrome entirely, closing every tab.

Unlike close_page (which always keeps one tab alive), this tears down the whole browser. Chrome relaunches automatically on the next tool call with the currently selected profile, so this is also how you apply pending launch flags without switching profiles.

On an ephemeral temp profile (the default) this discards cookies, logins and localStorage — save_session first if you need them back. Persistent profiles created with create_profile keep everything.

dragA

Drag one element onto another (press, move, release).

Both uids must come from the same take_snapshot. Works for native HTML5 drag-and-drop and for mouse-driven sortable lists.

Some JS drag libraries require a stream of intermediate mousemove events that this does not emit; if a drag silently does nothing, drive it manually with click_at and press_key instead.

emulateA

Emulate network, CPU, geolocation, user agent, color scheme or viewport.

Applies to the selected page and persists across navigations until reset_emulation. Every parameter is independent — pass only what you want to change, leave the rest at their defaults.

To emulate a real phone or tablet, use emulate_device instead: it sets user agent, client hints, viewport, DPR and touch as one coherent set, which hand-assembled overrides here get wrong in ways anti-bot systems detect.

Turning touch on in the viewport also changes how click behaves — CDP mouse input can crash a touch-emulated renderer, so clicks fall back to the scripted path (isTrusted=false) for as long as touch is enabled.

reset_emulationA

Clear every emulation override on the selected page.

Resets in one call: network throttling, CPU throttling, geolocation, user agent and client hints, color scheme, viewport, device pixel ratio, page scale and touch emulation — back to the real browser defaults.

Use after emulate or emulate_device to make the page behave like ordinary desktop Chrome again. Turning touch emulation off here also restores trusted CDP clicks.

emulate_deviceA

Emulate a phone or tablet with one internally consistent set of signals.

Preferred over assembling emulate parameters by hand for mobile work: user agent, UA client hints (Sec-CH-UA-*), viewport, device pixel ratio, touch support and Accept-Language are set together so they cannot contradict each other — a mismatch between them is a classic automation tell.

Presets are pixel_7, pixel_7_landscape and ipad_air (aliases are listed in the device parameter). ipad_air deliberately reports desktop-class Safari with touch and sends no client hints, which is what a real iPad does.

Applies to the selected page and survives navigation. To have mobile signals present on a page's very first request, pass device to new_page or navigate_page instead of calling this afterwards. Undo with reset_emulation.

evaluate_scriptA

Run JavaScript inside the page and get the result back as JSON.

The escape hatch for anything the other tools do not cover: reading computed styles, calling a page's own JS API, or extracting structured data in a single round trip instead of a dozen snapshot-and-click cycles.

Without args the function runs at page level in the main world. With args, the given uids become real element references, which is how you operate on one specific element from a snapshot.

Return values must be JSON-serialisable — DOM nodes, functions and circular structures are not, so map them to plain values inside the function. Errors are returned as a string beginning with "Error:" rather than raised.

fillA

Set the value of an input, textarea or select element.

Clears the field first, then types the value character by character so that input events fire and React/Vue-style controlled components actually register the change (assigning .value directly does not). For , the option is selected by value and a change event is dispatched.

Use type_text instead when you want to append to a focused field rather than replace its contents. For several fields at once, fill_form does it in one round trip.

fill_formA

Fill several form fields in a single call.

Behaves like fill per field, but costs one round trip instead of one per field. Fields are processed in the order given, which matters on pages that reveal or enable later fields in response to earlier ones.

A field that fails does not abort the rest: the response reports success or the specific error per uid, so a partially filled form is always visible rather than silent.

get_console_messageA

Read one console message in full, by id.

list_console_messages truncates every entry to 200 characters; use this to get a complete stack trace or error payload.

Requires enable_console_collection to have been called for this page. The server leaves the CDP Runtime domain disabled by default because enabling it is itself something sites can detect.

get_cookiesA

List browser cookies with their domain, path and secure flag.

Reads the whole browser cookie jar, not just the current page, unless you pass url. Values are returned in full, so treat the output as sensitive — it contains live session tokens.

To carry these across a browser restart use save_session, or switch to a persistent profile with use_profile.

get_local_storageA

Read all localStorage entries for the current page's origin.

Values are truncated to 200 characters each; for one entry in full use evaluate_script with () => localStorage.getItem("key").

localStorage is scoped per origin, so this returns nothing on about:blank — navigate to the site first. sessionStorage is not included.

get_network_requestA

Inspect one network request: URL, method, resource type and both bodies.

Often the fastest way to get structured data out of a site: find the page's own API call with list_network_requests, then read the JSON it already received here, instead of scraping the rendered DOM.

Inline bodies are truncated at 5000 characters, so pass a file path for anything larger. Response bodies are only available while Chrome still holds them in its buffer — for a long-finished request the entry may still be listed while its body is already gone.

handle_dialogA

Answer an open JavaScript dialog — alert, confirm or prompt.

A dialog blocks the page and every subsequent tool call until it is handled, so call this as soon as one appears. Returns an error if no dialog is open.

beforeunload dialogs triggered by navigating away are handled automatically via navigate_page's handle_before_unload parameter; this tool is for dialogs the page opens by itself.

hoverA

Move the mouse over an element without clicking it.

This is how you open hover-triggered menus, tooltips and dropdowns before reading what they reveal — pass include_snapshot=true to get the revealed content back in the same call.

Only moves the pointer; it does not scroll. If the element sits outside the viewport, call scroll_to_selector first.

list_console_messagesA

List console output collected for the selected page.

Requires enable_console_collection first. Console capture is opt-in because it needs the CDP Runtime domain, which some sites use to detect an attached debugger; without it this returns a reminder instead of messages.

Each line is prefixed with its id and truncated to 200 characters — pass the id to get_console_message for the full text. Only the most recent 1000 messages are retained.

enable_console_collectionA

Start capturing console output on the current page.

Call this before list_console_messages or get_console_message — neither returns anything until collection is on. Network capture, by contrast, is always on and needs no equivalent call.

This is opt-in rather than automatic because it enables the CDP Runtime domain, which some anti-bot scripts probe for to detect an attached debugger. Leave it off while stealth matters, and use disable_console_collection when you are done debugging.

Applies per page: a new tab needs its own call.

disable_console_collectionA

Stop capturing console output on the current page.

Turns the CDP Runtime domain back off, which restores the quieter, harder-to-detect default — worth doing once you are finished debugging and the page still has anti-bot checks ahead of it.

Messages already collected stay readable; only new ones stop arriving.

list_network_requestsA

List the network requests the selected page has made.

Collection is automatic — unlike console capture, nothing needs enabling.

The main use is finding the JSON API a page calls: filter with resource_types=["XHR", "Fetch"], then pass the id from the square brackets to get_network_request to read the actual response body. That is usually far cheaper and more reliable than scraping the rendered DOM.

Only the most recent 1000 requests are retained, and each URL is truncated to 150 characters in this listing.

list_pagesA

List every open browser tab with its index, URL and title.

The index shown here is the page_id taken by select_page and close_page. Indices are positional and shift whenever a tab opens or closes, so re-read them here rather than reusing an index from an earlier call.

Tools act on the page chosen with select_page, or on the most recently opened tab if none was selected.

navigate_pageA

Navigate the selected page — load a URL, or go back, forward or reload.

Navigating rotates the collected logs: the previous page's console and network entries move into the preserved history (the last 3 navigations are kept), still reachable via include_preserved_* on the list tools.

Passing device applies the emulation before the request is sent, so the server sees mobile signals on the very first byte — calling emulate_device afterwards is too late for that first request.

Returns the resulting URL together with all open pages. This reuses the current tab; use new_page to open an additional one.

new_pageA

Open a new browser tab and load a URL.

Chrome always starts with one tab, so the first call here reuses that empty startup tab rather than leaving a stray blank page behind for the rest of the session. A blank tab sitting among others is left alone, and an isolated context always gets a target of its own.

Unless background is set, the new page becomes the selected page for every subsequent tool call.

Passing device applies emulation before the first real request, so mobile signals are present from the very first byte — which calling emulate_device afterwards cannot achieve.

performance_start_traceA

Record a Chrome performance trace of page load or interaction.

Captures the same DevTools timeline categories the Performance panel uses: rendering, scripting, loading, screenshots and the V8 CPU profile.

With the defaults (reload + auto_stop) this is one self-contained call that reloads, records for ~5s and returns. Only one trace can run at a time.

Pass file_path to keep the data — without it you get just an event count, which confirms the trace ran but says nothing about what it measured. The output is raw trace JSON: load it via DevTools -> Performance -> Load profile.

performance_stop_traceA

Stop the running performance trace and collect its data.

Only needed when performance_start_trace was called with auto_stop=false — otherwise the trace has already ended and this returns an error.

Waits up to 30 seconds for Chrome to flush its buffered events.

press_keyA

Send a key press or keyboard shortcut to the page.

Goes to whatever currently holds focus, so click or fill the target element first — with nothing focused the key lands on the document body.

Modifiers are held around the main key with the proper modifier bitmask, so real chords such as Control+A or Control+Shift+R register as shortcuts instead of arriving as unrelated key presses.

For entering text use fill (replaces the field) or type_text (appends to it); this tool is for single keys and shortcuts.

resize_pageA

Resize the browser window so the page gets the given dimensions.

Moves the actual OS window, which is what you want for responsive-layout checks that should also come out right in a screenshot.

To emulate a viewport size without touching the window — including device pixel ratio, the mobile flag and touch — use emulate's viewport parameter or emulate_device. Those are what a site's media queries and fingerprinting read as a genuine device change.

scroll_pageA

Scroll the page up or down by a percentage of the viewport.

The way to trigger lazy-loaded content and infinite scroll; take a fresh take_snapshot afterwards to see what was added.

To bring one known element into view, scroll_to_selector is more precise. click already scrolls to its target, so no scrolling is needed before it.

select_pageA

Choose which open tab every subsequent tool call acts on.

Without a selection, tools act on the most recently opened tab. Selecting makes that choice explicit and sticky — needed when a click opened a tab you now want to drive, or when working across several sites at once.

Indices come from list_pages and shift as tabs open and close, so the response lists them again. If the selected tab is later closed, the selection is dropped and the default applies again.

set_cookieA

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.

set_local_storageA

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.

take_memory_snapshotA

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.

take_screenshotA

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.

take_snapshotA

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.

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.

For plain page text without uids, get_page_content is cheaper; to find elements by CSS selector, use query_selector.

type_textA

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.

upload_fileA

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.

Many sites hide the real behind a styled button or drop zone. It is still in the snapshot; if you cannot spot it, locate it with query_selector("input[type=file]").

wait_forA

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.

save_sessionA

Save cookies, localStorage and open page URLs to a reusable file.

This is how you keep a login obtained interactively, so a later run can skip the login flow entirely — restore it with load_session.

Stored as JSON under ~/.nodriver-mcp/sessions/. That file holds live session tokens in plain text, so treat it as a credential.

Only the current page's origin contributes localStorage. For a login meant to survive without an explicit restore step, a persistent profile (create_profile + use_profile) is the sturdier option.

load_sessionA

Restore cookies and localStorage from a saved session file.

Use it at the start of a run to arrive already logged in. The page is first navigated to the saved origin so localStorage lands where it belongs, then reloaded so the restored cookies take effect.

Cookies that no longer apply are skipped rather than failing the whole restore, and the response reports how many were actually restored. Expired tokens still leave you logged out, so check the page afterwards.

list_sessionsA

List saved session files with their name, save time and contents.

Shows the filename to hand to load_session, along with how many cookies and localStorage entries each one holds, newest first.

Sessions live in ~/.nodriver-mcp/sessions/ and are never cleaned up automatically, so old logins accumulate there over time.

get_page_contentA

Get the page's visible text, or its full HTML.

The cheapest way to read a page when you only need content and not the uids take_snapshot provides: no accessibility tree is built, and the text form carries no markup overhead.

Returns the DOM as it stands right now, so on pages that render asynchronously call wait_for or wait_for_selector first.

Use take_snapshot when you intend to interact with elements, and query_selector when you want specific elements rather than the whole page.

query_selectorA

Find elements by CSS selector; list their tag, text, href, id and class.

The efficient way to pull a repeated structure off a page — search results, product tiles, table rows — without paying for a full snapshot.

Returns a compact listing only. It yields no uids, so it cannot drive clicks: take a snapshot when you need to interact, or operate on the elements directly via evaluate_script.

Element text is truncated to 200 characters.

scroll_to_selectorA

Scroll the first element matching a CSS selector into view, centered.

More precise than scroll_page when you already know what you are looking for, and the usual preparation for click_at, which needs its target inside the viewport.

click scrolls to its own target, so this is unnecessary before it. Reports whether anything matched instead of failing silently.

block_resourcesA

Block images, fonts, stylesheets or media to speed up page loads.

Removes most of the bytes on a media-heavy page, which makes scraping several times faster and far cheaper over a metered or proxied connection.

Blocking stylesheets breaks layout, so anything that depends on element geometry becomes unreliable — click_at, element screenshots, and the visible check in wait_for_selector. Text extraction is unaffected.

Applies to the current page session and stays in effect across navigations until called again with no types.

wait_for_selectorA

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.

save_pdfA

Export the current page to a PDF using Chrome's print-to-PDF.

Renders the entire document rather than just the viewport, and applies the page's print stylesheet — so the result can differ from the screen layout.

A good way to archive a rendered page as one file. For a pixel-accurate copy of what is on screen, use take_screenshot with full_page instead.

clear_cookiesA

Delete every cookie in the browser.

Browser-wide, not per site and not per tab — this logs you out of everything at once, with no undo. Restore a previous state with load_session if you have one saved.

Useful for testing a first-time-visitor flow or resetting a consent banner decision. localStorage is left untouched, so sites that keep state there may still recognise you.

set_browser_flagsA

Change Chrome's launch flags at runtime, and show the current ones.

Call with no arguments at all to just read the effective configuration — that form changes nothing.

These are launch-time flags, so applying them restarts Chrome and closes every open page; on an ephemeral profile that also drops its cookies. Values set here override the NODRIVER_ENABLE_* environment variables.

manage_extensionsA

List, enable, disable or load Chrome extensions.

Two separate mechanisms are in play. The master switch: Chrome runs with --disable-extensions by default, so extensions installed in the profile stay dark until it is turned on. And unpacked extensions loaded from a folder on disk. The master switch governs both — with it off, unpacked extensions stay registered but do not load.

"load" only works on Chromium or Chrome for Testing. Official Google Chrome builds have ignored --load-extension since v137 (still true on Chrome 151, even with --enable-unsafe-extension-debugging), and this tool says so rather than pretending it worked. On official Chrome the working path is: install the extension once from the Web Store into a persistent profile, then switch it on with "on".

Extensions only persist in a persistent profile — on the default ephemeral one nothing can stay installed.

list_profilesA

List persistent Chrome profiles and show which one is active.

By default the browser runs on a fresh ephemeral temp profile that is deleted when the session ends. That default is what lets several nodriver instances — Claude Desktop, Claude Code, the VS Code extension — run at the same time without fighting over one profile directory.

Persistent profiles keep cookies, logins and installed extensions across sessions. Create one with create_profile, switch with use_profile, and return to ephemeral with use_temp_profile.

create_profileA

Create a named persistent Chrome profile — a reusable user-data dir.

A persistent profile keeps cookies, logins and Web Store extensions between runs, so a site you log into once stays logged in. It is the sturdier alternative to save_session / load_session.

Creating one is harmless by itself: nothing changes until you activate it, here or via use_profile. An existing profile of the same name is left untouched rather than overwritten. Profiles live under ~/.nodriver-mcp/profiles/.

Only one browser instance can use a given profile at a time, so give concurrent setups different names.

use_temp_profileA

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.

use_profileA

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.

delete_profileA

Permanently delete a persistent Chrome profile directory.

Irreversible: the profile's cookies, logins, history and installed extensions are removed from disk, with no undo.

The active profile cannot be deleted — switch away with use_temp_profile first.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/andresolbach/nodriver-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server