Reusing everything you built in the MCP course
This lesson assumes the MCP course through Lesson 12, servers, tools, and a client that can call them. The MCP course built servers and a LangChain-based client, langchain-mcp-adapters. Pydantic AI has its own first-class MCP client, MCPToolset, so any MCP server you already built, or any third-party one, becomes a normal set of agent tools with no adapter library needed.
Connecting to a server
StdioTransport launches a server as a subprocess and speaks MCP over its stdin/stdout, exactly the transport covered through MCP Lesson 19. MCPToolset wraps that transport as something an Agent accepts directly in its toolsets= list.
import asyncioimport sysfrom pathlib import Path
from dotenv import load_dotenvfrom pydantic_ai import Agentfrom pydantic_ai.mcp import MCPToolset, StdioTransport
load_dotenv()
SERVER_SCRIPT = Path(__file__).parent / "server.py"
transport = StdioTransport(command=sys.executable, args=[str(SERVER_SCRIPT)])toolset = MCPToolset(transport)
agent = Agent("google:gemini-3.5-flash-lite", toolsets=[toolset])
async def main() -> None: async with agent: result = await agent.run("What is 12 plus 30? Use a tool to compute it.") print("Output:", result.output)
result = await agent.run("What is 6 times 7? Use a tool to compute it.") print("Output:", result.output)
if __name__ == "__main__": asyncio.run(main())The server itself is a small FastMCP server, the exact shape from the MCP course:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("calculator-server")
@mcp.tool()def add(a: int, b: int) -> int: """Add two numbers together.""" return a + b
@mcp.tool()def multiply(a: int, b: int) -> int: """Multiply two numbers together.""" return a * b
if __name__ == "__main__": mcp.run(transport="stdio")Why async with agent:
MCP servers are external processes with a real connection lifecycle, start the subprocess, initialize the protocol handshake, eventually tear it down. Wrapping your runs in async with agent: opens that connection once and keeps it open for every run/run_sync call inside the block, then cleans it up automatically on exit. Without the context manager, Pydantic AI would have to start and stop the server subprocess on every single call, which works but is wasteful for anything beyond a one-off script.
What the agent actually sees
From the model's point of view, add and multiply look exactly like any @agent.tool_plain function from Lesson 6, same schema-from-type-hints mechanism, just discovered over MCP's tools/list instead of read off a local Python function. This is the payoff of MCP as a protocol: the same server this lesson connects to could equally be wired into Claude Desktop, Claude Code, or a LangChain agent, no rewriting needed on the server side.
Checkpoint
StdioTransportplusMCPToolsetis Pydantic AI's native MCP client, no separate adapter library needed.toolsets=[toolset]onAgent(...)makes an MCP server's tools available exactly like local@agent.toolfunctions.async with agent:manages the server subprocess's lifecycle for the whole block, rather than per call.
If anything here still feels unclear, ask before moving to Lesson 22.