nodriver-mcp-server
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| NODRIVER_PROXY | No | Proxy server address. Default: None | |
| NODRIVER_HEADLESS | No | Headless mode (true/false) | false |
| NODRIVER_BROWSER_PATH | No | Chrome executable path. Auto-detected if not set. | |
| NODRIVER_USER_DATA_DIR | No | Explicit persistent Chrome profile dir (overrides the default). By default an ephemeral temp profile is used and auto-deleted per session. | |
| NODRIVER_ENABLE_TRANSLATE | No | Set 'true' to re-enable Chrome's Google Translate popup. Default: disabled. | false |
| NODRIVER_ENABLE_EXTENSIONS | No | Set 'true' to allow externally-installed Chrome extensions (and their prompts). Default: disabled. | false |
Capabilities
Features and capabilities supported by this server
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| 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 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 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 |
| 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 |
| 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 Presets are pixel_7, pixel_7_landscape and ipad_air (aliases are listed in
the Applies to the selected page and survives navigation. To have mobile signals
present on a page's very first request, pass |
| 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 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
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 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 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 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 |
| 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 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 Passing 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 Passing |
| 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 |
| 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.
|
| 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 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.
|
| 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
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
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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