Skip to content

Commit 66433da

Browse files
authored
Merge pull request #10 from seleniumbase/mcp-server-update-6
MCP Server: Update 6
2 parents af57a15 + 9bd68e6 commit 66433da

4 files changed

Lines changed: 127 additions & 78 deletions

File tree

cdp_server.py

Lines changed: 103 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@
2323
having to choose between multiple near-identical tools.
2424
2525
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.
2827
- Use get_content for reading visible text or HTML.
2928
- Use find_elements for discovering and inspecting multiple matching
3029
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.
3231
- Use wait_for when the agent needs to wait for a condition to become true.
3332
- Use assert_condition when the agent needs to verify an expected condition
3433
and treat failure as an assertion error.
@@ -205,18 +204,33 @@ def start_browser(
205204
f"(url={url!r}, headless={effective_headless}, "
206205
f"use_chromium={use_chromium})"
207206
)
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+
)
220234

221235

222236
@mcp.tool()
@@ -268,14 +282,12 @@ def get_page_info() -> dict | str:
268282
- title: The current document title.
269283
- origin: The current page origin (scheme, host, and port).
270284
- user_agent: The browser's current User-Agent string.
271-
- history: The browser navigation history for the current session.
272285
273286
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.
276288
- Need visible page text or HTML -> use get_content.
277289
- 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.
279291
- Need to wait for a condition -> use wait_for.
280292
- Need to verify an expected condition -> use assert_condition.
281293
@@ -296,7 +308,6 @@ def get_page_info() -> dict | str:
296308
"title": _sb.get_title(),
297309
"origin": _sb.get_origin(),
298310
"user_agent": _sb.get_user_agent(),
299-
"history": _sb.get_navigation_history(),
300311
}
301312
except Exception as e:
302313
return {
@@ -436,7 +447,7 @@ def find_elements(
436447
use get_content.
437448
- Need to click one of several matches -> use click with nth.
438449
- Need to know whether an element is present/visible ->
439-
use check_state.
450+
use check_for_condition.
440451
441452
Note:
442453
Element handles cannot be persisted across MCP calls. If you find
@@ -512,15 +523,14 @@ def get_content(
512523
URLs before navigating to them.
513524
514525
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.
517527
- Need visible text -> use output_format="text".
518528
- Need page or element HTML -> use output_format="html".
519529
- Need URLs from the page or an element -> use output_format="urls".
520530
- Need structured information about matching elements ->
521531
use find_elements.
522532
- Need to check whether an element is present or visible ->
523-
use check_state.
533+
use check_for_condition.
524534
- Need to wait for content to appear -> use wait_for.
525535
"""
526536
sb = _get_sb()
@@ -568,7 +578,7 @@ def get_attributes(
568578
- Need to discover multiple matching elements or inspect their text ->
569579
use 'find_elements'.
570580
- Need visible text or HTML content -> use 'get_content'.
571-
- Need to check presence or visibility -> use 'check_state'.
581+
- Need to check presence or visibility -> use 'check_for_condition'.
572582
573583
This is a read-only operation and does not modify the element.
574584
"""
@@ -582,7 +592,7 @@ def get_attributes(
582592

583593
@mcp.tool()
584594
@handle_sb_errors
585-
def check_state(
595+
def check_for_condition(
586596
check: Literal["present", "visible", "count", "text_visible"] = "visible",
587597
selector: str = "body",
588598
text: str | None = None,
@@ -610,7 +620,7 @@ def check_state(
610620
count. Missing elements do not cause an exception for these checks.
611621
612622
Tool selection:
613-
- Immediate yes/no/count observation -> use check_state.
623+
- Immediate yes/no/count observation -> use check_for_condition.
614624
- Wait until a state becomes true/false -> use wait_for.
615625
- Verify an expected condition and fail when it is not met ->
616626
use assert_condition.
@@ -970,8 +980,9 @@ def wait_for(
970980
Use this tool when the page is dynamic and an automation step must wait
971981
for a condition before continuing.
972982
973-
Unlike check_state, this tool intentionally waits. Unlike assert_condition,
974-
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.
975986
976987
Args:
977988
state:
@@ -990,7 +1001,7 @@ def wait_for(
9901001
A confirmation when the requested condition is reached.
9911002
9921003
Tool selection:
993-
- Check current state immediately -> use check_state.
1004+
- Check current state immediately -> use check_for_condition.
9941005
- Wait for a state/content transition -> use wait_for.
9951006
- Verify an expected value/condition -> use assert_condition.
9961007
"""
@@ -1038,9 +1049,10 @@ def assert_condition(
10381049
) -> str:
10391050
"""Verify an expected browser condition and fail when it is not met.
10401051
1041-
Use this tool for explicit verification. Unlike check_state, which simply
1042-
reports the current state, assert_condition treats a failed expectation
1043-
as an 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.
10441056
10451057
Args:
10461058
check:
@@ -1067,7 +1079,7 @@ def assert_condition(
10671079
fails; the MCP error wrapper converts it to a descriptive result.
10681080
10691081
Tool selection:
1070-
- Just inspect current state -> use check_state.
1082+
- Just inspect current state -> use check_for_condition.
10711083
- Wait for a condition to become true -> use wait_for.
10721084
- Verify that an expected condition is true -> use assert_condition.
10731085
"""
@@ -1201,6 +1213,24 @@ def manage_storage(
12011213
) -> Any:
12021214
"""Get or set a key in localStorage or sessionStorage.
12031215
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+
12041234
Args:
12051235
key: Storage key to read or modify.
12061236
value: Value to store when action="set". Required for set.
@@ -1219,27 +1249,6 @@ def manage_storage(
12191249
Storage belongs to the current page origin. Values from one website
12201250
are not generally available to another origin.
12211251
"""
1222-
sb = _get_sb()
1223-
1224-
if action not in ("get", "set"):
1225-
return "Error: action must be 'get' or 'set'."
1226-
1227-
if action == "set" and value is None:
1228-
return "Error: value is required when action='set'."
1229-
1230-
if storage == "local":
1231-
if action == "get":
1232-
return sb.get_local_storage_item(key)
1233-
sb.set_local_storage_item(key, value)
1234-
return f"Set localStorage[{key!r}]"
1235-
1236-
if storage == "session":
1237-
if action == "get":
1238-
return sb.get_session_storage_item(key)
1239-
sb.set_session_storage_item(key, value)
1240-
return f"Set sessionStorage[{key!r}]"
1241-
1242-
return f"Error: unknown storage '{storage}'. Use 'local' or 'session'."
12431252

12441253

12451254
# ---------------------------------------------------------------------------
@@ -1453,8 +1462,8 @@ def solve_captcha() -> str:
14531462
1. Inspect the page with get_content when you need to determine
14541463
whether CAPTCHA-related controls are present.
14551464
2. Call solve_captcha to attempt the interaction.
1456-
3. Use get_page_info, get_content, check_state, or manage_cookies
1457-
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.
14581467
14591468
Returns:
14601469
A message confirming that the CAPTCHA interaction was attempted, not
@@ -1521,22 +1530,51 @@ def save_output(
15211530
@mcp.tool()
15221531
@handle_sb_errors
15231532
def run_javascript(expression: str) -> Any:
1524-
"""Evaluate arbitrary JavaScript in the current page context.
1533+
"""Evaluate a JavaScript expression in the current page context.
15251534
15261535
Use this only when the required browser operation cannot be accomplished
15271536
through the higher-level SeleniumBase tools.
15281537
1529-
The expression is evaluated through the Chrome DevTools Protocol
1530-
Runtime.evaluate mechanism. Promise results are awaited and values are
1531-
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.
15321554
15331555
Args:
1534-
expression: JavaScript expression to evaluate in the current page
1535-
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.
15361570
15371571
Returns:
1538-
The JavaScript evaluation result when it can be represented across
1539-
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.
15401578
15411579
Security:
15421580
This provides unrestricted JavaScript execution in the current browser

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
seleniumbase[mcp]>=4.53.5
1+
seleniumbase[mcp]>=4.53.6
22
mcp[cli]>=2.1.1,<3.0.0

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
setup(
7272
name="seleniumbase-mcp",
73-
version="1.2.3",
73+
version="1.2.4",
7474
description="MCP servers exposing SeleniumBase as tools for MCP clients.",
7575
long_description=long_description,
7676
long_description_content_type="text/markdown",
@@ -140,7 +140,7 @@
140140
],
141141
python_requires=">=3.10",
142142
install_requires=[
143-
"seleniumbase[mcp]>=4.53.5",
143+
"seleniumbase[mcp]>=4.53.6",
144144
"mcp[cli]>=2.1.1,<3.0.0",
145145
],
146146
extras_require={

tests/test_mcp.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ async def test_server(name: str, command: str) -> None:
2525
assert "start_browser" in tools
2626
assert "close_browser" in tools
2727
assert "navigate" in tools
28-
# assert "get_title" in tools
2928

3029
result = await client.call_tool(
3130
"start_browser",
@@ -45,15 +44,27 @@ async def test_server(name: str, command: str) -> None:
4544
)
4645
assert not result.is_error
4746

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"
5768

5869
result = await client.call_tool("close_browser", {})
5970
assert not result.is_error

0 commit comments

Comments
 (0)