Skip to content

feat(mastra): add Stagehand code-mode MCP example - #2627

Open
shrey150 wants to merge 2 commits into
shrey/stg-2765-codemode-vercelfrom
shrey/stg-2765-codemode-mastra
Open

feat(mastra): add Stagehand code-mode MCP example#2627
shrey150 wants to merge 2 commits into
shrey/stg-2765-codemode-vercelfrom
shrey/stg-2765-codemode-mastra

Conversation

@shrey150

@shrey150 shrey150 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Why

The code-mode MCP should be proven through each framework's native MCP client before the package is published. This layer ports the Mastra draft beside the canonical Stagehand implementation so it consumes and tests the exact build under review.

Stack

  1. #2597 — private package and tool-free MCP host
  2. #2619code_execute, executor, configuration, and runtime tests
  3. #2620 — canonical skill, reference, generated exports, and loading checks
  4. #2626 — Vercel AI SDK adapter and smoke flows
  5. This PR — 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 Mastra example
  • launches the canonical compiled Stagehand MCP through Mastra's stdio client
  • discovers exactly code_execute and validates that its description carries the canonical guidance
  • keeps one MCP client alive for the complete agent run so browser state survives across calls
  • forwards local and Browserbase configuration through the inherited environment
  • adds a dedicated local-browser CI smoke job

E2E Test Matrix

Command / flow Observed output Confidence / sufficiency
pnpm --filter @browserbasehq/stagehand-integrations-example-mastra typecheck TypeScript completed without errors. Confirms the example matches the pinned Mastra MCP and core APIs.
pnpm --filter @browserbasehq/stagehand-integrations test 9 test files and 63 tests passed after rebuilding the package. Guards the canonical executor, schema, configuration, lifecycle, redaction, and guidance surfaces consumed by this example.
Mastra MCP smoke with STAGEHAND_BROWSER=local code_execute ran twice; the second call observed the title and DOM marker created by the first; disconnect passed. Proves discovery, execution, persistent state, and cleanup with a real local browser.
Mastra MCP smoke with STAGEHAND_BROWSER=browserbase code_execute ran twice against one remote page; title, marker, and page count persisted; disconnect passed. Proves Browserbase configuration reaches the child and the remote session is reused.
Real Mastra agent with groq/openai/gpt-oss-120b and Browserbase startup The model invoked code_execute, then returned the Example Domain heading and title; disconnect passed. Proves the real model loop, Mastra MCP client, canonical guidance, and remote browser work together.
Process cleanup audit Matching stdio children were 0 before and after both Browserbase flows. Confirms the example's explicit disconnect closes the owned MCP child in normal operation.

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: 3901388

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.

4 issues found and verified against the latest diff

Confidence score: 4/5

  • In packages/integrations/examples/mastra/src/smoke.ts, throwing from cleanup in finally can overwrite the original execute/assert failure, which makes smoke-test regressions harder to diagnose and can mislead CI triage—preserve the primary error and only surface disconnect failures when no earlier error occurred.
  • In packages/integrations/examples/mastra/src/agent.ts (runStagehandAgent), await handle.close() in finally can replace a real agent result/error if mcp.disconnect() rejects, potentially turning successful runs into false failures—guard or swallow disconnect errors after capturing the main outcome.
  • In .github/workflows/codemode-framework-examples.yml, duplicated vercel/mastra job logic can drift over time so one framework may pass while the other silently stops exercising equivalent steps—consolidate shared logic via a matrix job to keep behavior aligned.
  • In packages/integrations/examples/mastra/package.json, the unused @browserbasehq/stagehand-integrations dependency adds maintenance/version-noise risk without functional value in this example—remove it unless it is intentionally required soon.
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=".github/workflows/codemode-framework-examples.yml">

<violation number="1" location=".github/workflows/codemode-framework-examples.yml:55">
P3: CI behavior is now duplicated across `vercel` and `mastra`, so future edits to shared setup/build/smoke steps can silently diverge between examples. A matrix-based single job (framework/package as parameters) would keep these steps in one place and reduce maintenance risk.</violation>
</file>

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

<violation number="1" location="packages/integrations/examples/mastra/src/smoke.ts:63">
P2: A disconnect error in cleanup can hide the real smoke-test failure because throws from `finally` override earlier `execute`/assert errors. It would be safer to preserve the primary error and only fail on disconnect when the main flow succeeded.</violation>
</file>

<file name="packages/integrations/examples/mastra/package.json">

<violation number="1" location="packages/integrations/examples/mastra/package.json:11">
P3: The `@browserbasehq/stagehand-integrations` dependency is declared here but never imported anywhere in this example. Unlike the sibling `examples/vercel` package (whose `agent.ts` does `import ... from "@browserbasehq/stagehand-integrations/..."`), the Mastra example launches the already-built stdio server by direct file URL (`../../../dist/codemode/stdio-server.mjs`) and only imports `@mastra/core` and `@mastra/mcp`. This `workspace:*` entry is therefore unused at runtime and only pins a package that is not referenced. If it's kept to document the build dependency on the integrations dist output, a comment would help, but as-is it reads as a dead dependency.</violation>
</file>

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

