Anthropic · Python and TypeScript
Build a research agent with Claude Agent SDK
Build a research agent on the Claude Agent SDK, adding unlob as an MCP server alongside the SDK’s built-in file and shell tools.
What you are building
Your process
query()
The Claude Code harness, as a library
Tools available
Built in
Read, Write, Bash, Grep, Glob
unlob over MCP
11 tools, no wrappers written
Services
Anthropic API
claude-opus-5
unlob
stdio JSON-RPC
Install
pip install claude-agent-sdkAlready have Claude Agent SDK wired up? TheClaude Agent SDK integration pagehas the connection config on its own — this page assumes you have it and gets on with building.
Step by step
Understand what you are installing
The Claude Agent SDK is a different product from the Tool Runner in the `anthropic` package. The SDK ships built-in tools and the Claude Code harness; the Tool Runner is a helper that loops over tools you define. Both are Anthropic, both are current, and they are frequently confused.
Register unlob as an MCP server
The Agent SDK speaks MCP natively, so the eleven unlob tools arrive without writing a single wrapper function. This is the shortest path from nothing to an agent that can search the web.
agent.pypython import os from claude_agent_sdk import query, ClaudeAgentOptions options = ClaudeAgentOptions( model="claude-opus-5", system_prompt=( "You are a research assistant. Search before you answer. Prefer claims " "carried by several independent sources and say so when one is alone. " "Always cite URLs." ), mcp_servers={ "unlob": { "command": "npx", "args": ["-y", "@unlob/mcp"], "env": {"UNLOB_API_KEY": os.environ["UNLOB_API_KEY"]}, } }, # Least privilege: the web tools plus reading, and nothing that writes. allowed_tools=[ "mcp__unlob__web_search", "mcp__unlob__assemble_context", "mcp__unlob__corroborate", "Read", ], )Run the query
query() is an async generator over the agent’s messages. The loop, the tool execution and the context management all happen inside it.
run.pypython import asyncio async def main(): async for message in query( prompt="What changed in the EU AI Act GPAI rules in 2026? Cite sources.", options=options, ): print(message) asyncio.run(main())
What happens when it runs
The whole thing
Complete and runnable. Set the two environment variables and it works.
"""A research agent on the Claude Agent SDK, backed by the unlob web index.
pip install claude-agent-sdk
export UNLOB_API_KEY=ulb_...
export ANTHROPIC_API_KEY=sk-ant-...
Note: this is the Claude Agent SDK (`claude-agent-sdk`), which is Claude Code
as a library. It is NOT the Tool Runner in the `anthropic` package — that is a
different tool for a different job.
"""
import asyncio
import os
from claude_agent_sdk import query, ClaudeAgentOptions
options = ClaudeAgentOptions(
model="claude-opus-5",
system_prompt=(
"You are a research assistant. Search before you answer. Prefer claims "
"carried by several independent sources and say so when one is alone. "
"Always cite URLs."
),
mcp_servers={
"unlob": {
"command": "npx",
"args": ["-y", "@unlob/mcp"],
"env": {"UNLOB_API_KEY": os.environ["UNLOB_API_KEY"]},
}
},
allowed_tools=[
"mcp__unlob__web_search",
"mcp__unlob__assemble_context",
"mcp__unlob__corroborate",
"Read",
],
)
async def main() -> None:
prompt = "What changed in the EU AI Act GPAI rules in 2026? Cite sources."
async for message in query(prompt=prompt, options=options):
print(message)
if __name__ == "__main__":
asyncio.run(main())What will go wrong
The failures that cost an afternoon rather than a minute, because they produce something that looks like it is working.
The Agent SDK is not the Tool Runner. `claude-agent-sdk` gives you Claude Code as a library with built-in tools; `client.beta.messages.tool_runner` in the `anthropic` package loops over tools you define and has no built-ins. Installing the wrong one produces confusing import errors.
MCP tool names are namespaced as `mcp__<server>__<tool>`. Listing `web_search` in allowed_tools silently matches nothing, and the agent then reports it has no way to search.
allowed_tools is your permission boundary. Omitting it grants the built-in write and shell tools as well, which is rarely what a research agent needs.
Use `thinking={"type": "adaptive"}` if you configure thinking directly. `budget_tokens` is rejected by current models rather than ignored.
Where this agent can go next
Every result the agent receives is a node in the coverage graph. These are the tools you can add to it without leaving Claude Agent SDK — each is one more entry in the same tools list.
Frequently asked questions
Is this the same as the Tool Runner?
No, and it is the most common confusion in this area. The Claude Agent SDK (`claude-agent-sdk` / `@anthropic-ai/claude-agent-sdk`) is Claude Code as a library, with built-in file and shell tools and the full harness. The Tool Runner (`client.beta.messages.tool_runner`) lives in the regular `anthropic` SDK and automates the loop over tools you write yourself. Different packages, different jobs.
Do I need to write tool wrappers for unlob?
No. The Agent SDK speaks MCP, and unlob ships an MCP server, so all eleven tools are available once you declare the server. That is the main reason this is the shortest tutorial on the site.
Can I use it from TypeScript?
Yes — `@anthropic-ai/claude-agent-sdk` is the same product with the same options. The MCP server block is identical.
You need a key to run this
10,000 requests a month on the free tier, no card. Enough to build the agent and put a real evaluation set through it.