Tools you didn't write (MCP)
Self-serve track. Not part of the live 90-minute block; do it any time after Foundations. Run with
npm run mcp(server:npm run 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
npm run mcp:serverin one terminal (leave it running), thennpm run mcpin another, and watch TripMate plan a trip but fail to book a hotel because it has nofindHoteltool. - Edit
start/agent.ts: TODO 1 (importcreateMCPClient), TODO 2 (connect over HTTP and readmcpClient.tools()), TODO 3 (spread...mcpToolsinto thetoolsobject), TODO 4 (mcpClient.close()infinally). - Done when “Discovered MCP tools: findHotel” prints and
findHotelappears in “tools called this run” next to the local tools, with the hotel coming from the server.
The standard for that is MCP, the Model Context Protocol. An MCP server exposes a set of tools (functions you can call as the client) and your agent connects to it and uses them as if you’d written them yourself. This challenge wires one in.
You build this one 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 findHotel appear next to
the local tools, with no other change to the agent. The agent loop does not change, only
where one tool comes from.
The mechanic, in another domain
Section titled “The mechanic, in another domain”Forget hotels. Connecting to any MCP server over HTTP is the same four moves, whatever it serves:
That spread 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 four moves for TripMate’s hotel server.
The setup
Section titled “The setup”Open start/agent.ts. It is the familiar shape: three local tools (lookupTraveler, getWeather, getFlights) and a ToolLoopAgent with a try/finally ready. You write the four moves against the findHotel server on http://localhost:4320/mcp: import (TODO 1), connect and read .tools() (TODO 2), spread ...mcpTools into the agent’s tools (TODO 3), and close() in the finally (TODO 4).
Run it
Section titled “Run it”Build it
Section titled “Build it”-
Run the agent on its own (cycle 1). No server needed yet:
TripMate looks up the traveller, gets the weather, and prices the flight, but it has no hotel tool. It either says it cannot book a hotel or invents one, and
findHotelis nowhere in “tools called this run”. That gap is what the MCP server fills. -
Start the MCP server in a second terminal (cycle 2). Leave it running:
You should see “MCP server (findHotel) listening on http://localhost:4320/mcp”. This is a separate program, a process you did not write into your agent.
-
Connect the client and read its tools (cycle 2, TODOs 1 and 2). Add the
createMCPClientimport, then write thecreateMCPClientcall with an HTTP transport pointing at the server’s URL (port 4320), and callmcpClient.tools()to discover what the server offers. That returns AI-SDK-shaped tools, the same shape as the local ones. LogObject.keys(mcpTools)so you can see what came back. Build it from the sketch above; the TODO comments instart/agent.tsmark where each piece goes. -
Spread the tools in and close the client (cycle 2, TODOs 3 and 4). Add
...mcpToolsto the agent’stoolsobject after the local tools, then callmcpClient.close()in thefinallyblock so the process can exit. Runnpm run mcpagain. Now you see:findHotelhas no[tool fired]line because it does not run in the agent; it runs in the server process, in the other terminal. But it shows up in “tools called”, right next to the local tools, because to the model it’s just another tool. -
Verify what you’ve got. Cycle 1 plans a trip but cannot book a hotel, and
findHotelis not in “tools called this run”. After the four TODOs and the server running, “Discovered MCP tools: findHotel” prints,findHotelappears in “tools called”, and the trip 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.
-
“Could not reach the MCP server.” You did not start it. Run
npm run mcp:serverin a separate terminal first, and leave it running. The starter’s catch tells you this. -
Port 4320 in use. Something else is on it. Change
PORTinmcp-server.tsand theurlinagent.tsto match. -
No
[tool fired] findHotelline. 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”The two paths share this one server
The MCP server does not know or care what language its clients are written in. The
TypeScript agent in this folder and the Python agent in the sibling pydantic-ai path
connect to the same server and call the same findHotel: a tool published once and usable
by any agent in any language. Start the server once and point both paths at it.
stdio vs HTTP transport
MCP servers can be reached two main ways. stdio launches the server as a child process and talks over its standard input/output: simpler setup, but the client runs the server’s code on your machine. HTTP points at a server running independently, which is how remote, shared servers work in production: no local code execution, but your agent now depends on that external service being up. This challenge uses HTTP so the server is a real separate process you start yourself, and so both language paths can share it. The AI SDK also supports SSE for streaming remote servers.
Go deeper: real MCP servers
The interesting MCP servers are ones you would never write yourself. The GitHub MCP server
exposes tools to open issues, create repos, and review pull requests; there are servers for
Slack, Postgres, the filesystem, web search, and more. Matt Pocock’s AI SDK crash course
wires the real GitHub server (over Docker and over HTTP) and is the best next step: see
exercises/03-agents/03.05-mcp-via-stdio and 03.06-mcp-via-http. The shape is exactly
what you just ran; only the server gets more powerful.
That’s tools you didn’t write, merged in with one spread. If you want to compare, npm run solution:mcp runs the finished version.