Skip to content

Commit 6adf008

Browse files
committed
feat(langchain): add Stagehand code-mode integration
1 parent 4a6e7b2 commit 6adf008

5 files changed

Lines changed: 118 additions & 0 deletions

File tree

.github/workflows/stagehand-codemode.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- '.github/workflows/stagehand-codemode.yml'
77
- 'examples/integrations/crewai/stagehand-codemode/**'
8+
- 'examples/integrations/langchain/stagehand-codemode/**'
89
- 'examples/integrations/mastra/stagehand-codemode/**'
910
- 'examples/integrations/vercel/stagehand-codemode/**'
1011
- 'packages/stagehand-codemode/**'
@@ -14,6 +15,7 @@ on:
1415
paths:
1516
- '.github/workflows/stagehand-codemode.yml'
1617
- 'examples/integrations/crewai/stagehand-codemode/**'
18+
- 'examples/integrations/langchain/stagehand-codemode/**'
1719
- 'examples/integrations/mastra/stagehand-codemode/**'
1820
- 'examples/integrations/vercel/stagehand-codemode/**'
1921
- 'packages/stagehand-codemode/**'
@@ -94,3 +96,25 @@ jobs:
9496
- run: pnpm --filter @browserbasehq/stagehand-codemode run build
9597
- run: python -m pip install -r examples/integrations/crewai/stagehand-codemode/requirements.txt
9698
- run: python examples/integrations/crewai/stagehand-codemode/smoke.py
99+
100+
deepagents:
101+
runs-on: ubuntu-latest
102+
timeout-minutes: 20
103+
steps:
104+
- uses: actions/checkout@v4
105+
- uses: pnpm/action-setup@v4
106+
with:
107+
version: 10.9.0
108+
- uses: actions/setup-node@v4
109+
with:
110+
node-version: 24.x
111+
- uses: actions/setup-python@v5
112+
with:
113+
python-version: '3.12'
114+
cache: pip
115+
cache-dependency-path: examples/integrations/langchain/stagehand-codemode/requirements.txt
116+
117+
- run: pnpm install --no-frozen-lockfile
118+
- run: pnpm --filter @browserbasehq/stagehand-codemode run build
119+
- run: python -m pip install -r examples/integrations/langchain/stagehand-codemode/requirements.txt
120+
- run: python examples/integrations/langchain/stagehand-codemode/smoke.py
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# LangChain Deep Agents + Stagehand code mode
2+
3+
This binding launches the local MCP over stdio and keeps one explicit LangChain MCP session open
4+
around the complete Deep Agent invocation.
5+
6+
Do not replace the explicit `client.session()` plus `load_mcp_tools(session)` flow with
7+
`client.get_tools()`. LangChain documents that `get_tools()` creates a new session for each tool
8+
call; for a stdio server, that means a new process and a new browser every time.
9+
10+
The shared Stagehand V4 syntax skill is loaded as the Deep Agent's `system_prompt` and is also
11+
included in the MCP tool description.
12+
13+
See [`agent.py`](./agent.py).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
import os
4+
from pathlib import Path
5+
from typing import Any
6+
7+
from deepagents import create_deep_agent
8+
from langchain_mcp_adapters.client import MultiServerMCPClient
9+
from langchain_mcp_adapters.tools import load_mcp_tools
10+
11+
12+
REPOSITORY_ROOT = Path(__file__).resolve().parents[4]
13+
CLI_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/dist/cli.js"
14+
SKILL_PATH = REPOSITORY_ROOT / "packages/stagehand-codemode/STAGEHAND_CODEMODE_SKILL.md"
15+
STAGEHAND_CODEMODE_SKILL = SKILL_PATH.read_text().strip()
16+
17+
18+
async def run_stagehand_agent(prompt: str, model: str | Any = "openai:gpt-5-mini") -> dict[str, Any]:
19+
client = MultiServerMCPClient(
20+
{
21+
"stagehand": {
22+
"transport": "stdio",
23+
"command": "node",
24+
"args": [str(CLI_PATH)],
25+
"env": dict(os.environ),
26+
}
27+
},
28+
tool_name_prefix=False,
29+
handle_tool_errors=True,
30+
)
31+
32+
# An explicit session is required. client.get_tools() would start a fresh stdio
33+
# process for each tool call and lose the browser between calls.
34+
async with client.session("stagehand") as session:
35+
tools = await load_mcp_tools(session)
36+
code_tools = [tool for tool in tools if tool.name == "code_execute"]
37+
if len(code_tools) != 1:
38+
raise RuntimeError(f"Expected one code_execute tool, got {[tool.name for tool in tools]}")
39+
agent = create_deep_agent(
40+
model=model,
41+
tools=code_tools,
42+
system_prompt=STAGEHAND_CODEMODE_SKILL,
43+
)
44+
return await agent.ainvoke(
45+
{"messages": [{"role": "user", "content": prompt}]},
46+
config={"recursion_limit": 20},
47+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
deepagents>=0.7.1,<1
2+
langchain-mcp-adapters>=0.3.1,<1
3+
langchain-openai>=1.2.1,<2
4+
mcp>=1.24.0,<2
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
5+
from langchain_mcp_adapters.client import MultiServerMCPClient
6+
from langchain_mcp_adapters.tools import load_mcp_tools
7+
8+
from agent import CLI_PATH, STAGEHAND_CODEMODE_SKILL
9+
10+
11+
async def main() -> None:
12+
client = MultiServerMCPClient(
13+
{
14+
"stagehand": {
15+
"transport": "stdio",
16+
"command": "node",
17+
"args": [str(CLI_PATH)],
18+
}
19+
}
20+
)
21+
async with client.session("stagehand") as session:
22+
tools = await load_mcp_tools(session)
23+
assert [tool.name for tool in tools] == ["code_execute"]
24+
assert "Stagehand V4 code-mode syntax" in (tools[0].description or "")
25+
assert "stagehand.extract" in STAGEHAND_CODEMODE_SKILL
26+
print("Deep Agents persistent stdio discovery PASS: code_execute")
27+
28+
29+
if __name__ == "__main__":
30+
asyncio.run(main())

0 commit comments

Comments
 (0)