diff --git a/src/lib/mcp/tools/browsers.test.ts b/src/lib/mcp/tools/browsers.test.ts index eadf2b9..04eace1 100644 --- a/src/lib/mcp/tools/browsers.test.ts +++ b/src/lib/mcp/tools/browsers.test.ts @@ -886,6 +886,37 @@ describe("manage_browsers id or name", () => { }); }); +describe("manage_browsers next actions", () => { + test("leads the post-create hint with the preference order", async () => { + const kernelClient = { + browsers: { + create: async () => ({ session_id: "brr_123" }), + }, + }; + const { client, close } = await connectTestMcp( + registerBrowserCapabilities, + kernelClient, + ); + + try { + const created = toolResultJSON( + await client.callTool({ + name: "manage_browsers", + arguments: { action: "create" }, + }), + ); + const hint: string = created.next_actions[0]; + expect(hint).toContain("brr_123"); + expect(hint).toContain("webmcp"); + expect(hint.indexOf("webmcp")).toBeLessThan( + hint.indexOf("computer_action"), + ); + } finally { + await close(); + } + }); +}); + describe("browser resources", () => { test("uses injected dependencies", async () => { const kernelClient = { diff --git a/src/lib/mcp/tools/browsers.ts b/src/lib/mcp/tools/browsers.ts index 9e1be0e..f5bbb58 100644 --- a/src/lib/mcp/tools/browsers.ts +++ b/src/lib/mcp/tools/browsers.ts @@ -360,7 +360,7 @@ async function readBrowserTelemetry( function browserSessionNextActions(sessionId: string) { return [ - `use computer_action with session_id "${sessionId}" to inspect or control the browser.`, + `drive the browser with webmcp, browser_repl, execute_playwright_code, or computer_action using session_id "${sessionId}", preferring the first that can carry the current step.`, `use manage_browsers with action "get" and session_id "${sessionId}" for full browser details.`, `use manage_browsers with action "delete" and session_id "${sessionId}" when the session is no longer needed.`, ]; @@ -459,7 +459,7 @@ export function registerBrowserCapabilities( "manage_browsers", { description: - 'manage browser sessions and their archived telemetry. use "list" to choose an existing session, "create" before browser control, "update" to change supported session settings, "get" for full details, "get_telemetry" to diagnose active or deleted sessions, and "delete" when finished. live sessions can be addressed by id or by the name given at creation or set on update; deleted sessions only by id. get_telemetry compacts events by default; set compact=false with explicit categories and a limit of at most 5 when raw headers, request data, response bodies, or other omitted fields are needed.', + 'manage the browser sessions that every web task runs in, and their archived telemetry. once a session exists, drive it with webmcp, browser_repl, execute_playwright_code, or computer_action, preferring the first that can carry the current step. use "list" to choose an existing session, "create" before browser control, "update" to change supported session settings, "get" for full details, "get_telemetry" to diagnose active or deleted sessions, and "delete" when finished. live sessions can be addressed by id or by the name given at creation or set on update; deleted sessions only by id. get_telemetry compacts events by default; set compact=false with explicit categories and a limit of at most 5 when raw headers, request data, response bodies, or other omitted fields are needed.', inputSchema: z.strictObject( { ...projectSelectionInputSchema(), diff --git a/src/lib/mcp/tools/computer-action.ts b/src/lib/mcp/tools/computer-action.ts index 0df1248..07abb4b 100644 --- a/src/lib/mcp/tools/computer-action.ts +++ b/src/lib/mcp/tools/computer-action.ts @@ -258,7 +258,7 @@ export function registerComputerActionTool(server: McpServer) { "computer_action", { description: - "execute computer actions on a browser session. pass a single action for simple operations (e.g. one click or one screenshot), or pass multiple actions to batch them into a single request for lower latency (e.g. click, type, press_key in one call). use sleep actions between steps when the page needs time to react (e.g. after a click that triggers navigation or animation). important: always include a screenshot as the last action so you can see the result of your actions. action types: click_mouse, move_mouse, type_text, press_key, scroll, drag_mouse, set_cursor, sleep, write_clipboard, read_clipboard, screenshot, get_mouse_position. screenshot, read_clipboard, and get_mouse_position return data, so they must be the last action if included.", + "control a page visually, by clicking and typing at coordinates and reading the screen. use this when the dom cannot carry the current step: prefer a webmcp tool where the site exposes one, then browser_repl or execute_playwright_code. pass a single action for simple operations (e.g. one click or one screenshot), or pass multiple actions to batch them into a single request for lower latency (e.g. click, type, press_key in one call). use sleep actions between steps when the page needs time to react (e.g. after a click that triggers navigation or animation). important: always include a screenshot as the last action so you can see the result of your actions. action types: click_mouse, move_mouse, type_text, press_key, scroll, drag_mouse, set_cursor, sleep, write_clipboard, read_clipboard, screenshot, get_mouse_position. screenshot, read_clipboard, and get_mouse_position return data, so they must be the last action if included.", inputSchema: z.object({ ...projectSelectionInputSchema(), session_id: z.string().describe("browser session id or name."), diff --git a/src/lib/mcp/tools/playwright.ts b/src/lib/mcp/tools/playwright.ts index f2af5bb..c5cb5a7 100644 --- a/src/lib/mcp/tools/playwright.ts +++ b/src/lib/mcp/tools/playwright.ts @@ -42,7 +42,7 @@ export function registerPlaywrightTool( "execute_playwright_code", { description: - "execute arbitrary playwright code in a fresh execution context against the browser. for reusable site actions, check `webmcp.listTools()` first and prefer a suitable structured tool; use playwright when none is exposed. does not create or delete browsers -- use manage_browsers for session lifecycle. the code runs in the same vm as the browser, minimizing latency and maximizing throughput. it has access to `page`, `context`, `browser`, and `webmcp` variables. use `webmcp.listTools()` to discover browser-wide webmcp tools and `webmcp.invokeTool(toolRef, input?, { timeoutSec? })` to invoke an exact registration. it can `return` a value, and this value is returned as the tool result.\n\n" + + "execute arbitrary playwright code in a fresh execution context against the browser. for reusable site actions, check `webmcp.listTools()` first and prefer a suitable structured tool; use playwright when none is exposed, and computer_action only when the dom cannot carry the step. does not create or delete browsers -- use manage_browsers for session lifecycle. the code runs in the same vm as the browser, minimizing latency and maximizing throughput. it has access to `page`, `context`, `browser`, and `webmcp` variables. use `webmcp.listTools()` to discover browser-wide webmcp tools and `webmcp.invokeTool(toolRef, input?, { timeoutSec? })` to invoke an exact registration. it can `return` a value, and this value is returned as the tool result.\n\n" + "every call runs in an executor: a dedicated node.js process with its own browser connection. calls on different executors run concurrently; calls on the same executor run one at a time. a timeout, crash, or blocked event loop in one executor does not affect other executors. after a timeout the executor keeps its process and drops its browser connection, so code abandoned by the timeout cannot keep driving the browser. after a crash or a blocked event loop, the next call on that executor starts a fresh process.\n\n" + 'calls without `executor` run in the executor named `default`, which always exists and is the same as passing `executor: "default"`. in the default executor, `page` is bound to an active tab reported by chrome. in single-window sessions, this is the foreground tab. when multiple browser windows are open, chrome reports one active tab per window and the selected window is unspecified. `context` is the `BrowserContext` that owns the selected page. use `browser.contexts()` to select a context or page explicitly.\n\n' + "pass any other name to run the call in a named executor. the first call with a new name creates it. each named executor owns a tab: its first call opens a new background tab in the default browser context, and `page` is bound to that tab on every later call while it stays open. opening it does not change the active tab of an existing window. if the tab is closed, the next call opens a new one and reports `tab.created: true`. executor code can still reach other tabs through `context` and `browser`; ownership only decides what `page` is bound to. use named executors to drive several tabs of one browser in parallel.\n\n" +