feat(mastra): add Stagehand code-mode MCP example - #2627
Conversation
|
There was a problem hiding this comment.
4 issues found and verified against the latest diff
Confidence score: 4/5
- In
packages/integrations/examples/mastra/src/smoke.ts, throwing from cleanup infinallycan 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()infinallycan replace a real agent result/error ifmcp.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, duplicatedvercel/mastrajob 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-integrationsdependency 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(); |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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:*", |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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>
| await handle.close(); | |
| } finally { | |
| await handle.close().catch(() => undefined); | |
| } |
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
code_execute, executor, configuration, and runtime testsWhat changed
code_executeand validates that its description carries the canonical guidanceE2E Test Matrix
pnpm --filter @browserbasehq/stagehand-integrations-example-mastra typecheckpnpm --filter @browserbasehq/stagehand-integrations testSTAGEHAND_BROWSER=localcode_executeran twice; the second call observed the title and DOM marker created by the first; disconnect passed.STAGEHAND_BROWSER=browserbasecode_executeran twice against one remote page; title, marker, and page count persisted; disconnect passed.groq/openai/gpt-oss-120band Browserbase startupcode_execute, then returned theExample Domainheading and title; disconnect passed.0before and after both Browserbase flows.Changeset
None. This adds a private example and test surface without changing a published package.