From 84dba8258f723fce2281556bf84908a642849cee Mon Sep 17 00:00:00 2001 From: miguel Date: Wed, 19 Aug 2026 14:46:23 -0700 Subject: [PATCH 1/6] feat(integrations): fx harness example (MCP config) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fx (Vercel Labs' Zig coding agent, v0.0.3) consumes the facade as a standard stdio MCP server; native tools and SDK embedding are not possible (no plugin API; libfx hard-disables MCP mounting). Ships the user-global mcp.json template (no environment block — fx replaces the child env wholesale when one is set, so shell inheritance is the safe path), a project .fx.json raising max_tool_result_bytes past snapshot size and max_agent_steps past the MCP discovery round trips, the headless permission allowlist (rule-key shape verified empirically), and a stagehand-facade skill generated from FACADE_AGENT_INSTRUCTIONS with an fx preamble naming the exact generated tool ids — fx v0.0.3's mcp_search_tools returns no results for the server, and exact-name mcp_select_tool is the working path. Smoke (fx ask --json, sandboxed HOME, Browserbase): skill -> select x2 -> run -> snapshot, 'Example Domain' [0-19], 5 steps, reproduced twice. --- packages/integrations/fx/.fx.json | 4 + packages/integrations/fx/README.md | 89 +++++++++++++++++++ packages/integrations/fx/mcp.json | 12 +++ .../fx/skills/stagehand-facade/SKILL.md | 16 ++++ 4 files changed, 121 insertions(+) create mode 100644 packages/integrations/fx/.fx.json create mode 100644 packages/integrations/fx/README.md create mode 100644 packages/integrations/fx/mcp.json create mode 100644 packages/integrations/fx/skills/stagehand-facade/SKILL.md diff --git a/packages/integrations/fx/.fx.json b/packages/integrations/fx/.fx.json new file mode 100644 index 000000000..946785f45 --- /dev/null +++ b/packages/integrations/fx/.fx.json @@ -0,0 +1,4 @@ +{ + "max_agent_steps": 60, + "max_tool_result_bytes": 262144 +} diff --git a/packages/integrations/fx/README.md b/packages/integrations/fx/README.md new file mode 100644 index 000000000..4ad2bbd50 --- /dev/null +++ b/packages/integrations/fx/README.md @@ -0,0 +1,89 @@ +# fx + Stagehand facade over MCP/stdio + +[fx](https://fx.sh) consumes the Stagehand facade as a standard MCP server — no integration +code, just an entry in fx's user-global MCP config. This directory ships the config template, +a project config that sizes fx's limits for browser work, and a skill carrying the facade +usage guidance. + + + +## Setup + +Use Node.js 24 or newer. From the repository root, build the integrations package first: + +```bash +pnpm install +pnpm exec turbo run build --filter @browserbasehq/stagehand-integrations +``` + +Install fx (pin the version — fx is experimental) and authenticate with Vercel AI Gateway: + +```bash +curl -fsSL https://fx.sh/setup.sh | bash -s -- v0.0.3 +fx login # or: export AI_GATEWAY_API_KEY=... +``` + +Export the browser credentials (Browserbase is the default and recommended backend): + +```bash +export BROWSERBASE_API_KEY=bb_live_... +export BROWSERBASE_PROJECT_ID=... +``` + +## Configure + +fx loads MCP servers only from the user-global `~/.fx/mcp.json`; repository-local MCP config +is deliberately never loaded. Merge `mcp.json` from this directory into `~/.fx/mcp.json`, +filling in the absolute path to your checkout, then run `/mcp reload` in an open session (or +just start a new one). + +Leave the `environment` block out, as the template does: fx passes the full shell environment +to the server when no block is set, so the exports above are the only configuration. Setting +`environment` replaces the child environment wholesale — even `PATH` and `HOME` disappear — +so if you must pin variables there, restate `PATH` explicitly. + +## Run + +Run fx from this directory so it picks up `.fx.json` (which raises `max_tool_result_bytes` — +page snapshots exceed fx's 64 KB default — and `max_agent_steps`, since fx's tool discovery +adds a `mcp_search_tools`/`mcp_select_tool` round trip before the browser tools are callable) +and the `skills/stagehand-facade` skill: + +```bash +cd packages/integrations/fx +fx ask --json "Use the stagehand browser tools: open https://example.com, snapshot it, and report the heading citing the snapshot ID." +``` + +The three tools surface as `mcp_stagehand_run`, `mcp_stagehand_snapshot`, and +`mcp_stagehand_screenshot`. fx v0.0.3's `mcp_search_tools` returns no results for this server, +so the shipped skill instructs the model to select the tools by those exact names instead — +without the skill, runs stall in discovery. Headless runs cannot answer permission prompts; either pre-allow +the tools in `~/.fx/settings.json`: + +```json +{ + "permission": { + "mcp_stagehand_run": "allow", + "mcp_stagehand_snapshot": "allow", + "mcp_stagehand_screenshot": "allow" + } +} +``` + +or pass `--auto`, accepting that fx adjudicates each gated call with an extra model request. + + +fx starts MCP servers with a fixed 10-second timeout and discards their stderr. The facade +connects immediately and launches the browser lazily on the first tool call, so startup fits +the budget — but if the server misbehaves, debug it standalone (spawn the bin directly and +speak JSON-RPC over stdio) rather than through fx. + + +## Security model + +The `run` tool executes model-authored JavaScript inside the Stagehand browser extension's +service worker — browser-side, never on your machine. Browserbase is the recommended isolation +boundary: the privileged execution environment is a disposable cloud browser. With no +`environment` block, the server inherits your shell environment; the facade only reads +`STAGEHAND_*`/`BROWSERBASE_*` variables, and nothing from the host environment is forwarded +into the browser session itself. diff --git a/packages/integrations/fx/mcp.json b/packages/integrations/fx/mcp.json new file mode 100644 index 000000000..ac33814ac --- /dev/null +++ b/packages/integrations/fx/mcp.json @@ -0,0 +1,12 @@ +{ + "mcp": { + "stagehand": { + "type": "stdio", + "command": [ + "node", + "/absolute/path/to/stagehand/packages/integrations/core/dist/facade/stdio-server.mjs" + ], + "required": true + } + } +} diff --git a/packages/integrations/fx/skills/stagehand-facade/SKILL.md b/packages/integrations/fx/skills/stagehand-facade/SKILL.md new file mode 100644 index 000000000..e2c6e312f --- /dev/null +++ b/packages/integrations/fx/skills/stagehand-facade/SKILL.md @@ -0,0 +1,16 @@ +--- +name: stagehand-facade +description: How to drive the Stagehand browser tools (run, snapshot, screenshot) exposed over MCP. +--- + +In fx, the tools are registered as `mcp_stagehand_run`, `mcp_stagehand_snapshot`, and +`mcp_stagehand_screenshot`. Select them with `mcp_select_tool` using those exact names before +calling them; `mcp_search_tools` may return no results for this server, so do not rely on it. + +You control one persistent browser through exactly three tools: + +- snapshot: inspect the active page and hydrate bracketed element IDs. +- run: provide either snapshot actions or JavaScript using the Playwright-shaped page API. +- screenshot: inspect the rendered page visually. + +Use snapshot actions for simple interactions and run code for multi-step workflows. Pass run exactly one of code or actions; every action uses "op" and "id", never "kind" or "ref". Snapshot IDs are valid only for the latest snapshot of the active page; snapshot again after navigation or stale IDs. Do not launch another browser. From 0133f8c544f18df3f794b2e50895b2a90c9534a1 Mon Sep 17 00:00:00 2001 From: miguel Date: Wed, 19 Aug 2026 14:47:13 -0700 Subject: [PATCH 2/6] docs(integrations): harden fx headless guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Field testing showed a run outside the example directory (skill not loaded) stalls in tool discovery and falls back to native run_command exploration — which dumped the shell environment, credentials included, into the model transcript. The README now marks the working directory as required and recommends denying run_command for browser-only headless workflows. --- packages/integrations/fx/README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/integrations/fx/README.md b/packages/integrations/fx/README.md index 4ad2bbd50..9bdb0c69b 100644 --- a/packages/integrations/fx/README.md +++ b/packages/integrations/fx/README.md @@ -44,7 +44,10 @@ so if you must pin variables there, restate `PATH` explicitly. ## Run -Run fx from this directory so it picks up `.fx.json` (which raises `max_tool_result_bytes` — +Run fx from this directory — this is required, not optional: the `skills/stagehand-facade` +skill teaches the model the exact `mcp_stagehand_*` tool names (fx's tool search cannot find +them), and without it runs stall in discovery and fall back to shell exploration. Running here +also picks up `.fx.json` (which raises `max_tool_result_bytes` — page snapshots exceed fx's 64 KB default — and `max_agent_steps`, since fx's tool discovery adds a `mcp_search_tools`/`mcp_select_tool` round trip before the browser tools are callable) and the `skills/stagehand-facade` skill: @@ -71,6 +74,21 @@ the tools in `~/.fx/settings.json`: ``` or pass `--auto`, accepting that fx adjudicates each gated call with an extra model request. +For browser-only workflows, also deny fx's shell tool — if the model cannot find the browser +tools (for example when the skill is not loaded), it falls back to exploring the machine with +`run_command`, which can dump your environment (including credentials) into the model +transcript: + +```json +{ + "permission": { + "mcp_stagehand_run": "allow", + "mcp_stagehand_snapshot": "allow", + "mcp_stagehand_screenshot": "allow", + "run_command": "deny" + } +} +``` fx starts MCP servers with a fixed 10-second timeout and discards their stderr. The facade From 1483e5ebf5edf77219d3f14bb598e9b5f61c9a22 Mon Sep 17 00:00:00 2001 From: miguel Date: Wed, 19 Aug 2026 14:54:58 -0700 Subject: [PATCH 3/6] fix(integrations): accurate fx env-inheritance docs; bound sdk-parity test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The facade can infer model-provider keys from an inherited environment for optional Stagehand model config, so the security section now says so and documents the environment-allowlist alternative (with the PATH/HOME restatement fx requires). Also bound the sdk-parity notification test at 30s — third test to trip the 5s default on cold runners. --- packages/integrations/fx/README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/integrations/fx/README.md b/packages/integrations/fx/README.md index 9bdb0c69b..3c948dc84 100644 --- a/packages/integrations/fx/README.md +++ b/packages/integrations/fx/README.md @@ -102,6 +102,19 @@ speak JSON-RPC over stdio) rather than through fx. The `run` tool executes model-authored JavaScript inside the Stagehand browser extension's service worker — browser-side, never on your machine. Browserbase is the recommended isolation boundary: the privileged execution environment is a disposable cloud browser. With no -`environment` block, the server inherits your shell environment; the facade only reads -`STAGEHAND_*`/`BROWSERBASE_*` variables, and nothing from the host environment is forwarded -into the browser session itself. +`environment` block, the server inherits your shell environment. The facade reads +`STAGEHAND_*`/`BROWSERBASE_*` variables and can also infer a model-provider key +(`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or a Google key) from its environment for optional +Stagehand model configuration. To keep provider keys away from the facade entirely, use an +`environment` allowlist instead of inheritance — restating `PATH` and `HOME`, which fx drops +when any block is set: + +```json +"environment": { + "PATH": "/usr/local/bin:/usr/bin:/bin", + "HOME": "/Users/you", + "STAGEHAND_BROWSER": "browserbase", + "BROWSERBASE_API_KEY": "bb_live_...", + "BROWSERBASE_PROJECT_ID": "..." +} +``` From 123317fd169efdba508761609599eaca1a5e38a5 Mon Sep 17 00:00:00 2001 From: Miguel <36487034+miguelg719@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:04:21 -0700 Subject: [PATCH 4/6] Apply suggestion from @miguelg719 --- packages/integrations/fx/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/integrations/fx/README.md b/packages/integrations/fx/README.md index 3c948dc84..bf3482e0f 100644 --- a/packages/integrations/fx/README.md +++ b/packages/integrations/fx/README.md @@ -27,7 +27,6 @@ Export the browser credentials (Browserbase is the default and recommended backe ```bash export BROWSERBASE_API_KEY=bb_live_... -export BROWSERBASE_PROJECT_ID=... ``` ## Configure From 0c546d4e0f57145e92725896e0113fde2832bb9b Mon Sep 17 00:00:00 2001 From: miguel Date: Wed, 19 Aug 2026 15:12:02 -0700 Subject: [PATCH 5/6] docs(integrations): drop project id from fx allowlist example --- packages/integrations/fx/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/integrations/fx/README.md b/packages/integrations/fx/README.md index bf3482e0f..e76e2dbf3 100644 --- a/packages/integrations/fx/README.md +++ b/packages/integrations/fx/README.md @@ -113,7 +113,6 @@ when any block is set: "PATH": "/usr/local/bin:/usr/bin:/bin", "HOME": "/Users/you", "STAGEHAND_BROWSER": "browserbase", - "BROWSERBASE_API_KEY": "bb_live_...", - "BROWSERBASE_PROJECT_ID": "..." + "BROWSERBASE_API_KEY": "bb_live_..." } ``` From 71d10451a43384be797fe9934c6fb3fbcef4ba43 Mon Sep 17 00:00:00 2001 From: miguel Date: Thu, 20 Aug 2026 10:23:13 -0700 Subject: [PATCH 6/6] test(extension): use runtime version in identity marker --- packages/extension/tests/stagehand-clients.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/extension/tests/stagehand-clients.test.ts b/packages/extension/tests/stagehand-clients.test.ts index ffa5c8b0e..48d3013cc 100644 --- a/packages/extension/tests/stagehand-clients.test.ts +++ b/packages/extension/tests/stagehand-clients.test.ts @@ -9,6 +9,7 @@ import { StagehandSendToHostBindingSchema, } from "../../protocol/schema-registry.ts"; import { startStagehandServiceWorker } from "../service-worker.ts"; +import { STAGEHAND_RUNTIME_VERSION } from "../version.ts"; import type { StagehandBrowserSession, UnderstudyRuntimeClipboardOptions, @@ -804,7 +805,7 @@ describe("Stagehand worker clients", () => { protocolVersion: STAGEHAND_PROTOCOL_VERSION, serverInfo: { name: "stagehand", - version: "1.0.0", + version: STAGEHAND_RUNTIME_VERSION, }, }, __stagehandReceiveFromHost: expect.any(Function),