Web search & MCP¶
A model's training data has a cutoff; the world doesn't. This page gives your agent the live web — and then connects it to whole catalogs of external tools through MCP.
Your agent can already search and fetch the web. You turned nothing on for that — it's a default. So let's start by using it, then wire up an external server.
import asyncio
from pydantic_deep import create_deep_agent, DeepAgentDeps, StateBackend
async def main():
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
instructions="You are a research assistant. Cite the pages you used.",
)
deps = DeepAgentDeps(backend=StateBackend())
result = await agent.run(
"What changed in the latest Python release? "
"Check the official docs and summarize the top three points.",
deps=deps,
)
print(result.output)
asyncio.run(main())
Run it¶
Save it to main.py and run:
The agent searches the web, opens the pages it finds, reads them, and answers with up-to-date information — none of which was in its training data. You didn't register a search tool or write an HTTP client. It was already there.
Check it
Ask it something that must be recent — "What's today's top story on the Python
blog?" Then flip web_search=False (next section) and ask again. Without the web,
the model can only guess from its training cutoff. The difference is the point.
Step by step¶
The web is on by default¶
create_deep_agent() enables two web capabilities for you:
web_search— search the web and get back a list of results.web_fetch— open a specific URL and read its content as text.
They're independent, so you can turn either one off:
agent = create_deep_agent(
web_search=True, # find pages
web_fetch=False, # but don't open them
)
Set both to False for a fully offline agent — handy when you want deterministic
runs or you're working in a sandbox with no network.
It uses the model's native web tools when it can
Under the hood these are Pydantic AI's built-in WebSearch and WebFetch
capabilities. When the model has native web tools (Anthropic's web tools,
OpenAI's web_search_preview), they're used directly. Otherwise pydantic-deep
falls back to a local implementation. Either way, the same code works across
providers.
Tightening the leash¶
The defaults are the easy path. When you need more control — restrict to certain domains, cap how many searches a run can make — disable the default and pass your own capability:
from pydantic_ai.capabilities import WebSearch, WebFetch
agent = create_deep_agent(
web_search=False, # turn off the default…
web_fetch=False,
capabilities=[ # …and bring your own, configured
WebSearch(allowed_domains=["docs.python.org"], max_uses=5),
WebFetch(allowed_domains=["docs.python.org", "peps.python.org"]),
],
)
Now the agent can only look at the Python docs, and only a handful of times. See Web Tools for every option.
Connecting external tools with MCP¶
The web is one source of tools. The Model Context Protocol is the universal one. An MCP server exposes a bundle of tools — query GitHub, look up a library's docs, read any public repo — and your agent picks them up as native tools, sitting right next to the filesystem and the web.
MCP is an optional extra:
Describe a server, then hand it to mcp_servers=:
import asyncio
from pydantic_deep import create_deep_agent, build_mcp_server, MCPServerConfig
async def main():
deepwiki = build_mcp_server(
MCPServerConfig(
name="deepwiki",
transport="http",
url="https://mcp.deepwiki.com/mcp",
)
)
agent = create_deep_agent(mcp_servers=[deepwiki])
async with agent: # connects the MCP server for the duration of the block
result = await agent.run("What does the pydantic/pydantic repo do?")
print(result.output)
asyncio.run(main())
The agent now answers using DeepWiki's tools — which can read any public repo — without you knowing the tool names. It just uses them.
Open the agent before you run it
MCP servers are live connections. Wrap your runs in async with agent: so the
servers connect for the block and disconnect cleanly when it ends. Forget it,
and the tools won't be there.
Built-in servers¶
You don't have to type out a config for the popular ones.
builtin_mcp_servers() ships curated
definitions — github (needs a token), context7 (library docs), deepwiki
(ask any public repo), and the Figma servers. They're all disabled until you opt
in, which an MCPRegistry makes easy:
from pydantic_deep import create_deep_agent, MCPRegistry, builtin_mcp_servers
registry = MCPRegistry(builtin_mcp_servers())
registry.set_enabled("deepwiki", True)
agent = create_deep_agent(mcp_servers=registry.build_active())
The registry builds only the servers that are both enabled and authenticated, so you can keep a whole shelf of servers defined and switch them on as needed.
Auth without committing secrets
Servers that need a token take an MCPAuth — e.g.
MCPAuth(secret_key="GITHUB_MCP_PAT", kind="bearer"). The token is never stored
in the config; it's resolved at build time from your environment (or the CLI's
keystore). See MCP Servers for OAuth, stdio subprocesses,
and importing servers from Claude Code.
Recap¶
You gave your agent the world:
- Web search and fetch are on by default.
web_search=True,web_fetch=True— flip either toFalsefor offline or read-only runs. - For domain limits and usage caps, disable the default and pass your own
WebSearch/WebFetchviacapabilities=. - MCP servers add whole catalogs of external tools. Install the
[mcp]extra, describe a server withMCPServerConfig, and attach it viamcp_servers=. - Always run MCP-backed agents inside
async with agent:so connections open and close cleanly. builtin_mcp_servers()+MCPRegistrygive you curated servers (GitHub, Context7, DeepWiki, Figma) you can enable one line at a time.
That's the tutorial. From here, the real depth opens up — the capability lifecycle, hooks, cost budgets, agent teams, and more.