{"slug": "mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling", "title": "MCP in Microsoft Foundry: The Toolbox Pattern for Trustworthy Tool Calling", "summary": "Microsoft Foundry Agent Service has adopted the Model Context Protocol (MCP) as a first-class remote tool type, introducing a Toolbox construct that acts as a governance layer for agent tool calling. The design separates server configuration, authentication, and identity into three distinct layers, supporting six authentication models and an approval gate in the platform's tool-execution layer. The writeup examines how MCP tool calls flow through the Responses API and where production issues such as timeouts, private networking, and prompt injection via tool metadata arise.", "body_md": "*Day 6 of the Microsoft Foundry 100 Days / 100 Blogs series.*\n\nEvery agent framework eventually runs into the same wall: you've got a model that reasons well, but the moment it needs to *do* something — read a GitHub issue, query an internal knowledge base, hit a partner API — you're back to writing bespoke client code, stuffing credentials into environment variables, and hoping nobody pastes a system prompt into a public repo. Multiply that by five agents, three environments, and a compliance team that wants an audit trail, and \"add a tool\" stops being a two-line change.\n\nModel Context Protocol (MCP) was designed to solve exactly this: a standard wire format so any MCP-compatible client can talk to any MCP-compatible server without custom glue code. Microsoft Foundry Agent Service adopted MCP as a first-class tool type, but the more interesting engineering decision is what Foundry built *on top* of it — a construct called the **Toolbox** that turns MCP from \"one more tool integration\" into a governance layer for how agents get their hands on external capabilities.\n\nThis article is about the parts of that system a developer actually has to reason about: how MCP tool calls flow through the Responses API, what the six authentication models mean for your identity design, why the Toolbox exists and when it's worth the extra indirection, and where things break in production (timeouts, private networking, prompt injection through tool metadata).\n\nMCP crossed from \"interesting protocol\" to \"the thing everyone is standardizing on\" faster than almost any AI infrastructure decision in the last two years. GitHub, Azure DevOps, Databricks Genie, Fabric, Neon, Vercel, and dozens of SaaS vendors now ship official MCP servers. If you're building agents on Foundry, the question isn't whether you'll connect to an MCP server — it's whether you'll do it in a way that's auditable, revocable, and doesn't leak a GitHub PAT into your agent instructions.\n\nFoundry's answer is architecturally interesting because it separates three concerns that most tutorials conflate:\n\n`server_url`, `server_label`).` none` to `agentic-identity`).\nUnderstanding why those are three separate layers — instead of one config blob — is the actual engineering lesson here.\n\nModel Context Protocol, published by Anthropic and now adopted widely across the industry (including Microsoft), defines a JSON-RPC-based contract between an **MCP client** (in our case, Foundry Agent Service) and an **MCP server** (GitHub, an internal REST wrapper, a data warehouse connector). The protocol standardizes three primitives:\n\n`tools/list` and invoked via `tools/call`.\nIn practice, almost all production MCP usage today revolves around tools. What MCP gives you that a hand-rolled function-calling integration doesn't is **discoverability** — the client asks the server what it can do at connection time, rather than the tool schema being hardcoded into the client's source. That's what makes a single `mcp` tool declaration in Foundry capable of exposing dozens of GitHub operations without you writing a single wrapper function.\n\nThe trade-off is that discoverability cuts both ways: the server controls the tool descriptions the model sees, and the server can change its surface area at any time. That fact drives a lot of the security posture discussed later.\n\nFoundry Agent Service implements MCP as a **remote tool type**, meaning the agent doesn't run an MCP client library itself — the platform's tool-execution layer does. When you declare an `MCPTool` on an agent, three things get wired together at the platform level:\n\n```\n┌────────────────────┐        ┌──────────────────────────┐        ┌───────────────────────┐\n│   Foundry Agent      │        │   Foundry Tool Execution  │        │   Remote MCP Server    │\n│  (Prompt or Hosted)  │──────▶│   Layer (approval gate,   │──────▶│  (GitHub, internal,    │\n│                       │        │   auth injection, retry)  │        │   Toolbox endpoint)    │\n└────────────────────┘        └──────────────────────────┘        └───────────────────────┘\n          ▲                                │\n          │                                ▼\n          │                    project_connection_id\n          │                    (auth type resolved here)\n          └────────────────────────────────┘\n```\n\n[IMAGE: Professional architecture diagram showing a Foundry Agent (Prompt or Hosted) on the left connecting to a central \"Foundry Toolbox (MCP-compatible endpoint)\" box, which fans out to three MCP servers on the right — a GitHub MCP server (OAuth2), an internal MCP server behind a private VNet/Container Apps boundary, and a public Microsoft Learn MCP server (no auth). Annotate the arrows with \"mcp_approval_request\", \"require_approval=always\", and \"project_connection_id\" labels. Corporate blue/gray/white palette, clean documentation style.]\n\nThe important architectural point: **the agent never sees raw credentials**. The `project_connection_id` on the `MCPTool` declaration points at a Foundry **project connection** — a stored, RBAC-governed object that holds the auth configuration (API key, OAuth app registration, or an identity reference). At call time, the tool execution layer resolves the connection, attaches the right credential or token, makes the `tools/call` request to the MCP server, and returns the result back into the model's context window as a tool output.\n\nThis is the same separation of concerns you'd want in any multi-tenant system: the *what* (tool declaration) is agent-scoped, the *how* (credentials) is connection-scoped and centrally managed, and the *where* (network path) depends on whether the MCP server is public or sits behind a private endpoint.\n\nHere's what actually happens on the wire when a Foundry agent with an MCP tool gets a user request that requires a tool call, assuming `require_approval=\"always\"` (the recommended default):\n\n`get_me` tool from the `api-specs` MCP server to answer \"what's my GitHub username?\"`mcp_approval_request`, containing the server label, tool name, and arguments. The response is `mcp_approval_response` (approve or deny) tied to the `approval_request_id`, referencing the previous `response.id` to keep continuity.\nThis matters because it means **MCP tool calls are not fire-and-forget** the way a local Python function tool might be. There's a full request/response round-trip for approval baked into the protocol surface, which is the mechanism Foundry uses to keep a human (or a policy engine) in the loop for anything that touches an external, third-party system.\n\nIf you set `require_approval=\"never\"`, this step is skipped entirely and the tool executes immediately — appropriate only for read-only, trusted, internal servers where the latency cost of a human-in-the-loop step isn't worth it.\n\nThis is the part of MCP integration that trips up most teams, because \"authentication\" for a remote tool call actually branches into six distinct patterns in Foundry, each suited to a different identity story:\n\n| Auth type | Use when | What Foundry does | \n|---|---|---|\n| `none` | Public, unauthenticated MCP server (e.g., Microsoft Learn docs MCP) | No credential attached; request goes out as-is | \n| `custom-keys` | Server needs a static header (PAT, API key) | Injects `Header=Value` pairs from the stored connection | \n| `oauth2` | Server supports OAuth2, either via a Foundry-managed connector or your own app registration | Handles the authorization code / token exchange, caches and refreshes tokens | \n| `user-entra-token` | Passthrough of the *calling user's* Entra identity (e.g., Fabric, Power BI) | Exchanges the user's token for the target audience via On-Behalf-Of flow | \n| `project-managed-identity` | Target resource trusts the Foundry project's system-assigned managed identity | Requests a token for the target audience using the project's MI | \n| `agentic-identity` | Target resource should authorize the *specific agent* rather than the whole project | Requests a token scoped to the agent's own identity (ties into the Autopilot identity model from Day 3 of this series) | \n\nThe decision tree in practice:\n\n`oauth2` with `--connector-name`, and let Foundry manage the OAuth app registration entirely.`project-managed-identity` or `custom-keys`, but treat the connection object as a secret boundary — RBAC on who can create/read that connection matters as much as the key itself.\nThe `agentic-identity` vs `project-managed-identity` distinction is worth sitting with. If ten different agents in a project all call the same downstream resource under `project-managed-identity`, you get one identity in your access logs for all of them — fine for coarse-grained systems, insufficient if you need to answer \"which *agent* did this\" during an incident review. `agentic-identity` gives every agent its own principal, which is the same design tension covered in the Autopilot identity model piece earlier in this series — Foundry is consistent about pushing identity granularity down to the agent level wherever it can.\n\nThe simplest integration path uses a server-side **prompt agent** with an inline `MCPTool`. This is a *simplified, illustrative* example based on Foundry's Python SDK pattern — verify exact method names against your installed SDK version before running in production:\n\n``` python\nimport json\nfrom azure.identity import DefaultAzureCredential\nfrom azure.ai.projects import AIProjectClient\nfrom azure.ai.projects.models import PromptAgentDefinition, MCPTool\nfrom openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam\n\nPROJECT_ENDPOINT = \"https://<account>.services.ai.azure.com/api/projects/<project>\"\nMCP_CONNECTION_NAME = \"my-mcp-connection\"  # project connection holding auth config\n\nproject = AIProjectClient(endpoint=PROJECT_ENDPOINT, credential=DefaultAzureCredential())\nopenai_client = project.get_openai_client()\n\n# Declare the MCP tool. require_approval=\"always\" is the safe default for\n# any server that isn't fully trusted and read-only.\nmcp_tool = MCPTool(\n    server_label=\"api-specs\",\n    server_url=\"https://api.githubcopilot.com/mcp\",\n    require_approval=\"always\",\n    project_connection_id=MCP_CONNECTION_NAME,\n)\n\nagent = project.agents.create_version(\n    agent_name=\"GitHubInsightsAgent\",\n    definition=PromptAgentDefinition(\n        model=\"gpt-5-mini\",\n        instructions=\"Use MCP tools as needed to answer GitHub-related questions.\",\n        tools=[mcp_tool],\n    ),\n)\n\nconversation = openai_client.conversations.create()\n\nresponse = openai_client.responses.create(\n    conversation=conversation.id,\n    input=\"What is my username in my GitHub profile?\",\n    extra_body={\"agent_reference\": {\"name\": agent.name, \"type\": \"agent_reference\"}},\n)\n\n# Walk the output for any approval requests before the agent can proceed.\npending_inputs: ResponseInputParam = []\nfor item in response.output:\n    if item.type == \"mcp_approval_request\":\n        print(f\"Server: {item.server_label} | Tool: {item.name} | Args: {json.dumps(item.arguments)}\")\n        # In production this should route to a policy engine or human reviewer,\n        # not a blocking input() call.\n        approved = input(\"Approve this MCP tool call? (y/N): \").strip().lower() == \"y\"\n        pending_inputs.append(\n            McpApprovalResponse(\n                type=\"mcp_approval_response\",\n                approve=approved,\n                approval_request_id=item.id,\n            )\n        )\n\n# Resume the same logical turn by chaining previous_response_id.\nfinal = openai_client.responses.create(\n    input=pending_inputs,\n    previous_response_id=response.id,\n    extra_body={\"agent_reference\": {\"name\": agent.name, \"type\": \"agent_reference\"}},\n)\n\nprint(final.output_text)\nproject.agents.delete_version(agent_name=agent.name, agent_version=agent.version)\n```\n\nThree things worth internalizing from this snippet:\n\n`previous_response_id` is how approvals chain back into the same reasoning turn.\nFor hosted agents built on Microsoft Agent Framework, the pattern shifts from an inline tool declaration to referencing a **Toolbox** endpoint — which is where things get more interesting architecturally:\n\n``` python\nimport asyncio\nfrom agent_framework import Agent\nfrom agent_framework.foundry import FoundryChatClient, FoundryToolbox\nfrom azure.ai.projects import AIProjectClient\nfrom azure.ai.projects.models import MCPToolboxTool\nfrom azure.identity import AzureCliCredential\n\nPROJECT_ENDPOINT = \"https://<account>.services.ai.azure.com/api/projects/<project>\"\nMCP_CONNECTION_NAME = \"my-mcp-connection\"\n\nasync def main() -> None:\n    credential = AzureCliCredential()\n    project = AIProjectClient(endpoint=PROJECT_ENDPOINT, credential=credential)\n\n    # 1. Register the MCP server inside a Toolbox (not directly on the agent).\n    server_tool = MCPToolboxTool(\n        server_label=\"api-specs\",\n        server_url=\"https://api.githubcopilot.com/mcp\",\n        require_approval=\"always\",\n        project_connection_id=MCP_CONNECTION_NAME,\n    )\n    toolbox = project.toolboxes.create_version(\n        name=\"mcp-server-toolbox\",\n        description=\"Toolbox with the GitHub MCP server\",\n        tools=[server_tool],\n    )\n\n    # 2. The Toolbox itself now exposes an MCP-compatible endpoint.\n    toolbox_mcp_url = (\n        f\"{PROJECT_ENDPOINT}/toolboxes/{toolbox.name}\"\n        f\"/versions/{toolbox.version}/mcp?api-version=v1\"\n    )\n\n    # 3. Any MCP-compatible runtime — Agent Framework, LangGraph, even a\n    #    GitHub Copilot SDK client — can now consume this one endpoint.\n    toolbox_tool = FoundryToolbox(credential, url=toolbox_mcp_url)\n\n    agent = Agent(\n        client=FoundryChatClient(credential=credential),\n        instructions=\"You are a helpful assistant that uses your MCP tool \"\n                     \"to help with Microsoft documentation questions.\",\n        tools=[toolbox_tool],\n    )\n\n    result = await agent.run(\"What is Microsoft Agent Framework?\")\n    print(result.text)\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\nThe key shift: the hosted agent doesn't talk to GitHub's MCP server directly — it talks to *your Toolbox's* MCP endpoint, which in turn proxies to GitHub. That extra hop is the entire point, and it's worth understanding why.\n\nA Foundry **Toolbox** bundles multiple tools — MCP servers, OpenAPI specs, Web Search, Code Interpreter, File Search, Azure AI Search, even Agent-to-Agent connections — behind a single MCP-compatible endpoint. Conceptually, it's an API gateway pattern applied to tool calling:\n\n```\nazd ai connection create my-mcp-conn \\\n  --kind remote-tool \\\n  --target https://api.githubcopilot.com/mcp/ \\\n  --auth-type oauth2 \\\n  --connector-name foundrygithubmcp\n# my-toolbox.yaml\ndescription: MCP server tools\nconnections:\n  - name: my-mcp-conn\nazd ai toolbox create my-toolbox --from-file my-toolbox.yaml\n```\n\nWhy does this indirection earn its keep instead of being unnecessary complexity?\n\n`allowed_tools`, approval policy, and rate limiting can be enforced at the Toolbox layer rather than duplicated per-agent.\nThe OAuth consent flow surfaces a specific, easy-to-miss failure mode worth calling out explicitly: the *first* call from any new user through an OAuth-backed Toolbox connection returns a JSON-RPC error, not a tool result:\n\n```\n{\n  \"error\": {\n    \"code\": -32006,\n    \"message\": \"User consent is required. Please visit: https://...\"\n  }\n}\n```\n\nThis is expected behavior, not a bug — code your client to catch `-32006`, surface the consent URL, and retry after the user completes the OAuth flow in a browser. Treat it the same way you'd treat a `401` with a `WWW-Authenticate` challenge in a normal OAuth client.\n\nConsider a developer-support agent that needs to: (1) look up internal ticket status from a private ticketing system, (2) search Microsoft Learn documentation, and (3) create GitHub issues on behalf of the user.\n\nA Toolbox-first design looks like this:\n\n`auth-type none`, `require_approval=\"never\"` since it's read-only and Microsoft-operated.`auth-type oauth2` with `--connector-name foundrygithubmcp`, `require_approval=\"always\"` because issue creation is a write operation with real consequences.\nAll three get registered as connections and bundled into one `support-agent-toolbox` Toolbox. The agent code stays static — swapping the ticketing backend later, or adding a fourth MCP server, is a Toolbox YAML change, not an agent redeploy. This is the pattern that pays for the extra indirection: mixed trust levels, mixed auth models, and a hard requirement (write access to GitHub) that must never silently downgrade from \"always approve.\"\n\n**Streaming and timeout behavior.** MCP tool calls sit inside a synchronous request/response turn by default, which means a slow downstream MCP server directly extends your agent's response latency — and can trigger client-side timeouts if the tool takes longer than your HTTP client's patience allows. For genuinely long-running operations, Foundry's Toolbox MCP endpoint supports **MCP tasks** (preview), an extension to the spec for background-style execution — but your agent harness has to explicitly support MCP tasks to take advantage of it. Don't assume long-running tool support exists just because you're using a Toolbox; check the harness compatibility first.\n\n**Private networking.** Public MCP endpoints work out of the box for both Basic and Standard agent setups, but internal MCP servers require a dedicated MCP subnet delegated to `Microsoft.App/environments`, with the server deployed on Azure Container Apps behind internal-only ingress. Foundry ships reference Bicep templates (`19-private-network-agent-tools`, `11-private-network-basic-project`) for exactly this topology — worth starting from those rather than hand-rolling the VNet plumbing, since the MCP subnet delegation requirement is easy to get wrong on a first pass.\n\n**Version drift on third-party servers.** Because MCP is discoverable, a server operator can change tool names, arguments, or descriptions at any time without notifying you. Pin `allowed_tools` explicitly rather than trusting \"whatever the server currently exposes,\" and re-review the allow-list whenever you notice the server's behavior or exposed toolset has changed.\n\nThis is where MCP integration differs meaningfully from calling a REST API you control. You're feeding **model context** that originates from a third party — the tool descriptions, argument schemas, and even the results — directly into your agent's reasoning loop. That's a textbook indirect prompt injection surface: a malicious or compromised MCP server can craft a tool description or a tool *result* that instructs the model to take an unintended action on a later turn.\n\nConcrete mitigations that map directly onto Foundry's controls:\n\nFrom a scale perspective, the Toolbox pattern is the right default the moment you have more than one agent needing the same external capability: it turns an O(agents × servers) credential and configuration matrix into O(servers) connections plus O(agents) toolbox references. The cost angle is subtler — every MCP tool call in an approval-gated flow costs you an extra model turn (the turn that surfaces the `mcp_approval_request` and the turn that resumes after approval), which is real token and latency overhead compared to `require_approval=\"never\"`. That overhead is the price of the audit trail; don't remove it purely to save a few hundred tokens on a write-capable tool.\n\nConnection reuse also matters for OAuth token lifecycle: a Toolbox-mediated connection handles token refresh centrally, so you're not paying the OAuth handshake cost (or risking expired-token failures) on every agent instance independently.\n\n`-32006` consent-required error\nMCP isn't the only tool-integration mechanism in Foundry, and it isn't always the right one:\n\nThe trade-off in the other direction: MCP's dynamic discovery is precisely what makes it a bigger trust surface than a statically-defined OpenAPI tool. If you fully control both sides, a native tool or a pinned OpenAPI spec is simpler and has a smaller attack surface — reach for MCP when the \"someone else's server, someone else's roadmap\" dynamic is actually part of your requirement.\n\n`input()` call is fine for a demo, not for a service with concurrent users.\nMCP support in Microsoft Foundry isn't just \"another tool type\" — it's a deliberate architectural bet that tool integration should be governed the same way network access and identity are: centrally managed, RBAC-scoped, and auditable by default. The approval-gated call lifecycle, the six-way authentication decision tree, and the Toolbox-as-API-gateway pattern all point at the same underlying philosophy: treat every external MCP server as an untrusted dependency until you've explicitly decided otherwise, and design the plumbing so that decision is enforced at the platform layer rather than left to each agent's author to remember.\n\nIf you're building anything beyond a single-agent demo, start with a Toolbox from day one, keep `require_approval=\"always\"` until you have real evidence a server deserves otherwise, and pick your auth type based on how granular your audit story needs to be — not just on what's fastest to wire up.\n\n`19-private-network-agent-tools`, `11-private-network-basic-vnet`)\n*(verify current SDK method signatures and preview-feature availability against the latest Foundry SDK release before shipping to production — MCP task support and some connector names are explicitly called out as preview/evolving in the docs)*\n\n*This is Day 6 of the Microsoft Foundry 100 Days / 100 Blogs series — one deep technical post a day covering the breadth of the Foundry ecosystem. Previous days covered crash-resilient long-running agents, hosted agent protocols (Responses vs. Invocations), the Autopilot identity model, Foundry Local on-device inference, and the closed-loop Agent Optimizer.*", "url": "https://wpnews.pro/news/mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling", "canonical_source": "https://dev.to/monuminu/mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling-2mn9", "published_at": "2026-09-18 05:28:03+00:00", "updated_at": "2026-09-18 05:52:55.161557+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-infrastructure", "ai-tools", "developer-tools"], "entities": ["Microsoft", "Microsoft Foundry", "Model Context Protocol", "Anthropic", "GitHub", "Azure DevOps", "Databricks", "Vercel"], "alternates": {"html": "https://wpnews.pro/news/mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling", "markdown": "https://wpnews.pro/news/mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling.md", "text": "https://wpnews.pro/news/mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling.txt", "jsonld": "https://wpnews.pro/news/mcp-in-microsoft-foundry-the-toolbox-pattern-for-trustworthy-tool-calling.jsonld"}}