AI Tutorial
Home All Courses Blog Interview Prep ↗ Export PDF 0%
1
Curious builder 0 XP earned · 300 to level 2
0 days Finish a lesson to begin
Badge collection
0 of 5 unlocked
small wins to finish your path
Next lesson →

Agent tools

Export PDF

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

Last updated: 01 Nov, 2026 · azure-ai-projects 2.x · Python 3.10+

Two kinds of tools

Definition · Built-in (server-side) tool

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.

Definition · Function tool (client-side)

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.

ToolKindWhat it doesTypical use
FunctionClient-sideCalls your Python/C#/JS functionOrder lookup, ticket creation, database queries
File searchBuilt-inHybrid search over a vector store you uploadedSmall knowledge bases, user uploads
Azure AI SearchBuilt-inQueries an existing search index through a connectionEnterprise knowledge
Code interpreterBuilt-inRuns Python in a sandbox; can read files and create chartsData analysis, calculations, file conversion
Web search / Bing groundingBuilt-inSearches the public web and returns cited resultsCurrent events, public facts
MCPBuilt-inCalls tools on a remote Model Context Protocol serverReuse existing tool servers (GitHub, internal APIs)
OpenAPIBuilt-inCalls a REST API described by an OpenAPI specExisting internal REST services
Azure FunctionsBuilt-inInvokes a serverless function, often through a queueLong-running or event-driven actions
SharePoint / Fabric / Work IQBuilt-inGrounds in Microsoft 365 or Fabric data with user permissionsInternal documents and analytics

Function tools, step by step

Here is the full lifecycle of a function call:

  1. You register the tool on the agent: name, description, and JSON Schema parameters.
  2. The user asks “Where is order A1002?”.
  3. The model returns an output item {"type": "function_call", "name": "get_order_status", "arguments": "{\"order_id\": \"A1002\"}", "call_id": "call_1"} instead of text.
  4. Your code parses the arguments, runs the function, and sends {"type": "function_call_output", "call_id": "call_1", "output": "..."} back in the same conversation.
  5. The model reads the result and either answers or calls another tool. Repeat until there are no more function calls.
Python
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:

Python
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)
{'type': 'function_call_output', 'call_id': 'call_1', 'output': '{"order_id": "A1002", "status": "processing"}'} {'type': 'function_call_output', 'call_id': 'call_2', 'output': '{"ticket_id": "T-5521", "summary": "Order A1002 delayed", "priority": "high"}'}

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:

Python
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)
Writing tool descriptions

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

Definition · Model Context Protocol (MCP)

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.

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

Python
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)
Trust

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

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

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

Python
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()],
    ),
)
Note

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 systemFunction tool (or OpenAPI if a spec exists)
Answer from a handful of documentsFile search
Answer from a large, permission-aware corpusAzure AI Search / Foundry IQ
Compute, analyse files, draw chartsCode interpreter
Get current public informationWeb search
Reuse an existing tool serverMCP

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.
Try it
  1. Add a third function cancel_order(order_id) to the dispatcher that returns an error unless the status is processing.
  2. Feed the dispatcher a call with malformed JSON arguments and confirm it returns an error instead of crashing.
  3. Create an agent with the Microsoft Learn MCP server and ask it a Foundry question; approve the call.
Key takeaways
  • Built-in tools run inside the Agent Service; function tools return a function_call for your code to execute.
  • Answer every call_id with a function_call_output, loop until no calls remain, and cap the rounds.
  • MCP lets agents reuse tool servers; restrict allowed_tools and 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.