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.

examples/claude-agent-sdk-ts
Contributors: Updated
Terminal

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-ts
cp .env.example .env # set STEEL_API_KEY and ANTHROPIC_API_KEY
npm install
npx playwright install chromium
npm 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 PROMPT and (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 the tools array in createSdkMcpServer. A click(selector) tool that calls page.click is the most common fifth one.
  • Hook the lifecycle. Pass a hooks option with callbacks for PreToolUse, PostToolUse, Stop, SessionStart to audit, log, or block individual tool calls.
  • Resume sessions. Capture session_id from the first system/init message, pass resume: sessionId on the next query() call to keep agent memory across runs.
  • Persist a login. Pair with credentials or auth-context so Steel sessions start already authenticated.

Anthropic Agent SDK docs · Python version · Claude Computer Use (TypeScript)

examples/claude-agent-sdk-py
Contributors: Updated
Terminal

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-py
cp .env.example .env # set STEEL_API_KEY and ANTHROPIC_API_KEY
uv run playwright install chromium
uv 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 PROMPT and, if useful, SYSTEM_PROMPT. The four tools are task-agnostic.
  • Use Opus 4.7 for harder pages. Set model="claude-opus-4-7" in ClaudeAgentOptions.
  • Add a tool. Decorate a new async function with @tool, append it to the tools list passed to create_sdk_mcp_server. A click(selector) tool that calls page.click is a useful fifth one.
  • Hook the lifecycle. Pass hooks={"PostToolUse": [...]} on ClaudeAgentOptions to 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, pass resume=session_id on the next ClaudeAgentOptions to continue with full context.
  • Hand off auth. Pair with credentials or auth-context so the Steel session starts already logged in.

Anthropic Agent SDK docs · TypeScript version · Claude Computer Use (Python)