seleniumbase-mcp 1.2.2__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seleniumbase-mcp
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: MCP servers exposing SeleniumBase as tools for MCP clients.
5
5
  Home-page: https://github.com/seleniumbase/seleniumbase-mcp
6
6
  Author: Michael Mintz
@@ -55,15 +55,15 @@ Classifier: Topic :: Utilities
55
55
  Requires-Python: >=3.10
56
56
  Description-Content-Type: text/markdown
57
57
  License-File: LICENSE
58
- Requires-Dist: seleniumbase[mcp]>=4.53.4
58
+ Requires-Dist: seleniumbase[mcp]>=4.53.6
59
59
  Requires-Dist: mcp[cli]<3.0.0,>=2.1.1
60
60
  Provides-Extra: deploy
61
61
  Requires-Dist: build>=1.0.0; extra == "deploy"
62
62
  Requires-Dist: twine>=7.0.0; extra == "deploy"
63
63
  Provides-Extra: uv
64
- Requires-Dist: uv>=0.12.8; extra == "uv"
64
+ Requires-Dist: uv>=0.12.9; extra == "uv"
65
65
  Provides-Extra: dev
66
- Requires-Dist: uv>=0.12.8; extra == "dev"
66
+ Requires-Dist: uv>=0.12.9; extra == "dev"
67
67
  Dynamic: author
68
68
  Dynamic: author-email
69
69
  Dynamic: classifier
@@ -281,9 +281,9 @@ in the loop at all. Reference:
281
281
  | Session | `start_browser(url, headless, use_chromium, browser_executable_path, incognito, guest, ad_block, proxy)`, `close_browser` |
282
282
  | Navigation | `navigate`, `navigate_history(action: back/forward/reload)`, `get_page_info` (running status, url, title, origin, user agent, history in one call) |
283
283
  | Finding & reading | `find_elements(selector, timeout, include_html)`, `get_content(selector, output_format: text/html/urls, include_shadow_dom)`, `get_attributes`, `check_state(check: present/visible/count/text_visible)` |
284
- | Interacting | `click(selector, nth, all_matches, only_if_visible, parent_selector, timeout, scroll)`, `hover_with_action(selector1, selector2, action: none/click/drag_and_drop)`, `fill_input(mode: type/append/set_value/fast_type/clear)`, `select_option(by: text/value/index)`, `focus_on(action: scroll_to_element/focus/highlight)` |
284
+ | Interacting | `click(selector, nth, all_matches, only_if_visible, parent_selector, timeout, scroll)`, `hover_with_action(selector1, selector2, action: none/click/drag_and_drop)`, `type_text(mode: fill_input/append/fast_type/set_value/clear_only)`, `select_option(by: text/value/index)`, `focus_on(action: scroll_to_element/focus/highlight)` |
285
285
  | Waiting | `wait_for(state: present/visible/not_visible/absent, text)` |
286
- | Assertions | `assert_that(check: element_present/element_visible/text/title/url/url_contains)` |
286
+ | Assertions | `assert_condition(check: element_present/element_visible/text_visible/title/url/url_contains)` |
287
287
  | Cookies & storage | `manage_cookies(action: get_all/clear/save/load)`, `manage_storage(storage: local/session, action: get/set)` |
288
288
  | Scrolling | `scroll(direction: up/down/top/bottom, amount)` |
289
289
  | Windows & tabs | `manage_window(action: get_rect/set_rect/maximize/minimize)`, `manage_tabs(action: list/open/switch/switch_newest/close_active)` |
@@ -201,9 +201,9 @@ in the loop at all. Reference:
201
201
  | Session | `start_browser(url, headless, use_chromium, browser_executable_path, incognito, guest, ad_block, proxy)`, `close_browser` |
202
202
  | Navigation | `navigate`, `navigate_history(action: back/forward/reload)`, `get_page_info` (running status, url, title, origin, user agent, history in one call) |
203
203
  | Finding & reading | `find_elements(selector, timeout, include_html)`, `get_content(selector, output_format: text/html/urls, include_shadow_dom)`, `get_attributes`, `check_state(check: present/visible/count/text_visible)` |
204
- | Interacting | `click(selector, nth, all_matches, only_if_visible, parent_selector, timeout, scroll)`, `hover_with_action(selector1, selector2, action: none/click/drag_and_drop)`, `fill_input(mode: type/append/set_value/fast_type/clear)`, `select_option(by: text/value/index)`, `focus_on(action: scroll_to_element/focus/highlight)` |
204
+ | Interacting | `click(selector, nth, all_matches, only_if_visible, parent_selector, timeout, scroll)`, `hover_with_action(selector1, selector2, action: none/click/drag_and_drop)`, `type_text(mode: fill_input/append/fast_type/set_value/clear_only)`, `select_option(by: text/value/index)`, `focus_on(action: scroll_to_element/focus/highlight)` |
205
205
  | Waiting | `wait_for(state: present/visible/not_visible/absent, text)` |
206
- | Assertions | `assert_that(check: element_present/element_visible/text/title/url/url_contains)` |
206
+ | Assertions | `assert_condition(check: element_present/element_visible/text_visible/title/url/url_contains)` |
207
207
  | Cookies & storage | `manage_cookies(action: get_all/clear/save/load)`, `manage_storage(storage: local/session, action: get/set)` |
