agentleFS
aiappsgbbaiappsgbb/awesome-gbb/skills/foundry-toolbox/SKILL.md

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
  1. Very specific:

    Use a Toolbox when an agent needs several managed tools, when tool composition must change without redeploying agent code, or when …

    line 32
  2. Distinctive:

    Do not infer a subfeature's status from the Toolbox resource's GA status.

    line 54
  3. Very specific:

    Do not invent tools=[{"type": "toolbox"}].

    line 107
  4. Very specific:

    code_interpreter tool: DO NOT use for long-running computations (>5 min wall-clock) — the container will timeout and fail silently.

    line 165
  5. Very specific:

    web_search tool: DO NOT enable in regulated-data contexts without explicit allow-listing of source domains.

    line 166
  6. Distinctive:

    Use Foundry User (formerly Azure AI User) for developer/Toolbox runtime access where a role assignment is required.

    line 218
  7. Very specific:

    do not require prompts/list.

    line 251
  8. Distinctive:

    keep Streamable HTTP tool calls in streaming mode.

    line 252
  9. Distinctive:

    do not add the removed preview feature header.

    line 254
  10. Distinctive:

    When integrating tools into a toolbox or wiring them to an agent, watch for these common authoring mistakes.

    line 262
  11. Very specific:

    Use MCPStreamableHTTPTool directly for an MCP server that is not managed through a Foundry Toolbox.

    line 366
  12. Very specific:

    When you wire a remote MCP via MCPStreamableHTTPTool (toolbox endpoint OR a public MCP like Microsoft Learn / GitHub MCP), the raw …

    line 390
  13. Distinctive:

    Never reverse underscores into dots: underscores are part of the original name, including the Toolbox's triple-underscore separator.

    line 513
  14. Distinctive:

    Before pointing an agent at a new version, sanity-check it with the raw MCP client.

    line 522
  15. Very specific:

    Read the current default and references before attaching/detaching.

    line 678
  16. Very specific:

    When Tool Search is enabled, the initial tool list exposes tool_search, call_tool and pinned tools instead of every full schema.

    line 774
  17. Very specific:

    Search uses BM25, indexing names, descriptions and parameter information.

    line 779
  18. Very specific:

    MUST: use the typed search composition in references/python/service_tools.py.

    line 799
  19. Very specific:

    Do not copy that static-token bridge into long-running hosted MAF agents.

    line 850
  20. Very specific:

    If you need KB-grade retrieval through a toolbox, use the mcp tool type pointing at the KB MCP endpoint with a connection that …

    line 1020
  21. Distinctive:

    Imperative provisioning (ARM REST + SDK): When the declarative connection pipeline is not available (Bicep-only / IaC-only pilots), …

    line 1042
  22. Very specific:

    Search results returned through Toolbox MCP carry citation metadata in structuredContent.documents[], not a universal citations array.

    line 1115
  23. Distinctive:

    Verify the exact discovered tool name, successful call, real documents and at least one title/URL citation in a grounding acceptance test.

    line 1132
  24. Very specific:

    When invoked through a toolbox in a hosted agent context, code_interpreter and file_search share container / vector store state across all …

    line 1137
  25. Very specific:

    When using auth.type: managed_identity, the Foundry project's MI (not the agent's MI) calls the API.

    line 1163
  26. Distinctive:

    Use the MCP transport, not a plain JSON POST: discovery may use Streamable HTTP/SSE and requires initialization.

    line 1213
  27. Very specific:

    Use A2AToolboxTool(a2a_version=A2AProtocolVersion.V1_0, ...) for new GA integrations.

    line 1251
  28. Very specific:

    MUST: use service_tools.py for the GA A2A model and Tool Search configuration.

    line 1258
  29. Very specific:

    Test a new immutable Toolbox version before promotion.

    line 1290
  30. Distinctive:

    No MAF runtime or existing upstream pin changed.

    line 1317

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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

SurfaceStatusCanonical contract
Toolbox resource, versions, promotion, CRUD, and MCP v1 endpointsGAAIProjectClient.toolboxes; no preview feature header
MCP, Web Search, Azure AI Search, Code Interpreter, File Search, OpenAPIGA Toolbox tool typesToolbox-specific SDK model classes
A2A protocol 1.0GAA2AToolboxTool, wire type a2a, required a2a_version
A2A protocol 0.3, Work IQ, Fabric IQ, Browser Automation, ReminderPreviewClasses retain Preview in their names
Tool SearchStable Toolbox capabilityToolSearchToolboxTool; returns tool_search + call_tool
Skills in ToolboxesPreviewOpt in explicitly; no SLA
Prompt Agent consumption of a ToolboxPreview integrationMCP bridge; GA management and Tool Search do not make this integration GA
agent_framework_foundry_hosting.FoundryToolboxPrerelease client packageHigh-level consumer for the GA Toolbox MCP endpoint
microsoft.foundry azd bundle 1.0.0-beta.1Beta client bundleRecommended 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 bridge

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

HostToolbox consumerStatus
Hosted Microsoft Agent Framework codeFoundryToolbox in the Agent tools listGA Toolbox path through a prerelease hosting wrapper
LangGraph or another code frameworkIts authenticated Streamable HTTP MCP clientFramework-specific
Prompt AgentMCPTool pointing at the Toolbox endpoint with Tool Search enabledPreview 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

NeedUseWhy
Multiple managed tools behind one endpointToolboxCentralized creds, swap tools without agent redeploy, versioning
Single hosted MCP with static API keyclient.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 / citationsfoundry-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 + deployfoundry-mcp-aca to build the server, then wire it INTO a ToolboxThese compose: build with one skill, manage centrally with this one
Cross-resource model invocationfoundry-cross-resourceToolbox is for tools, not models
Vision / DocIntel / Speech toolsfoundry-doc-vision-speech patterns wrapped via Toolbox openapi or mcpToolbox 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 modelWire typeStatusRequired configuration
MCPToolboxToolmcpGAserver_label plus server_url or connector_id; optional project connection
WebSearchToolboxToolweb_searchGANo required fields; optional filters and search context
AzureAISearchToolboxToolazure_ai_searchGAazure_ai_search=AzureAISearchToolResource(...)
CodeInterpreterToolboxToolcode_interpreterGANo required fields; optional container/files
FileSearchToolboxToolfile_searchGAVector-store configuration
OpenApiToolboxToolopenapiGAopenapi=OpenApiFunctionDefinition(...)
ToolSearchToolboxTooltoolbox_searchGANo required fields; activates tool_search and call_tool
A2AToolboxToola2aGA, protocol 1.0Required a2a_version=A2AProtocolVersion.V1_0; remote agent connection
A2APreviewToolboxToola2a_previewPreviewRemote agent URL and project connection as needed
WorkIQPreviewToolboxToolwork_iq_previewPreviewWork IQ project connection
FabricIQPreviewToolboxToolfabric_iq_previewPreviewFabric IQ project connection and target server details
BrowserAutomationPreviewToolboxToolbrowser_automation_previewPreviewBrowser Automation connection parameters
ReminderPreviewToolboxToolreminder_previewPreviewNo required fields

Identifier requirement (live invariant): the per-tool name above 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 — name for built-in tools, or server_label for MCPToolboxTool. Omitting it makes create_version fail with 400 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_search tool: wraps an index, not a Knowledge Base. Private endpoint support does not turn index retrieval into agentic KB retrieval.
  • file_search tool: 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_interpreter tool: DO NOT use for long-running computations (>5 min wall-clock) — the container will timeout and fail silently. DO use ACA Jobs via azd-patterns for batch work.
  • web_search tool: DO NOT enable in regulated-data contexts without explicit allow-listing of source domains. DO use the allowed_domains parameter when you need to scope results to a restricted set of trusted sources.

Stable Toolbox request contract

The GA request shape is:

