Build a browser agent with the Claude Agent SDK
Use Steel with the Claude Agent SDK (TypeScript) to build a tool-using browser agent on Anthropic's first-party agent loop.
Scaffolds a starter project locally. Requires the Steel CLI.
@anthropic-ai/claude-agent-sdk is the engine behind the Claude Code CLI, exposed as a Node library. You get the CLI's agent loop, hooks, subagents, MCP support, and built-in tool catalog (Read, Edit, Bash, Grep, ...) without spawning the CLI yourself.
This recipe disables those built-ins and attaches a Steel cloud browser instead. Four MCP tools (openSession, navigate, snapshot, extract) sit in front of Playwright; the agent calls them by name and streams back typed messages.
const navigate = tool("navigate","Navigate the open session to a URL and wait for it to load.",{ url: z.string().describe("Absolute URL to navigate to") },async ({ url }) => {await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45_000 });return {content: [{ type: "text", text: JSON.stringify({ url: page.url(), title: await page.title() }) },],};},);const steelServer = createSdkMcpServer({name: "steel",version: "1.0.0",tools: [openSession, navigate, snapshot, extract],});for await (const message of query({prompt: PROMPT,options: {model: "claude-sonnet-4-6",systemPrompt: SYSTEM_PROMPT,mcpServers: { steel: steelServer },allowedTools: ["mcp__steel__*"],tools: [],settingSources: [],maxTurns: 20,permissionMode: "bypassPermissions",},})) {if (message.type === "assistant") {for (const block of message.message.content) {if (block.type === "tool_use") {const name = block.name.replace(/^mcp__steel__/, "");console.log(` -> ${name}(${JSON.stringify(block.input).slice(0, 120)})`);}}} else if (message.type === "result") {if (message.subtype === "success") finalText = message.result ?? "";}}
tools: [] drops the entire Claude Code built-in catalog: no filesystem reads, no Bash, no WebFetch. settingSources: [] skips loading .claude/ from your working directory or home, so the recipe behaves the same on every machine.
Run it
cd examples/claude-agent-sdk-tscp .env.example .env # set STEEL_API_KEY and ANTHROPIC_API_KEYnpm installnpx playwright install chromiumnpm start
Get keys at app.steel.dev/settings/api-keys and console.anthropic.com. A Steel session viewer URL prints when openSession runs; open it in another tab to watch the browser live.
Your output varies. Structure looks like this:
Steel + Claude Agent SDK (TypeScript) Starter============================================================Sure, let me open a browser session and pull that page.-> open_session({})open_session: 1747ms-> navigate({"url":"https://github.com/trending/python?since=daily"})navigate: 2007ms-> snapshot({})snapshot: 272ms (4000 chars, 49 links)I have everything I need. Top three trending repos ...--- Final answer ---Top 3 AI/ML-related repos:1. owner/repo - description (X stars)...Releasing Steel session...Session released. Replay: https://app.steel.dev/sessions/ab12cd34...
A run takes ~25 to 45 seconds and 3 to 6 turns. Cost is Steel session-minutes plus Anthropic tokens. The finally block calls steel.sessions.release().
Make it yours
- Swap the task. Change
PROMPTand (optionally)SYSTEM_PROMPT. The four tools are task-agnostic. - Reach for Opus 4.7. Set
model: "claude-opus-4-7"for harder reasoning. - Add a tool. Define another
tool(), append it to thetoolsarray increateSdkMcpServer. Aclick(selector)tool that callspage.clickis the most common fifth one. - Hook the lifecycle. Pass a
hooksoption with callbacks forPreToolUse,PostToolUse,Stop,SessionStartto audit, log, or block individual tool calls. - Resume sessions. Capture
session_idfrom the firstsystem/initmessage, passresume: sessionIdon the nextquery()call to keep agent memory across runs. - Persist a login. Pair with credentials or auth-context so Steel sessions start already authenticated.
Related
Anthropic Agent SDK docs · Python version · Claude Computer Use (TypeScript)
Scaffolds a starter project locally. Requires the Steel CLI.
The Claude Agent SDK is the agent loop that powers Claude Code, packaged as a library. Tools are async functions decorated with @tool, bundled into an in-process MCP server with create_sdk_mcp_server, and registered through the mcp_servers option.
This recipe wires four browser tools (open_session, navigate, snapshot, extract) into one Steel session and points the agent at GitHub Trending.
@tool("navigate","Navigate the open session to a URL and wait for the page to load.",{"url": str},)async def navigate(args: dict[str, Any]) -> dict[str, Any]:await _page.goto(args["url"], wait_until="domcontentloaded", timeout=45_000)return {"content": [{"type": "text", "text": json.dumps({"url": _page.url, "title": await _page.title()})}]}steel_server = create_sdk_mcp_server(name="steel",version="1.0.0",tools=[open_session, navigate, snapshot, extract],)options = ClaudeAgentOptions(model="claude-sonnet-4-6",system_prompt=SYSTEM_PROMPT,mcp_servers={"steel": steel_server},allowed_tools=["mcp__steel__*"],tools=[],setting_sources=[],max_turns=20,permission_mode="bypassPermissions",)
tools=[] drops the SDK's built-ins (Read, Write, Edit, Bash, Grep, WebFetch). setting_sources=[] skips loading .claude/ from your working directory or home, so the recipe runs identically everywhere.
query() returns an async iterator over typed messages:
async for message in query(prompt=PROMPT, options=options):if isinstance(message, AssistantMessage):for block in message.content:if isinstance(block, ToolUseBlock):name = block.name.removeprefix("mcp__steel__")print(f" -> {name}({json.dumps(block.input)[:120]})")elif isinstance(message, ResultMessage):if message.subtype == "success":final_text = message.result or ""
Run it
cd examples/claude-agent-sdk-pycp .env.example .env # set STEEL_API_KEY and ANTHROPIC_API_KEYuv run playwright install chromiumuv run main.py
Get keys from app.steel.dev and console.anthropic.com.
Your output varies. Structure looks like this:
Steel + Claude Agent SDK (Python) Starter============================================================Sure, let me open a browser session and pull that page.-> open_session({})open_session: 1840ms-> navigate({"url": "https://github.com/trending/python?since=daily"})navigate: 2484ms-> snapshot({})snapshot: 487ms (4000 chars, 49 links)I have everything I need. Here are the top 3 ...--- Final answer ---Top 3 AI/ML-related Python repos on today's trending list:1. owner/repo - <description> (X stars)...Releasing Steel session...Session released. Replay: https://app.steel.dev/sessions/ab12cd34...
A run takes ~30 to 50 seconds and 3 to 6 turns. Cost is Steel session-minutes plus Anthropic tokens. The finally block closes Playwright and calls steel.sessions.release().
Make it yours
- Swap the task. Change
PROMPTand, if useful,SYSTEM_PROMPT. The four tools are task-agnostic. - Use Opus 4.7 for harder pages. Set
model="claude-opus-4-7"inClaudeAgentOptions. - Add a tool. Decorate a new async function with
@tool, append it to thetoolslist passed tocreate_sdk_mcp_server. Aclick(selector)tool that callspage.clickis a useful fifth one. - Hook the lifecycle. Pass
hooks={"PostToolUse": [...]}onClaudeAgentOptionsto log every tool call, validate arguments, or veto destructive actions. Hook events:PreToolUse,PostToolUse,Stop,SessionStart. - Resume sessions. Capture
SystemMessage.data["session_id"]from the first run, passresume=session_idon the nextClaudeAgentOptionsto continue with full context. - Hand off auth. Pair with credentials or auth-context so the Steel session starts already logged in.
Related
Anthropic Agent SDK docs · TypeScript version · Claude Computer Use (Python)
Related recipes
Deep research with Claude Agent SDK subagents
Lead orchestrator dispatches parallel researcher subagents, each driving its own Steel browser, and synthesizes findings into a cited Markdown report.
Build a typed browser agent with Pydantic AI
Use Steel with Pydantic AI to build typed, provider-agnostic browser agents with dependency injection.
Build a typed browser agent with LangGraph
Use Steel with LangGraph to build a typed browser agent with an explicit state-machine loop and a structured-output formatter node.