Skip to content

feat(vercel): add Stagehand code-mode MCP example - #2626

Open
shrey150 wants to merge 3 commits into
shrey/stg-2765-codemode-guidancefrom
shrey/stg-2765-codemode-vercel
Open

feat(vercel): add Stagehand code-mode MCP example#2626
shrey150 wants to merge 3 commits into
shrey/stg-2765-codemode-guidancefrom
shrey/stg-2765-codemode-vercel

Conversation

@shrey150

@shrey150 shrey150 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Why

The code-mode MCP needs to be exercised through real agent frameworks before its package is published. This first consumer layer keeps the Vercel AI SDK example beside the private workspace package so it can test the exact MCP build under review.

Stack

  1. #2597 — private package and tool-free MCP host
  2. #2619code_execute, executor, local/Browserbase configuration, and runtime tests
  3. #2620 — canonical skill, reference, generated exports, and loading checks
  4. This PR — Vercel AI SDK stdio MCP example and live smoke flows
  5. #2627 — Mastra adapter and smoke flows
  6. #2628 — CrewAI adapter and smoke flows
  7. #2629 — LangChain Deep Agents adapter and smoke flows

What changed

  • adds a private package-local Vercel AI SDK example
  • launches the canonical compiled Stagehand MCP through the AI SDK stdio transport
  • keeps one MCP client alive for the complete agent run so browser state survives across calls
  • passes the generated Stagehand skill as system guidance while retaining the same guidance in the MCP tool description
  • includes a deterministic two-call browser smoke and a real model-driven two-call flow
  • supports both STAGEHAND_BROWSER=local and STAGEHAND_BROWSER=browserbase through the inherited MCP environment
  • adds a dedicated local-browser CI smoke job

The AI SDK stdio transport is currently marked experimental upstream. Keeping execution in the MCP child preserves the process boundary needed for owner-enforced recovery from non-cooperative generated JavaScript.

E2E Test Matrix

Command / flow Observed output Confidence / sufficiency
pnpm --filter @browserbasehq/stagehand-integrations-example-vercel typecheck TypeScript completed without errors. Confirms the example matches the pinned AI SDK MCP and Stagehand package types.
pnpm --filter @browserbasehq/stagehand-integrations test 9 test files and 63 tests passed after rebuilding the package. Guards the complete canonical MCP, executor, schema, lifecycle, redaction, and generated-guidance surface consumed by the example.
Vercel AI SDK MCP smoke with STAGEHAND_BROWSER=local {"status":"PASS","tools":["code_execute"],"statePersisted":true} Proves the framework discovers exactly one executable MCP tool and two direct tool calls share a real local browser.
Real Vercel AI SDK agent with local startup The agent made exactly two code_execute calls and returned Example Domain with two open pages. Proves the real model loop, stdio client, generated guidance, local browser, and persistent session work together.
Real Vercel AI SDK agent with Browserbase startup The agent made exactly two code_execute calls and returned Example Domain with two open pages. Proves the same framework adapter provisions and reuses a real remote browser without adapter-specific runtime code.

Changeset

None. This adds a private example and test surface without changing a published package.

@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 95902d6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@shrey150 shrey150 closed this Aug 6, 2026
@shrey150 shrey150 reopened this Aug 6, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found and verified against the latest diff

Confidence score: 2/5

  • In packages/integrations/examples/vercel/src/agent.ts (runStagehandAgent), passing the full parent process.env into model-executed JS exposes unrelated secrets to accidental or malicious exfiltration, which is the highest-impact risk here — restrict child env to a minimal allowlist of required variables.
  • In .github/workflows/codemode-framework-examples.yml, running browser smoke jobs for external fork PRs without a maintainer opt-in can consume hosted-runner capacity and invite avoidable CI abuse — add the same safe-to-test/same-repo gate used in other workflows.
  • In packages/integrations/examples/vercel/src/e2e.ts and packages/integrations/examples/vercel/src/smoke.ts, weak assertions can report PASS when behavior is actually wrong (page-count substring match and missing second-call title check), reducing signal from automated checks and allowing regressions through — tighten assertions to page/count context or structured output and verify title restoration on the second call.
  • In packages/integrations/examples/vercel/src/agent.ts, failures before the try path can leave the stdio child process running when MCP setup/tool registration errors occur, causing process leaks and flaky local/CI runs over time — ensure child cleanup is guaranteed on all early-error paths.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/integrations/examples/vercel/src/e2e.ts">

