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

Source: https://conversationalaidesign.com/modules/08-tools-and-mcp.html. Author: Nelson Abacherli Kawakami. Reviewed October 27, 2026.

## 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:

- User message → Model decides: answer or call a tool → Your code validates and runs the tool → Result returned to the model → Model writes the reply

Here is the whole loop with the Anthropic Python SDK. Other providers use nearly the same shape.

```python
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 `initialize` handshake and the session ID are gone. Every request carries its protocol version and the client's capabilities inline.
- A `server/discover` method 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.

```python
# 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.

## Sources
- MCP: the 2026-07-28 specification (official post): https://blog.modelcontextprotocol.io/posts/2026-07-28/
- MCP specification: https://modelcontextprotocol.io
- MCP Python SDK docs: https://py.sdk.modelcontextprotocol.io
- MCP Went Stateless: what the spec changes (DEV Community): https://dev.to/krlz/mcp-went-stateless-what-the-2026-07-28-spec-actually-changes-273k
- A2A protocol: https://a2a-protocol.org
- Agentic AI Foundation: https://aaif.io
