Goal: package the Step 3 flights MCP into a project-managed Foundry Toolbox that also adds Web Search and Code Interpreter, then consume the whole bundle from one endpoint with
FoundryToolbox— while keeping the Step 2 function tools.
- What a Foundry Toolbox is and how it differs from the tool patterns you've used so far
- The three tool surfaces now in play — in-process function tools (Step 2), a directly-wired MCP server (Step 3), and a project-managed toolbox (this step) — and when to reach for each
- What the built-in Web Search and Code Interpreter tools give the agent
- Why moving the flights MCP into the toolbox makes it reusable and centrally managed
- How
FoundryToolbox(credential)connects to the toolbox through a singleTOOLBOX_ENDPOINT - Why the toolbox is created out-of-band, so your deployment shape doesn't change (still
resources: [], noazd provision)
travel_assistant/main.py,travel_assistant/tools.py,agent.yaml,agent.manifest.yaml— carried over from Step 3 (your TravelBuddy agent with the three function tools and the directly-wired OctoTrip flights MCP). Nothing was deleted when you advanced.travel_assistant/data/itinerary.csv— a small sample itinerary (mixed currencies on purpose) for Code Interpreter to analyze. It was delivered when you advanced.travel_toolbox/toolbox.yaml— a starter toolbox definition at the repo root (a sibling oftravel_assistant/, delivered when you advanced). Step 1 walks through it.
In this step you make delta-only edits: create the toolbox from toolbox.yaml, set one new environment variable (TOOLBOX_ENDPOINT), swap the direct client.get_mcp_tool(...) line in main.py for FoundryToolbox(credential), and update agent.yaml + the manifest metadata. You keep your carried-over instructions and function tools — you only adjust what the toolbox changes.
You've now met two tool patterns. Function tools (Step 2) are Python functions that run in-process in your container — perfect for small, app-specific logic you own (get_weather, get_local_time, convert_currency). MCP tools (Step 3) live behind a protocol boundary on a remote server; you wired the OctoTrip flights server directly in code with client.get_mcp_tool(...), reading its label and URL from .env.
A Foundry Toolbox is the managed option. It's a project-level bundle of tools — Web Search, Code Interpreter, MCP servers, Azure AI Search, and more — that Foundry hosts behind one endpoint. You define the bundle once (in toolbox.yaml), create it in your project, and any hosted agent can consume it by pointing FoundryToolbox at the toolbox's TOOLBOX_ENDPOINT. Credentials, connections, and policy live in the Foundry project, not scattered across each agent's code.
Here's the contrast across all three:
| Function tools (Step 2) | Directly-wired MCP (Step 3) | Foundry Toolbox (this step) | |
|---|---|---|---|
| Where it's defined | Python in your app | client.get_mcp_tool(...) in code |
toolbox.yaml, created in the project |
| Who manages it | You | You (per agent) | The Foundry project |
| Reuse across agents | Copy the code | Re-wire per agent | Share one toolbox endpoint |
| Config lives in | Your source | Each agent's .env + manifest |
The toolbox (one place) |
| Registered in code as | tools=[fn, ...] |
client.get_mcp_tool(...) |
FoundryToolbox(credential) |
| Deployment impact | None | None (resources: []) |
None (resources: [], created out-of-band) |
This step migrates the flights MCP from Step 3 into the toolbox, and adds two Foundry-managed capabilities alongside it:
- Web Search — grounds answers in current travel advisories, disruptions, and events. You don't run or key a search engine; Foundry manages it.
- Code Interpreter — a sandboxed Python runtime that can read an uploaded file and return tables, totals, and charts. TravelBuddy uses it to turn
itinerary.csvinto a budget breakdown with currency conversion.
The three function tools (weather, time, currency) stay in-process — a toolbox hosts Foundry-managed tools, not your arbitrary Python. So after this step TravelBuddy uses two surfaces at once: local function tools and the shared toolbox.
flowchart LR
User[Traveler prompt] --> Agent[TravelBuddy hosted agent]
CSV[itinerary.csv attached] --> Agent
Agent --> Decision{Which capability?}
Decision -->|weather / time / currency| Local[Function tools<br/>in-process Python]
Decision -->|flights / web / CSV math| TB[Foundry Toolbox]
TB --> WS[Web Search]
TB --> CI[Code Interpreter]
TB --> MCP[OctoTrip Flights<br/>MCP server]
Local --> Agent
WS --> Agent
CI --> Agent
MCP --> Agent
Agent --> Out[Answer: flights,<br/>budget, advisories]
One important detail for Code Interpreter: uploading a file isn't enough — the agent input must include the uploaded file reference so the sandbox can mount it. In the Foundry playground you attach the file to the message; from custom code you pass Content.from_hosted_file(file_id=...) (see Troubleshooting).
Portal alternative. You don't have to use the CLI. A toolbox can also be created and managed in the Foundry portal under Toolboxes (and in the Microsoft Foundry Toolkit for VS Code, which lists each toolbox's Endpoint URL). This step uses
azd ai toolbox create --from-fileso the definition stays in source control, but the portal is a fine way to inspect or tweak it.
Helpful references:
- Create, test, and deploy a toolbox in Foundry — the
toolbox.yamlschema,azd ai toolboxcommands, and theTOOLBOX_ENDPOINTconnection pattern. - Web Search tool — what Web Search grounds on and how to enable it.
- Code Interpreter tool — the sandboxed Python runtime, file inputs, and file outputs.
- Model Context Protocol tools in Microsoft Foundry Agents — the MCP tool you're moving into the toolbox.
- What are hosted agents? — the hosted boundary the toolbox connection runs inside.
- Upstream
04-foundry-toolboxhosted-agent sample — the sample this step is based on.
A toolbox is described by a single YAML file. Yours was delivered at the repo root when you advanced — open travel_toolbox/toolbox.yaml and read it:
# travel_toolbox/toolbox.yaml
description: TravelBuddy tools — web search, Code Interpreter, and flight search.
tools:
- type: web_search
name: web_search
require_approval: "never"
- type: code_interpreter
name: code_interpreter
require_approval: "never"
- type: mcp
# OctoTrip Flights MCP — public and anonymous, so no connection/auth is needed.
server_label: octotrip-flights
server_url: "https://mcp.octotrip.app/flights/mcp"
require_approval: "never"web_searchandcode_interpreterare built-in Foundry tool types — you name them and you're done; Foundry hosts them.- The
mcpentry is the OctoTrip flights server migrated in from Step 3. Because it's public and anonymous, you give it aserver_labelandserver_urland nothing else. (A tool that needs auth would instead reference aproject_connection_id— a named connection stored in the project.) require_approval: "never"lets the runtime call each tool automatically without pausing for human approval — appropriate for these read-only, public capabilities.
Why does this file live at the repo root, not in
travel_assistant/?azd ai agent initsnapshots thetravel_assistant/folder into the deployable project. The toolbox is created separately (Step 2) and referenced only by endpoint, so its definition must sit outside the agent folder — otherwise it would be needlessly copied into the agent image. Keepingtravel_toolbox/a sibling oftravel_assistant/keeps the two concerns separate.
Creating the toolbox is a one-time setup that produces the endpoint your agent connects to. Two things matter for the command to succeed:
- Load your repo-root
.envsoWORKSHOP_RESOURCE_PREFIXexpands into the toolbox name. - Run it from inside the
${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/project folder that Step 1 generated.azd ai toolbox createneeds a current azd environment — the one Step 1 created at.azure/<env-name>/inside that folder. Run it from the repo root and it fails withfailed to read current azd environment: … no project existseven though the project endpoint resolves. (azd ai project setalone does not satisfy this — azd still needs an environment in the working directory.)
The toolbox.yaml stays at the repo root, so from inside the project folder you reference it as ../travel_toolbox/toolbox.yaml. Sign in, load .env, cd into the folder, then create the toolbox:
# bash / zsh — run from the repo root
set -a; source .env; set +a
cd "${WORKSHOP_RESOURCE_PREFIX}-travel-buddy" # the azd project folder from Step 1 (holds the azd env)
azd ai toolbox create "${WORKSHOP_RESOURCE_PREFIX}-travel-toolbox" \
--from-file ../travel_toolbox/toolbox.yaml
cd .. # back to the repo root# PowerShell — run from the repo root
Get-Content .env | Where-Object { $_ -match '^\s*[^#].*=' } | ForEach-Object {
$name, $value = $_ -split '=', 2
Set-Item "Env:$($name.Trim())" $value.Trim()
}
cd "$($env:WORKSHOP_RESOURCE_PREFIX)-travel-buddy" # the azd project folder from Step 1 (holds the azd env)
azd ai toolbox create "$($env:WORKSHOP_RESOURCE_PREFIX)-travel-toolbox" `
--from-file ../travel_toolbox/toolbox.yaml
cd .. # back to the repo rootThe command prints a versioned MCP endpoint, shaped like:
https://<account>.services.ai.azure.com/api/projects/<project>/toolboxes/<toolbox-name>/versions/<version>/mcp?api-version=v1
Copy that endpoint and record it as the one new variable this step introduces. The Foundry values (AZURE_AI_PROJECT_ENDPOINT, AZURE_AI_MODEL_DEPLOYMENT_NAME, WORKSHOP_RESOURCE_PREFIX) are already in .env from earlier steps — don't touch them:
# .env — add only this new line (keep the earlier variables as-is)
TOOLBOX_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>/toolboxes/<toolbox-name>/versions/<version>/mcp?api-version=v1Lost the endpoint later? Retrieve it any time — from inside the
${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/folder (theazdcommands need its environment) — withazd ai toolbox show "${WORKSHOP_RESOURCE_PREFIX}-travel-toolbox" --output json, or read it from the Toolboxes view in the Foundry portal / VS Code Toolkit. You no longer needMCP_SERVER_LABEL/MCP_SERVER_URLin.env— the flights server now lives inside the toolbox.
Once the toolbox is deployed, you can also see it in the Foundry Toolkit for VS Code: expand your project under My Resources → Tools and open the Toolboxes tab to view your toolbox along with its Endpoint URL.
Your main.py is complete from Step 3 — this is a small, surgical delta. The flights capability moves from a direct client.get_mcp_tool(...) call to the toolbox, so you make three edits and keep everything else.
First, add FoundryToolbox to the hosting import (you already import ResponsesHostServer from the same package):
# travel_assistant/main.py
from agent_framework_foundry_hosting import FoundryToolbox, ResponsesHostServer # <-- add FoundryToolboxNext, keep one credential object so both the chat client and the toolbox share it, and construct the toolbox. FoundryToolbox(credential) resolves which toolbox to connect to from the environment — it uses TOOLBOX_ENDPOINT if set, otherwise it composes the endpoint from FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME — then authenticates every request with the credential and connects on first use:
credential = DefaultAzureCredential()
client = FoundryChatClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=credential, # <-- reuse the same credential
)
# FoundryToolbox resolves the toolbox endpoint from the environment
# (TOOLBOX_ENDPOINT, or FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME), authenticates
# every request with the credential, and transparently forwards the platform
# per-request call-id to the toolbox. The hosting server enters the agent, which
# connects the toolbox on first use and closes it at shutdown.
toolbox = FoundryToolbox(credential)Then replace the Step 3 client.get_mcp_tool(...) entry in the tools list with the toolbox. Keep the three function tools exactly as they are:
tools = [
get_weather, # <-- kept from Step 2
get_local_time, # <-- kept from Step 2
convert_currency, # <-- kept from Step 2
toolbox, # <-- replaces the Step 3 client.get_mcp_tool(...) entry
]Finally, update the instructions: keep your carried-over instructions, but remove the Step 3 OctoTrip flights MCP sentence and replace it with one that describes the toolbox capabilities. The flights capability now comes from the toolbox, not a directly-wired MCP server, so the old sentence no longer applies.
Delete this Step 3 sentence:
# remove this Step 3 sentence:
"Use the OctoTrip Flights MCP server when the traveler asks about "
"flights, routes, fares, or schedules; pass IATA airport codes and a "
"departure date (YYYY-MM-DD) — if the traveler doesn't give one, call "
"get_local_time and use the date part of its iso_time as today's date — "
"and summarize the options you find."and put the toolbox sentence in its place:
instructions=(
# ... keep your other earlier instructions here, unchanged ...
# (the OctoTrip flights MCP sentence above is removed)
"Use the Foundry Toolbox for flight search (when the traveler gives no "
"departure date, call get_local_time and use the date part of its "
"iso_time as today's date), for web search of current "
"travel advisories and events, and for Code Interpreter to analyze an "
"uploaded itinerary.csv (budget totals, currency conversion, charts)."
),That's the whole code change. Everything else — the FoundryChatClient setup, the three function tools, default_options={"store": False}, and the synchronous ResponsesHostServer(agent).run() — is unchanged from Step 3. If you get stuck, the finished file is in .workshop/solutions/04-toolbox/.
Why one
credential? The toolbox and the chat client both authenticate to Foundry. Sharing a singleDefaultAzureCredential()avoids two sign-in flows and keeps token caching in one place.
agent.yaml carries its own environment-variable list for the local run (azd ai agent run). The flights MCP is no longer wired in code, so remove its two variables and add TOOLBOX_ENDPOINT:
# travel_assistant/agent.yaml
environment_variables:
# ... AZURE_AI_PROJECT_ENDPOINT, AZURE_AI_MODEL_DEPLOYMENT_NAME, WORKSHOP_RESOURCE_PREFIX ...
- name: TOOLBOX_ENDPOINT # <-- added
value: ${TOOLBOX_ENDPOINT}
# remove the Step 3 MCP_SERVER_LABEL / MCP_SERVER_URL entries — the flights
# server now lives inside the toolboxLeave the name, kind, protocols, and CPU/memory blocks exactly as they were.
The toolbox is created out-of-band and referenced only by endpoint, so the manifest structure barely changes — same template, same protocols, and resources stays empty ([]) because no new Azure resource is declared. You make two kinds of edit: metadata (update the description, add a Toolbox Tools tag, swap the MCP tool_declarations entry for a toolbox one) and configuration (swap the MCP environment variables for TOOLBOX_ENDPOINT).
Update the description:
# travel_assistant/agent.manifest.yaml
description: >
TravelBuddy is an Agent Framework hosted agent with local Python function tools
for weather, local time, and currency, plus a Foundry Toolbox that bundles web
search, Code Interpreter, and the OctoTrip flights MCP server behind one
project-managed endpoint.Add the Toolbox Tools tag (keep the rest, including Travel Assistant and MCP Tools — the toolbox still carries an MCP server) and replace the Step 3 octotrip-flights MCP entry with a toolbox entry (keep the three function-tool entries):
metadata:
tags:
- Agent Framework
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Travel Assistant
- Function Tools
- MCP Tools
- Toolbox Tools # <-- added
tool_declarations:
# ... keep the get_weather / get_local_time / convert_currency entries ...
- name: travel-toolbox # <-- replaces the octotrip-flights mcp entry
description: >
Foundry Toolbox bundling web_search, code_interpreter, and the OctoTrip
flights MCP server, consumed from one project-managed endpoint.
type: toolbox
endpoint: ${TOOLBOX_ENDPOINT}Then swap the MCP variables for TOOLBOX_ENDPOINT in the existing template.environment_variables list (keep the Step 1/Step 2 entries):
template:
# ... name, kind, protocols unchanged ...
environment_variables:
# ... AZURE_AI_PROJECT_ENDPOINT, AZURE_AI_MODEL_DEPLOYMENT_NAME, WORKSHOP_RESOURCE_PREFIX ...
- name: TOOLBOX_ENDPOINT # <-- added
value: ${TOOLBOX_ENDPOINT}
# remove the Step 3 MCP_SERVER_LABEL / MCP_SERVER_URL entries
resources: [] # <-- unchanged: the toolbox is created out-of-bandtool_declarations is descriptive metadata — it documents the agent's capabilities for humans and tooling that browse the manifest. The toolbox is connected in code via FoundryToolbox(credential); the toolbox itself decides which concrete tools it exposes. resources stays [] because the toolbox is a project-level resource you created separately — the manifest declares no new Azure resource, so there's nothing to provision.
Do you need to re-init? Yes. In earlier steps, azd ai agent init copied your travel_assistant/ code into the generated ${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/ project folder — that copy is the snapshot azd actually builds and deploys. Your Step 4 edits (the main.py toolbox swap and the manifest/agent.yaml changes) live in travel_assistant/, so the copied snapshot is now stale. Re-run azd ai agent init to refresh it before you run or deploy.
Do you need azd provision? No. You added no new Azure resource to the manifest (resources: is still []) — the toolbox was created out-of-band in Step 2. The infrastructure from earlier steps is unchanged.
-
Re-init from the repository root. Load your
.envinto the shell first — the repo.envisn't auto-loaded, and the shell needsWORKSHOP_RESOURCE_PREFIXto expand--agent-name(and tocdinto the folder later):# bash / zsh set -a; source .env; set +a azd ai agent init -m travel_assistant/agent.manifest.yaml \ --agent-name "${WORKSHOP_RESOURCE_PREFIX}-travel-buddy"
# PowerShell Get-Content .env | Where-Object { $_ -match '^\s*[^#].*=' } | ForEach-Object { $name, $value = $_ -split '=', 2 Set-Item "Env:$($name.Trim())" $value.Trim() } azd ai agent init -m travel_assistant/agent.manifest.yaml ` --agent-name "$($env:WORKSHOP_RESOURCE_PREFIX)-travel-buddy"
This refreshes the
${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/folder with your updatedmain.pyand manifest metadata (including the newTOOLBOX_ENDPOINTvariable). -
cdinto the project folder and add the new value to the azd env. azd keeps its own environment store (.azure/<env-name>/.env), separate from the repo.env. The Foundry values are already in the azd env from earlier steps, so you only need to set the one newTOOLBOX_ENDPOINT. Keep.envloaded in the shell so you can pass the value through:# bash / zsh — after: set -a; source .env; set +a cd "${WORKSHOP_RESOURCE_PREFIX}-travel-buddy" azd env set TOOLBOX_ENDPOINT "$TOOLBOX_ENDPOINT"
# PowerShell — after loading .env into the shell cd "$($env:WORKSHOP_RESOURCE_PREFIX)-travel-buddy" azd env set TOOLBOX_ENDPOINT "$env:TOOLBOX_ENDPOINT"
-
Run TravelBuddy locally in the hosted Responses runtime:
azd ai agent run
azdreadsagent.yaml, substitutes values from your azd environment, and starts the server onhttp://localhost:8088— now with your three function tools and the Foundry Toolbox (web search, Code Interpreter, flights) loaded. Leave this terminal running. -
Invoke the local agent from a second terminal. Open a new terminal (in the same project folder) and ask a question that uses the toolbox:
azd ai agent invoke --local "Find flights from Seattle (SEA) to Lisbon (LIS), and what current travel advisories should I know about for Lisbon? Use the web."Expected: TravelBuddy calls the flights MCP (through the toolbox) for options and Web Search for advisories. For the itinerary-budget prompt, attach
itinerary.csvin a UI or pass it as a hosted file from custom code (see Troubleshooting).Prefer a UI? With the local agent still running, open the Agent Inspector from the Foundry Toolkit (Command Palette → Foundry Toolkit: Open Agent Inspector). It connects to
http://localhost:8088and shows each streamed toolbox call and result — and lets you attachitinerary.csvto a message for Code Interpreter. -
Deploy to Foundry:
azd deploy
This builds the container image from the refreshed project-folder snapshot — now consuming the toolbox — pushes it to your Azure Container Registry, and rolls out a new hosted agent version. No
azd provisionis needed because the infrastructure is unchanged. -
Invoke the deployed agent:
azd ai agent invoke "Find flights from Seattle (SEA) to Lisbon (LIS)."Prefer a UI? Open the Hosted Agent Playground from the Foundry Toolkit (Developer Tools → Build → Hosted Agent Playground), pick your deployed agent and version, attach
itinerary.csv, and watch the toolbox calls in the session details.
- "Find flights from Seattle (SEA) to Lisbon (LIS)." — routes to the flights MCP inside the toolbox.
- "What current travel advisories or major events should I know about for my trip dates in Lisbon? Use the web." — routes to Web Search.
- "Read the attached itinerary.csv. Build a total-by-category budget breakdown, convert everything to EUR, and show a chart." — routes to Code Interpreter (attach the file).
- (combined) "Based on my attached itinerary and any current Lisbon advisories, suggest one change and re-total the budget in EUR."
When you ask a budget prompt, remember to attach the itinerary file reference. Web-only and flight-only prompts don't need the attachment.
If the create command exits with ERROR: failed to read current azd environment: … no project exists; to create a new project, run 'azd init', you ran it from the repo root, which has no azd environment. Setting the project with azd ai project set isn't enough — azd ai toolbox commands still need a current azd environment in the working directory. Use the one Step 1 created inside the agent project folder:
set -a; source .env; set +a
cd "${WORKSHOP_RESOURCE_PREFIX}-travel-buddy"
azd ai toolbox create "${WORKSHOP_RESOURCE_PREFIX}-travel-toolbox" \
--from-file ../travel_toolbox/toolbox.yaml
cd ..If the ${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/ folder doesn't exist yet, run the Step 1 azd ai agent init first — that's what creates the azd environment.
Most toolbox failures are a missing or wrong TOOLBOX_ENDPOINT, or a stale sign-in. Confirm the value and that you're signed in (run the show from inside the ${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/ folder so azd finds its environment):
cd "${WORKSHOP_RESOURCE_PREFIX}-travel-buddy"
azd ai toolbox show "${WORKSHOP_RESOURCE_PREFIX}-travel-toolbox" --output json
cd ..Make sure the endpoint in .env (and in the azd env, via azd env set TOOLBOX_ENDPOINT ...) matches the versioned URL the toolbox reports. FoundryToolbox(credential) authenticates with DefaultAzureCredential, so az login (or a valid managed identity when deployed) must resolve.
Some regions or projects don't have Web Search enabled. In the Foundry portal, open your project and check Settings → Tools. Make sure Web Search is available for the project, then retry with a prompt that clearly asks for current information.
Uploading a file creates a file object, but Code Interpreter only sees it when the agent input includes the file reference. In a UI (Agent Inspector or Hosted Agent Playground), attach itinerary.csv to the message. From custom code, pass it as a hosted-file content item:
# travel_assistant/main.py
from agent_framework import Content, Message, Role
message = Message(
role=Role.USER,
contents=[
"Read the attached itinerary.csv and total the trip by category in EUR.",
Content.from_hosted_file(file_id=itinerary_file_id),
],
)
result = await agent.run(message)If you only mention the file name in a string, the runtime hasn't attached the file. Use the attachment / Content.from_hosted_file(...).
Code Interpreter often returns plots as file outputs rather than inline images. Look for file annotations in the response and download the generated file from the container. The Foundry Code Interpreter file sample shows the output-file download pattern with container_file_citation annotations: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py.
For the workshop, it's enough if the response names the chart file.
Flight search now lives inside the toolbox, not in main.py. If flights stop working, confirm travel_toolbox/toolbox.yaml still has the mcp entry with the OctoTrip server_url, and that you created the toolbox from that file. If you edited toolbox.yaml after creating the toolbox, re-run azd ai toolbox create from inside the ${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/ folder (it publishes a new version) and update TOOLBOX_ENDPOINT.
The final tools list should contain four entries: the three function tools from Step 2 (get_weather, get_local_time, convert_currency) plus the toolbox. If you replaced the whole list with only the toolbox, restore the function tools and run again.
azd ai agent init copied your code into the ${WORKSHOP_RESOURCE_PREFIX}-travel-buddy/ project folder, so edits in travel_assistant/ don't deploy on their own. Re-run azd ai agent init (step 1 above) to refresh that snapshot — or copy your edited files into the folder's code directory — then azd deploy again.
If you get stuck:
.workshop/solutions/04-toolbox/
Based on the upstream
04-foundry-toolboxsample.