<violation number="1" location="packages/integrations/examples/vercel/src/e2e.ts:20">
P2: The e2e can report PASS when the model returns the wrong page-count value because the assertion only searches for the substring `"2"`. Checking for `2` in a page/count context (or structured output) would make this validation meaningful.</violation>
</file>

<file name=".github/workflows/codemode-framework-examples.yml">

<violation number="1" location=".github/workflows/codemode-framework-examples.yml:36">
P2: External fork PRs will run this hosted-runner browser smoke job immediately, which can spend CI resources before a maintainer opt-in. Adding the same `safe-to-test`/same-repo gate used elsewhere would keep this workflow aligned with your existing fork-safety pattern.

(Based on your team's feedback about maintainer approval gates for external PR workflows.)</violation>
</file>

<file name="packages/integrations/examples/vercel/src/smoke.ts">

<violation number="1" location="packages/integrations/examples/vercel/src/smoke.ts:43">
P2: This smoke assertion can pass even when the active page is not restored across calls, because it validates second-call page count but not second-call title. Including the second-call title in the condition would better catch state-restoration regressions.</violation>
</file>

<file name="packages/integrations/examples/vercel/src/agent.ts">

<violation number="1" location="packages/integrations/examples/vercel/src/agent.ts:28">
P1: This passes the full parent environment into a process running model-generated JavaScript, so unrelated secrets become available for accidental or malicious exfiltration. A small allowlist of required variables (browser mode/credentials, PATH, etc.) would reduce that exposure.</violation>

<violation number="2" location="packages/integrations/examples/vercel/src/agent.ts:42">
P3: If MCP connection or tool registration fails, the spawned stdio child process isn't cleaned up. `createStagehandMcpBinding()` runs before the try in `runStagehandAgent`, so when `client.tools()` rejects the just-created client is never closed, leaking the child MCP process and any browser it launched. The official `@ai-sdk/mcp` usage guards this exact path by closing the client even on early failure. Consider closing the client inside `createStagehandMcpBinding` if `tools()` throws, and/or creating the binding within the try/finally so the error path also releases the client.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

transport: new Experimental_StdioMCPTransport({
command: process.execPath,
args: [stdioServerPath],
env: definedEnvironment(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This passes the full parent environment into a process running model-generated JavaScript, so unrelated secrets become available for accidental or malicious exfiltration. A small allowlist of required variables (browser mode/credentials, PATH, etc.) would reduce that exposure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/examples/vercel/src/agent.ts, line 28:

<comment>This passes the full parent environment into a process running model-generated JavaScript, so unrelated secrets become available for accidental or malicious exfiltration. A small allowlist of required variables (browser mode/credentials, PATH, etc.) would reduce that exposure.</comment>

<file context>
@@ -0,0 +1,66 @@
+    transport: new Experimental_StdioMCPTransport({
+      command: process.execPath,
+      args: [stdioServerPath],
+      env: definedEnvironment(),
+    }),
+  });
</file context>

result.toolNames.length !== 2 ||
result.toolNames.some((name) => name !== "code_execute") ||
!result.text.includes("Example Domain") ||
!result.text.includes("2")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The e2e can report PASS when the model returns the wrong page-count value because the assertion only searches for the substring "2". Checking for 2 in a page/count context (or structured output) would make this validation meaningful.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/examples/vercel/src/e2e.ts, line 20:

<comment>The e2e can report PASS when the model returns the wrong page-count value because the assertion only searches for the substring `"2"`. Checking for `2` in a page/count context (or structured output) would make this validation meaningful.</comment>

<file context>
@@ -0,0 +1,25 @@
+  result.toolNames.length !== 2 ||
+  result.toolNames.some((name) => name !== "code_execute") ||
+  !result.text.includes("Example Domain") ||
+  !result.text.includes("2")
+) {
+  throw new Error(`Unexpected agent result: ${JSON.stringify(result)}`);
</file context>

jobs:
vercel:
name: Vercel AI SDK
runs-on: ubuntu-latest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: External fork PRs will run this hosted-runner browser smoke job immediately, which can spend CI resources before a maintainer opt-in. Adding the same safe-to-test/same-repo gate used elsewhere would keep this workflow aligned with your existing fork-safety pattern.

(Based on your team's feedback about maintainer approval gates for external PR workflows.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/codemode-framework-examples.yml, line 36:

<comment>External fork PRs will run this hosted-runner browser smoke job immediately, which can spend CI resources before a maintainer opt-in. Adding the same `safe-to-test`/same-repo gate used elsewhere would keep this workflow aligned with your existing fork-safety pattern.

(Based on your team's feedback about maintainer approval gates for external PR workflows.) </comment>

<file context>
@@ -0,0 +1,53 @@
+jobs:
+  vercel:
+    name: Vercel AI SDK
+    runs-on: ubuntu-latest
+    timeout-minutes: 20
+    steps:
</file context>


const firstText = JSON.stringify(first);
const secondText = JSON.stringify(second);
if (!firstText.includes("Example Domain") || !secondText.includes('"pages":2')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This smoke assertion can pass even when the active page is not restored across calls, because it validates second-call page count but not second-call title. Including the second-call title in the condition would better catch state-restoration regressions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/examples/vercel/src/smoke.ts, line 43:

<comment>This smoke assertion can pass even when the active page is not restored across calls, because it validates second-call page count but not second-call title. Including the second-call title in the condition would better catch state-restoration regressions.</comment>

<file context>
@@ -0,0 +1,52 @@
+
+  const firstText = JSON.stringify(first);
+  const secondText = JSON.stringify(second);
+  if (!firstText.includes("Example Domain") || !secondText.includes('"pages":2')) {
+    throw new Error(`Expected browser state to persist across calls: ${firstText} ${secondText}`);
+  }
</file context>

model: LanguageModel,
prompt: string,
): Promise<StagehandAgentResult> {
const { client, tools } = await createStagehandMcpBinding();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: If MCP connection or tool registration fails, the spawned stdio child process isn't cleaned up. createStagehandMcpBinding() runs before the try in runStagehandAgent, so when client.tools() rejects the just-created client is never closed, leaking the child MCP process and any browser it launched. The official @ai-sdk/mcp usage guards this exact path by closing the client even on early failure. Consider closing the client inside createStagehandMcpBinding if tools() throws, and/or creating the binding within the try/finally so the error path also releases the client.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/examples/vercel/src/agent.ts, line 42:

<comment>If MCP connection or tool registration fails, the spawned stdio child process isn't cleaned up. `createStagehandMcpBinding()` runs before the try in `runStagehandAgent`, so when `client.tools()` rejects the just-created client is never closed, leaking the child MCP process and any browser it launched. The official `@ai-sdk/mcp` usage guards this exact path by closing the client even on early failure. Consider closing the client inside `createStagehandMcpBinding` if `tools()` throws, and/or creating the binding within the try/finally so the error path also releases the client.</comment>

<file context>
@@ -0,0 +1,66 @@
+  model: LanguageModel,
+  prompt: string,
+): Promise<StagehandAgentResult> {
+  const { client, tools } = await createStagehandMcpBinding();
+  try {
+    const result = await generateText({
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant