foundry-toolbox
Skill8 starsChanged 12 days agoMITView on GitHub
Use Microsoft Foundry Toolbox GA to manage versioned multi-tool bundles behind a single MCP endpoint and connect them to hosted agents. Covers stable AIProjectClient.toolboxes CRUD, SDK models, agent_framework_foundry_hosting.FoundryToolbox, authentication, immutable-version promotion and rollback, the azd ai toolbox declarative path, A2A 1.0 GA and stable Tool Search. Distinguishes these from preview A2A 0.3, Work IQ, Fabric IQ, Browser Automation, Reminder, and skills in Toolboxes; explains that azure_ai_search wraps an index, not a Foundry IQ knowledge base. USE FOR: foundry toolbox, toolbox MCP endpoint, AIProjectClient toolboxes, FoundryToolbox, toolbox version promote, multi-tool MCP endpoint, ToolboxSearchPreviewToolboxTool, ToolSearchToolboxTool, kind toolbox, host azure.ai.toolbox, toolbox.yaml. DO NOT USE FOR: building MCP servers (use foundry-mcp-aca), KB-only RAG (use foundry-iq), generic hosted-agent runtime (use foundry-hosted-agents), cross-resource models (use foundry-cross-resource).
- Reads credentials
We split this skill into the rules it gives an agent
30 rules. About 0 are common, 11 are distinctive, and 19 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
19
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 32
Use a Toolbox when an agent needs several managed tools, when tool composition must change without redeploying agent code, or when …
- Distinctive: line 54
Do not infer a subfeature's status from the Toolbox resource's GA status.
- Very specific: line 107
Do not invent tools=[{"type": "toolbox"}].
- Very specific: line 165
code_interpretertool: DO NOT use for long-running computations (>5 min wall-clock) — the container will timeout and fail silently. - Very specific: line 166
web_searchtool: DO NOT enable in regulated-data contexts without explicit allow-listing of source domains. - Distinctive: line 218
Use Foundry User (formerly Azure AI User) for developer/Toolbox runtime access where a role assignment is required.
- Very specific: line 251
do not require prompts/list.
- Distinctive: line 252
keep Streamable HTTP tool calls in streaming mode.
- Distinctive: line 254
do not add the removed preview feature header.
- Distinctive: line 262
When integrating tools into a toolbox or wiring them to an agent, watch for these common authoring mistakes.
- Very specific: line 366
Use MCPStreamableHTTPTool directly for an MCP server that is not managed through a Foundry Toolbox.
- Very specific: line 390
When you wire a remote MCP via MCPStreamableHTTPTool (toolbox endpoint OR a public MCP like Microsoft Learn / GitHub MCP), the raw …
- Distinctive: line 513
Never reverse underscores into dots: underscores are part of the original name, including the Toolbox's triple-underscore separator.
- Distinctive: line 522
Before pointing an agent at a new version, sanity-check it with the raw MCP client.
- Very specific: line 678
Read the current default and references before attaching/detaching.
- Very specific: line 774
When Tool Search is enabled, the initial tool list exposes tool_search, call_tool and pinned tools instead of every full schema.
- Very specific: line 779
Search uses BM25, indexing names, descriptions and parameter information.
- Very specific: line 799
MUST: use the typed search composition in references/python/service_tools.py.
- Very specific: line 850
Do not copy that static-token bridge into long-running hosted MAF agents.
- Very specific: line 1020
If you need KB-grade retrieval through a toolbox, use the mcp tool type pointing at the KB MCP endpoint with a connection that …
- Distinctive: line 1042
Imperative provisioning (ARM REST + SDK): When the declarative connection pipeline is not available (Bicep-only / IaC-only pilots), …
- Very specific: line 1115
Search results returned through Toolbox MCP carry citation metadata in structuredContent.documents[], not a universal citations array.
- Distinctive: line 1132
Verify the exact discovered tool name, successful call, real documents and at least one title/URL citation in a grounding acceptance test.
- Very specific: line 1137
When invoked through a toolbox in a hosted agent context, code_interpreter and file_search share container / vector store state across all …
- Very specific: line 1163
When using auth.type: managed_identity, the Foundry project's MI (not the agent's MI) calls the API.
- Distinctive: line 1213
Use the MCP transport, not a plain JSON POST: discovery may use Streamable HTTP/SSE and requires initialization.
- Very specific: line 1251
Use A2AToolboxTool(a2a_version=A2AProtocolVersion.V1_0, ...) for new GA integrations.
- Very specific: line 1258
MUST: use service_tools.py for the GA A2A model and Tool Search configuration.
- Very specific: line 1290
Test a new immutable Toolbox version before promotion.
- Distinctive: line 1317
No MAF runtime or existing upstream pin changed.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Microsoft Foundry Toolbox GA - Reference Guide
The Foundry Toolbox is a GA managed resource that bundles versioned tools behind one MCP endpoint. The platform centralizes connection references, credentials, token refresh, policy, and immutable-version promotion while the agent connects to one URL.
Use a Toolbox when an agent needs several managed tools, when tool composition
must change without redeploying agent code, or when credentials belong in the
Foundry project rather than the agent container. Stable management uses
AIProjectClient.toolboxes. Stable
Tool Search
uses ToolSearchToolboxTool; skills in Toolboxes retain their preview support
terms even though the parent Toolbox resource is GA.
Status boundary
| Surface | Status | Canonical contract |
Toolbox resource, versions, promotion, CRUD, and MCP v1 endpoints | GA | AIProjectClient.toolboxes; no preview feature header |
| MCP, Web Search, Azure AI Search, Code Interpreter, File Search, OpenAPI | GA Toolbox tool types | Toolbox-specific SDK model classes |
| A2A protocol 1.0 | GA | A2AToolboxTool, wire type a2a, required a2a_version |
| A2A protocol 0.3, Work IQ, Fabric IQ, Browser Automation, Reminder | Preview | Classes retain Preview in their names |
| Tool Search | Stable Toolbox capability | ToolSearchToolboxTool; returns tool_search + call_tool |
| Skills in Toolboxes | Preview | Opt in explicitly; no SLA |
| Prompt Agent consumption of a Toolbox | Preview integration | MCP bridge; GA management and Tool Search do not make this integration GA |
agent_framework_foundry_hosting.FoundryToolbox | Prerelease client package | High-level consumer for the GA Toolbox MCP endpoint |
microsoft.foundry azd bundle 1.0.0-beta.1 | Beta client bundle | Recommended CLI install; currently bundles Toolbox component azure.ai.toolboxes 1.0.0-beta.2 |
Do not infer a subfeature's status from the Toolbox resource's GA status. A GA Toolbox can contain stable Tool Search and a preview skill at the same time; each subfeature keeps its own support and SLA boundary.
Foundry project
└── Toolbox: agent-tools
├── default_version -> 3
├── immutable versions: 1, 2, 3
├── GA tool models
│ ├── MCPToolboxTool
│ ├── WebSearchToolboxTool
│ ├── AzureAISearchToolboxTool
│ ├── CodeInterpreterToolboxTool
│ ├── FileSearchToolboxTool
│ ├── ToolSearchToolboxTool
│ ├── OpenApiToolboxTool
│ └── A2AToolboxTool (protocol 1.0)
└── preview tool models
├── A2APreviewToolboxTool
├── WorkIQPreviewToolboxTool
├── FabricIQPreviewToolboxTool
├── BrowserAutomationPreviewToolboxTool
└── ReminderPreviewToolboxTool
Version-specific MCP endpoint:
{project}/toolboxes/agent-tools/versions/3/mcp?api-version=v1
Default-version MCP endpoint:
{project}/toolboxes/agent-tools/mcp?api-version=v1
Every request:
Entra bearer token scoped to https://ai.azure.com/.default
No preview feature header
Consumers:
Hosted MAF -> FoundryToolbox
Direct non-Toolbox MCP -> MCPStreamableHTTPTool
LangGraph / other frameworks -> authenticated Streamable HTTP MCP client
Prompt Agent Tool Search -> MCPTool bridgeConsumption boundary
The GA Toolbox resource exposes authenticated Streamable HTTP MCP endpoints; it
is not itself an Agent tools[].type. Use the consumer that matches the host:
| Host | Toolbox consumer | Status |
| Hosted Microsoft Agent Framework code | FoundryToolbox in the Agent tools list | GA Toolbox path through a prerelease hosting wrapper |
| LangGraph or another code framework | Its authenticated Streamable HTTP MCP client | Framework-specific |
| Prompt Agent | MCPTool pointing at the Toolbox endpoint with Tool Search enabled | Preview integration of GA Tool Search |
Do not invent tools=[{"type": "toolbox"}]; that Agent tool type does not
exist. A Prompt Agent can use the documented Tool Search bridge, where the
Toolbox endpoint exposes tool_search, call_tool and pinned tools. For existing hosted
production composition, use FoundryToolbox in code.
When to use Toolbox vs alternatives
| Need | Use | Why |
| Multiple managed tools behind one endpoint | Toolbox | Centralized creds, swap tools without agent redeploy, versioning |
| Single hosted MCP with static API key | client.get_mcp_tool() (foundry-hosted-agents § MCP) | Simpler — no toolbox abstraction layer |
| MCP with short-lived AAD bearer (KB MCP, Storage behind PMI) | MCPStreamableHTTPTool + header_provider (foundry-hosted-agents) | Toolbox MCP endpoint can wrap it, but direct header_provider is one less hop |
| KB-only RAG with agentic retrieval / multi-hop / citations | foundry-iq (Knowledge Base, NOT Toolbox azure_ai_search) | Toolbox azure_ai_search wraps an INDEX, not a KB — no query planning or answer synthesis |
| Custom MCP server you build + deploy | foundry-mcp-aca to build the server, then wire it INTO a Toolbox | These compose: build with one skill, manage centrally with this one |
| Cross-resource model invocation | foundry-cross-resource | Toolbox is for tools, not models |
| Vision / DocIntel / Speech tools | foundry-doc-vision-speech patterns wrapped via Toolbox openapi or mcp | Toolbox is the consumption layer |
Tool type reference
ToolboxTool is the abstract base. Use the concrete subclasses below for
Toolbox versions. Generic Agent models such as MCPTool and WebSearchTool
belong to Agent definitions and are not Toolbox models. The existing consumer
stack remains on SDK 2.4; the additive A2A management path below is isolated.
| SDK model | Wire type | Status | Required configuration |
MCPToolboxTool | mcp | GA | server_label plus server_url or connector_id; optional project connection |
WebSearchToolboxTool | web_search | GA | No required fields; optional filters and search context |
AzureAISearchToolboxTool | azure_ai_search | GA | azure_ai_search=AzureAISearchToolResource(...) |
CodeInterpreterToolboxTool | code_interpreter | GA | No required fields; optional container/files |
FileSearchToolboxTool | file_search | GA | Vector-store configuration |
OpenApiToolboxTool | openapi | GA | openapi=OpenApiFunctionDefinition(...) |
ToolSearchToolboxTool | toolbox_search | GA | No required fields; activates tool_search and call_tool |
A2AToolboxTool | a2a | GA, protocol 1.0 | Required a2a_version=A2AProtocolVersion.V1_0; remote agent connection |
A2APreviewToolboxTool | a2a_preview | Preview | Remote agent URL and project connection as needed |
WorkIQPreviewToolboxTool | work_iq_preview | Preview | Work IQ project connection |
FabricIQPreviewToolboxTool | fabric_iq_preview | Preview | Fabric IQ project connection and target server details |
BrowserAutomationPreviewToolboxTool | browser_automation_preview | Preview | Browser Automation connection parameters |
ReminderPreviewToolboxTool | reminder_preview | Preview | No required fields |
Identifier requirement (live invariant): the per-tool
nameabove is otherwise optional, but a Toolbox version may contain at most one tool without an identifier. As soon as a version bundles two or more tools, every tool beyond that single unnamed allowance must carry a unique identifier —namefor built-in tools, orserver_labelforMCPToolboxTool. Omitting it makescreate_versionfail with400 invalid_payload: Multiple tools without identifiers found. This is not limited to duplicate instances of the same tool type — two unnamed tools of any types collide.
The toolbox_search configuration directive is not a callable tool and does
not consume this unnamed-tool allowance.
Per-tool anti-patterns
azure_ai_searchtool: wraps an index, not a Knowledge Base. Private endpoint support does not turn index retrieval into agentic KB retrieval.file_searchtool: private networking is supported. Validate the project's data-resource private endpoints, DNS and intended caller; do not disable isolation to work around a failed probe.code_interpretertool: DO NOT use for long-running computations (>5 min wall-clock) — the container will timeout and fail silently. DO use ACA Jobs viaazd-patternsfor batch work.web_searchtool: DO NOT enable in regulated-data contexts without explicit allow-listing of source domains. DO use theallowed_domainsparameter when you need to scope results to a restricted set of trusted sources.
Stable Toolbox request contract
The GA request shape is:
| Element | Value |
| AAD token scope | https://ai.azure.com/.default |
| Authorization | OAuth 2.0 bearer token minted for the Toolbox scope |
| MCP API version | ?api-version=v1 |
| Preview feature header | None |
azure-ai-projects 2.4.0 and FoundryToolbox apply this contract without
Foundry-Features: Toolboxes=V1Preview. Remove that header when migrating
preview-era clients; do not make correctness depend on a retired feature gate.
Preview-to-GA migration
| Preview-era contract | GA contract |
project.beta.toolboxes | project.toolboxes |
client.get_toolbox(...) | project.toolboxes.get(name) for the latest version, or project.toolboxes.get_version(name, version) for an explicit immutable version |
select_toolbox_tools(...) | Define the exact tools=[...] set in a Toolbox version, then consume that version through FoundryToolbox |
Generic MCPTool, WebSearchTool, AzureAISearchTool | MCPToolboxTool, WebSearchToolboxTool, AzureAISearchToolboxTool |
Foundry-Features: Toolboxes=V1Preview | Remove the feature header |
AzureAIToolbox | agent_framework_foundry_hosting.FoundryToolbox |
create_toolbox_version(...) in stale examples | create_version(...) in SDK 2.4.0 |
This table is the only place presenting obsolete APIs as migration mapping; status/troubleshooting may name retired header, but fenced canonical code must not contain preview-era API.
Two endpoint shapes
| Role | Endpoint | When |
| Toolbox developer | {project}/toolboxes/{name}/versions/{version}/mcp?api-version=v1 | Test or validate a specific version before promoting it |
| Toolbox consumer | {project}/toolboxes/{name}/mcp?api-version=v1 | Production agents — always serves default_version |
The first version of a new toolbox is auto-promoted to default_version
(v1). Subsequent versions stay un-promoted until you explicitly update
the toolbox's default_version.
Auth & RBAC
Use Foundry User (formerly Azure AI User) for developer/Toolbox runtime access where a role assignment is required. For end users consuming an agent with OAuth passthrough, prefer Foundry Agent Consumer where the surface supports it; Playground authoring/editing requires Foundry User.
| Identity | Required for | Why |
| Developer | Always | Create / update / promote / delete toolbox versions |
| Agent identity (UAMI / agent MI) | Hosted agents calling tools | Agent calls tools/call at runtime |
| End user | OAuth-based MCP or supported UserEntraToken connections | Per-user credentials for the downstream audience; native OAuth is not by itself proof of an Entra OBO exchange |
For custom delegated MCP authentication, use the foundry-mcp-auth candidate. It distinguishes platform identity from downstream user identity. Single-user live execution passed for Hosted/Toolbox and the native Prompt/Toolbox bridge; remaining Playground, multi-user and lifecycle acceptance gaps are documented separately.
Hosted MAF / GHCP agent identity: the calling identity at runtime is
instance_identity.principal_id, not the project / account MIs. Seefoundry-iq§ Hosted-agent runtime identity for the full breakdown (same identity model applies to Toolbox tool calls).
Low-level MCP compatibility notes
FoundryToolbox is the canonical MAF consumer. On MAF 1.13 it handles
per-request Entra authorization, defaults load_prompts=False, treats MCP
method-not-found from ping as a supported server capability boundary, and
owns connection cleanup.
Custom MCP clients still need these protocol rules:
- do not require
prompts/list; Toolboxes expose tools; - keep Streamable HTTP tool calls in streaming mode;
- request tokens for
https://ai.azure.com/.default; and - do not add the removed preview feature header.
The hosted platform reserves FOUNDRY_* environment variables. Use the
platform-provided FOUNDRY_PROJECT_ENDPOINT, and use TOOLBOX_ENDPOINT plus
TOOLBOX_NAME only when overriding FoundryToolbox endpoint discovery.
Tool-authoring failure modes
When integrating tools into a toolbox or wiring them to an agent, watch for these common authoring mistakes. This table documents how they manifest, root causes, and defensive patterns.
| Symptom | Root cause | DO NOT do | DO instead |
| Tool not invoked when expected | Tool description too vague or contradicts agent instructions | Write generic tool descriptions like "search for things" | Write specific descriptions naming inputs/outputs/preconditions — e.g. "Search product catalog by name or SKU; returns title + price + stock" |
| Tool invoked too aggressively | Tool description too tempting OR no scoping in agent system prompt | Rely solely on description to control invocation | Add explicit tool-use scope rules in the agent's instructions= parameter; e.g. "Only call product_search after confirming the user requested a search" |
| Malformed JSON returned by tool | Tool returns Python object via plain print() or return | Return non-JSON-serializable objects (dicts, tuples, custom classes without serialization) | Use Pydantic models with .model_dump() or explicit json.dumps() serialization before returning |
Tool exception bubbles up as session_not_ready (424) | Tool raises unhandled exception → container crashes → no error trace surfaced | Let tool exceptions propagate to the framework | Wrap tool body in try/except and return a structured error dict the model can reason about — e.g. {"error": "...reason...", "retry_in_seconds": 30} |
| Tool description doesn't match agent intent | Spec says "summarize PDFs" but tool description says "extract text" | Let tool descriptions drift from the agent's stated capabilities | Sync tool descriptions against spec.md § Agent Tools during every spec review — re-check before each promotion |
Step 1 — Create a toolbox version
Python SDK (azure-ai-projects):
import os
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
AISearchIndexResource,
AzureAISearchToolResource,
AzureAISearchToolboxTool,
MCPToolboxTool,
WebSearchToolboxTool,
)
from azure.identity import DefaultAzureCredential
with (
DefaultAzureCredential() as credential,
AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=credential,
) as project,
):
toolbox_version = project.toolboxes.create_version(
name="agent-tools",
description="Web search + product index + Microsoft Learn MCP",
tools=[
WebSearchToolboxTool(name="web-search"),
AzureAISearchToolboxTool(
name="product-index",
azure_ai_search=AzureAISearchToolResource(
indexes=[
AISearchIndexResource(
index_name="products",
project_connection_id="aisearch-conn",
),
],
),
),
MCPToolboxTool(
server_label="mslearn",
server_url="https://learn.microsoft.com/api/mcp",
require_approval="never",
),
],
)
print(
f"Created {toolbox_version.name} "
f"version {toolbox_version.version}"
)project.toolboxes exposes create_version, get, list, delete,
update, get_version, list_versions, delete_version; stable management
does not require allow_preview=True.
Current API matrix (validated 2026-08-04):
Package Supported line azure-ai-projects~=2.4.0agent-framework~=1.13.0agent-framework-foundry-hosting==1.0.0b260730mcp~=1.29.0(<2until KI-002 closes)
The first call creates the toolbox AND version 1, auto-promoted to
default. Subsequent calls create new versions that stay un-promoted
until you call update(default_version=...).
Step 2 — Wire into a hosted agent
Pattern A - Microsoft Agent Framework FoundryToolbox
agent_framework_foundry_hosting.FoundryToolbox is the high-level Toolbox
consumer for hosted MAF agents. It:
- resolves
TOOLBOX_ENDPOINT, orFOUNDRY_PROJECT_ENDPOINTplusTOOLBOX_NAME; - requests
https://ai.azure.com/.defaulttokens per outbound request; - forwards the request-scoped
x-agent-foundry-call-id; and - connects and closes with the Agent lifecycle.
MUST: Copy the complete composition from
references/python/toolbox_wiring.py. Do not redefine it inline. Pass the resultingFoundryToolboxdirectly in the Agent'stoolslist.
Pattern B - direct non-Toolbox MCP
Use MCPStreamableHTTPTool directly for an MCP server that is not managed
through a Foundry Toolbox. This remains a separate pattern; do not manually
rebuild FoundryToolbox authentication for a Toolbox endpoint.
MUST: Always pass
parse_tool_results=when usingMCPStreamableHTTPTool. Without it, the agent sees raw MCP JSON instead of text content, causing confabulated responses.
MUST: Use
references/python/mcp_text_extractor.pyas the canonical extractor (exportsextract_mcp_text). Do NOT redefine inline — the validator enforces single-source-of-truth.
from references.python.mcp_text_extractor import extract_mcp_text
learn_mcp = MCPStreamableHTTPTool(
name="microsoft-learn",
url="https://learn.microsoft.com/api/mcp",
parse_tool_results=extract_mcp_text, # ← MUST include this
)Cross-skill ownership
MAF runtime wiring (Agent, ChatClient, ResponsesHostServer, ping handling, load_prompts=False) is OWNED by
foundry-hosted-agentsSKILL. THIS SKILL owns toolbox resource semantics + authentication patterns + multi-tool composition. When in doubt: runtime →foundry-hosted-agents; toolbox resource → this SKILL.
Canonical MCP result parser (recommended for non-toolbox MCPs too)
When you wire a remote MCP via MCPStreamableHTTPTool (toolbox endpoint OR a
public MCP like Microsoft Learn / GitHub MCP), the raw tools/call response
contains an array of content items with {"type": "text", "text": "..."}
shape. MAF 1.7 doesn't unwrap this for you — without a parse_tool_results
extractor, the model sees the wire-level JSON envelope instead of just the
text. Models will sometimes parse through it, sometimes not, leading to
"the agent found the docs but didn't cite them" type bugs.
Pass an extractor to the tool constructor:
MUST: Import the canonical extractor from
references/python/mcp_text_extractor.py(functionextract_mcp_text). Do NOT redefine inline — the validator enforces single-source-of-truth.
from references.python.mcp_text_extractor import extract_mcp_text
mcp_tool = MCPStreamableHTTPTool(
name="microsoft-learn",
url="https://learn.microsoft.com/api/mcp",
parse_tool_results=extract_mcp_text, # ← key line
)Verified on the 2026-05-28 learn-assistant pilot: without parse_tool_results,
the agent answered "I found articles" but didn't quote them; with the
extractor, every answer cited 4+ learn.microsoft.com URLs. Same helper
works for any remote streamable-http MCP.
Validated remote-MCP recipe — Microsoft Learn MCP (no-auth, public)
The simplest possible remote-MCP wiring: a public, free, no-auth MCP server.
Verified end-to-end on a hosted-agent pilot (MAF 1.7.0, May 2026) — the
agent ran 4 demo scenarios with 5–6 learn.microsoft.com citations per
grounded answer.
from agent_framework import Agent, MCPStreamableHTTPTool
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer
from references.python.mcp_text_extractor import extract_mcp_text
learn_mcp = MCPStreamableHTTPTool(
name="microsoft-learn",
url="https://learn.microsoft.com/api/mcp", # public, no-auth, free
parse_tool_results=extract_mcp_text,
)
agent = Agent(
client=FoundryChatClient(), # MAF 1.7 API
tools=[learn_mcp],
instructions=(
"You are a Microsoft Learn assistant. For every Azure / .NET / "
"Microsoft 365 question, call the microsoft-learn tool and cite "
"the article URLs in your answer using inline markdown links."
),
)
ResponsesHostServer(agent).run()Why this works without extra plumbing:
- No auth headers needed — Learn MCP is public per Microsoft Learn Terms of Use. No bearer token, no API key, no rate-limit ceiling for casual usage.
- ACA egress is open by default — no
egressPoliciesrequired for*.microsoft.comin standard ACA environments. parse_tool_results=extract_mcp_textis mandatory to surface the Learn knowledge service's text payloads cleanly — without it, the model sees the raw{"content": [...]}envelope and citation quality collapses.
This is the canonical no-auth remote-MCP shape; any other public MCP
(GitHub MCP for unauthenticated requests, generic knowledge MCPs) drops
into the same pattern by swapping the url.
Removed Agent Framework convenience helpers
Preview-era Toolbox convenience helpers are not current APIs. Use the migration
table above, move whole-Toolbox consumption to FoundryToolbox, and keep
per-tool FoundryChatClient factories only for agents that intentionally
attach one hosted tool rather than a Toolbox bundle.
Pattern C — LangGraph
Requires langchain-azure-ai[tools]>1.2.3. The platform-injected
FOUNDRY_AGENT_TOOLBOX_* env vars are picked up automatically:
from langchain_azure_ai.tools import AzureAIProjectToolbox
toolbox = AzureAIProjectToolbox(toolbox_name=os.environ["TOOLBOX_NAME"])
tools = await toolbox.get_tools()
# Pass `tools` to your StateGraph / agent_executor as usualPattern D — GitHub Copilot SDK (bridge)
Toolbox discovery returns SDK-compatible names such as
learn___microsoft_docs_search and azure_ai_search. Register and dispatch
those names verbatim through the application's existing bridge:
bridge = McpBridge(endpoint=TOOLBOX_ENDPOINT, token=_get_toolbox_token())
await bridge.initialize()
mcp_tools = await bridge.list_tools()
copilot_tools = [
{
"name": t["name"],
"description": t.get("description", ""),
"parameters": t.get("inputSchema", {}),
}
for t in mcp_tools
]
async def tool_handler(name: str, arguments: dict) -> str:
return await bridge.call_tool(name, arguments)
agent = Agent(
tools=copilot_tools,
tool_handler=tool_handler,
token=os.environ["GITHUB_TOKEN"],
)Never reverse underscores into dots: underscores are part of the original name, including the Toolbox's triple-underscore separator. If a different server supplies names the SDK rejects, use an explicit, collision-checked alias-to-original map in that adapter; do not infer names with replacements.
Step 3 — Verify the toolbox loads
Before pointing an agent at a new version, sanity-check it with the raw MCP client. Use the versioned endpoint to validate before promoting:
import asyncio
import os
from azure.identity import DefaultAzureCredential
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def verify(url: str, headers: dict[str, str]) -> None:
async with streamablehttp_client(
url,
headers=headers,
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools_result = await session.list_tools()
print(f"Tools found: {len(tools_result.tools)}")
for toolbox_tool in tools_result.tools:
description = (toolbox_tool.description or "")[:80]
print(f" - {toolbox_tool.name}: {description}")
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
toolbox_version = "3"
url = (
f"{project_endpoint}/toolboxes/agent-tools"
f"/versions/{toolbox_version}/mcp?api-version=v1"
)
with DefaultAzureCredential() as credential:
token = credential.get_token("https://ai.azure.com/.default").token
headers = {
"Authorization": " ".join(("Bearer", token)),
}
asyncio.run(verify(url, headers))What to check:
len(tools) > 0— empty means the version isn't provisioned yet (wait 10s and retry) OR an MCP / A2A connection is broken (see troubleshooting)- Each tool has
name,description,inputSchema inputSchema.propertiesis present (some MCP servers omit this, which breaks OpenAI tool-calling)- MCP-sourced tool names follow
{server_label}___{tool_name}(three underscores); call the exact name returned by discovery, not a dotted alias _meta.tool_configuration.require_approvalreflects what you set
Deploy with Azure Developer CLI
| Component | Minimum/pin | Status |
| Azure Developer CLI | azd >= 1.27.0 | GA CLI |
microsoft.foundry extension bundle | 1.0.0-beta.1 | Beta bundle; official install for Foundry CLI workflows |
Bundled azure.ai.toolboxes component | 1.0.0-beta.2 | Beta component calling the GA Toolbox API |
Sources: install Foundry
extensions,
CLI project
context,
and azure.ai.toolboxes release
history.
Install the bounded unified bundle:
azd extension install microsoft.foundry --version 1.0.0-beta.1Bundle 1.0.0-beta.1 currently brings Toolbox component
azure.ai.toolboxes 1.0.0-beta.2. Both remain Beta clients even though
the Toolbox service/API is GA. The bundled Toolbox component no longer sends the retired
Foundry-Features: Toolboxes=V1Preview header.
Canonical azd service target
Declare a Toolbox as its own host: azure.ai.toolbox service. The service key
is the Toolbox name. Each azd up or azd deploy creates a new immutable
version and publishes its MCP endpoint to the active azd environment.
# azure.yaml
name: toolbox-app
services:
ai-project:
host: azure.ai.project
agent-tools:
host: azure.ai.toolbox
uses:
- ai-project
description: Shared tools for hosted agents.
tools:
- type: web_search
- type: code_interpreterazd up
# Later, after changing the Toolbox service:
azd deploy agent-toolsStandalone CLI against an existing project
For imperative management, the input file contains description,
connections, skills, tools, and optional policies; it is not an Agent
manifest and therefore has no kind: toolbox field.
# toolbox.yaml
description: Shared tools for hosted agents.
tools:
- type: web_search
- type: code_interpreterazd ai project set "$FOUNDRY_PROJECT_ENDPOINT" --no-prompt
azd ai project show
azd ai toolbox create agent-tools \
--from-file ./toolbox.yaml \
--no-prompt
azd ai toolbox show agent-tools --output jsonConnection lifecycle and CLI management
The CLI supports more than deployment-time creation. Use the bounded extension bundle above and inspect the installed subcommand help before mutation:
| Command | Effect |
azd ai toolbox list | List existing toolboxes |
azd ai toolbox show <name> --version <version> | Read the candidate and its version-specific MCP endpoint |
azd ai toolbox versions list <name> | List immutable versions |
azd ai toolbox connection add <name> <connection> | Attach an existing project connection in a new version |
azd ai toolbox connection add <name> <search-connection> --index <index> | Attach one Search index, not a Knowledge Base |
azd ai toolbox connection remove <name> <connection> | Stage detachment; refuses to leave no tools |
azd ai toolbox publish <name> <version> | Promote or roll back the default |
azd ai toolbox delete <name> --version <version> | Delete an unreferenced version after authorization |
Resolve/create the project connection first using the appropriate
auth recipe. Record its category, target and
execution identity without retrieving secrets. connection add attaches the
connection; it does not provision the upstream MCP server, Search index or
OAuth consent. RemoteTool, CognitiveSearch and RemoteA2A are different
connection categories, not interchangeable aliases.
Read the current default and references before attaching/detaching. Read back
the resulting candidate version and confirm the default is unchanged; test its
version-specific MCP endpoint and consent/approval behavior before explicit
publish. Verify the floating consumer after promotion. Detaching does not
delete the project connection or revoke upstream credentials. Delete a
connection only with separate resource-owner approval after checking all users.
The connection add --from-file convenience input is for connections, not
arbitrary built-in tools. For a changed built-in tool set, append a complete
version with the stable SDK, retaining required tools/skills/policies. Do not
recreate the parent or copy older examples using toolbox_search_preview or
a2a_preview as defaults. Keep GA Tool Search and A2A 1.0 in the explicitly
scoped management cohort documented below.
Do not conflate manifest shapes:
host: azure.ai.toolboxis the canonicalazure.yamlservice target.toolbox.yamlabove is the standaloneazd ai toolbox create --from-fileinput. Akind: toolboxblock belongs to the separate Agent manifest path and is not interchangeable with either.
Versioning workflow
Toolbox versions are immutable snapshots. Every create produces a
new ToolboxVersionObject. The parent ToolboxObject.default_version
controls which version the consumer endpoint serves.
Create + test + promote
from azure.ai.projects.models import (
CodeInterpreterToolboxTool,
WebSearchToolboxTool,
)
new_version = project.toolboxes.create_version(
name="agent-tools",
description="Added code interpreter",
tools=[
WebSearchToolboxTool(name="web-search"),
CodeInterpreterToolboxTool(name="code-interpreter"),
],
)
toolbox = project.toolboxes.update(
name="agent-tools",
default_version=new_version.version,
)
print(f"Active: {toolbox.default_version}")Rollback
project.toolboxes.update(name="agent-tools", default_version="2")default_version cannot be empty — to "remove" a version, promote
something else first, then delete_version the unwanted one.
List + delete
for version in project.toolboxes.list_versions("agent-tools"):
print(f"{version.version} created {version.created_at}")
project.toolboxes.delete_version("agent-tools", "1")Numeric version strings are returned and rendered under
/versions/{version}.
Deployment creates versions; the standalone CLI also supports inspection, connection edits, publication and deletion. See CLI management. SDK/REST remains appropriate for typed full-version composition and capabilities not exposed by the installed extension.
Tool Search
Tool Search is a stable Toolbox capability. Use ToolSearchToolboxTool to
activate it:
from azure.ai.projects.models import ToolSearchToolboxTool
tool_search = ToolSearchToolboxTool()
assert tool_search.as_dict() == {"type": "toolbox_search"}| Preview name | Stable name |
ToolboxSearchPreviewToolboxTool | ToolSearchToolboxTool |
toolbox_search_preview | toolbox_search |
When Tool Search is enabled, the initial tool list exposes tool_search,
call_tool and pinned tools instead of every full schema. Search first, then
call the discovered tool by its exact returned name. This Toolbox mechanism
is distinct from request-scoped deferred-tool search in the Responses API.
Search uses BM25, indexing names, descriptions and parameter information.
tool_search takes query and optional limit (default 5, maximum 10).
tool_configs belongs on each underlying Toolbox tool entry, not the search
directive or the Toolbox parent:
| Configuration | Effect |
Exact source-tool name, such as lookup_order | Configures that tool; takes precedence over * |
* | Default configuration for every tool from that entry |
pin=True | Keeps the tool visible alongside the two meta-tools |
additional_search_text | Adds ranking keywords, not model-visible schema text |
Unknown configuration keys are silently ignored by the service: verify the
source names and inspect discovery rather than treating a successful create as
proof. MCP calls still use the discovered {server_label}___{tool_name} name;
do not substitute that prefixed name for the source key in tool_configs.
Learn also documents per-user auto-pinning after repeated calls. Record the
caller's history when assessing extra visible tools; do not interpret pinning
as permission or approval. Keep the existing consent and approval gates.
MUST: use the typed search composition in
references/python/service_tools.py.
It constructs models only; it neither connects nor promotes a Toolbox.
For an explicitly approved public no-auth MCP server, pass connection_id=None;
do not create a dummy project connection or silently omit a required one.
Microsoft showed one side-by-side trace with 467 input tokens versus roughly 4,700 without Tool Search, a 90.1% arithmetic reduction for that trace. Treat this as an illustrative demo, not a benchmark, SLA, quality guarantee, or universal savings claim. Source: Tokenomics - The new AI currency and your options explained.
Prompt Agent bridge
Not a delegated-user recipe. The static-token bridge below does not prove the Playground user's identity reaches a nested OAuth MCP. Do not copy a personal token into it to claim passthrough. The live-verified native first-party
UserEntraTokenbridge in foundry-mcp-auth is the delegated recipe; its inner custom MCP connection remains OAuth2 with the MCP's own audience.
The Prompt/Toolbox integration remains preview. Prompt Agents do not accept
a Toolbox resource as an Agent tool type. For Prompt Agent
scenarios that need Tool Search, expose the versioned Toolbox endpoint as an
MCPTool and pass one short-lived https://ai.azure.com/.default token. This is a
structural excerpt that uses the toolbox_version returned by the preceding
create call; the full create/invoke/delete lifecycle is in the
official Toolbox sample:
import os
from azure.ai.projects.models import MCPTool
from azure.identity import DefaultAzureCredential
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
toolbox_mcp_url = (
f"{project_endpoint}/toolboxes/{toolbox_version.name}"
f"/versions/{toolbox_version.version}/mcp?api-version=v1"
)
with DefaultAzureCredential() as credential:
token = credential.get_token("https://ai.azure.com/.default").token
toolbox_mcp = MCPTool(
server_label="agent-tools",
server_url=toolbox_mcp_url,
authorization=token,
require_approval="never",
)Do not copy that static-token bridge into long-running hosted MAF agents. Use
FoundryToolbox there so each outbound request obtains a fresh token.
MCP auth flavors (deeper)
The toolbox uses Foundry's connection registry to resolve MCP credentials.
Pick the connection authType that matches your MCP server.
Key-based (CustomKeys)
- kind: connection
name: mcp-conn
target: https://your-mcp-server.example.com
category: RemoteTool
authType: CustomKeys
credentials:
keys:
Authorization: "Bearer {{ mcp_api_key }}"Reference mcp-conn from the matching MCPToolboxTool.
OAuth — managed connector (Foundry Tools Catalog)
- kind: connection
name: github-oauth-conn
category: RemoteTool
authType: OAuth2
target: https://api.githubcopilot.com/mcp
connectorName: foundrygithubmcp # name from Foundry Tools CatalogReference github-oauth-conn from the matching MCPToolboxTool.
OAuth — custom app registration (BYO)
Use the single
connection definition
and setup contract.
The pinned connection CLI uses credentials.clientId, an array of custom API
scopes plus offline_access, and credential environment references.
It must not receive a Microsoft-audience token for the custom endpoint.
Callback registration, denial and refresh are part of setup, not consequences
of signing into the portal. The candidate now has single-user live evidence;
it does not certify every caller, endpoint or consent lifecycle.
Agent identity (Entra)
For MCP servers that authenticate via Entra and accept the agent's MI as
the caller. Assign the appropriate RBAC role on the target resource
to the agent identity first or tools/list returns 0:
- kind: connection
name: language-mcp
category: RemoteTool
authType: AgenticIdentity
audience: <entra-audience> # 🔑 required
target: https://<resource>.cognitiveservices.azure.com/language/mcp?api-version=2025-11-15-previewARM REST equivalent:
authType: ProjectManagedIdentity, audience moves tometadata.audience. See § ARM REST equivalents below for the full mapping and a working PUT body.
User Entra token (1P OBO)
This is distinct from custom OAuth. Use it only for a service that explicitly supports this mechanism; it is not a universal custom-server or OBO substitute.
For MCP servers that need the calling user's identity (mail, calendar, files):
- kind: connection
name: workiq-mail-conn
category: RemoteTool
authType: UserEntraToken
audience: <entra-app-id> # 🔑 required — without it, tools/list returns 0
target: https://agent365.svc.cloud.microsoft/agents/servers/mcp_MailToolsFirst-time consent. Consent can be required during
initialize,tools/listortools/call, not only on the first tool execution. A Toolbox aggregate failure uses outer JSON-RPC code-32006; the outererror.messagecontains a human-readable prefix followed by JSON. The actionableCONSENT_REQUIREDcode and consent URL are in the nestederrors[].error, not necessarily in the outer message itself.
MUST: For a custom/BYO client, use
extract_consent_requests to decode
the JSON-RPC error object. It also accepts the documented direct
User consent is required. Please visit: ... form. Do not JSON-decode the
entire prefixed aggregate message or interpret every -32006 as consent.
Malformed envelopes and non-consent source failures raise
ToolboxConsentError; a mixed-source failure must not become an empty
successful tool list.
Show each consent URL only to the intended user, identify the connection, and pause the affected operation. Do not automatically follow the URL, complete consent for another user, or log the opaque URL/query. After the user explicitly completes OAuth, retry the original failed operation. Revocation, a new user or a new connection can require consent again. The parser does not implement token exchange or certify a complete OAuth lifecycle; the platform and the connection retain that responsibility.
This integrates the official Toolbox consumer error contract
without replacing the GA SDK, stable Tool Search or the canonical
FoundryToolbox wrapper with preview-era equivalents.
ARM REST equivalents for connection resources
Everything above uses the declarative connection-resource DSL. When you
provision the same RemoteTool connection imperatively (ARM REST PUT, Bicep,
or any non-azd path — common in BYOC / Bicep-only pilots), the ARM API
rejects authType: AgenticIdentity AND authType: AAD with:
ValidationError: AuthType for RemoteTool Connection can only be
None, CustomKeys, ProjectManagedIdentity, OAuth2, DeveloperConnection,
UserEntraToken, ...Mapping table (declarative DSL → ARM REST authType):
Declarative authType | ARM REST authType | Where the audience goes |
AgenticIdentity (agent / Entra MI) | ProjectManagedIdentity | metadata.audience |
UserEntraToken (1P OBO) | UserEntraToken | metadata.audience |
OAuth2Managed (built-in OAuth) | OAuth2 | metadata.{authorizationUrl,tokenUrl,scopes,clientId,...} |
Key | CustomKeys | credentials.keys.{name} |
None | None | (n/a) |
Working ARM REST PUT body for the AgenticIdentity case (Search KB MCP):
PUT /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<acct>/projects/<proj>/connections/<conn-name>?api-version=2025-10-01-preview
{
"properties": {
"category": "RemoteTool",
"target": "https://<search>.search.windows.net/knowledgebases/<kb>/mcp?api-version=2025-11-01-preview",
"authType": "ProjectManagedIdentity",
"isSharedToAll": true,
"metadata": { "audience": "https://search.azure.com" }
}
}SDK split ownership:
azure-ai-projects2.4.0 provides writableproject.toolboxes, whileproject.connectionsremains read-only for project connection discovery. Provision newRemoteToolconnections with Bicep or the documented ARM connection API, then reference the connection ID from the Toolbox model.
Tool-by-tool quirks
azure_ai_search — INDEX, not Knowledge Base
Toolbox azure_ai_search only exposes index_name + query_type
(default vector_semantic_hybrid) + top_k (default 5) + filter. It
does NOT invoke a Knowledge Base's agentic retrieval (query planning,
multi-hop, answer synthesis with citations).
If you need KB-grade retrieval through a toolbox, use the mcp tool
type pointing at the KB MCP endpoint with a connection that authenticates
PMI to https://search.azure.com/.default.
Cross-skill ownership note: For producing a KB-MCP server on ACA, see
foundry-mcp-aca. For consuming a KB via the IQ-style pattern, seefoundry-iq. THIS SKILL covers the toolbox WRAPPER pattern for a KB-MCP — i.e. exposing it as a managed multi-tool through the toolbox endpoint.
Declarative connection excerpt:
- kind: connection
name: kb-mcp
category: RemoteTool
authType: AgenticIdentity
audience: https://search.azure.com
target: https://<search>.search.windows.net/knowledgebases/<kb-name>/mcp?api-version=2025-11-01-previewPass kb-mcp as the project_connection_id of an MCPToolboxTool. The
trade-off is an extra hop through Toolbox, while the Toolbox handles
centralized token refresh. See foundry-iq SKILL § "KB access from a hosted
MAF agent — three routes" for the direct-wire alternative.
Imperative provisioning (ARM REST + SDK): When the declarative connection pipeline is not available (Bicep-only / IaC-only pilots), provision the connection via ARM REST and the Toolbox via the SDK:
import os
import requests
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import MCPToolboxTool
from azure.identity import DefaultAzureCredential
with DefaultAzureCredential() as credential:
arm_token = credential.get_token(
"https://management.azure.com/.default"
).token
connection_url = (
f"https://management.azure.com/subscriptions/{os.environ['SUB']}"
f"/resourceGroups/{os.environ['RG']}/providers"
f"/Microsoft.CognitiveServices/accounts/{os.environ['ACCT']}"
f"/projects/{os.environ['PROJ']}/connections/kb-mcp"
f"?api-version=2025-10-01-preview"
)
response = requests.put(
connection_url,
headers={
"Authorization": " ".join(("Bearer", arm_token)),
"Content-Type": "application/json",
},
json={
"properties": {
"category": "RemoteTool",
"target": os.environ["KB_MCP_URL"],
"authType": "ProjectManagedIdentity",
"isSharedToAll": True,
"metadata": {"audience": "https://search.azure.com"},
},
},
timeout=60,
)
response.raise_for_status()
with AIProjectClient(
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=credential,
) as project:
connection = project.connections.get("kb-mcp")
toolbox_version = project.toolboxes.create_version(
name="agent-tools",
tools=[
MCPToolboxTool(
server_label="kb",
server_url=os.environ["KB_MCP_URL"],
project_connection_id=connection.id,
require_approval="never",
),
],
)
project.toolboxes.update(
name="agent-tools",
default_version=toolbox_version.version,
)Consumer endpoint the agent binds to:
{FOUNDRY_ENDPOINT}/api/projects/{PROJ}/toolboxes/agent-tools/mcp?api-version=v1Extract Azure AI Search citations
Search results returned through Toolbox MCP carry citation metadata in
structuredContent.documents[], not a universal citations array.
Each document can expose title, url, id and score. Preserve the raw
result for evidence and display only validated links; never invent URLs from
document IDs. Empty results are not proof of grounded answering.
MUST: use
references/python/search_citations.py
for strict title/URL extraction from a normalized MCP result dictionary.
The helper reports malformed/error envelopes and unsafe URLs instead of
treating them as a successful empty result. It does not fetch URLs or prove
that a source supports the answer. Keep the existing text extractor for text
content, and the consent parser for consent: neither replaces citation parsing.
File Search resource metadata and Web Search annotations use different locations. Follow the Toolbox result schemas for those tools rather than applying this Search-specific helper to all tools. Verify the exact discovered tool name, successful call, real documents and at least one title/URL citation in a grounding acceptance test.
code_interpreter & file_search — no user isolation
When invoked through a toolbox in a hosted agent context, code_interpreter
and file_search share container / vector store state across all users
of the project. Do not put per-user data in either when deployed
multi-tenant. Use a per-user MCP server backed by Cosmos with row-level
security if you need isolation.
<a id="file_search--no-vnet-support"></a>
file_search — private networking
The current Toolbox network matrix supports File Search through a private endpoint. Earlier blanket bans are obsolete. Verify retrieval from the intended vector store on a runner inside the approved network, with public access still disabled. Network support is not proof of per-user data isolation or evidence that this repository's historical public-endpoint smoke exercised the private path.
web_search — first-party billing, no DPA
Grounding with Bing Search is governed by separate terms and is not covered by Microsoft's Data Protection Addendum. Data sent to the Bing backend leaves Foundry's compliance boundary. Confirm with the customer before enabling in regulated industries (FSI, HCLS, public sector).
openapi — Foundry project MI must have RBAC
When using auth.type: managed_identity, the Foundry project's MI
(not the agent's MI) calls the API. Assign the appropriate RBAC role on
the target service to that MI before deploying or every call returns
401 Unauthorized.
Multiple tools — one unnamed allowance, name / server_label otherwise
400 invalid_payload: Multiple tools without identifiers found...A Toolbox version may leave at most one tool without an identifier. The
error fires whenever a second tool lacks one — regardless of whether the two
unnamed tools share a type. Give every tool beyond that single unnamed
allowance a unique identifier: a name field for built-in tools, or
server_label for MCPToolboxTool. The name doubles as the user-facing tool
name (without the server_label prefix that MCP tools carry).
Approval gating (require_approval)
The toolbox returns _meta.tool_configuration.require_approval on every
tool entry from tools/list. Enforcement is the agent runtime's
responsibility — the MCP endpoint does NOT block tools/call based on
this flag.
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def fetch_approval_map(endpoint, headers):
async with streamablehttp_client(endpoint, headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.list_tools()
approval_map = {}
for toolbox_tool in result.tools:
metadata = toolbox_tool.model_dump(by_alias=True).get("_meta") or {}
if not isinstance(metadata, dict):
raise ValueError("Malformed tool metadata; cannot determine approval")
configuration = metadata.get("tool_configuration", {})
if not isinstance(configuration, dict):
raise ValueError("Malformed tool configuration; cannot determine approval")
if "require_approval" in configuration:
approval_map[toolbox_tool.name] = configuration["require_approval"]
return approval_mapUse the MCP transport, not a plain JSON POST: discovery may use Streamable HTTP/SSE and requires initialization. RPC/consent errors must surface rather than becoming an empty approval map. The map is discovery metadata, not an authorization decision or proof that a missing entry is safe to execute.
In MAF / LangGraph, query the selected Toolbox version, build the approval
map, and enforce it in a tool-execution interceptor that pauses before the
tool body runs. A system-prompt reminder is not an approval control.
Bind approval to the requested tool and arguments, and refresh the map when
the Toolbox version changes. See
threadlight-hitl-patterns
for the Adaptive Card 1.5 wrapper.
VNet support matrix
| Tool type | VNet | Traffic flow |
mcp | ✅ | Through your VNet subnet |
azure_ai_search | ✅ | Through private endpoint |
code_interpreter | ✅ | Microsoft backbone network |
web_search | ✅ | Public endpoint (Bing — outside DPA) |
openapi | ✅ | Through your VNet subnet; target must be reachable |
file_search | ✅ | Through private endpoint |
a2a / a2a_preview | ✅ | Through your VNet subnet; protocol maturity remains separate |
| Skills | ✅ | Skill loading supported; any tools used by a skill keep their own network requirements |
| Work IQ / Browser Automation | ❌ | Not supported in network-isolated projects |
| Fabric IQ | Partial | Depends on the Fabric item and its MCP/network configuration |
| Tool Search | N/A | Discovery configuration, not a downstream network route |
The Toolbox inherits its project's network configuration; it does not deploy
a separate network. Pair with foundry-vnet-deploy for infrastructure. Validate
both private ingress to the Toolbox and each downstream route before claiming
private end-to-end support. Never add a public fallback without owner approval.
A2A 1.0 management without a runtime upgrade
Use A2AToolboxTool(a2a_version=A2AProtocolVersion.V1_0, ...) for new GA
integrations. A2APreviewToolboxTool / a2a_preview remains protocol 0.3
preview; renaming that wire type alone is not a migration. Current azd A2A
examples still emit the preview type: inspect their resulting version before
calling it GA. Preserve existing 0.3 consumers until their peer is verified
on 1.0.
MUST: use service_tools.py for the
GA A2A model and Tool Search configuration. Install
tests/requirements-management.txt only
in a separate management environment. It caps azure-ai-projects~=2.6.1;
A2A models first appeared in 2.5.0. That SDK line requires OpenAI 3, so do
not inject it into the existing SDK 2.4 / MAF 1.13 / hosting environment.
The upstream pin and canonical hosted wiring remain unchanged.
Reference an already-approved RemoteA2A connection. For a Foundry peer its
target is the agent's /endpoint/protocols/a2a base path, not the agent-card
URL; incoming A2A must already be enabled. Its audience is https://ai.azure.com.
The actual calling identity requires Foundry Agent Consumer or higher on the
target project. Connection creation, incoming-protocol enablement and role
grants are separate authorized work, not side effects of this model helper.
Foundry peers currently support text JSON-RPC requests, not streaming.
For a RemoteA2A ARM connection, the audience belongs in
properties.audience. Do not copy the RemoteTool-specific metadata.audience
mapping above: the A2A resolver can return TokenAudience could not be resolved
even though creation stored that metadata successfully.
Invoke the exact tool name and input schema returned by discovery. The tested
SendMessage tool accepts a message object containing parts, each with
kind: "text" and text; passing a plain string as message is rejected.
This MCP-facing schema is not the raw A2A JSON-RPC request schema.
A2A record lifecycle: Foundry documents 60-day retention for tasks and
contexts, reset by each write. Agent/Toolbox/connection deletion does not by
itself prove erasure of those service-managed records. Before a test that
requires immediate erasure, establish a supported purge path or explicitly
accept that bounded retention. An AGENT_NOT_FOUND read after deleting the
peer is not a TASK_NOT_FOUND result and must not be reported as record cleanup.
Test a new immutable Toolbox version before promotion. A reachable agent card,
valid connection, or successful tools/list is not an A2A invocation PASS:
call the discovered peer tool with a harmless request and verify its returned
result. Follow the service acceptance cases.
2026-09-25 A2A functional acceptance: the canonical A2A helper created a GA
1.0 Toolbox tool using a disposable RemoteA2A connection and prompt-agent peer.
Project-managed-identity authentication used pre-existing effective endpoint
access, without role grants. The discovered tool returned a completed task
and the expected synthetic peer result. The peer, connection and Toolbox were
deleted with authenticated absence readback. Immediate removal of the returned
service-managed task/context was not proven; the owner explicitly accepted
the documented 60-day retention for these synthetic records, with no further
writes. This is accepted bounded retention, not verified deletion. The public
no-auth MCP test below is separate evidence and does not substitute for A2A.
2026-09-25 scoped live acceptance: using isolated SDK 2.6.1 / MCP 1.29.x,
the canonical searchable_mcp helper created a version with the public
Microsoft Learn MCP and no project connection. Initial discovery included both
meta-tools and the explicitly pinned search tool. A keyword search returned the
intended fetch tool; one explicitly authorized synthetic documentation query
returned non-error content through the pinned tool's exact discovered name.
An additional scoped case invoked the discovered fetch tool through call_tool
and verified the expected public document content. Version metadata confirmed
explicit promotion, rollback and continued access to the original immutable
version. Both disposable Toolboxes were deleted with authenticated 404 readback.
This proves these configured search/discovery/call paths and version metadata,
not ranking quality, every call_tool variant, A2A, the Prompt bridge or private networking.
No MAF runtime or existing upstream pin changed.
A separate SDK 2.6.1 Prompt/Toolbox preview test created a temporary prompt
agent with the documented short-lived-token MCP bridge. Its response contained
actual tool_search and call_tool calls and the expected public documentation
URL. The agent, Toolbox and stored response were deleted and read back as
absent. This does not establish delegated-user passthrough.
Private-network acceptance used an isolated Basic project with public access disabled and a managed-identity ACA runner. The project hostname resolved to the intended private address; the same service rejected an authenticated external request because public access was disabled. Canonical Skills downloads, MCP resource reads and a File Search call returning the indexed synthetic document all passed on the private route. This covers private ingress with platform-managed backing stores, not BYO datastore or general agent-egress isolation. See the validation record for exact coverage and resource disposition.
Sources: A2A protocol/version contract, incoming A2A, SDK 2.6.1 changelog.
Troubleshooting
| Symptom | Likely cause | Fix |
server_error on every hosted MAF invoke | Preview-era custom Toolbox MCP wrapper still probes ping | Use FoundryToolbox; it treats MCP method-not-found from ping as a supported capability boundary |
500 on agent startup | Direct MCP client called prompts/list against a tools-only server | Set load_prompts=False |
500 on tools/call with no streaming | Direct MCP client set stream=False | Keep Streamable HTTP tool calls in streaming mode |
| Custom env var disappeared at runtime | Hosted Foundry reserves the FOUNDRY_* prefix | Rename custom values to TOOLBOX_* |
AttributeError: ... beta ... toolboxes | Preview-era SDK path with azure-ai-projects 2.4 | Use project.toolboxes |
Toolbox create rejects generic MCPTool / WebSearchTool | Agent model passed to Toolbox CRUD | Use the matching *ToolboxTool model |
Standalone azd ai toolbox command reports no project context | Component-only install or no active Foundry project | Install pinned microsoft.foundry, then run azd ai project set "$FOUNDRY_PROJECT_ENDPOINT" --no-prompt; inspect with azd ai project show |
FoundryToolbox cannot resolve its endpoint | Neither TOOLBOX_ENDPOINT nor FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME is set | Set the versioned Toolbox MCP URL or both fallback variables |
Only tool_search and call_tool are listed | Tool Search is active | Search first, then call the discovered tool |
tools/list returns 0 tools (MCP / A2A) | Bad connection creds, missing audience, MI lacks RBAC | Verify project_connection_id exists; UserEntraToken / AgenticIdentity need audience; check RBAC on target |
tools/list returns 0 tools (OpenAPI) | Malformed OpenAPI spec | Validate spec is OpenAPI 3.0 / 3.1 with paths, operationId, parameter schemas |
tools/list returns 0 tools (built-in) | Toolbox not provisioned yet, or tool unsupported in region | Wait 10s and retry; check region compatibility table |
tools/list returns fewer tools than expected | allowed_tools filter has wrong / misspelled names (case-sensitive) | Remove filter, list all, set exact names from response |
400 Multiple tools without identifiers | More than one unnamed tool in the version (any types) | Leave at most one tool unnamed; give the rest a unique name (or server_label for MCP) |
CONSENT_REQUIRED inside -32006 | OAuth grant needed by a tool source | Decode direct/nested consent with the canonical parser, show the URL to the intended user, await consent, then retry |
401 on MCP calls | Expired token or wrong audience | Distinguish the boundary: https://ai.azure.com/.default is for Toolbox, not the custom MCP API; validate that API's audience and connection scopes |
| Tool name not found | Adapter changed the discovered name or omitted the source prefix | Preserve the exact discovered {server_label}___{tool_name} through registration and dispatch, including the Copilot SDK bridge |
Cross-skill references
| If you need to... | Go to |
| Build a custom MCP server, then add it to a Toolbox | foundry-mcp-aca for the server, then this skill's host: azure.ai.toolbox or MCPToolboxTool path |
| Deploy and operate the hosted MAF agent runtime | foundry-hosted-agents; use this skill's FoundryToolbox reference for the Toolbox consumer |
| Get KB-grade RAG with planning, multi-hop retrieval, and citations | foundry-iq (not Toolbox azure_ai_search) |
| Wrap a knowledge-base MCP endpoint behind a Toolbox | foundry-iq for the KB endpoint, then this skill's MCPToolboxTool connection pattern |
Apply shared azd hooks, Bicep, and environment conventions | azd-patterns; the Toolbox service itself remains host: azure.ai.toolbox |
| Deploy behind private networking | foundry-vnet-deploy, then check this skill's per-tool VNet matrix |
| Add human approval UX around tool execution | threadlight-hitl-patterns |
| Wrap Vision, Document Intelligence, or Speech behind a Toolbox | foundry-doc-vision-speech plus this skill's openapi or mcp tool model |
Catalog history
2.3.0- additive isolated A2A 1.0 management, typed Tool Search tuning, current private-network support and explicit Prompt integration preview boundary. Existing runtime pins and live acceptance remain unchanged.2.2.0- integrate direct/nested OAuth consent decoding for BYO clients, preserve non-consent failures, correct proxy tool names and approval-map transport, and clarify runtime approval enforcement while retaining GA Toolbox and stable Tool Search contracts. Preserve discovered names through the Copilot SDK bridge instead of reversing underscores into dots.2.1.2- distinguish native OAuth from literal Entra OBO; link the local delegated-auth candidate's canonical connection setup and qualify the static Prompt bridge and audience-specific troubleshooting. No app-only behavior change.2.1.1- post-merge correction: fixture Toolbox delete now runs from afinallyblock so a raised assert/get/verification failure still triggers best-effort cleanup instead of leaking the CI toolbox; cleanup failures stay sanitized (toolbox name + exception type only) and transcript-only; removed a dangling troubleshooting remediation clause left over from removed tool-pinning guidance (no ToolConfig support exists in this skill); realignedmetadata.validatedto the upstream pin'slast_validated; and gave every tool in the fixture's livecreate_versionpayload and the multi-tool SKILL examples a unique identifier (name, orserver_labelfor MCP) so a second unnamed tool no longer trips400 Multiple tools without identifiers— the live invariant allows at most one unnamed tool per Toolbox version regardless of tool type.2.1.0- stabilized Tool Search withToolSearchToolboxTool/toolbox_search, refreshed the supported package lines, and kept skills in Toolboxes preview-only.2.0.0- migrated the core Toolbox contract to GA:AIProjectClient.toolboxes, Toolbox-specific SDK models, no preview feature header,FoundryToolboxconsumption, and an explicit preview boundary for Tool Search and preview-only tool types.1.0.0— initial skill. Covers all 7 tool types, preview-era MCP compatibility guidance, MAF / LangGraph / Copilot SDK / azd wire-up, versioning workflow, MCP auth flavors, VNet matrix, and cross-references to existingfoundry-hosted-agentsandfoundry-iqToolbox callouts.
In this repository46 other files it gives its agents.
- AGENTS.mdAGENTS.md
- docs/llms.txtllms.txt
- agent-framework-harnessSkill
- auto-demo-producerSkill
- azd-patternsSkill
- azure-backup-readinessSkill
- azure-monitor-alert-baselineSkill
- azure-resource-diagnosticsSkill
- azure-sre-agentSkill
- azure-tenant-isolationSkill
- citadel-hub-deploySkill
- citadel-spoke-onboardingSkill
- copilot-doctorSkill
- foundry-agentopsSkill
- foundry-agtSkill
- foundry-caphost-lifecycleSkill
- foundry-cost-monitoringSkill
- foundry-cross-resourceSkill
- foundry-doc-vision-speechSkill
- foundry-evalsSkill
- foundry-hosted-agentsSkill
- foundry-iqSkill
- foundry-mcp-aca-jobsSkill
- foundry-mcp-acaSkill
- foundry-mcp-authSkill
- foundry-network-runbookSkill
- foundry-observabilitySkill
- foundry-prompt-agentsSkill
- foundry-rbac-auditSkill
- foundry-routinesSkill
- foundry-skill-catalogSkill
- foundry-teams-botSkill
- foundry-vnet-deploySkill
- foundry-voice-agentsSkill
- foundry-voice-liveSkill
- gbb-humanizerSkill
- gbb-pptxSkill
- ghcp-cli-configSkill
- ghcp-hosted-agentsSkill
- html-deck-to-pptxSkill
- html-presentationSkill
- ip-catalogSkill
- paygo-ptu-cost-analyzerSkill
- progress-guardSkill
- token-economicsSkill
- web-experience-designSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

