Course / Module 8
Tools, function calling and MCP
Let an assistant act: how tool calling works, how to design tools models can use safely, and what the July 2026 stateless MCP specification changes.
In short
- The model asks for a tool call; your code runs it. The model never acts directly.
- Few, well-named tools with strict schemas beat many overlapping ones.
What you will learn
- Explain the tool-calling loop and implement it
- Design tools that models use correctly and safely
- Describe what MCP is and what changed in the 2026-07-28 specification
- Build a minimal MCP server and manage state with explicit handles
- Separate MCP (tools) from A2A (agents) and name the main security risks
What tool use is
A language model produces text. To do things, such as look up an order or book a slot, it needs tools. The pattern, often called function calling, is simple:
The model requests; your application executes. The model never acts on its own.
Here is the whole loop with the Anthropic Python SDK. Other providers use nearly the same shape.
import json, anthropic client = anthropic.Anthropic() MODEL = "claude-sonnet-5-5" # check the docs for current model IDs tools = [{ "name": "get_order_status", "description": "Look up the status of a customer's order by order number. Read-only.", "input_schema": { "type": "object", "properties": {"order_id": {"type": "string", "description": "e.g. A1234"}}, "required": ["order_id"], }, }] def run_tool(name, args): if name == "get_order_status": return {"order_id": args["order_id"], "status": "shipped", "eta": "2026-11-03"} return {"error": f"unknown tool {name}"} messages = [{"role": "user", "content": "Where is order A1234?"}] while True: resp = client.messages.create(model=MODEL, max_tokens=1024, system="You are a helpful order assistant.", tools=tools, messages=messages) if resp.stop_reason != "tool_use": break messages.append({"role": "assistant", "content": resp.content}) results = [] for block in resp.content: if block.type == "tool_use": results.append({"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(run_tool(block.name, block.input))}) messages.append({"role": "user", "content": results}) print(resp.content[0].text)
In production, add argument validation, timeouts, retries, logging and a cap on loop iterations.
Designing tools models can use
The model chooses tools from their names and descriptions alone, so tool design is interface design.
| Do | Avoid |
|---|---|
| A few clear tools with distinct jobs | Many overlapping tools |
Names that say what happens: cancel_booking |
Vague names: process, handle |
| Descriptions that say when not to use it | Descriptions that restate the name |
| Strict schemas with enums and formats | Free-form string arguments |
| Concise structured results | Dumping raw database rows |
| Errors that explain how to fix the call | Silent failures or stack traces |
| Separate read tools from write tools | One tool that both looks up and changes |
Give each tool the least privilege it needs, and require explicit confirmation for write actions that cost money, delete data or can't be undone. The model should propose; your policy decides.
MCP: a standard way to plug in tools
Writing a custom integration for every model and every tool does not scale. The Model Context Protocol (MCP) is an open standard that lets any MCP-compatible application connect to any MCP server. A server can expose:
- Tools: functions the model can call.
- Resources: data the application can read as context.
- Prompts: reusable, templated instructions.
Anthropic donated MCP to the Linux Foundation in December 2025, where it is governed under the Agentic AI Foundation alongside other open agent standards. Major vendors support it.
What changed on July 28, 2026
The 2026-07-28 specification is the largest revision since MCP launched. The headline change: the protocol core is stateless.
- The
initializehandshake and the session ID are gone. Every request carries its protocol version and the client's capabilities inline. - A
server/discovermethod lets a client learn what a server supports. - Routing headers on HTTP requests let gateways and load balancers route and rate-limit without reading the body.
- List results (tools, prompts, resources) can be cached with a time-to-live.
- Server-initiated requests, such as asking the user a question mid-call, became multi-round-trip requests: the server answers "input required" and the client retries with the answer.
- Extensions, including Tasks for long-running work and MCP Apps, were formalized, authorization was hardened, and a deprecation policy was introduced. Roots, Sampling and Logging, and the older HTTP+SSE transport, are deprecated.
For you as a designer and developer, the practical effect is that servers scale like ordinary web services. The catch is that a stateless protocol does not mean a stateless application.
A minimal server, and state without sessions
The v2 line of the official Python SDK renamed the in-SDK FastMCP class to MCPServer. This example follows a community walkthrough of the SDK; always confirm syntax against the SDK documentation and pin your version, because the SDK is moving quickly.
# server.py (pip install "mcp[cli]"; pin the version you test against) import uuid from mcp.server.mcpserver import MCPServer mcp = MCPServer("reports") JOBS: dict[str, dict] = {} # in production: Redis or a database, keyed by the handle below @mcp.tool() def start_export(dataset: str) -> str: """Begin an export. Returns a job handle to pass to check_export.""" job_id = f"job_{uuid.uuid4().hex[:8]}" JOBS[job_id] = {"dataset": dataset, "status": "running"} return job_id @mcp.tool() def check_export(job_id: str) -> dict[str, str]: """Check an export by its handle.""" return JOBS.get(job_id, {"status": "unknown"}) if __name__ == "__main__": mcp.run(transport="streamable-http", stateless_http=True, json_response=True)
The two calls may reach two different machines. The handle is the state, and because it appears in the model's context, the model can reason about it and pass it between tools. You can explore any server with the MCP Inspector (npx -y @modelcontextprotocol/inspector).
Migrating from older servers
If you wrote servers against the earlier specification, review the migration notes: the class rename, moving transport options to run(), and rewriting anything that asked the user questions mid-call.
MCP and A2A
They solve different problems. MCP connects an agent to tools and data. A2A (Agent2Agent) lets independent agents, possibly built on different frameworks, talk to each other as peers. A support assistant might use MCP to read tickets and A2A to hand a task to a billing agent run by another team.
Security: the part people skip
- Tool descriptions are an attack surface. A malicious server can embed instructions in a description to steer the model. Review third-party servers like you review third-party code.
- Least privilege. Scope credentials per tool and per user. Never give a read-only task a write-capable token.
- Human confirmation for consequential actions, with the full details shown.
- Audit logs of every call, with arguments and results.
- Stateless is not secure. The 2026 change helps scaling, not safety.
Module 18 maps these risks to the OWASP Top 10 for LLM applications.
Key takeaways
- The model asks for a tool call; your code runs it. The model never acts directly.
- Few, well-named tools with strict schemas beat many overlapping ones.
- Separate reads from writes, and confirm writes.
- MCP is now stateless: carry state in explicit handles the model can see.
- Treat third-party MCP servers and tool descriptions as untrusted input.