MCP: Tools you didn't write
Self-serve track. Not part of the live 90-minute block; do it any time after Foundations. Run with
make mcp(server:make mcp-server).
Every tool so far has lived in your own file. Real agents pull most of their tools from elsewhere: services someone else built and deployed, that your agent connects to and calls.
Quick path
Section titled “Quick path”In a hurry? These three steps are the whole challenge. Everything below is the why and the how.
- Run
make mcp-serverin one terminal (leave it onhttp://localhost:4321/mcp), thenmake mcpin another, and watch TripMate plan a trip but fail to book a hotel. - Edit
start/agent.py: do TODO 1 (importMCPToolset), TODO 2 (connect withMCPToolset("http://localhost:4321/mcp")), TODO 3 (addtoolsets=[hotel_server]to the agent). - Done when the hotel tool appears in “tools called this run” and the plan includes a real hotel from the server, with no change to the agent loop.
The standard for that is MCP, the Model Context Protocol. An MCP server exposes a set of tools over the wire, and your agent connects to it and calls them as if you’d written them yourself. This challenge wires one in.
You build this in two runs. First run TripMate with only its local tools and watch it fail to book a hotel. Then connect to an MCP server and watch the hotel tool appear next to the local ones, with no other change to the agent.
The mechanic, in another domain
Section titled “The mechanic, in another domain”Forget hotels. Connecting to any MCP server over HTTP is the same three moves, whatever it serves:
That toolsets=[server] is the whole integration: a remote tool sits next to the local ones and the model cannot tell which is which. Below you write these three moves for TripMate’s hotel server.
The setup
Section titled “The setup”Open start/agent.py. It is the familiar shape: three local @agent.tool_plain functions and an Agent, with async with agent: already wrapping the run. You write three pieces against the find_hotel server on http://localhost:4321/mcp: import MCPToolset (TODO 1), connect (TODO 2), and register toolsets=[hotel_server] on the agent (TODO 3).
Run it
Section titled “Run it”Build it
Section titled “Build it”-
Run the agent on its own. No server needed yet:
TripMate looks up the traveller, gets the weather, and prices the flight, but it has no hotel tool. It says it cannot book a hotel or skips it, and nothing hotel-shaped appears in “tools called this run”. That gap is what the MCP server fills.
-
Start the MCP server in a second terminal. Leave it running:
You should see the server announce itself on
http://localhost:4321/mcp. This is a separate program, a process you did not write into your agent. -
Import the client and connect to the server (TODOs 1 and 2). Type the import for
MCPToolsetfrompydantic_ai.mcp, then construct one pointing at the server’s URL (port 4321) and keep it in a variable. Each MCP server is a toolset: the agent lists and calls its tools over HTTP. The TODO comments instart/agent.pymark where each piece goes; write them yourself rather than uncommenting. -
Register the toolset on the agent (TODO 3). Add
toolsets=[hotel_server]to theAgent(...)call so the server’s tool joins the local ones. That one argument is the whole integration.Run
make mcpagain. The hotel tool is now discovered from the server and shows up in “tools called this run” next to the local tools, and the trip plan includes a real hotel under £200. It has no[tool fired]line of its own, because it does not run in the agent process: it runs in the server, in the other terminal. To the model it’s just another tool. -
Verify what you’ve got. With no server the run plans a trip but cannot book a hotel. After the three TODOs and the server running, the hotel tool appears in “tools called this run” and the plan includes a hotel that came from the server, not the model. You should be able to say why the agent loop did not change to add a tool from another process.
make solution-mcp(with the server running) runs the finished version if you want to compare.
- “Could not reach the MCP server.” You did not start it. Run
make mcp-serverin a separate terminal first, and leave it running. The starter’s except tells you this. - Port 4321 in use. Something else is on it. Change
port=inmcp_server.pyand the URL in the client to match. - No
[tool fired] find_hotelline. Expected: that log lives in the local tools. Confirm the call in the “tools called this run” list, or in the console trace, where MCP tool calls appear like any other span.
A couple of things worth knowing
Section titled “A couple of things worth knowing”What is async with agent doing?
An MCP server is a live connection (here, HTTP). async with agent: opens connections to
every server in the agent’s toolsets before the run and closes them after, which is more
efficient than reconnecting per call. With no MCP servers it is a harmless no-op, which is
why the starter already uses it: wiring the server in just gives it something to open.
The two paths share this one server
The MCP server does not know or care what language its clients are written in. The Python
agent in this folder and the TypeScript agent in ../../vercel-ai-sdk connect to the
same kind of server and call the same hotel tool: a tool published once and usable by any
agent in any language.
These self-serve tracks sit outside the numbered path. If you came here from resilience, head back to the main tracks, foundations f1–f7 and patterns p1–p7, when you’re ready.