ElementValue
AAD token scopehttps://ai.azure.com/.default
AuthorizationOAuth 2.0 bearer token minted for the Toolbox scope
MCP API version?api-version=v1
Preview feature headerNone

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 contractGA contract
project.beta.toolboxesproject.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, AzureAISearchToolMCPToolboxTool, WebSearchToolboxTool, AzureAISearchToolboxTool
Foundry-Features: Toolboxes=V1PreviewRemove the feature header
AzureAIToolboxagent_framework_foundry_hosting.FoundryToolbox
create_toolbox_version(...) in stale examplescreate_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

RoleEndpointWhen
Toolbox developer{project}/toolboxes/{name}/versions/{version}/mcp?api-version=v1Test or validate a specific version before promoting it
Toolbox consumer{project}/toolboxes/{name}/mcp?api-version=v1Production 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.

IdentityRequired forWhy
DeveloperAlwaysCreate / update / promote / delete toolbox versions
Agent identity (UAMI / agent MI)Hosted agents calling toolsAgent calls tools/call at runtime
End userOAuth-based MCP or supported UserEntraToken connectionsPer-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. See foundry-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.

SymptomRoot causeDO NOT doDO instead
Tool not invoked when expectedTool description too vague or contradicts agent instructionsWrite 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 aggressivelyTool description too tempting OR no scoping in agent system promptRely solely on description to control invocationAdd 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 toolTool returns Python object via plain print() or returnReturn 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 surfacedLet tool exceptions propagate to the frameworkWrap 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 intentSpec says "summarize PDFs" but tool description says "extract text"Let tool descriptions drift from the agent's stated capabilitiesSync 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):

