From 753846cca13b6ead76603781b09f725b3236ae79 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 13 Sep 2026 20:30:02 -0400 Subject: [PATCH 1/5] Update the MCP Server --- mcp_servers/server.py | 134 +++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 53 deletions(-) diff --git a/mcp_servers/server.py b/mcp_servers/server.py index 2b2a63b4161..eadee581c33 100644 --- a/mcp_servers/server.py +++ b/mcp_servers/server.py @@ -700,11 +700,11 @@ def click_element( parent element. Selection behavior: - - `nth` is 1-based and takes precedence over every other click mode. - - Otherwise, `all_matches=True` clicks every currently visible match. - - Otherwise, `only_if_visible=True` clicks only if a match is visible. - - Otherwise, `parent_selector` scopes the click to a nested element. - - With none of the above, performs a normal SeleniumBase click. + - `nth` is 1-based and takes precedence over every other click mode. + - Otherwise, `all_matches=True` clicks every currently visible match. + - Otherwise, `only_if_visible=True` clicks only if a match is visible. + - Otherwise, `parent_selector` scopes the click to a nested element. + - With none of the above, performs a normal SeleniumBase click. Args: selector: CSS selector, XPath selector, or supported SeleniumBase @@ -720,8 +720,8 @@ def click_element( in order of appearance. Ignored when `nth` is provided. Use only when multiple clicks are intentionally desired, such as for clicking all the checkboxes in a section of a webpage. - If a click induces page navigation, then subsequent clicks are - cancelled. + If any of the click actions induces page navigation, then + subsequent clicks are cancelled without any exceptions raised. only_if_visible: If True, click only when the target is already visible; do not wait for it to become visible. @@ -737,14 +737,23 @@ def click_element( indexed click. Default: True. Examples: - - Click one element: `click_element("button.submit")` - - Click the 2nd matching element: `click_element("button", nth=2)` - - Click all visible matches: - `click_element(".dismiss", all_matches=True)` - - Click only if already visible: - `click_element("#menu", only_if_visible=True)` - - Click inside a container: - `click_element(".item", parent_selector="#result")` + - Click the first button: `click_element("button")` + - Click the 2nd button: `click_element("button", nth=2)` + - Click all checkboxes: + `click_element('input[type="checkbox"]', all_matches=True)` + - Click the first visible link: + `click_element("a", only_if_visible=True)` + - Click the first button that's inside the first iframe: + `click_element("button", parent_selector="iframe")` + + Error behavior: + With the exception of using 'only_if_visible=True', if there's no + matching element found within the timeout, then @handle_sb_errors + returns details from the exception raised. + + When not to use: + - Do not use this tool if you need to hover an element first before + clicking; use hover_action with action="hover_and_click" instead. """ sb = _get_sb() @@ -815,11 +824,15 @@ def hover_action( Returns: A confirmation message describing the performed operation's result. - Errors: + Error behavior: If a required element cannot be found or interacted with within the applicable wait period, or if an error occurs during the action, the resulting exception message is returned through @handle_sb_errors. + Failing actions such as failed hover_and_click will raise exceptions. + When not to use: + - Do not use this tool to click if you don't need to hover an element + before clicking another; use 'click' instead. """ sb = _get_sb() @@ -1410,11 +1423,14 @@ def scroll_page( up/down scrolling. For example, amount=25 scrolls approximately one quarter of the viewport height. - Values greater than 100 for `amount` are allowed. - For example, 200 means approximately two viewport heights. + Notes: + Values greater than 100 for `amount` are allowed. + For example, 200 means approximately two viewport heights. - Use focus_element(action="scroll_to_element") when the goal is to reveal - a specific element rather than scroll the page by a relative amount. + Tool selection: + - Need to reveal a specific element -> + use 'focus_element' with action="scroll_to_element". + - Need to scroll the page by a relative amount -> use 'scroll_page'. """ sb = _get_sb() @@ -1475,8 +1491,9 @@ def manage_window( height: Window height for "set_rect". - Use this tool for browser-window geometry and state. - Use `manage_tabs` for switching between browser tabs. + Notes: + Use this tool for browser-window geometry and state. + Use `manage_tabs` for switching between browser tabs. """ sb = _get_sb() @@ -1507,12 +1524,12 @@ def manage_window( @handle_sb_errors def manage_tabs( action: Literal[ - "list", - "open", - "switch", - "switch_newest", - "close_active", - ] = "list", + "list_tabs", + "open_new_tab", + "switch_to_tab", + "switch_to_newest_tab", + "close_active_tab", + ] = "list_tabs", url: str | None = None, tab_index: int | None = None, switch_to: bool = True, @@ -1524,25 +1541,34 @@ def manage_tabs( Args: action: - - "list": Return each tab's index, URL, and title. - Use this to find the tab_index for "switch". - - "open": Open a new tab, optionally navigating it to `url`. - - "switch": Switch to the tab at tab_index from "list". - - "switch_newest": Switch to the newest tab. - - "close_active": Close the active tab. + - "list_tabs": Return each tab's index, URL, and title. + Use this to find the tab_index for "switch_to_tab". + - "open_new_tab": Open a new tab, optionally navigating to `url`. + - "switch_to_tab": Switch to the tab at tab_index from "list_tabs". + - "switch_to_newest_tab": Switch to the newest tab. + - "close_active_tab": Close the active tab. This action must be + followed by a 'manage_tabs' action that switches to a new + tab, such as "switch_to_tab" or "switch_to_newest_tab". + + url: URL for "open_new_tab". If not provided, "about:blank" is used. - url: URL for "open". + tab_index: Tab index from "list_tabs" that is only used for the + "switch_to_tab" action.) - tab_index: Tab index from "list" for "switch". + switch_to: If using "open_new_tab", switch to the new tab when True. - switch_to: For "open", switch to the new tab when True. + Notes: + Tab indexes are session-relative and may change after tabs are opened + or closed. Use "list_tabs" to get current indexes before switching + by index. - Tab indexes are session-relative and may change after tabs are opened or - closed. Use "list" to get current indexes before switching by index. + Error behavior: + If there's an error during any of the tab actions, then + @handle_sb_errors will propagate the exception as an error message. """ sb = _get_sb() - if action == "list": + if action == "list_tabs": tabs = sb.get_tabs() return [ { @@ -1553,11 +1579,13 @@ def manage_tabs( for i, t in enumerate(tabs) ] - if action == "open": + if action == "open_new_tab": + if not url: + url = "about:blank" sb.open_new_tab(url=url, switch_to=switch_to) return f"Opened new tab (url={url!r}, switch_to={switch_to})" - if action == "switch": + if action == "switch_to_tab": if tab_index is None: return ( "Error: action='switch' requires tab_index " @@ -1575,17 +1603,17 @@ def manage_tabs( sb.switch_to_tab(tabs[tab_index]) return f"Switched to tab {tab_index}" - if action == "switch_newest": + if action == "switch_to_newest_tab": sb.switch_to_newest_tab() return "Switched to newest tab." - if action == "close_active": + if action == "close_active_tab": sb.close_active_tab() return "Closed active tab." return ( - f"Error: unknown action '{action}'. Use 'list', 'open', 'switch', " - f"'switch_newest', or 'close_active'." + f"Error: unknown action '{action}'. Use 'list_tabs', 'open_new_tab', " + f"'switch_to_tab', 'switch_to_newest_tab', or 'close_active_tab'." ) @@ -1601,12 +1629,12 @@ def solve_captcha() -> str: This tool attempts to interact with CAPTCHA controls such as Cloudflare Turnstile, reCAPTCHA, hCaptcha, DataDome Slider, or FriendlyCaptcha via - the Chrome DevTools Protocol (CDP), which is stealthier than JavaScript - actions because CDP actions can avoid triggering `isTrusted: false`. + the Chrome DevTools Protocol (CDP), which is usually stealthier than + JavaScript because CDP actions can avoid triggering `isTrusted: false`. This tool automatically detects the coordinates of CAPTCHA checkboxes for determining the correct location to perform the click. If no CAPTCHA - is detected on the current page, then no click action is performed. + is detected on the current page, then no click action is attempted. The tool does not guarantee that the CAPTCHA was solved. Some CAPTCHA controls are embedded inside shadow DOM or otherwise do not expose an @@ -1616,13 +1644,13 @@ def solve_captcha() -> str: Tool workflow: 1. Inspect the webpage with get_content when you need to determine whether CAPTCHA-related controls are present. - 2. Call solve_captcha to attempt the CAPTCHA interaction. - 3. Use get_page_info, get_content, check_if_condition, - or manage_cookies to inspect resulting page/session state. + 2. Call 'solve_captcha' to attempt the CAPTCHA interaction. + 3. Use 'get_page_info', 'get_content', 'check_if_condition', + or 'manage_cookies' to inspect resulting page/session state. Returns: A message confirming that the CAPTCHA interaction was attempted. - (There's no guarantee that the CAPTCHA challenge was solved.) + The message is the same for both successful and failed attempts. """ sb = _get_sb() sb.solve_captcha() From fda62a2b83b9de176342f1c21c5f72032ffd4628 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 13 Sep 2026 20:30:31 -0400 Subject: [PATCH 2/5] Update the MCP Server ReadMe --- mcp_servers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp_servers/README.md b/mcp_servers/README.md index 66b022220eb..6e377aca53b 100644 --- a/mcp_servers/README.md +++ b/mcp_servers/README.md @@ -168,7 +168,7 @@ Tools here are grouped around a shared `selector` convention. Several near-ident | Assertions | `assert_condition(check: element_present/element_visible/text_visible/title/url/url_contains)` | | Cookies & storage | `manage_cookies(action: get_all/clear/save/load)`, `manage_storage(storage: local/session, action: get/set)` | | Scrolling | `scroll_page(direction: up/down/top/bottom, amount)` | -| Windows & tabs | `manage_window(action: get_rect/set_rect/maximize/minimize)`, `manage_tabs(action: list/open/switch/switch_newest/close_active)` | +| Windows & tabs | `manage_window(action: get_rect/set_rect/maximize/minimize)`, `manage_tabs(action: list_tabs/open_new_tab/switch_to_tab/switch_to_newest_tab/close_active_tab)` | | Captcha | `solve_captcha` | | Output & misc | `save_page(format: screenshot/html/pdf)`, `run_javascript` | From 37cffca4f6333d87a9f86a3c1322922ddec85d0b Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 13 Sep 2026 20:31:24 -0400 Subject: [PATCH 3/5] Update MCP Server versioning --- mcp_servers/pyproject.toml | 2 +- server.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mcp_servers/pyproject.toml b/mcp_servers/pyproject.toml index 01be6334101..b52f0fdf255 100644 --- a/mcp_servers/pyproject.toml +++ b/mcp_servers/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "seleniumbase-mcp" -version = "1.3.4dev0" +version = "1.3.5dev0" description = "MCP server exposing SeleniumBase CDP Mode as tools for MCP clients." readme = "README.md" requires-python = ">=3.10" diff --git a/server.json b/server.json index 0d8db97b280..5fbbe4fbf8b 100644 --- a/server.json +++ b/server.json @@ -3,7 +3,7 @@ "name": "io.github.seleniumbase/seleniumbase", "title": "SeleniumBase MCP", "description": "Stealthy browser automation, testing, and web-scraping via CDP Mode.", - "version": "4.54.4", + "version": "4.54.5", "repository": { "url": "https://github.com/seleniumbase/SeleniumBase", "source": "github" @@ -13,7 +13,7 @@ { "registryType": "pypi", "identifier": "seleniumbase", - "version": "4.54.4", + "version": "4.54.5", "transport": { "type": "stdio" }, From b6cd1e7c787cbe2c580e624e3576112d0a0de959 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 13 Sep 2026 20:32:01 -0400 Subject: [PATCH 4/5] Refresh optional Python dependencies --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5bfc8d98176..e1c7bd2a8bc 100755 --- a/requirements.txt +++ b/requirements.txt @@ -65,7 +65,7 @@ rich>=15.0.0,<16 # --- Testing Requirements --- # # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) -coverage>=7.16.0 +coverage>=7.16.1 pytest-cov>=7.1.0 flake8==7.3.0 mccabe==0.7.0 diff --git a/setup.py b/setup.py index 16cf4072305..9c829b2ccb4 100755 --- a/setup.py +++ b/setup.py @@ -240,7 +240,7 @@ # pip install -e .[coverage] # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ - 'coverage>=7.16.0', + 'coverage>=7.16.1', 'pytest-cov>=7.1.0', ], # pip install -e .[flake8] From 4c0aaa7ca4fae594cc2826d3bdbb08cb91ea8ab3 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Sun, 13 Sep 2026 20:33:12 -0400 Subject: [PATCH 5/5] Version 4.54.5 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 664d528f8ef..cc91c81bb3f 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.54.4" +__version__ = "4.54.5"