feat: improve the tab switching tool
This commit is contained in:
@@ -458,7 +458,7 @@ let currentView = 'grid';
|
||||
// Tool categories for sidebar grouping
|
||||
const CATEGORIES = {
|
||||
'Lifecycle': ['browser_setup', 'browser_start', 'browser_stop', 'browser_status'],
|
||||
'Tabs': ['browser_tabs', 'browser_open', 'browser_close', 'browser_close_all', 'browser_close_finished', 'browser_focus'],
|
||||
'Tabs': ['browser_tabs', 'browser_open', 'browser_close', 'browser_close_all', 'browser_close_finished', 'browser_activate_tab'],
|
||||
'Navigation': ['browser_navigate', 'browser_go_back', 'browser_go_forward', 'browser_reload'],
|
||||
'Interactions': ['browser_click', 'browser_click_coordinate', 'browser_type', 'browser_type_focused', 'browser_fill', 'browser_press', 'browser_press_at', 'browser_hover', 'browser_hover_coordinate', 'browser_select', 'browser_scroll', 'browser_drag'],
|
||||
'Inspection': ['browser_screenshot', 'browser_snapshot', 'browser_console', 'browser_html', 'browser_get_text', 'browser_get_attribute', 'browser_get_rect', 'browser_shadow_query', 'browser_evaluate', 'browser_wait'],
|
||||
|
||||
@@ -42,7 +42,7 @@ def register_tools(mcp: FastMCP) -> None:
|
||||
|
||||
Tools are organized into categories:
|
||||
- Lifecycle: browser_start, browser_stop, browser_status
|
||||
- Tabs: browser_tabs, browser_open, browser_close, browser_focus
|
||||
- Tabs: browser_tabs, browser_open, browser_close, browser_activate_tab
|
||||
- Navigation: browser_navigate, browser_go_back, browser_go_forward, browser_reload
|
||||
- Inspection: browser_screenshot, browser_snapshot, browser_console
|
||||
- Interactions: browser_click, browser_click_coordinate, browser_type, browser_type_focused,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Browser tab management tools - tabs, open, close, focus.
|
||||
Browser tab management tools - tabs, open, close, activate.
|
||||
|
||||
All operations go through the Beeline extension - no Playwright required.
|
||||
"""
|
||||
@@ -8,9 +8,10 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from pydantic import Field
|
||||
|
||||
from ..bridge import get_bridge
|
||||
from ..session import _active_profile
|
||||
@@ -232,16 +233,33 @@ def register_tab_tools(mcp: FastMCP) -> None:
|
||||
return result
|
||||
|
||||
@mcp.tool()
|
||||
async def browser_focus(tab_id: int, profile: str | None = None) -> dict:
|
||||
async def browser_activate_tab(
|
||||
tab_id: Annotated[
|
||||
int,
|
||||
Field(
|
||||
description=(
|
||||
"REQUIRED. Integer Chrome tab ID of the tab to switch to. "
|
||||
"Must be a concrete integer (not null). "
|
||||
"Call browser_tabs first to list available tabs and their IDs."
|
||||
),
|
||||
),
|
||||
],
|
||||
profile: str | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Focus a browser tab.
|
||||
Switch the active browser tab to the given tab ID.
|
||||
|
||||
Use this to bring an existing tab to the foreground before interacting
|
||||
with it. The ``tab_id`` argument is required and must be an integer
|
||||
returned by ``browser_tabs``; passing null/None is not supported (use
|
||||
``browser_tabs`` to discover a valid ID first).
|
||||
|
||||
Args:
|
||||
tab_id: Chrome tab ID to focus
|
||||
tab_id: Chrome tab ID to activate. Required integer.
|
||||
profile: Browser profile name (default: "default")
|
||||
|
||||
Returns:
|
||||
Dict with focus status
|
||||
Dict with activation status
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
params = {"tab_id": tab_id, "profile": profile}
|
||||
@@ -249,13 +267,13 @@ def register_tab_tools(mcp: FastMCP) -> None:
|
||||
bridge = get_bridge()
|
||||
if not bridge or not bridge.is_connected:
|
||||
result = {"ok": False, "error": "Browser extension not connected"}
|
||||
log_tool_call("browser_focus", params, result=result)
|
||||
log_tool_call("browser_activate_tab", params, result=result)
|
||||
return result
|
||||
|
||||
ctx = _get_context(profile)
|
||||
if not ctx:
|
||||
result = {"ok": False, "error": "Browser not started. Call browser_start first."}
|
||||
log_tool_call("browser_focus", params, result=result)
|
||||
log_tool_call("browser_activate_tab", params, result=result)
|
||||
return result
|
||||
|
||||
try:
|
||||
@@ -263,7 +281,7 @@ def register_tab_tools(mcp: FastMCP) -> None:
|
||||
ctx["activeTabId"] = tab_id
|
||||
result = {"ok": True, "tabId": tab_id}
|
||||
log_tool_call(
|
||||
"browser_focus",
|
||||
"browser_activate_tab",
|
||||
params,
|
||||
result=result,
|
||||
duration_ms=(time.perf_counter() - start) * 1000,
|
||||
@@ -271,7 +289,12 @@ def register_tab_tools(mcp: FastMCP) -> None:
|
||||
return result
|
||||
except Exception as e:
|
||||
result = {"ok": False, "error": str(e)}
|
||||
log_tool_call("browser_focus", params, error=e, duration_ms=(time.perf_counter() - start) * 1000)
|
||||
log_tool_call(
|
||||
"browser_activate_tab",
|
||||
params,
|
||||
error=e,
|
||||
duration_ms=(time.perf_counter() - start) * 1000,
|
||||
)
|
||||
return result
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@@ -411,19 +411,19 @@ class TestTabLifecycle:
|
||||
assert close_result.get("ok") is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tab_focus_switching(self, mcp: FastMCP, mock_bridge: MagicMock):
|
||||
"""Test switching focus between tabs."""
|
||||
async def test_tab_activate_switching(self, mcp: FastMCP, mock_bridge: MagicMock):
|
||||
"""Test switching the active tab."""
|
||||
mock_bridge.activate_tab = AsyncMock(return_value={"ok": True})
|
||||
|
||||
register_tab_tools(mcp)
|
||||
browser_focus = mcp._tool_manager._tools["browser_focus"].fn
|
||||
browser_activate_tab = mcp._tool_manager._tools["browser_activate_tab"].fn
|
||||
|
||||
with patch("gcu.browser.tools.tabs.get_bridge", return_value=mock_bridge):
|
||||
with patch(
|
||||
"gcu.browser.tools.tabs._get_context",
|
||||
return_value={"groupId": 1, "activeTabId": 100},
|
||||
):
|
||||
result = await browser_focus(tab_id=200)
|
||||
result = await browser_activate_tab(tab_id=200)
|
||||
|
||||
assert result.get("ok") is True
|
||||
mock_bridge.activate_tab.assert_awaited_once_with(200)
|
||||
|
||||
Reference in New Issue
Block a user