208
208
  | Scrolling | `scroll(direction: up/down/top/bottom, amount)` |
209
209
  | Windows & tabs | `manage_window(action: get_rect/set_rect/maximize/minimize)`, `manage_tabs(action: list/open/switch/switch_newest/close_active)` |
@@ -23,16 +23,15 @@ access to the underlying browser-automation capabilities without
23
23
  having to choose between multiple near-identical tools.
24
24
 
25
25
  Tool-selection philosophy:
26
- - Use get_page_info for browser/page metadata such as URL, title, origin,
27
- and navigation history.
26
+ - Use get_page_info for browser/page metadata such as URL, title, or origin.
28
27
  - Use get_content for reading visible text or HTML.
29
28
  - Use find_elements for discovering and inspecting multiple matching
30
29
  elements as structured data.
31
- - Use check_state for an immediate, non-waiting state check.
30
+ - Use check_for_condition for an immediate, non-waiting state check.
32
31
  - Use wait_for when the agent needs to wait for a condition to become true.
33
- - Use assert_that when the agent needs to verify an expected condition and
34
- treat failure as an assertion error.
35
- - Use click/fill_input/select_option/hover_with_action/focus_on for
32
+ - Use assert_condition when the agent needs to verify an expected condition
33
+ and treat failure as an assertion error.
34
+ - Use click/type_text/select_option/hover_with_action/focus_on for
36
35
  interactions and element positioning.
37
36
  """
38
37
  from __future__ import annotations
@@ -93,7 +92,7 @@ def start_browser(
93
92
  """Launch a persistent SeleniumBase Pure CDP Mode browser session.
94
93
 
95
94
  This must be called before browser interaction tools such as navigate,
96
- get_content, click, fill_input, or find_elements. The same browser
95
+ get_content, click, type_text, or find_elements. The same browser
97
96
  session remains active across subsequent MCP tool calls until
98
97
  close_browser is called or the server process exits.
99
98
 
@@ -205,18 +204,33 @@ def start_browser(
205
204
  f"(url={url!r}, headless={effective_headless}, "
206
205
  f"use_chromium={use_chromium})"
207
206
  )
208
- except Exception as e:
209
- if _sb is not None:
210
- try:
211
- _sb.quit()
212
- except Exception:
213
- pass
214
- _sb = None
215
-
216
- return (
217
- f"Error starting browser: "
218
- f"{e.__class__.__name__} - {str(e).strip()}"
219
- )
207
+ except Exception:
208
+ # Retry once if the first launch attempt fails.
209
+ # (A retry helped when testing on Glama's MCP Inspector.)
210
+ try:
211
+ if _sb is not None:
212
+ try:
213
+ _sb.quit()
214
+ except Exception:
215
+ pass
216
+ _sb = None
217
+ _sb = sb_cdp.Chrome(url, **kwargs)
218
+ return (
219
+ f"Started Pure CDP Mode browser "
220
+ f"(url={url!r}, headless={effective_headless}, "
221
+ f"use_chromium={use_chromium})"
222
+ )
223
+ except Exception as e:
224
+ if _sb is not None:
225
+ try:
226
+ _sb.quit()
227
+ except Exception:
228
+ pass
229
+ _sb = None
230
+ return (
231
+ f"Error starting browser: "
232
+ f"{e.__class__.__name__} - {str(e).strip()}"
233
+ )
220
234
 
221
235
 
222
236
  @mcp.tool()
@@ -268,16 +282,14 @@ def get_page_info() -> dict | str:
268
282
  - title: The current document title.
269
283
  - origin: The current page origin (scheme, host, and port).
270
284
  - user_agent: The browser's current User-Agent string.
271
- - history: The browser navigation history for the current session.
272
285
 
273
286
  Tool selection:
274
- - Need URL, title, origin, User-Agent, or navigation history ->
275
- use get_page_info.
287
+ - Need URL, title, origin, or User-Agent -> use get_page_info.
276
288
  - Need visible page text or HTML -> use get_content.
277
289
  - Need information about matching elements -> use find_elements.
278
- - Need an immediate state check -> use check_state.
290
+ - Need an immediate state check -> use check_for_condition.
279
291
  - Need to wait for a condition -> use wait_for.
280
- - Need to verify an expected condition -> use assert_that.
292
+ - Need to verify an expected condition -> use assert_condition.
281
293
 
282
294
  Unlike a dedicated browser-status tool, get_page_info is the single
283
295
  source of browser/page metadata. If no browser session is active, it
@@ -296,7 +308,6 @@ def get_page_info() -> dict | str:
296
308
  "title": _sb.get_title(),
297
309
  "origin": _sb.get_origin(),
298
310
  "user_agent": _sb.get_user_agent(),
299
- "history": _sb.get_navigation_history(),
300
311
  }
301
312
  except Exception as e:
