From 2e6d681148689029ca9767408dd3ccf31dd2be2a Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 4 Aug 2026 14:33:53 -0700 Subject: [PATCH 1/3] feat(crewai): add Stagehand code-mode integration --- .github/workflows/stagehand-codemode.yml | 24 ++++++++++++ .../crewai/stagehand-codemode/README.md | 10 +++++ .../crewai/stagehand-codemode/agent.py | 38 +++++++++++++++++++ .../stagehand-codemode/requirements.txt | 2 + .../crewai/stagehand-codemode/smoke.py | 24 ++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 examples/integrations/crewai/stagehand-codemode/README.md create mode 100644 examples/integrations/crewai/stagehand-codemode/agent.py create mode 100644 examples/integrations/crewai/stagehand-codemode/requirements.txt create mode 100644 examples/integrations/crewai/stagehand-codemode/smoke.py diff --git a/.github/workflows/stagehand-codemode.yml b/.github/workflows/stagehand-codemode.yml index 68c9ce7..cec967f 100644 --- a/.github/workflows/stagehand-codemode.yml +++ b/.github/workflows/stagehand-codemode.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - '.github/workflows/stagehand-codemode.yml' + - 'examples/integrations/crewai/stagehand-codemode/**' - 'examples/integrations/mastra/stagehand-codemode/**' - 'examples/integrations/vercel/stagehand-codemode/**' - 'packages/stagehand-codemode/**' @@ -12,6 +13,7 @@ on: branches: [main] paths: - '.github/workflows/stagehand-codemode.yml' + - 'examples/integrations/crewai/stagehand-codemode/**' - 'examples/integrations/mastra/stagehand-codemode/**' - 'examples/integrations/vercel/stagehand-codemode/**' - 'packages/stagehand-codemode/**' @@ -70,3 +72,25 @@ jobs: - run: pnpm --filter @browserbasehq/stagehand-codemode run build - run: pnpm --filter stagehand-codemode-mastra-example run check - run: pnpm --filter stagehand-codemode-mastra-example run smoke + + crewai: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10.9.0 + - uses: actions/setup-node@v4 + with: + node-version: 24.x + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: pip + cache-dependency-path: examples/integrations/crewai/stagehand-codemode/requirements.txt + + - run: pnpm install --no-frozen-lockfile + - run: pnpm --filter @browserbasehq/stagehand-codemode run build + - run: python -m pip install -r examples/integrations/crewai/stagehand-codemode/requirements.txt + - run: python examples/integrations/crewai/stagehand-codemode/smoke.py diff --git a/examples/integrations/crewai/stagehand-codemode/README.md b/examples/integrations/crewai/stagehand-codemode/README.md new file mode 100644 index 0000000..8a956d1 --- /dev/null +++ b/examples/integrations/crewai/stagehand-codemode/README.md @@ -0,0 +1,10 @@ +# CrewAI + Stagehand code mode + +CrewAI's native `MCPServerStdio` launches the local MCP beside the agent. One server configuration is +attached to the `Agent` for the whole `kickoff` run, allowing repeated `code_execute` calls to reuse +the same browser. CrewAI cleans up the MCP client after agent execution. + +The shared Stagehand V4 syntax skill is placed in the agent's `backstory` and is also included in the +tool description discovered from MCP. + +See [`agent.py`](./agent.py). diff --git a/examples/integrations/crewai/stagehand-codemode/agent.py b/examples/integrations/crewai/stagehand-codemode/agent.py new file mode 100644 index 0000000..27815fb --- /dev/null +++ b/examples/integrations/crewai/stagehand-codemode/agent.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Any + +from crewai import Agent +from crewai.mcp import MCPServerStdio + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[4] +CLI_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/dist/cli.js" +SKILL_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/STAGEHAND_CODEMODE_SKILL.md" +STAGEHAND_CODEMODE_SKILL = SKILL_PATH.read_text().strip() + + +def build_stagehand_agent(llm: str | Any = "openai/gpt-5-mini") -> Agent: + server = MCPServerStdio( + command="node", + args=[str(CLI_PATH)], + env=dict(os.environ), + tool_filter=lambda _context, tool: tool.get("name") == "code_execute", + ) + return Agent( + role="Stagehand browser agent", + goal="Complete browser tasks by writing compact, correct Stagehand V4 JavaScript.", + backstory=STAGEHAND_CODEMODE_SKILL, + llm=llm, + mcps=[server], + max_iter=8, + verbose=False, + ) + + +def run_stagehand_agent(prompt: str, llm: str | Any = "openai/gpt-5-mini") -> str: + # CrewAI keeps this stdio server connected for the Agent.kickoff run and cleans + # its MCP clients when agent execution finishes. + return str(build_stagehand_agent(llm).kickoff(prompt)) diff --git a/examples/integrations/crewai/stagehand-codemode/requirements.txt b/examples/integrations/crewai/stagehand-codemode/requirements.txt new file mode 100644 index 0000000..e7bc384 --- /dev/null +++ b/examples/integrations/crewai/stagehand-codemode/requirements.txt @@ -0,0 +1,2 @@ +crewai>=1.15.9,<2 +crewai-tools[mcp]>=1.15.9,<2 diff --git a/examples/integrations/crewai/stagehand-codemode/smoke.py b/examples/integrations/crewai/stagehand-codemode/smoke.py new file mode 100644 index 0000000..fa4a44a --- /dev/null +++ b/examples/integrations/crewai/stagehand-codemode/smoke.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import os + +os.environ.setdefault("OPENAI_API_KEY", "discovery-only-placeholder") + +from agent import STAGEHAND_CODEMODE_SKILL, build_stagehand_agent # noqa: E402 + + +def main() -> None: + agent = build_stagehand_agent("openai/gpt-4o-mini") + try: + tools = agent.get_mcp_tools(agent.mcps or []) + names = [getattr(tool, "original_tool_name", tool.name) for tool in tools] + assert names == ["code_execute"] + assert "Stagehand V4 code-mode syntax" in tools[0].description + assert "stagehand.extract" in STAGEHAND_CODEMODE_SKILL + print("CrewAI MCPServerStdio discovery PASS: code_execute") + finally: + agent._cleanup_mcp_clients() + + +if __name__ == "__main__": + main() From a4e833e9663b7fa5f14673164e2c244112c4095d Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 4 Aug 2026 15:33:45 -0700 Subject: [PATCH 2/3] fix(crewai): load canonical Stagehand skill --- examples/integrations/crewai/stagehand-codemode/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrations/crewai/stagehand-codemode/agent.py b/examples/integrations/crewai/stagehand-codemode/agent.py index 27815fb..3c74381 100644 --- a/examples/integrations/crewai/stagehand-codemode/agent.py +++ b/examples/integrations/crewai/stagehand-codemode/agent.py @@ -10,7 +10,7 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[4] CLI_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/dist/cli.js" -SKILL_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/STAGEHAND_CODEMODE_SKILL.md" +SKILL_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/SKILL.md" STAGEHAND_CODEMODE_SKILL = SKILL_PATH.read_text().strip() From e17219ab74ab4fd68c67901a7b0a2f88d54e03b5 Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 4 Aug 2026 15:55:25 -0700 Subject: [PATCH 3/3] refactor(crewai): launch internal stdio server --- examples/integrations/crewai/stagehand-codemode/agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/integrations/crewai/stagehand-codemode/agent.py b/examples/integrations/crewai/stagehand-codemode/agent.py index 3c74381..17e63cf 100644 --- a/examples/integrations/crewai/stagehand-codemode/agent.py +++ b/examples/integrations/crewai/stagehand-codemode/agent.py @@ -9,7 +9,7 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[4] -CLI_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/dist/cli.js" +STDIO_SERVER_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/dist/stdio-server.js" SKILL_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/SKILL.md" STAGEHAND_CODEMODE_SKILL = SKILL_PATH.read_text().strip() @@ -17,7 +17,7 @@ def build_stagehand_agent(llm: str | Any = "openai/gpt-5-mini") -> Agent: server = MCPServerStdio( command="node", - args=[str(CLI_PATH)], + args=[str(STDIO_SERVER_PATH)], env=dict(os.environ), tool_filter=lambda _context, tool: tool.get("name") == "code_execute", )