Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cli/skills/browse/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,12 @@ adds a loud warning, so set one first.
Execute code can use `page`, `context`, `browser`, persistent `state`, the
`files` helper, compute-only Node modules through `modules` (plus `path`, `url`,
`util`, `crypto`, `buffer`, `zlib`, `events`, `stream`), and the browse helpers
above. Timers, `URL`, `TextEncoder`, `Buffer` and `crypto.randomUUID()` are
available. `execute` returns the value of `return`; a single expression returns
automatically. Use `--file` for longer scripts:
above. Those names exist only inside the code string browse runs, never as
globals of a client's own runtime: in a client that runs tools in its own code
mode, pass the Playwright code as a string in `code`. Timers, `URL`,
`TextEncoder`, `Buffer` and `crypto.randomUUID()` are available. `execute`
returns the value of `return`; a single expression returns automatically. Use
`--file` for longer scripts:

```bash
browse execute --session github --file ./perform-flow.js
Expand Down
2 changes: 1 addition & 1 deletion cli/src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Use one loop: inspect, act, verify.
- Act with the narrowest stable control, and keep dependent steps in one \`execute\` while transient UI is open.
- Verify with a fresh page read (\`page.url()\`, a heading, or a new snapshot). A click that returned, or a handoff the user acknowledged, is not proof the task succeeded.
\`execute\` returns the value of your \`return\`; a single expression returns automatically. The execute tool's description lists the helpers it provides (snapshot, ref, handoff, files, secrets, screenshots, network capture, and more).
\`execute\` returns the value of your \`return\`; a single expression returns automatically. The execute tool's description lists the helpers it provides (snapshot, ref, handoff, files, secrets, screenshots, network capture, and more). \`page\`, \`browser\`, \`context\`, \`state\` and the other helpers exist only inside the \`code\` string \`execute\` receives, never as globals of the client's own runtime: in a client that runs tools in its own code mode, pass the Playwright code as a string in \`code\`.
When only a person can proceed — login, 2FA, passkey, captcha or payment confirmation — call \`handoff("what the user should do")\` and wait. Pass \`start\` for an action that itself blocks. Never solve a captcha yourself and never retry-loop a blocked site; if a challenge is interactive or the site refuses the visit, hand off or stop and tell the user.
Expand Down
2 changes: 1 addition & 1 deletion cli/src/mcp/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ registerMcpTools({
code: {
type: "string",
description:
'JavaScript code to execute. It receives browser, context, page, state, modules (path, url, util, crypto, buffer, zlib, events, stream), files (dir and roots properties; path(name), write(target, data), read(target, encoding?)), fillInput, fillInputs, snapshot(options?) for compact semantic outlines of the page and the content of its iframes (options: find to search them, diff or delta for changes, frames: false to leave iframe content out), persistent compatible ref(id) locators that resolve inside the frame they came from, webmcp (list/call), screenshotWithLabels(options?) and screenshotDiff(options) for screenshots, ariaSnapshot for Playwright\'s YAML tree followed by a labelled block per iframe with its content, ghostCursor, handoff, demonstrate, network (start/status/stop/cancel), pageConsole (start/stop capturing the page\'s own console messages and errors, off by default because pages can detect it), and secrets.type(locator, name) to type a stored site secret (see secrets_list) only on its allowed origins, without seeing it. There is no location, document or fetch global (they belong to the page: use page.url() or page.evaluate(...)), and no structuredClone.',
'JavaScript code to execute. It receives browser, context, page, state, modules (path, url, util, crypto, buffer, zlib, events, stream), files (dir and roots properties; path(name), write(target, data), read(target, encoding?)), fillInput, fillInputs, snapshot(options?) for compact semantic outlines of the page and the content of its iframes (options: find to search them, diff or delta for changes, frames: false to leave iframe content out), persistent compatible ref(id) locators that resolve inside the frame they came from, webmcp (list/call), screenshotWithLabels(options?) and screenshotDiff(options) for screenshots, ariaSnapshot for Playwright\'s YAML tree followed by a labelled block per iframe with its content, ghostCursor, handoff, demonstrate, network (start/status/stop/cancel), pageConsole (start/stop capturing the page\'s own console messages and errors, off by default because pages can detect it), and secrets.type(locator, name) to type a stored site secret (see secrets_list) only on its allowed origins, without seeing it. There is no location, document or fetch global (they belong to the page: use page.url() or page.evaluate(...)), and no structuredClone. These helpers exist only inside this string, never as globals of a client\'s own code runtime: in a client that runs tools in its own code mode, pass the Playwright code here as a string.',
},
session: {
type: "string",
Expand Down
1 change: 1 addition & 0 deletions cli/test/mcp-protocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ describe("MCP protocol versions", () => {
const init = await mcp.request("initialize", { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "test", version: "0" } })
expect(init.result?.protocolVersion).toBe("2025-11-25")
expect(init.result?.instructions).toContain("snapshot()")
expect(init.result?.instructions).toContain("exist only inside the `code` string")
mcp.notify("notifications/initialized")
expect(((await mcp.request("tools/list")).result?.tools as unknown[]).length).toBeGreaterThan(0)
expect((await mcp.request("tools/call", { name: "secrets_list", arguments: {} })).result?.isError).toBe(false)
Expand Down
Loading