MCPMCP Python SDK 2.2 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
24 small wins to finish your pathNext lesson

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.

Example
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.

Example
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

Example
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.

Try it yourself
  • Swap Client(mcp) for the HTTP client from lesson 16 with the server running, and run the same messages.
  • Make run_agent skip 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.