MAF tools — functions, approval, MCP
How Python functions become MAF tools, when to require human approval, and the hosted tools (code interpreter, file search, MCP) you get for free.
What you'll learn
- Turning a Python function into an MAF tool with type hints + docstring
- Annotating parameters with descriptions the model actually sees
- Human-in-the-loop approval for sensitive tool calls
- Hosted tools — code interpreter, file search, web search, MCP
Before you start
Tools are where MAF earns its keep. A tool is any capability the LLM can invoke — a Python function, a hosted service, or an external API — described to the model as a JSON schema so it knows what to call and when. The framework introspects your Python functions and builds that schema automatically — you write ordinary Python, MAF handles the wire format. This lesson covers the patterns you’ll actually use in production: function tools, parameter annotations, human approval, and the hosted tools Microsoft ships.
A function becomes a tool
The contract: a Python function with type hints and a docstring is a
valid MAF tool. Pass it in tools=[...] and the agent can call it.
# requires: pip install agent-framework (for the commented agent wiring)
# The get_weather function below is plain Python — the agent setup is the
# real MAF shape you'd paste into a project.
from typing import Annotated
from pydantic import Field
# 1. Define a plain Python function. Type hints + docstring are the contract.
def get_weather(
city: Annotated[str, Field(description="City name, e.g. 'Tokyo'")],
unit: Annotated[str, Field(description="celsius or fahrenheit")] = "celsius",
) -> dict:
"""Return the current temperature for a city."""
fake = {"Tokyo": 18, "Lagos": 32, "Reykjavik": 4}
temp = fake.get(city, 20)
if unit == "fahrenheit":
temp = temp * 9 / 5 + 32
return {"city": city, "temperature": temp, "unit": unit}
# 2. Hand it to the agent. MAF generates the JSON schema from your signature.
# from agent_framework import ChatAgent
# from agent_framework.azure import AzureOpenAIChatClient
#
# agent = ChatAgent(
# chat_client=AzureOpenAIChatClient(...),
# name="weather-bot",
# instructions="Use get_weather for any weather question. Never guess.",
# tools=[get_weather],
# )
#
# result = await agent.run("What's it like in Tokyo right now?")
# print(result.messages[-1].text)
# Sanity-check the tool itself (this part DOES run):
print(get_weather("Tokyo"))
print(get_weather("Lagos", unit="fahrenheit"))
{'city': 'Tokyo', 'temperature': 18, 'unit': 'celsius'}
{'city': 'Lagos', 'temperature': 89.6, 'unit': 'fahrenheit'}
A few things to notice:
Annotated[type, Field(description="...")]is how you give the model a per-parameter description. Without it, the model only sees the type and parameter name — often enough, sometimes not.- The docstring becomes the tool description. Treat it like documentation for the model, not for a human reader. Be explicit about what the function does and when to call it.
- Return values should be JSON-serializable. A
dict,list, Pydantic model, or primitive. MAF serializes it back to the model.
Human-in-the-loop approval
For sensitive actions — sending email, modifying a database, calling a paid API — you don’t want the model to act unsupervised. MAF supports tool approval (a human-in-the-loop gate between the model’s decision and the tool’s execution): the framework pauses before executing, surfaces the proposed call to your application, and waits for approval.
from agent_framework import ChatAgent, ApprovalRequired
def delete_user(user_id: str) -> dict:
"""Permanently delete a user account. Irreversible."""
return {"deleted": user_id}
agent = ChatAgent(
chat_client=chat_client,
instructions="Help the admin manage user accounts.",
tools=[
ApprovalRequired(delete_user), # gate this one
# ... other tools without approval
],
)
async for event in agent.run_stream("Delete user u_42", thread=thread):
if event.kind == "tool_approval_requested":
# show the proposed call to a human, then:
if human_approves(event.tool_call):
await event.approve()
else:
await event.deny(reason="Manager declined")
The point: the LLM proposes, a human disposes. Your UI shows the tool name, the arguments, and a confirm button. This pattern is the only safe way to wire agents to destructive actions.
Hosted tools you get for free
MAF ships with a handful of tools you don’t have to implement:
| Tool | What it does |
|---|---|
| Code interpreter | Runs sandboxed Python. Use for math, data analysis, plotting. |
| File search | Vector-indexes uploaded files and answers questions over them. |
| Web search | Bing-backed grounding for fresh information. |
| MCP tools | Connect to any Model Context Protocol server. |
Wiring them in is one line each:
from agent_framework.tools import CodeInterpreterTool, FileSearchTool, MCPTool
agent = ChatAgent(
chat_client=chat_client,
instructions="Analyze the uploaded CSV and answer questions.",
tools=[
CodeInterpreterTool(),
FileSearchTool(vector_store_ids=["vs_abc123"]),
MCPTool(server_url="https://my-mcp-server.example.com"),
get_weather, # your own tool sits alongside
],
)
MCP — connect to anything
MCP (Model Context Protocol) is the open standard for “give my agent access to this external system.” Instead of writing a custom tool for GitHub, Notion, your internal API, you point MAF at an MCP server and all of that server’s tools become available to the agent.
The practical effect: you stop writing glue code. If a vendor publishes
an MCP server, your agent can use it. MAF’s MCPTool handles auth,
transport, and the tool discovery handshake.
What MAF does and doesn’t do for you
Does: schema generation, the tool-call loop, parallel tool execution, error handling (a tool exception is fed back to the model as an error message, not a crash), token accounting, OpenTelemetry traces per tool call.
Doesn’t: tool retry logic with backoff (you write it), tenant-aware
authorization (you check user_id before the tool runs), rate limiting
across users (use middleware — next lesson).
In one breath
- A plain Python function with type hints + a docstring is a valid MAF tool; the framework introspects it into the JSON schema the model reads.
Annotated[type, Field(description=...)]gives each parameter a description; the docstring becomes the tool description — write both for the model, not a human.- Gate destructive/sensitive tools with
ApprovalRequired(...)— the LLM proposes, a human disposes; never wire writes/sends/deletes without it. - MAF ships hosted tools (code interpreter, file search, web search) and
MCPToolto connect any MCP server — one line each, no glue code. - It does schema generation, the tool loop, parallel calls, and error-as-message; you still own retries, tenant authorization, and rate limiting — and keep the tool list to ~5–15, routing by intent beyond that.
Quick check
Quick check
Next
Tools handle single-agent capabilities. The next lesson covers workflows — when a task needs multiple specialized agents collaborating as a graph.
Practice this in an interview
All questionsMCP is an open protocol from Anthropic that standardizes how LLM applications discover and connect to external tools, data sources, and prompts through a common client-server interface. It replaces bespoke per-integration glue with a single protocol, so any MCP-compatible host can use any MCP server, and has been adopted across the broader ecosystem.
Tool poisoning embeds malicious influence in a tool description or result. Cross-server shadowing lets one low-trust server steer use of another server's capability. A rug pull changes a previously reviewed schema, description, implementation, dependency, or destination later. Defend with isolated trust contexts, capability snapshots, change review, sandboxing and runtime authorization.
Function calling lets an LLM output a structured request to invoke an external function with arguments, which the runtime executes and feeds back, enabling agents to act in the world. Good tool design uses clear names and descriptions, minimal well-typed parameters, narrow single-purpose scope, least privilege, and informative error messages so the model can choose and call them reliably.
Key risks include prompt injection, especially indirect injection via tool or retrieval outputs, hijacking the agent, excessive tool permissions enabling damaging actions, data exfiltration, confused-deputy privilege escalation, and unbounded loops driving cost or harm. Mitigations include least-privilege tools, sandboxing, input and output guardrails, human-in-the-loop approval for sensitive actions, and audit logging.