The agent loop: model, tool, answer
List the server's tools, let the model choose, call the tool through MCP, and turn the result into a reply: what every MCP host does around a model.
async def run_agent(client, message):
listed = await client.list_tools()
call = choose_tool(message, to_model_tools(listed.tools))
if call is None:
return "Sorry, I can only help with orders and help articles."
print(f" calling {call['name']} {call['arguments']}")
result = await client.call_tool(call["name"], call["arguments"])
if result.is_error:
return f"I could not do that: {result.content[0].text}"
return f"Here is what I found: {result.content[0].text}"run_agent lists tools on every message, because a server's tools can change while it runs. It checks is_error before trusting a result, and passes the error text on, which is where lesson 6's messages end up.
A real host sends the tool result back to the model and lets the model write the reply, and may loop several times before answering. Here the reply is a fixed sentence around the result, so the flow stays visible.
import asyncio
from mcp import Client
from agent import run_agent
from shop import mcp
async def main():
async with Client(mcp) as client:
for message in ["Where is my order B42?", "Where is my order Z9?", "I need help with my refund"]:
print(message)
print(await run_agent(client, message))
asyncio.run(main())Three messages, three paths: a lookup that worked, a lookup that failed with the model-readable message, and a help search. agent.py holds choose_tool, to_model_tools and run_agent from these lessons.
The refund goes through
import asyncio
from mcp import Client
from agent import run_agent
from shop import mcp
async def main():
async with Client(mcp) as client:
print(await run_agent(client, "Please refund order B42, it arrived broken"))
asyncio.run(main())Nobody approved that. This shop.py is the one from lesson 7, whose refund_order has only annotations, and this host ignores them. The project in lesson 23 uses the elicitation from lesson 14, so the server enforces approval whatever the host does.
- Swap
Client(mcp)for the HTTP client from lesson 16 with the server running, and run the same messages. - Make
run_agentskip tools whose annotations are not read-only. - Log every call and its result to a list, and print the list at the end.
Little by little, you're building something great.