feat(mcp): target DOM tools by tab
- Add server-side search filtering to the MCP tabs_list tool. - Let MCP DOM query, click, and type tools operate on an explicit tab_id. - Use tab-scoped DOM eval fallbacks when the SDK active-tab helpers are not enough. - Cover tab filtering and tab-targeted DOM operations in MCP tests. - Bump package and extension versions to 0.16.8 and refresh the lockfile.
This commit is contained in:
+46
-3
@@ -33,11 +33,14 @@ class FakeClient:
|
||||
self.nav = SimpleNamespace(to=self.navigate_to)
|
||||
self.page = SimpleNamespace(info=self.page_info)
|
||||
self.extract = SimpleNamespace(text=self.extract_text, markdown=self.extract_markdown)
|
||||
self.dom = SimpleNamespace(query=self.dom_query, click=self.dom_click, type=self.dom_type)
|
||||
self.dom = SimpleNamespace(query=self.dom_query, click=self.dom_click, type=self.dom_type, eval=self.dom_eval)
|
||||
self.instances.append(self)
|
||||
|
||||
def tabs_list(self):
|
||||
return [{"id": 7, "title": "Example", "url": "https://example.com"}]
|
||||
return [
|
||||
{"id": 7, "title": "Example", "url": "https://example.com"},
|
||||
{"id": 9, "title": "Browser App", "url": "https://browser-host.example/app"},
|
||||
]
|
||||
|
||||
def tabs_open(self, url, **kwargs):
|
||||
self.calls.append(("open", url, kwargs))
|
||||
@@ -83,6 +86,12 @@ class FakeClient:
|
||||
def dom_type(self, selector, text):
|
||||
self.calls.append(("type", selector, text))
|
||||
|
||||
def dom_eval(self, code, tab_id=None):
|
||||
self.calls.append(("eval", code, tab_id))
|
||||
if "querySelectorAll" in code:
|
||||
return [{"tag": "BUTTON", "text": "Buchen", "attrs": {"id": "buchen"}}]
|
||||
return True
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_instances():
|
||||
FakeClient.instances.clear()
|
||||
@@ -110,7 +119,10 @@ async def test_each_tool_call_constructs_a_fresh_targeted_client(client, monkeyp
|
||||
second = await client.call_tool("browser_page_info", {})
|
||||
|
||||
assert first.structured_content == {
|
||||
"result": [{"id": 7, "title": "Example", "url": "https://example.com"}]
|
||||
"result": [
|
||||
{"id": 7, "title": "Example", "url": "https://example.com"},
|
||||
{"id": 9, "title": "Browser App", "url": "https://browser-host.example/app"},
|
||||
]
|
||||
}
|
||||
assert second.structured_content == {
|
||||
"title": "Example",
|
||||
@@ -158,6 +170,37 @@ async def test_explicit_target_overrides_environment_pin(client, monkeypatch):
|
||||
|
||||
assert FakeClient.instances[-1].target == ("main", None, None)
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tabs_list_can_filter_large_tab_sets_server_side(client, monkeypatch):
|
||||
monkeypatch.delenv("BROWSER_CLI_KEY", raising=False)
|
||||
|
||||
result = await client.call_tool("browser_tabs_list", {"search": "browser-host.example", "browser": "main"})
|
||||
|
||||
assert result.structured_content == {
|
||||
"result": [{"id": 9, "title": "Browser App", "url": "https://browser-host.example/app"}]
|
||||
}
|
||||
assert FakeClient.instances[-1].target == ("main", None, None)
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_dom_tools_accept_explicit_tab_id(client):
|
||||
queried = await client.call_tool("browser_dom_query", {"selector": "button", "tab_id": 9})
|
||||
clicked = await client.call_tool("browser_dom_click", {"selector": "#buchen", "tab_id": 9})
|
||||
typed = await client.call_tool("browser_dom_type", {"selector": "#name", "text": "Daniel", "tab_id": 9})
|
||||
|
||||
assert queried.structured_content == {
|
||||
"result": [{"tag": "BUTTON", "text": "Buchen", "attrs": {"id": "buchen"}}]
|
||||
}
|
||||
assert clicked.structured_content == {
|
||||
"title": "Example",
|
||||
"url": "https://example.com",
|
||||
"tab_id": 9,
|
||||
}
|
||||
assert typed.structured_content == {"typed": True}
|
||||
assert FakeClient.instances[0].calls == [("eval", FakeClient.instances[0].calls[0][1], 9)]
|
||||
assert FakeClient.instances[1].calls[0][0] == "eval"
|
||||
assert FakeClient.instances[1].calls[1] == ("page_info", 9)
|
||||
assert FakeClient.instances[2].calls[0][0] == "eval"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mutating_tools_use_sdk_and_return_fresh_state(client):
|
||||
opened = await client.call_tool("browser_tabs_open", {
|
||||
|
||||
Reference in New Issue
Block a user