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 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| 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 |
|---|---|
| list_framesA | List the page's frames, so their content can be reached at all. Chrome's accessibility tree stops at a frame boundary, and document.querySelector never crosses one, so anything inside an iframe — payment fields, consent walls, embedded editors, CAPTCHA widgets — is invisible to take_snapshot and query_selector by default. Index 0 is always the main frame. Pass an index or frame id as the |
| 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. Needs opencv, which is an optional extra: install nodriver-mcp[cf], or this reports that it is missing rather than failing obscurely. Many challenges clear by themselves after a few seconds, so wait_for(["some text from the real page"]) is worth trying first — and this says so when it looks at the page and finds no challenge at all. |
| 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 first, so it need not be visible beforehand. Sends real CDP input events, so the page sees Two other situations force the scripted fallback regardless: a touch-emulated target, where CDP mouse input can crash the renderer, and a CDP click that times out or errors. 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 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. It keeps the browser itself. The name, its selected profile and its launch flags survive for the next relaunch, so the name goes on counting against the 12-browser limit. When an agent is finished for good, call shutdown_browser instead: it also releases the name. |
| dragA | Drag one element onto another (press, move, release). Both uids must come from the same take_snapshot. Both ends are scrolled into view and hit-tested first, and the pointer is moved in steps with the button held, which is what mouse-driven sortables and sliders listen for. This drives the mouse. Native HTML5 drag-and-drop — the dataTransfer kind — is a separate protocol that a synthetic mouse does not trigger in Chrome; if nothing happens on a page that uses it, that is why. |
| 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. Settings already in force are
carried over rather than dropped: a user_agent on its own keeps the client
hints a device preset established, and a viewport on its own keeps its touch
and mobile flags unless you clear them with 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. A script that throws comes back as a failed call carrying the JavaScript error, not as a result you have to inspect for the word "Error". Pass script_path to run a function kept in a .js file, and file_path to write the result to disk instead of into the conversation. |
| fillA | Set the value of an input, textarea, select or contenteditable element. Selects whatever is already in the field and types over it, character by
character, so The value is read back afterwards. If it did not land you get an error, not a success you cannot trust — a field can be read-only, disabled, covered by an overlay, or rewrite what you type, and silently reporting success would send you looking for the problem several steps later. 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. |
| set_checkedA | 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
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. |
| select_optionA | Choose an option in a , by value, visible label or index. fill also works on a , but only matches the value attribute, which is not what a snapshot shows you — so it had to be guessed. This matches what you can actually see, and when nothing matches it lists the real options instead of only saying no. |
| 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. The element is scrolled into view and hit-tested first, the same way click does it: a point that lands on a sticky header instead opens that header's menu, not the one you asked for. If something covers the element, this says what, rather than hovering the wrong thing. |
| 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; the selected one is marked here. Each entry also carries the page's CDP targetId, which is the stable identity of a tab: unlike the index it survives other tabs opening and closing, and it is what CDP-level tooling and logs refer to. |
| 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. The response reports how far the page actually moved and whether the end has been reached, so a scroll loop has something to stop on: "did not move" means you are at the end, and a page height that keeps growing means more content is still loading. 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 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. |
| 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. 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. |
| 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. |
| get_computed_stylesA | Read an element's computed styles, as the browser actually resolved them. Computed styles are the end result of the whole cascade, so this answers
what a stylesheet alone cannot: why an element is invisible
( Also reports the element's box: position, size, and whether it is currently in the viewport. An element with zero width or height is present in the DOM but not rendered, which is the usual reason a click appears to do nothing. By default only properties that differ from the browser default are returned, because the full set runs to several hundred entries and buries the interesting ones. |
| 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
Requests are matched on the resource type Chrome reports for them, so an asset served from an extension-less URL is still blocked, and blocking one type never catches another. Applies to ONE TAB — the selected page — and stays in effect there across navigations until called again with no types. A tab opened afterwards with new_page starts unblocked and needs its own call. |
| capture_bodiesA | Save every matching response body to disk the moment it arrives, in the background. get_network_request asks Chrome for a body only when you ask — by then it is often gone: Chrome drops bodies over ~10 MB at once, and a navigation to another site drops the rest. This takes each body while the response is still paused, before the page even sees it, and writes it to a file. Covers the whole browser until stopped: every tab, including ones the page opens itself, plus dedicated workers and service workers. Each body is its own file; capture.jsonl beside them records URL, method, POST data, status and headers per file. list_network_requests marks captured requests "saved", and get_network_request falls back to the file. Not captured: responses from Chrome's cache (they never touch the network — reload with ignore_cache), redirects, and event streams inside workers. Event streams on a page are written as they arrive; every other matching response reaches the page only once complete, so keep the pattern off long-running downloads. |
| export_harA | Write the network log to a HAR 1.2 file — importable into Burp, ZAP, Chrome DevTools, Charles and anything else that reads HAR. Headers are the ones that went over the wire, Cookie and Set-Cookie included, with POST bodies, status, timing, server IP and WebSocket frames. Response bodies come from capture_bodies' files where available — start a capture first for a HAR that has every body — and otherwise from Chrome's buffer, which only still holds recent ones on the current tab. The file holds live session cookies and tokens; treat it like a password. |
| audit_securityA | Passive security review of the selected page, from what the browser already has — a starting point for a manual test, not a verdict. WARN marks a concrete weakness, INFO context worth a look. Headers come from the recorded page load; if it was not recorded (a tab the server did not open), the page URL is fetched once from the page and the answer says so. Chrome's issues are collected from the moment the server first touches a tab, so on a tab it had not touched, reload the page for a full list. Cookie values are never printed — use get_cookies for those. |
| inspect_storageA | Everything the selected page's origin keeps in the browser: localStorage, sessionStorage, IndexedDB, Cache Storage, service workers and quota usage. Flags values that look like credentials — by key name, JWT shape or a "Bearer" prefix, also inside JSON values — because anything in web storage is readable by every script on the origin. JWTs are decoded (algorithm, expiry, claims; the signature is not checked). Read through DevTools, not page JavaScript, except for the list of service worker registrations. IndexedDB and Cache Storage are listed with their sizes, not dumped; use evaluate_script to read records. |
| search_bodiesA | Find where a value shows up in the traffic capture_bodies recorded — in which response it first arrived, and which later requests sent it back. The usual question when following data through an app: where did this ID, token or price come from, and where does it go next. Results are in capture order, so the first hit is the first response that carried it; POST data and URLs from capture.jsonl are searched too, as the "sent" side. |
| 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. |
| set_proxyA | 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. |
| 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_running_browserA | 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: then this tool attaches to it. Every tool then acts on that browser and its real tabs.
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. |
| 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. |
| list_browsersA | List every browser this server currently holds: its name, whether its Chrome is running, which profile it uses, how many tabs it has and what they show. Reports without starting anything. There is always a browser called "default", which runs in the server process. Others are created implicitly, by passing a new name as the chrome_running=false means the browser exists but has no Chrome, either because no tool has needed one yet or because close_browser was called. The next call relaunches it. |
| shutdown_browserA | 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. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 70 tools
Every tool targets a distinct action and resource, with descriptions that explicitly cross-reference alternatives (e.g., fill vs type_text vs press_key; take_snapshot vs get_page_content). Even subtle pairs like close_browser vs shutdown_browser are carefully delineated. There is little risk of an agent misselecting a tool.
The overwhelming majority follow a verb_noun snake_case pattern (list_pages, create_profile, set_cookie). Minor deviations exist: cf_verify uses an abbreviation+verb, performance_start_trace inverts the verb position, and a few bare verbs (fill, click, hover, drag) stand alone. These are readable but break the strict pattern.
70 tools is far beyond the typical well-scoped server and makes selection expensive for an agent. Although the domain of browser automation is broad, many niche features (audit_security, capture_bodies, inspect_storage, manage_extensions) could be separate services or consolidated. The count feels heavy rather than lean.
The surface covers the full browser automation lifecycle: pages, navigation, interaction, waiting, scrolling, forms, frames, dialogs, network, performance, storage, sessions, profiles, emulation, and browser configuration. There are no dead ends; the only conceivable gaps (e.g., download handling) can be worked around with evaluate_script or other existing tools. This is one of the most complete tool sets imaginable for its domain.