<violation number="1" location="packages/integrations/examples/mastra/src/agent.ts:94">
P3: In `runStagehandAgent` the `finally` block does `await handle.close()`, where `close()` maps directly to `mcp.disconnect()`. If `disconnect()` rejects, that rejection replaces the agent's generated text (or the original error from `generate`), so the caller sees an unrelated disconnect failure instead of the actual outcome. The failure path inside `createStagehandAgent` already guards this with `.catch(() => undefined)`; `runStagehandAgent` should apply the same guard so cleanup cannot swallow a successful agent result or mask the real error.</violation>
</file>

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

Re-trigger cubic

}),
);
} finally {
await mcp.disconnect();

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: A disconnect error in cleanup can hide the real smoke-test failure because throws from finally override earlier execute/assert errors. It would be safer to preserve the primary error and only fail on disconnect when the main flow succeeded.

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

<comment>A disconnect error in cleanup can hide the real smoke-test failure because throws from `finally` override earlier `execute`/assert errors. It would be safer to preserve the primary error and only fail on disconnect when the main flow succeeded.</comment>

<file context>
@@ -0,0 +1,79 @@
+    }),
+  );
+} finally {
+  await mcp.disconnect();
+  console.log("Mastra MCP disconnect PASS");
+}
</file context>

CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
STAGEHAND_BROWSER: local

mastra:

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: CI behavior is now duplicated across vercel and mastra, so future edits to shared setup/build/smoke steps can silently diverge between examples. A matrix-based single job (framework/package as parameters) would keep these steps in one place and reduce maintenance risk.

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 55:

<comment>CI behavior is now duplicated across `vercel` and `mastra`, so future edits to shared setup/build/smoke steps can silently diverge between examples. A matrix-based single job (framework/package as parameters) would keep these steps in one place and reduce maintenance risk.</comment>

<file context>
@@ -51,3 +51,24 @@ jobs:
           CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
           STAGEHAND_BROWSER: local
+
+  mastra:
+    name: Mastra
+    runs-on: ubuntu-latest
</file context>

"typecheck": "tsc --noEmit"
},
"dependencies": {
"@browserbasehq/stagehand-integrations": "workspace:*",

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: The @browserbasehq/stagehand-integrations dependency is declared here but never imported anywhere in this example. Unlike the sibling examples/vercel package (whose agent.ts does import ... from "@browserbasehq/stagehand-integrations/..."), the Mastra example launches the already-built stdio server by direct file URL (../../../dist/codemode/stdio-server.mjs) and only imports @mastra/core and @mastra/mcp. This workspace:* entry is therefore unused at runtime and only pins a package that is not referenced. If it's kept to document the build dependency on the integrations dist output, a comment would help, but as-is it reads as a dead dependency.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/examples/mastra/package.json, line 11:

<comment>The `@browserbasehq/stagehand-integrations` dependency is declared here but never imported anywhere in this example. Unlike the sibling `examples/vercel` package (whose `agent.ts` does `import ... from "@browserbasehq/stagehand-integrations/..."`), the Mastra example launches the already-built stdio server by direct file URL (`../../../dist/codemode/stdio-server.mjs`) and only imports `@mastra/core` and `@mastra/mcp`. This `workspace:*` entry is therefore unused at runtime and only pins a package that is not referenced. If it's kept to document the build dependency on the integrations dist output, a comment would help, but as-is it reads as a dead dependency.</comment>

<file context>
@@ -0,0 +1,23 @@
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@browserbasehq/stagehand-integrations": "workspace:*",
+    "@mastra/core": "catalog:",
+    "@mastra/mcp": "catalog:"
</file context>

const result = await handle.agent.generate(prompt, { maxSteps: 8 });
return result.text;
} finally {
await handle.close();

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: In runStagehandAgent the finally block does await handle.close(), where close() maps directly to mcp.disconnect(). If disconnect() rejects, that rejection replaces the agent's generated text (or the original error from generate), so the caller sees an unrelated disconnect failure instead of the actual outcome. The failure path inside createStagehandAgent already guards this with .catch(() => undefined); runStagehandAgent should apply the same guard so cleanup cannot swallow a successful agent result or mask the real error.

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

<comment>In `runStagehandAgent` the `finally` block does `await handle.close()`, where `close()` maps directly to `mcp.disconnect()`. If `disconnect()` rejects, that rejection replaces the agent's generated text (or the original error from `generate`), so the caller sees an unrelated disconnect failure instead of the actual outcome. The failure path inside `createStagehandAgent` already guards this with `.catch(() => undefined)`; `runStagehandAgent` should apply the same guard so cleanup cannot swallow a successful agent result or mask the real error.</comment>

<file context>
@@ -0,0 +1,112 @@
+    const result = await handle.agent.generate(prompt, { maxSteps: 8 });
+    return result.text;
+  } finally {
+    await handle.close();
+  }
+}
</file context>
Suggested change
await handle.close();
} finally {
await handle.close().catch(() => undefined);
}

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