302
313
  return {
@@ -436,7 +447,7 @@ def find_elements(
436
447
  use get_content.
437
448
  - Need to click one of several matches -> use click with nth.
438
449
  - Need to know whether an element is present/visible ->
439
- use check_state.
450
+ use check_for_condition.
440
451
 
441
452
  Note:
442
453
  Element handles cannot be persisted across MCP calls. If you find
@@ -512,15 +523,14 @@ def get_content(
512
523
  URLs before navigating to them.
513
524
 
514
525
  Tool selection:
515
- - Need URL, title, origin, User-Agent, or navigation history ->
516
- use get_page_info.
526
+ - Need URL, title, origin, or User-Agent -> use get_page_info.
517
527
  - Need visible text -> use output_format="text".
518
528
  - Need page or element HTML -> use output_format="html".
519
529
  - Need URLs from the page or an element -> use output_format="urls".
520
530
  - Need structured information about matching elements ->
521
531
  use find_elements.
522
532
  - Need to check whether an element is present or visible ->
523
- use check_state.
533
+ use check_for_condition.
524
534
  - Need to wait for content to appear -> use wait_for.
525
535
  """
526
536
  sb = _get_sb()
@@ -549,15 +559,26 @@ def get_attributes(
549
559
  ) -> Any:
550
560
  """Read HTML attributes from a matching element.
551
561
 
562
+ Use this tool when you need the value of one or more HTML attributes
563
+ such as href, src, value, class, id, name, type, aria-label, or data-*.
564
+
552
565
  Args:
553
566
  selector: CSS selector or SeleniumBase text-matching selector for
554
567
  the target element.
555
568
  attribute: Specific HTML attribute to retrieve. When omitted, return
556
- all available attributes as a dictionary.
569
+ all HTML attributes of the element as a dictionary.
557
570
 
558
571
  Returns:
559
572
  The requested attribute value, or a dictionary containing all
560
- attributes when attribute is omitted.
573
+ HTML attributes of the element when attribute is omitted.
574
+
575
+ Tool selection:
576
+ - Need one or more HTML attribute values from a specific element ->
577
+ use this tool.
578
+ - Need to discover multiple matching elements or inspect their text ->
579
+ use 'find_elements'.
580
+ - Need visible text or HTML content -> use 'get_content'.
581
+ - Need to check presence or visibility -> use 'check_for_condition'.
561
582
 
562
583
  This is a read-only operation and does not modify the element.
563
584
  """
@@ -571,7 +592,7 @@ def get_attributes(
571
592
 
572
593
  @mcp.tool()
573
594
  @handle_sb_errors
574
- def check_state(
595
+ def check_for_condition(
575
596
  check: Literal["present", "visible", "count", "text_visible"] = "visible",
576
597
  selector: str = "body",
577
598
  text: str | None = None,
@@ -580,7 +601,7 @@ def check_state(
580
601
 
581
602
  Use this tool when you need an observation of the current state and do
582
603
  NOT want to wait for a condition. For waiting behavior, use wait_for.
583
- For an expectation that should fail as an assertion, use assert_that.
604
+ For an expectation that should fail as an assertion, use assert_condition.
584
605
 
585
606
  Args:
586
607
  check:
@@ -599,10 +620,10 @@ def check_state(
599
620
  count. Missing elements do not cause an exception for these checks.
600
621
 
601
622
  Tool selection:
602
- - Immediate yes/no/count observation -> use check_state.
623
+ - Immediate yes/no/count observation -> use check_for_condition.
603
624
  - Wait until a state becomes true/false -> use wait_for.
604
625
  - Verify an expected condition and fail when it is not met ->
605
- use assert_that.
626
+ use assert_condition.
606
627
 
607
628
  Note:
608
629
  Except for count's short lookup, this tool does not wait for elements
@@ -784,62 +805,64 @@ def hover_with_action(
784
805
 
785
806
  @mcp.tool()
786
807
  @handle_sb_errors
787
- def fill_input(
808
+ def type_text(
788
809
  selector: str,
789
810
  text: str = "",
790
811
  mode: Literal[
791
- "type",
812
+ "fill_input",
792
813
  "append",
793
- "set_value",
794
814
  "fast_type",
795
- "clear",
796
- ] = "type",
815
+ "set_value",
816
+ "clear_only",
817
+ ] = "fill_input",
797
818
  timeout: int | float | None = 7,
798
819
  ) -> str:
799
- """Enter, append, directly set, or clear text in a form control.
820
+ """Fill, append, fast-type, directly set, or clear a form control.
800
821
 
801
822
  Use this tool for input elements, textareas, and contenteditable elements.
802
823
 
803
824
  Args:
804
825
  selector: CSS selector or SeleniumBase selector identifying the
805
826
  input, textarea, or contenteditable element.
806
- text: Text to enter or set. Ignored when mode="clear".
827
+ text: Text to enter or set. Not used when mode="clear_only".
807
828
  mode:
808
- - "type": Clear the field and type text normally.
809
- - "append": Keep the existing value and send text as keystrokes.
829
+ - "fill_input": Clear the field and then type text normally.
830
+ - "append": Keep the existing value and add text as keystrokes.
831
+ - "fast_type": Clear the field and type text without pauses.
810
832
  - "set_value": Set the value directly and immediately. This can
811
833
  be useful for fast form filling but does not simulate normal
812
- key events.
813
- - "fast_type": Clear the field and type text quickly.
814
- - "clear": Empty the field; text is ignored.
834
+ key events. It can also be used to handle input sliders,
835
+ e.g. 'input[type="range"]'.
836
+ - "clear_only": Empty the text field; text is ignored.
815
837
  timeout: Maximum seconds to wait for the target element.
816
838
 
817
839
  Tool selection:
818
- - Normal human-like text entry -> mode="type".
819
- - Add text without clearing -> mode="append".
820
- - Directly set a value -> mode="set_value".
821
- - Fast typing -> mode="fast_type".
822
- - Empty a field -> mode="clear".
840
+ - Normal text entry to replace existing text -> mode="fill_input".
841
+ - Add text without clearing the field first -> mode="append".
842
+ - Fast typing to replace existing text -> mode="fast_type".
843
+ - Directly set a value (e.g. input slider) -> mode="set_value".
844
+ - Empty a field of all text -> mode="clear_only".
823
845
  """
824
846
  sb = _get_sb()
825
847
 
826
- if mode == "type":
848
+ if mode == "fill_input":
827
849
  sb.type(selector, text, timeout=timeout)
828
850
  elif mode == "append":
829
851
  sb.send_keys(selector, text, timeout=timeout)
830
- elif mode == "set_value":
831
- sb.set_value(selector, text, timeout=timeout)
832
852
  elif mode == "fast_type":
833
853
  sb.fast_type(selector, text, timeout=timeout)
834
- elif mode == "clear":
854
+ elif mode == "set_value":
855
+ sb.set_value(selector, text, timeout=timeout)
856
+ elif mode == "clear_only":
835
857
  sb.clear_input(selector, timeout=timeout)
836
858
  else:
837
859
  return (
838
860
  f"Error: unknown mode '{mode}'. "
839
- "Use 'type', 'append', 'set_value', 'fast_type', or 'clear'."
861
+ "Use 'fill_input', 'append', 'fast_type', "
862
+ "'set_value', or 'clear_only'."
840
863
  )
841
864
 
842
- return f"fill_input(mode={mode!r}) done for {selector}"
865
+ return f"type_text(mode={mode!r}) done for {selector}"
843
866
 
844
867
 
845
868
  @mcp.tool()
@@ -915,7 +938,7 @@ def focus_on(
915
938
  - Focus an element -> use focus_on(action="focus").
916
939
  - Highlight element for debugging -> use focus_on(action="highlight").
917
940
  - Click -> use click.
918
- - Type into a form control -> use fill_input.
941
+ - Type text into a text field -> use type_text.
919
942
  - Hover -> use hover_with_action.
920
943
  """
921
944
  sb = _get_sb()
@@ -957,8 +980,9 @@ def wait_for(
957
980
  Use this tool when the page is dynamic and an automation step must wait
958
981
  for a condition before continuing.
959
982
 
960
- Unlike check_state, this tool intentionally waits. Unlike assert_that,
961
- its purpose is synchronization rather than validating a test expectation.
983
+ Unlike check_for_condition, this tool intentionally waits.
984
+ Unlike assert_condition, its purpose is synchronization
985
+ rather than validating a test expectation.
962
986
 
963
987
  Args:
964
988
  state:
@@ -977,9 +1001,9 @@ def wait_for(
977
1001
  A confirmation when the requested condition is reached.
978
1002
 
979
1003
  Tool selection:
980
- - Check current state immediately -> use check_state.
1004
+ - Check current state immediately -> use check_for_condition.
981
1005
  - Wait for a state/content transition -> use wait_for.
982
- - Verify an expected value/condition -> use assert_that.
1006
+ - Verify an expected value/condition -> use assert_condition.
983
1007
  """
984
1008
  sb = _get_sb()
985
1009
 
@@ -1009,11 +1033,11 @@ def wait_for(
1009
1033
 
1010
1034
  @mcp.tool()
1011
1035
  @handle_sb_errors
1012
- def assert_that(
1036
+ def assert_condition(
1013
1037
  check: Literal[
1014
1038
  "element_present",
1015
1039
  "element_visible",
1016
- "text",
1040
+ "text_visible",
1017
1041
  "title",
1018
1042
  "url",
1019
1043
  "url_contains",
@@ -1025,24 +1049,26 @@ def assert_that(
1025
1049
  ) -> str:
1026
1050
  """Verify an expected browser condition and fail when it is not met.
1027
1051
 
1028
- Use this tool for explicit verification. Unlike check_state, which simply
1029
- reports the current state, assert_that treats a failed expectation as an
1030
- error. Unlike wait_for, URL/title checks do not wait.
1052
+ Use this tool for explicit verification. Unlike check_for_condition,
1053
+ which simply reports True or False on the current state,
1054
+ assert_condition treats a failed expectation as an error.
1055
+ Unlike wait_for, URL/title checks do not wait.
1031
1056
 
1032
1057
  Args:
1033
1058
  check:
1034
1059
  - "element_present": Verify selector identifies a present element.
1035
1060
  - "element_visible": Verify selector identifies a visible element.
1036
- - "text": Verify expected text within selector, or within the
1037
- whole HTML document when selector is omitted.
1061
+ - "text_visible": Verify expected text is visible within selector,
1062
+ or within the whole HTML document when selector is omitted.
1038
1063
  - "title": Verify the exact page title.
1039
1064
  - "url": Verify the exact current URL.
1040
1065
  - "url_contains": Verify that the current URL contains expected.
1041
1066
  selector: Element selector for element_present, element_visible, and
1042
- text checks.
1043
- expected: Expected text/title/URL value for text, title, url, and
1044
- url_contains.
1045
- exact: For check="text", require exact text rather than a substring.
1067
+ text_visible checks.
1068
+ expected: Expected text/title/URL value for text_visible, title, url,
1069
+ and url_contains.
1070
+ exact: For check="text_visible", require exact text
1071
+ rather than a substring.
1046
1072
  timeout: Maximum seconds to wait for element/text checks.
1047
1073
 
1048
1074
  Returns:
@@ -1053,16 +1079,18 @@ def assert_that(
1053
1079
  fails; the MCP error wrapper converts it to a descriptive result.
1054
1080
 
1055
1081
  Tool selection:
1056
- - Just inspect current state -> use check_state.
1082
+ - Just inspect current state -> use check_for_condition.
1057
1083
  - Wait for a condition to become true -> use wait_for.
1058
- - Verify that an expected condition is true -> use assert_that.
1084
+ - Verify that an expected condition is true -> use assert_condition.
1059
1085
  """
1060
1086
  sb = _get_sb()
1061
1087
 
1062
1088
  if check in ("element_present", "element_visible") and selector is None:
1063
1089
  return f"Error: check='{check}' requires value for `selector`."
1064
1090
 
1065
- if check in ("text", "title", "url", "url_contains") and expected is None:
1091
+ if check in (
1092
+ "text_visible", "title", "url", "url_contains"
1093
+ ) and expected is None:
1066
1094
  return f"Error: check='{check}' requires value for `expected`."
1067
1095
 
1068
1096
  if check == "element_present":
@@ -1073,13 +1101,13 @@ def assert_that(
1073
1101
  sb.assert_element_visible(selector, timeout=timeout)
1074
1102
  return f"Confirmed {selector} is visible."
1075
1103
 
1076
- if check == "text":
1104
+ if check == "text_visible":
1077
1105
  target = selector or "html"
1078
1106
  if exact:
1079
1107
  sb.assert_exact_text(expected, target, timeout=timeout)
1080
1108
  else:
1081
1109
  sb.assert_text(expected, target, timeout=timeout)
1082
- return f"Confirmed text in {target}."
1110
+ return f"Confirmed visible text {expected} in {target}."
1083
1111
 
1084
1112
  if check == "title":
1085
1113
  sb.assert_title(expected)
@@ -1095,7 +1123,8 @@ def assert_that(
1095
1123
 
1096
1124
  return (
1097
1125
  f"Error: unknown check '{check}'. Use 'element_present', "
1098
- f"'element_visible', 'text', 'title', 'url', or 'url_contains'."
1126
+ "'element_visible', 'text_visible', 'title', 'url', "
1127
+ "or 'url_contains'."
1099
1128
  )
1100
1129
 
1101
1130
 
@@ -1184,6 +1213,24 @@ def manage_storage(
1184
1213
  ) -> Any:
1185
1214
  """Get or set a key in localStorage or sessionStorage.
1186
1215
 
1216
+ Use this tool when the browser workflow needs to inspect or modify
1217
+ JavaScript Web Storage belonging to the current page origin.
1218
+
1219
+ Tool selection:
1220
+ - Need localStorage/sessionStorage -> use this tool.
1221
+ - Need cookies or authentication cookies -> use manage_cookies.
1222
+ - Need arbitrary JavaScript or storage operations not covered here ->
1223
+ use run_javascript.
1224
+ - Need visible page content or HTML -> use get_content.
1225
+ - Need an element's HTML attributes -> use get_attributes.
1226
+
1227
+ When not to use:
1228
+ - Do not use this tool for HTTP cookies; use manage_cookies instead.
1229
+ - Do not use this tool for arbitrary page JavaScript;
1230
+ use run_javascript when a higher-level tool is insufficient.
1231
+ - Do not use this tool to inspect values from another origin;
1232
+ storage is scoped to the current page origin.
1233
+
1187
1234
  Args:
1188
1235
  key: Storage key to read or modify.
1189
1236
  value: Value to store when action="set". Required for set.
@@ -1202,27 +1249,6 @@ def manage_storage(
1202
1249
  Storage belongs to the current page origin. Values from one website
1203
1250
  are not generally available to another origin.
1204
1251
  """
1205
- sb = _get_sb()
1206
-
1207
- if action not in ("get", "set"):
1208
- return "Error: action must be 'get' or 'set'."
1209
-
1210
- if action == "set" and value is None:
1211
- return "Error: value is required when action='set'."
1212
-
1213
- if storage == "local":
1214
- if action == "get":
1215
- return sb.get_local_storage_item(key)
1216
- sb.set_local_storage_item(key, value)
1217
- return f"Set localStorage[{key!r}]"
1218
-
1219
- if storage == "session":
1220
- if action == "get":
1221
- return sb.get_session_storage_item(key)
1222
- sb.set_session_storage_item(key, value)
1223
- return f"Set sessionStorage[{key!r}]"
1224
-
1225
- return f"Error: unknown storage '{storage}'. Use 'local' or 'session'."
1226
1252
 
1227
1253
 
1228
1254
  # ---------------------------------------------------------------------------
@@ -1436,8 +1462,8 @@ def solve_captcha() -> str:
1436
1462
  1. Inspect the page with get_content when you need to determine
1437
1463
  whether CAPTCHA-related controls are present.
1438
1464
  2. Call solve_captcha to attempt the interaction.
1439
- 3. Use get_page_info, get_content, check_state, or manage_cookies
1440
- to inspect resulting page/session state.
1465
+ 3. Use get_page_info, get_content, check_for_condition,
1466
+ or manage_cookies to inspect resulting page/session state.
1441
1467
 
1442
1468
  Returns:
1443
1469
  A message confirming that the CAPTCHA interaction was attempted, not
@@ -1504,22 +1530,51 @@ def save_output(
1504
1530
  @mcp.tool()
1505
1531
  @handle_sb_errors
1506
1532
  def run_javascript(expression: str) -> Any:
1507
- """Evaluate arbitrary JavaScript in the current page context.
1533
+ """Evaluate a JavaScript expression in the current page context.
1508
1534
 
1509
1535
  Use this only when the required browser operation cannot be accomplished
1510
1536
  through the higher-level SeleniumBase tools.
1511
1537
 
1512
- The expression is evaluated through the Chrome DevTools Protocol
1513
- Runtime.evaluate mechanism. Promise results are awaited and values are
1514
- returned by value.
1538
+ The expression is evaluated through Chrome DevTools Protocol
1539
+ Runtime.evaluate in the currently active page. It executes with access
1540
+ to the page's JavaScript context, including DOM APIs, browser storage,
1541
+ and other same-origin page resources available to JavaScript.
1542
+
1543
+ Tool selection:
1544
+ - Prefer click, type_text, select_option, hover_with_action,
1545
+ focus_on, scroll, and other higher-level tools for normal browser
1546
+ interactions.
1547
+ - Prefer get_content, get_attributes, and find_elements for reading
1548
+ page content or element information.
1549
+ - Prefer manage_storage for ordinary localStorage/sessionStorage
1550
+ reads and writes.
1551
+ - Prefer manage_cookies for browser cookie operations.
1552
+ - Use this tool when a required operation needs arbitrary JavaScript
1553
+ that the higher-level tools do not expose.
1515
1554
 
1516
1555
  Args:
1517
- expression: JavaScript expression to evaluate in the current page
1518
- context.
1556
+ expression: A JavaScript expression or executable JavaScript code
1557
+ evaluated in the current page. It may reference standard browser
1558
+ globals such as document and window and may use DOM APIs.
1559
+
1560
+ Examples:
1561
+ - "document.title"
1562
+ - "document.querySelector('button')?.textContent"
1563
+ - "localStorage.getItem('theme')"
1564
+ - "document.body.classList.contains('dark')"
1565
+ - "document.querySelector('#slider').value = '50'"
1566
+
1567
+ The expression should produce a value when a result is needed.
1568
+ JavaScript that returns a Promise is supported and its resolved
1569
+ value is returned.
1519
1570
 
1520
1571
  Returns:
1521
- The JavaScript evaluation result when it can be represented across
1522
- the MCP boundary.
1572
+ The JavaScript evaluation result when it can be serialized and
1573
+ returned across the MCP boundary. Primitive values, arrays, plain
1574
+ objects, and null are generally suitable return values. DOM objects,
1575
+ functions, symbols, and other non-serializable JavaScript values may
1576
+ not be returned directly; extract the needed property or convert the
1577
+ value to a serializable form first.
1523
1578
 
1524
1579
  Security:
1525
1580
  This provides unrestricted JavaScript execution in the current browser
@@ -252,6 +252,7 @@ def type_text(
252
252
  ) -> str:
253
253
  """Type text into an input field / textarea.
254
254
  Raises an exception if the element isn't found within the timeout.
255
+ Optionally clear the text field first. (Default: True)
255
256
  Args:
256
257
  selector: The selector for the field.
257
258
  text: The text to type.
@@ -260,9 +261,10 @@ def type_text(
260
261
  d = _get_driver()
261
262
  if clear_first:
262
263
  d.type(selector, text, timeout=timeout)
264
+ return f"Typed {text} into {selector} after clearing the field."
263
265
  else:
264
- d.add_text(selector, text, timeout=timeout)
265
- return f"Typed into {selector}"
266
+ d.send_keys(selector, text, timeout=timeout)
267
+ return f"Typed {text} into {selector}"
266
268
 
267
269
 
268
270
  @mcp.tool()
@@ -287,11 +289,14 @@ def select_option_by_value(dropdown_selector: str, option: str) -> str:
287
289
 
288
290
  @mcp.tool()
289
291
  @handle_sb_errors
290
- def select_option_by_index(dropdown_selector: str, option: str) -> str:
292
+ def select_option_by_index(
293
+ dropdown_selector: str,
294
+ option: str | int,
295
+ ) -> str:
291
296
  """Select a <select> dropdown option by its 0-based index.
292
297
  Raises an exception if the element or option aren't found
293
298
  within the default timeout, which is 7 seconds."""
294
- _get_driver().select_option_by_index(dropdown_selector, option)
299
+ _get_driver().select_option_by_index(dropdown_selector, int(option))
295
300
  return f"Selected index '{option}' in {dropdown_selector}"
296
301
 
297
302
 
@@ -330,15 +335,33 @@ def switch_to_default_content() -> str:
330
335
 
331
336
  @mcp.tool()
332
337
  @handle_sb_errors
333
- def assert_text(text: str, selector: str | None = None) -> str:
334
- """Assert that text is present on the page, or within a specific element.
338
+ def assert_text(
339
+ text: str,
340
+ selector: str | None = None,
341
+ timeout: int | float | None = 7,
342
+ ) -> str:
343
+ """Assert that text is visible on the page, or within a specific element.
335
344
  Raises an error (returned as a tool error to the client) if not found."""
336
345
  d = _get_driver()
337
346
  if selector:
338
- d.assert_text(text, selector)
347
+ d.assert_text(text, selector, timeout=timeout)
348
+ return f"Confirmed text '{text}' is visible in selector {selector}."
339
349
  else:
340
- d.assert_text(text)
341
- return f"Confirmed '{text}' is present."
350
+ d.assert_text(text, timeout=timeout)
351
+ return f"Confirmed text '{text}' is visible."
352
+
353
+
354
+ @mcp.tool()
355
+ @handle_sb_errors
356
+ def assert_element(
357
+ selector: str,
358
+ timeout: int | float | None = 7
359
+ ) -> str:
360
+ """Assert that an element is visible on the page.
361
+ Raises an error (returned as a tool error to the client) if not found."""
362
+ d = _get_driver()
363
+ d.assert_element(selector, timeout=timeout)
364
+ return f"Confirmed '{selector}' is present."
342
365
 
343
366
 
344
367
  # ---------------------------------------------------------------------------
@@ -26,7 +26,7 @@ SeleniumBase = "https://github.com/seleniumbase/SeleniumBase"
26
26
 
27
27
  [dependency-groups] # Used by `uv sync`
28
28
  dev = [
29
- "uv>=0.12.8", # Needed for `mcp dev`
29
+ "uv>=0.12.9", # Needed for `mcp dev`
30
30
  ]
31
31
 
32
32
  [build-system]
@@ -410,22 +410,27 @@ def context_click(selector: str) -> str:
410
410
 
411
411
  @mcp.tool()
412
412
  @handle_sb_errors
413
- def type_text(selector: str, text: str) -> str:
414
- """Clear a field and type text into it.
415
- Raises an exception if the element isn't found within the default timeout.
416
- """
417
- _get_sb().type(selector, text)
418
- return f"Typed into {selector}"
419
-
420
-
421
- @mcp.tool()
422
- @handle_sb_errors
423
- def send_keys(selector: str, text: str) -> str:
424
- """Send keystrokes to an element without clearing it first.
425
- Raises an exception if the element isn't found within the default timeout.
413
+ def type_text(
414
+ selector: str,
415
+ text: str,
416
+ clear_first: bool = True,
417
+ timeout: int | float | None = 7,
418
+ ) -> str:
419
+ """Type text into an input field / textarea.
420
+ Raises an exception if the element isn't found within the timeout.
421
+ Optionally clear the text field first. (Default: True)
422
+ Args:
423
+ selector: The selector for the field.
424
+ text: The text to type.
425
+ clear_first: Clear the field's existing contents before typing.
426
+ timeout: The maximum time to wait for an element in seconds.
426
427
  """
427
- _get_sb().send_keys(selector, text)
428
- return f"Sent keys to {selector}"
428
+ if clear_first:
429
+ _get_sb().type(selector, text, timeout=timeout)
430
+ return f"Typed {text} into {selector} after clearing the field."
431
+ else:
432
+ _get_sb().send_keys(selector, text, timeout=timeout)
433
+ return f"Typed {text} into {selector}"
429
434
 
430
435
 
431
436
  @mcp.tool()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seleniumbase-mcp
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: MCP servers exposing SeleniumBase as tools for MCP clients.
5
5
  Home-page: https://github.com/seleniumbase/seleniumbase-mcp
6
6
  Author: Michael Mintz
@@ -55,15 +55,15 @@ Classifier: Topic :: Utilities
55
55
  Requires-Python: >=3.10
56
56
  Description-Content-Type: text/markdown
57
57
  License-File: LICENSE
58
- Requires-Dist: seleniumbase[mcp]>=4.53.4
58
+ Requires-Dist: seleniumbase[mcp]>=4.53.6
59
59
  Requires-Dist: mcp[cli]<3.0.0,>=2.1.1
60
60
  Provides-Extra: deploy
61
61
  Requires-Dist: build>=1.0.0; extra == "deploy"
62
62
  Requires-Dist: twine>=7.0.0; extra == "deploy"
63
63
  Provides-Extra: uv
64
- Requires-Dist: uv>=0.12.8; extra == "uv"
64
+ Requires-Dist: uv>=0.12.9; extra == "uv"
65
65
  Provides-Extra: dev
66
- Requires-Dist: uv>=0.12.8; extra == "dev"
66
+ Requires-Dist: uv>=0.12.9; extra == "dev"
67
67
  Dynamic: author
68
68
  Dynamic: author-email
69
69
  Dynamic: classifier
@@ -281,9 +281,9 @@ in the loop at all. Reference:
281
281
  | Session | `start_browser(url, headless, use_chromium, browser_executable_path, incognito, guest, ad_block, proxy)`, `close_browser` |
282
282
  | Navigation | `navigate`, `navigate_history(action: back/forward/reload)`, `get_page_info` (running status, url, title, origin, user agent, history in one call) |
283
283
  | Finding & reading | `find_elements(selector, timeout, include_html)`, `get_content(selector, output_format: text/html/urls, include_shadow_dom)`, `get_attributes`, `check_state(check: present/visible/count/text_visible)` |
284
- | Interacting | `click(selector, nth, all_matches, only_if_visible, parent_selector, timeout, scroll)`, `hover_with_action(selector1, selector2, action: none/click/drag_and_drop)`, `fill_input(mode: type/append/set_value/fast_type/clear)`, `select_option(by: text/value/index)`, `focus_on(action: scroll_to_element/focus/highlight)` |
284
+ | Interacting | `click(selector, nth, all_matches, only_if_visible, parent_selector, timeout, scroll)`, `hover_with_action(selector1, selector2, action: none/click/drag_and_drop)`, `type_text(mode: fill_input/append/fast_type/set_value/clear_only)`, `select_option(by: text/value/index)`, `focus_on(action: scroll_to_element/focus/highlight)` |
285
285
  | Waiting | `wait_for(state: present/visible/not_visible/absent, text)` |
286
- | Assertions | `assert_that(check: element_present/element_visible/text/title/url/url_contains)` |
286
+ | Assertions | `assert_condition(check: element_present/element_visible/text_visible/title/url/url_contains)` |
287
287
  | Cookies & storage | `manage_cookies(action: get_all/clear/save/load)`, `manage_storage(storage: local/session, action: get/set)` |
288
288
  | Scrolling | `scroll(direction: up/down/top/bottom, amount)` |
289
289
  | Windows & tabs | `manage_window(action: get_rect/set_rect/maximize/minimize)`, `manage_tabs(action: list/open/switch/switch_newest/close_active)` |
@@ -1,4 +1,4 @@
1
- seleniumbase[mcp]>=4.53.4
1
+ seleniumbase[mcp]>=4.53.6
2
2
  mcp[cli]<3.0.0,>=2.1.1
3
3
 
4
4
  [deploy]
@@ -6,7 +6,7 @@ build>=1.0.0
6
6
  twine>=7.0.0
7
7
 
8
8
  [dev]
9
- uv>=0.12.8
9
+ uv>=0.12.9
10
10
 
11
11
  [uv]
12
- uv>=0.12.8
12
+ uv>=0.12.9
@@ -70,7 +70,7 @@ if sys.argv[-1] == "publish":
70
70
 
71
71
  setup(
72
72
  name="seleniumbase-mcp",
73
- version="1.2.2",
73
+ version="1.2.4",
74
74
  description="MCP servers exposing SeleniumBase as tools for MCP clients.",
75
75
  long_description=long_description,
76
76
  long_description_content_type="text/markdown",
@@ -140,7 +140,7 @@ setup(
140
140
  ],
141
141
  python_requires=">=3.10",
142
142
  install_requires=[
143
- "seleniumbase[mcp]>=4.53.4",
143
+ "seleniumbase[mcp]>=4.53.6",
144
144
  "mcp[cli]>=2.1.1,<3.0.0",
145
145
  ],
146
146
  extras_require={
@@ -149,10 +149,10 @@ setup(
149
149
  "twine>=7.0.0",
150
150
  ],
151
151
  "uv": [
152
- "uv>=0.12.8",
152
+ "uv>=0.12.9",
153
153
  ],
154
154
  "dev": [
155
- "uv>=0.12.8",
155
+ "uv>=0.12.9",
156
156
  ],
157
157
  },
158
158
  entry_points={
@@ -25,7 +25,6 @@ async def test_server(name: str, command: str) -> None:
25
25
  assert "start_browser" in tools
26
26
  assert "close_browser" in tools
27
27
  assert "navigate" in tools
28
- # assert "get_title" in tools
29
28
 
30
29
  result = await client.call_tool(
31
30
  "start_browser",
@@ -45,15 +44,27 @@ async def test_server(name: str, command: str) -> None:
45
44
  )
46
45
  assert not result.is_error
47
46
 
48
- '''result = await client.call_tool("get_title", {})
49
- assert not result.is_error
50
- assert result.content[0].text == "MCP Test"
51
-
52
- result = await client.call_tool(
53
- "assert_text",
54
- {"text": "Hello MCP"},
55
- )
56
- assert not result.is_error'''
47
+ result = result.structured_content["result"]
48
+ assert "<title>MCP Test</title>" in result
49
+
50
+ if name == "seleniumbase-sb" or name == "seleniumbase-driver":
51
+ result = await client.call_tool("get_title", {})
52
+ assert not result.is_error
53
+ assert result.content[0].text == "MCP Test"
54
+
55
+ result = await client.call_tool(
56
+ "assert_text",
57
+ {"text": "Hello MCP"},
58
+ )
59
+ assert not result.is_error
60
+ else:
61
+ result = await client.call_tool(
62
+ "get_content", {
63
+ "selector": "title",
64
+ "output_format": "text"
65
+ }
66
+ )
67
+ assert result.content[0].text == "MCP Test"
57
68
 
58
69
  result = await client.call_tool("close_browser", {})
59
70
  assert not result.is_error