PackageSupported line
azure-ai-projects~=2.4.0
agent-framework~=1.13.0
agent-framework-foundry-hosting==1.0.0b260730
mcp~=1.29.0 (<2 until 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, or FOUNDRY_PROJECT_ENDPOINT plus TOOLBOX_NAME;
  • requests https://ai.azure.com/.default tokens 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 resulting FoundryToolbox directly in the Agent's tools list.

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 using MCPStreamableHTTPTool. Without it, the agent sees raw MCP JSON instead of text content, causing confabulated responses.

MUST: Use references/python/mcp_text_extractor.py as the canonical extractor (exports extract_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-agents SKILL. 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 (function extract_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 egressPolicies required for *.microsoft.com in standard ACA environments.
  • parse_tool_results=extract_mcp_text is 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 usual

Pattern 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.properties is 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_approval reflects what you set

Deploy with Azure Developer CLI

ComponentMinimum/pinStatus
Azure Developer CLIazd >= 1.27.0GA CLI
microsoft.foundry extension bundle1.0.0-beta.1Beta bundle; official install for Foundry CLI workflows
Bundled azure.ai.toolboxes component1.0.0-beta.2Beta 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.1

Bundle 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_interpreter
azd up
# Later, after changing the Toolbox service:
azd deploy agent-tools

Standalone 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_interpreter
azd 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 json

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

CommandEffect
azd ai toolbox listList 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.toolbox is the canonical azure.yaml service target. toolbox.yaml above is the standalone azd ai toolbox create --from-file input. A kind: toolbox block 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 nameStable name
ToolboxSearchPreviewToolboxToolToolSearchToolboxTool
toolbox_search_previewtoolbox_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:

ConfigurationEffect
Exact source-tool name, such as lookup_orderConfigures that tool; takes precedence over *
*Default configuration for every tool from that entry
pin=TrueKeeps the tool visible alongside the two meta-tools
additional_search_textAdds 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 UserEntraToken bridge 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 Catalog

Reference 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-preview

ARM REST equivalent: authType: ProjectManagedIdentity, audience moves to metadata.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_MailTools

First-time consent. Consent can be required during initialize, tools/list or tools/call, not only on the first tool execution. A Toolbox aggregate failure uses outer JSON-RPC code -32006; the outer error.message contains a human-readable prefix followed by JSON. The actionable CONSENT_REQUIRED code and consent URL are in the nested errors[].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 authTypeARM REST authTypeWhere the audience goes
AgenticIdentity (agent / Entra MI)ProjectManagedIdentitymetadata.audience
UserEntraToken (1P OBO)UserEntraTokenmetadata.audience
OAuth2Managed (built-in OAuth)OAuth2metadata.{authorizationUrl,tokenUrl,scopes,clientId,...}
KeyCustomKeyscredentials.keys.{name}
NoneNone(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-projects 2.4.0 provides writable project.toolboxes, while project.connections remains read-only for project connection discovery. Provision new RemoteTool connections 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, see foundry-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-preview

Pass 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=v1

Extract 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_map

Use 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 typeVNetTraffic 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 IQPartialDepends on the Fabric item and its MCP/network configuration
Tool SearchN/ADiscovery 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

SymptomLikely causeFix
server_error on every hosted MAF invokePreview-era custom Toolbox MCP wrapper still probes pingUse FoundryToolbox; it treats MCP method-not-found from ping as a supported capability boundary
500 on agent startupDirect MCP client called prompts/list against a tools-only serverSet load_prompts=False
500 on tools/call with no streamingDirect MCP client set stream=FalseKeep Streamable HTTP tool calls in streaming mode
Custom env var disappeared at runtimeHosted Foundry reserves the FOUNDRY_* prefixRename custom values to TOOLBOX_*
AttributeError: ... beta ... toolboxesPreview-era SDK path with azure-ai-projects 2.4Use project.toolboxes
Toolbox create rejects generic MCPTool / WebSearchToolAgent model passed to Toolbox CRUDUse the matching *ToolboxTool model
Standalone azd ai toolbox command reports no project contextComponent-only install or no active Foundry projectInstall pinned microsoft.foundry, then run azd ai project set "$FOUNDRY_PROJECT_ENDPOINT" --no-prompt; inspect with azd ai project show
FoundryToolbox cannot resolve its endpointNeither TOOLBOX_ENDPOINT nor FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME is setSet the versioned Toolbox MCP URL or both fallback variables
Only tool_search and call_tool are listedTool Search is activeSearch first, then call the discovered tool
tools/list returns 0 tools (MCP / A2A)Bad connection creds, missing audience, MI lacks RBACVerify project_connection_id exists; UserEntraToken / AgenticIdentity need audience; check RBAC on target
tools/list returns 0 tools (OpenAPI)Malformed OpenAPI specValidate 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 regionWait 10s and retry; check region compatibility table
tools/list returns fewer tools than expectedallowed_tools filter has wrong / misspelled names (case-sensitive)Remove filter, list all, set exact names from response
400 Multiple tools without identifiersMore 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 -32006OAuth grant needed by a tool sourceDecode direct/nested consent with the canonical parser, show the URL to the intended user, await consent, then retry
401 on MCP callsExpired token or wrong audienceDistinguish 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 foundAdapter changed the discovered name or omitted the source prefixPreserve 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 Toolboxfoundry-mcp-aca for the server, then this skill's host: azure.ai.toolbox or MCPToolboxTool path
Deploy and operate the hosted MAF agent runtimefoundry-hosted-agents; use this skill's FoundryToolbox reference for the Toolbox consumer
Get KB-grade RAG with planning, multi-hop retrieval, and citationsfoundry-iq (not Toolbox azure_ai_search)
Wrap a knowledge-base MCP endpoint behind a Toolboxfoundry-iq for the KB endpoint, then this skill's MCPToolboxTool connection pattern
Apply shared azd hooks, Bicep, and environment conventionsazd-patterns; the Toolbox service itself remains host: azure.ai.toolbox
Deploy behind private networkingfoundry-vnet-deploy, then check this skill's per-tool VNet matrix
Add human approval UX around tool executionthreadlight-hitl-patterns
Wrap Vision, Document Intelligence, or Speech behind a Toolboxfoundry-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 a finally block 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); realigned metadata.validated to the upstream pin's last_validated; and gave every tool in the fixture's live create_version payload and the multi-tool SKILL examples a unique identifier (name, or server_label for MCP) so a second unnamed tool no longer trips 400 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 with ToolSearchToolboxTool / 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, FoundryToolbox consumption, 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 existing foundry-hosted-agents and foundry-iq Toolbox callouts.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.