Agent tools
Export PDFTools turn an agent from a talker into a doer. Foundry offers two kinds: built-in tools that the service executes for you (search, code, web, MCP), and function tools that your own code executes. This lesson explains each important tool, the function-calling loop step by step, MCP with human approval, and how to choose, describe, and secure tools.
Two kinds of tools
A tool the Agent Service runs itself. When the model calls it, the service executes it and feeds the result back to the model in the same response. Examples: file search, code interpreter, web search, Azure AI Search, MCP, OpenAPI.
A tool you describe with a name, description, and JSON Schema for its arguments. When the model calls it, the response ends with a function_call item; your code runs the function and sends back a function_call_output. This is how agents reach your private systems.
| Tool | Kind | What it does | Typical use |
|---|---|---|---|
| Function | Client-side | Calls your Python/C#/JS function | Order lookup, ticket creation, database queries |
| File search | Built-in | Hybrid search over a vector store you uploaded | Small knowledge bases, user uploads |
| Azure AI Search | Built-in | Queries an existing search index through a connection | Enterprise knowledge |
| Code interpreter | Built-in | Runs Python in a sandbox; can read files and create charts | Data analysis, calculations, file conversion |
| Web search / Bing grounding | Built-in | Searches the public web and returns cited results | Current events, public facts |
| MCP | Built-in | Calls tools on a remote Model Context Protocol server | Reuse existing tool servers (GitHub, internal APIs) |
| OpenAPI | Built-in | Calls a REST API described by an OpenAPI spec | Existing internal REST services |
| Azure Functions | Built-in | Invokes a serverless function, often through a queue | Long-running or event-driven actions |
| SharePoint / Fabric / Work IQ | Built-in | Grounds in Microsoft 365 or Fabric data with user permissions | Internal documents and analytics |
Function tools, step by step
Here is the full lifecycle of a function call:
- You register the tool on the agent: name, description, and JSON Schema parameters.
- The user asks “Where is order A1002?”.
- The model returns an output item
{"type": "function_call", "name": "get_order_status", "arguments": "{\"order_id\": \"A1002\"}", "call_id": "call_1"}instead of text. - Your code parses the arguments, runs the function, and sends
{"type": "function_call_output", "call_id": "call_1", "output": "..."}back in the same conversation. - The model reads the result and either answers or calls another tool. Repeat until there are no more function calls.
from azure.ai.projects.models import FunctionTool, PromptAgentDefinition
order_tool = FunctionTool(
name="get_order_status",
description="Look up the shipping status of a customer order by its ID.",
parameters={
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "Order ID such as A1001"},
},
"required": ["order_id"],
"additionalProperties": False,
},
strict=True, # the model must produce arguments that match the schema exactly
)
agent = project.agents.create_version(
agent_name="order-helper",
definition=PromptAgentDefinition(
model=deployment,
instructions="Help customers with orders. Always use get_order_status for order questions.",
tools=[order_tool],
),
)
The heart of the client side is a dispatcher: map tool names to functions, execute each call safely, and never crash on bad arguments — return the error to the model so it can recover. This offline version runs on plain dicts shaped like the real output items:
import json
def get_order_status(order_id: str) -> dict:
orders = {"A1001": "shipped", "A1002": "processing"}
return {"order_id": order_id, "status": orders.get(order_id, "not_found")}
def create_ticket(summary: str, priority: str = "normal") -> dict:
return {"ticket_id": "T-5521", "summary": summary, "priority": priority}
TOOLS = {"get_order_status": get_order_status, "create_ticket": create_ticket}
def run_function_calls(output_items: list[dict]) -> list[dict]:
"""Turn the model's function_call items into function_call_output items."""
results = []
for item in output_items:
if item["type"] != "function_call":
continue
fn = TOOLS.get(item["name"])
try:
args = json.loads(item["arguments"])
result = fn(**args) if fn else {"error": f"unknown tool {item['name']}"}
except (json.JSONDecodeError, TypeError) as err:
result = {"error": str(err)}
results.append({"type": "function_call_output",
"call_id": item["call_id"],
"output": json.dumps(result)})
return results
# what response.output looks like when the model asks for two tools at once
model_output = [
{"type": "function_call", "call_id": "call_1", "name": "get_order_status",
"arguments": '{"order_id": "A1002"}'},
{"type": "function_call", "call_id": "call_2", "name": "create_ticket",
"arguments": '{"summary": "Order A1002 delayed", "priority": "high"}'},
]
for out in run_function_calls(model_output):
print(out)
The model may request several tools in one turn (parallel tool calls). Answer every call_id, or the next request fails. The live loop looks like this:
agent_ref = {"agent_reference": {"name": "order-helper", "type": "agent_reference"}}
response = client.responses.create(conversation=conv.id, input=user_text, extra_body=agent_ref)
for _ in range(5): # cap tool rounds to avoid runaway loops
outputs = run_function_calls([item.model_dump() for item in response.output])
if not outputs:
break
response = client.responses.create(conversation=conv.id, input=outputs, extra_body=agent_ref)
print(response.output_text)
The model chooses tools from their names and descriptions alone. Use verb-noun names (get_order_status, not orders), say when to use the tool, describe every parameter with an example, and use enum for fixed choices. With strict=True, list every property in required and set additionalProperties to false.
MCP tools and approvals
An open standard for exposing tools (and resources and prompts) to AI applications through a server. Any MCP-compatible client — Foundry agents, IDEs, desktop assistants — can use any MCP server, so a tool is written once and reused everywhere.
from azure.ai.projects.models import MCPTool
docs_tool = MCPTool(
server_label="microsoft_learn",
server_url="https://learn.microsoft.com/api/mcp",
allowed_tools=["microsoft_docs_search"], # expose only what the agent needs
require_approval="always", # a human must approve each call
)
# for private servers, store auth in a project connection and pass project_connection_id=...
With require_approval="always", the response contains an mcp_approval_request item instead of calling the tool. Your app shows it to a person and sends back the decision:
approvals = [
{"type": "mcp_approval_response", "approval_request_id": item.id, "approve": True}
for item in response.output
if item.type == "mcp_approval_request"
and item.name in {"microsoft_docs_search"} # your policy check
]
if approvals:
response = client.responses.create(conversation=conv.id, input=approvals, extra_body=agent_ref)
An MCP server can see the arguments the model sends and can return text that tries to manipulate the model (prompt injection). Only connect servers you trust, restrict allowed_tools, require approval for anything that writes or spends money, and keep secrets in connections, not headers in code.
Knowledge tools: file search and Azure AI Search
from azure.ai.projects.models import FileSearchTool
kb_tool = FileSearchTool(vector_store_ids=[store.id], max_num_results=5)
# or AzureAISearchTool(...) from the RAG lesson for an existing index
When the agent uses either tool, the output message carries annotations — file citations or URL citations — that point to the exact sources. Render them as links so users can verify answers.
for item in response.output:
if item.type == "message":
for part in item.content:
for ann in getattr(part, "annotations", []) or []:
print(ann.type, getattr(ann, "filename", None) or getattr(ann, "url", None))
Code interpreter
Code interpreter lets the model write and run Python in an isolated sandbox. It is ideal when the answer requires exact calculation, data analysis over an uploaded CSV, or generating a chart or file.
from azure.ai.projects.models import CodeInterpreterTool
analyst = project.agents.create_version(
agent_name="data-analyst",
definition=PromptAgentDefinition(
model=deployment,
instructions="Analyse data with code. Show the key numbers and explain them simply.",
tools=[CodeInterpreterTool()],
),
)
Code interpreter sessions are billed per session in addition to tokens. Do not use it for simple arithmetic the model can do reliably, or for anything that a deterministic function tool can do better.
Web search
The web search tool (and Bing grounding) lets the agent fetch fresh public information with citations. Use it for current events and public facts; do not use it when answers must come only from approved internal sources. Check the data-handling terms: queries are sent to the search service, which is outside your Azure compliance boundary.
Choosing tools
| You need to… | Use |
|---|---|
| Read or change data in your own system | Function tool (or OpenAPI if a spec exists) |
| Answer from a handful of documents | File search |
| Answer from a large, permission-aware corpus | Azure AI Search / Foundry IQ |
| Compute, analyse files, draw charts | Code interpreter |
| Get current public information | Web search |
| Reuse an existing tool server | MCP |
Tool safety rules
- Least privilege: each tool gets only the permissions it needs; read-only by default.
- Validate arguments in your function code — the model can be wrong or manipulated.
- Human approval for irreversible actions: payments, deletions, emails to customers.
- Cap loops: limit tool rounds and total tokens per request.
- Treat tool output as untrusted input: retrieved documents and web pages can contain injected instructions. Prompt Shields' indirect-attack detection helps (guardrails lesson).
- Keep the toolset small: more tools means more confusion. Five well-described tools beat twenty vague ones.
- Add a third function
cancel_order(order_id)to the dispatcher that returns an error unless the status isprocessing. - Feed the dispatcher a call with malformed JSON arguments and confirm it returns an error instead of crashing.
- Create an agent with the Microsoft Learn MCP server and ask it a Foundry question; approve the call.
- Built-in tools run inside the Agent Service; function tools return a
function_callfor your code to execute. - Answer every
call_idwith afunction_call_output, loop until no calls remain, and cap the rounds. - MCP lets agents reuse tool servers; restrict
allowed_toolsand require approval for risky actions. - File search and Azure AI Search ground answers and return citation annotations.
- Clear names, descriptions, and strict schemas drive good tool choices; validation and approvals keep them safe.