{"id":2848,"date":"2026-08-17T12:20:07","date_gmt":"2026-08-17T19:20:07","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/foundry\/?p=2848"},"modified":"2026-08-17T12:20:07","modified_gmt":"2026-08-17T19:20:07","slug":"five-new-claude-capabilities-now-available-in-foundry","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/foundry\/five-new-claude-capabilities-now-available-in-foundry\/","title":{"rendered":"From single call to agents: five new Claude capabilities now available in Microsoft Foundry"},"content":{"rendered":"<h1>From single call to agents: five new Claude capabilities now available in Microsoft Foundry<\/h1>\n<p><strong>Structured outputs, Web search, Web fetch, MCP connector, and Tool search are now available for Claude models in Microsoft Foundry hosted on Azure, the building blocks that turn a model endpoint into a production agent platform.<\/strong><\/p>\n<hr \/>\n<p>When Claude models became generally available in Microsoft Foundry in June 2026, the headline was new hosted on Azure access: frontier Claude models, Azure-native endpoints, Entra ID authentication, and Azure Marketplace billing. That solved the <em>procurement and governance<\/em> problem. Teams could finally run Claude inside the same subscription, network perimeter, and cost-management surface as the rest of their Azure estate.<\/p>\n<p>But access to a model is not the same as a platform for agents. In the weeks since the generally available launch, we\u2019ve now added more capabilities to the Hosted on Azure options. The pattern we have seen across Foundry customers is the same: a team ships a strong Claude-powered feature, then spends the next quarter rebuilding the same four pieces of scaffolding.<\/p>\n<ol>\n<li>A retry loop that re-prompts the model because it returned JSON with a trailing comma.<\/li>\n<li>A bespoke search-and-scrape service, with its own crawler, cache, robots.txt handling, and citation plumbing.<\/li>\n<li>A hand-rolled MCP client so the model can reach Jira, ServiceNow, Confluence, and three internal APIs.<\/li>\n<li>A tool router, because once you wire up 300 tools the model starts picking the wrong one.<\/li>\n<\/ol>\n<p>Every one of those is undifferentiated engineering. None of it is your product. This release moves all four into the platform and, critically, moves them onto deployments hosted on Azure, so you no longer choose between agentic capability and keeping prompts and completions within Azure.<\/p>\n<p>This post walks through each capability: what it does, the API shape on Foundry, a realistic enterprise use case, and the constraints that will bite you in production. Code examples are given in Python and TypeScript using the Anthropic Foundry SDKs.<\/p>\n<hr \/>\n<h2>The part that changes the architecture: these now run hosted on Azure<\/h2>\n<p>Claude models in Microsoft Foundry come in two hosting options, chosen when you create the deployment. Previously, the agentic feature set was available only on Hosted on Anthropic deployments, which forced trade-off: teams with a data-handling commitment that prompts and completions stay within Azure had to either give up that commitment or rebuild search, fetch, MCP, and tool routing client-side.<\/p>\n<p><strong>That trade-off is now resolved. Structured outputs, Web search, Web fetch, MCP connector, and Tool search are available on deployments hosted on Azure.<\/strong><\/p>\n<table>\n<thead>\n<tr>\n<th><\/th>\n<th><strong>Hosted on Azure<\/strong><\/th>\n<th><strong>Hosted on Anthropic<\/strong><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Where inference runs<\/td>\n<td>Anthropic-operated service on Azure infrastructure<\/td>\n<td>Anthropic-operated service on Anthropic infrastructure<\/td>\n<\/tr>\n<tr>\n<td>Model availability<\/td>\n<td>Latest Opus, Sonnet, and Haiku models<\/td>\n<td>The full Claude catalogue on Foundry<\/td>\n<\/tr>\n<tr>\n<td>Deployment types<\/td>\n<td>Global Standard, US Data Zone Standard<\/td>\n<td>Global Standard<\/td>\n<\/tr>\n<tr>\n<td>The five features in this post<\/td>\n<td>\u2713<\/td>\n<td>\u2713<\/td>\n<\/tr>\n<tr>\n<td>Recommended for<\/td>\n<td>Most workloads<\/td>\n<td>Access to models not yet hosted on Azure<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For deployments hosted on Azure, prompts and completions remain within Azure; only usage metadata and content flagged by Anthropic&#8217;s safety systems egress to Anthropic. Anthropic acts as an independent processor for Microsoft, and customers using Claude through Foundry are subject to Anthropic&#8217;s data use terms.<\/p>\n<p>The practical consequence for regulated industries is significant. A US Data Zone Standard deployment keeps inference within the United States, equivalent to setting <code>inference_geo: \"us\"<\/code> on the Claude API and that deployment can now run a web-search-backed research agent, connect to your internal MCP servers, and return grammar-constrained JSON. Twelve months ago that combination required choosing between capability and residency posture. It no longer does.<\/p>\n<p>See these capabilities in action on this upcoming webinar. [LINK: https:\/\/www.anthropic.com\/webinars\/claude-in-microsoft-foundry-tool-integrations-in-practice?utm_source=partner-msft&amp;utm_medium=webinar&amp;utm_campaign=msft-promotion]<\/p>\n<hr \/>\n<h2>1. Structured Outputs: the end of JSON.parse() roulette<\/h2>\n<h3>The problem<\/h3>\n<p>Every team that has put an LLM in a data pipeline has written this code:<\/p>\n<pre><code class=\"language-python\">for attempt in range(3):\r\n    raw = call_model(prompt)\r\n    try:\r\n        data = json.loads(raw)\r\n        validate(data)\r\n        break\r\n    except (json.JSONDecodeError, ValidationError):\r\n        prompt += \"\\n\\nYour last response was invalid JSON. Try again.\"<\/code><\/pre>\n<p>It works most of the time. &#8220;Most of the time&#8221; is a terrible property for a batch job that processes 400,000 documents overnight, because 0.3% failure is 1,200 rows in a dead-letter queue that somebody has to triage on Monday.<\/p>\n<h3>What it does<\/h3>\n<p>Structured outputs constrain generation itself. The model&#8217;s decoding is restricted by a grammar compiled from your JSON Schema, so the output <em>cannot<\/em> be malformed. Two complementary features, usable independently or together:<\/p>\n<ul>\n<li><strong>JSON outputs<\/strong> (<code>output_config.format<\/code>) \u2014 controls the shape of Claude&#8217;s response text.<\/li>\n<li><strong>Strict tool use<\/strong> (<em>strict: true<\/em> on a tool) \u2014 guarantees schema-valid tool <em>inputs<\/em>.<\/li>\n<\/ul>\n<p>The first governs what Claude says. The second governs how Claude calls your functions.<\/p>\n<h3>Use case: claims intake at a specialty insurer<\/h3>\n<p>A commercial insurer receives first-notice-of-loss submissions as free-text email, broker PDFs, and adjuster voice-note transcripts. The downstream system is an Azure SQL table with a rigid schema and a Logic Apps workflow that routes by severity. Historically, extraction ran through a regex-and-heuristics pipeline that covered about 60% of formats and dumped the rest into a manual queue.<\/p>\n<p>With structured outputs, the extraction contract is the schema:<\/p>\n<pre><code class=\"language-python\">from pydantic import BaseModel\r\nfrom typing import Literal\r\nfrom anthropic import AnthropicFoundry\r\n\r\nclass ClaimIntake(BaseModel):\r\n    policy_number: str\r\n    claimant_name: str\r\n    loss_date: str                       # ISO 8601\r\n    loss_type: Literal[\r\n        \"property_damage\", \"bodily_injury\", \"business_interruption\",\r\n        \"auto_liability\", \"other\",\r\n    ]\r\n    estimated_severity_usd: float\r\n    third_party_involved: bool\r\n    injuries_reported: bool\r\n    summary: str\r\n    escalate_to_adjuster: bool\r\n\r\nclient = AnthropicFoundry(resource=\"contoso-ai\")\r\n\r\nresponse = client.messages.parse(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=2048,\r\n    system=(\r\n        \"You are a claims intake analyst. Extract only what is stated or \"\r\n        \"clearly implied in the submission. If severity is not stated, \"\r\n        \"estimate conservatively from comparable losses.\"\r\n    ),\r\n    messages=[{\"role\": \"user\", \"content\": submission_text}],\r\n    output_format=ClaimIntake,\r\n)\r\n\r\nclaim = response.parsed_output      # a ClaimIntake instance, already validated\r\nif claim.escalate_to_adjuster:\r\n    enqueue_for_adjuster(claim)<\/code><\/pre>\n<p>The TypeScript equivalent, using Zod:<\/p>\n<pre><code class=\"language-typescript\">import { z } from \"zod\";\r\nimport AnthropicFoundry from \"@anthropic-ai\/foundry-sdk\";\r\nimport { zodOutputFormat } from \"@anthropic-ai\/sdk\/helpers\/zod\";\r\n\r\nconst ClaimIntake = z.object({\r\n  policy_number: z.string(),\r\n  claimant_name: z.string(),\r\n  loss_date: z.string(),\r\n  loss_type: z.enum([\r\n    \"property_damage\", \"bodily_injury\", \"business_interruption\",\r\n    \"auto_liability\", \"other\",\r\n  ]),\r\n  estimated_severity_usd: z.number(),\r\n  third_party_involved: z.boolean(),\r\n  injuries_reported: z.boolean(),\r\n  summary: z.string(),\r\n  escalate_to_adjuster: z.boolean(),\r\n});\r\n\r\nconst client = new AnthropicFoundry({ resource: \"contoso-ai\" });\r\n\r\nconst response = await client.messages.parse({\r\n  model: \"claude-opus-5\",\r\n  max_tokens: 2048,\r\n  messages: [{ role: \"user\", content: submissionText }],\r\n  output_config: { format: zodOutputFormat(ClaimIntake) },\r\n});<\/code><\/pre>\n<hr \/>\n<h2>2. Web Search: current information, with citations, without a crawler<\/h2>\n<h3>What it does<\/h3>\n<p>Add one tool to the request and Claude decides when to search, runs as many searches as it needs within your limit, and returns an answer with citations attached to the specific spans it drew from. You do not run a crawler, manage an index, or write a re-ranker.<\/p>\n<p>Version available is <code>web_search_20250305<\/code> \u2014 basic search<\/p>\n<pre><code>response = client.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=4096,\r\n    messages=[{\"role\": \"user\", \"content\": \"What's the current state of the EU AI Act's GPAI obligations?\"}],\r\n    tools=[{\"type\": \"web_search_20250305\", \"name\": \"web_search\", \"max_uses\": 5}],\r\n)<\/code><\/pre>\n<h3>Dynamic filtering: the token economics change<\/h3>\n<p>With basic search, every result loads into the context window in full \u2014 including the boilerplate, the nav chrome, and the four paragraphs that had nothing to do with your question. On a research-heavy request that is tens of thousands of wasted input tokens per turn.<\/p>\n<p>With <code>web_search_20260209<\/code> and later, Claude instead writes and runs code that filters results <em>before<\/em> they reach context, keeping only relevant content. Mechanically, search runs from inside the code execution tool: on these versions <code>allowed_callers<\/code> defaults to <code>[\"code_execution_20260120\"]<\/code>, and Foundry provisions the code execution the request needs automatically. You do not add code execution to your <em>tools<\/em> array, and there is no extra charge for those calls beyond standard token costs.<\/p>\n<p>To force direct calls without dynamic filtering, set <code>allowed_callers: [\"direct\"]<\/code>. Models that do not support programmatic tool calling require this; without it you get a 400 telling you so.<\/p>\n<h3>Use case: regulatory change monitoring at a global bank<\/h3>\n<p>A Tier 1 bank&#8217;s regulatory affairs team tracks rule changes across a dozen jurisdictions. The old process was a team of analysts with RSS feeds and a shared inbox; the median time from publication to an internal impact note was six days.<\/p>\n<p>The rebuild is a nightly Azure Container Apps job. The critical design choice is not the prompt \u2014 it is <code>allowed_domains<\/code>. Regulatory monitoring is exactly the case where you cannot afford a secondary source paraphrasing a rule incorrectly:<\/p>\n<pre><code>REGULATOR_DOMAINS = [\r\n    \"eba.europa.eu\", \"esma.europa.eu\", \"eur-lex.europa.eu\",\r\n    \"federalreserve.gov\", \"sec.gov\", \"occ.gov\",\r\n    \"bankofengland.co.uk\", \"fca.org.uk\",\r\n    \"mas.gov.sg\", \"apra.gov.au\",\r\n]\r\n\r\nresponse = client.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=8192,\r\n    system=(\r\n        \"You monitor prudential and conduct regulation for a global bank. \"\r\n        \"Report only changes published in the last 7 days. For each change, \"\r\n        \"state the regulator, the instrument, the effective date, and the \"\r\n        \"business lines affected. Do not speculate beyond the source text.\"\r\n    ),\r\n    messages=[{\"role\": \"user\", \"content\": \"What changed this week in capital and liquidity rules?\"}],\r\n    tools=[{\r\n        \"type\": \"web_search_20260318\",\r\n        \"name\": \"web_search\",\r\n        \"max_uses\": 12,\r\n        \"allowed_domains\": REGULATOR_DOMAINS,\r\n        \"user_location\": {\r\n            \"type\": \"approximate\",\r\n            \"city\": \"London\",\r\n            \"region\": \"England\",\r\n            \"country\": \"GB\",\r\n            \"timezone\": \"Europe\/London\",\r\n        },\r\n    }],\r\n)<\/code><\/pre>\n<pre><code class=\"language-typescript\">const response = await client.messages.create({\r\n  model: \"claude-opus-5\",\r\n  max_tokens: 8192,\r\n  system: \"You monitor prudential and conduct regulation for a global bank. ...\",\r\n  messages: [{ role: \"user\", content: \"What changed this week in capital and liquidity rules?\" }],\r\n  tools: [{\r\n    type: \"web_search_20260318\",\r\n    name: \"web_search\",\r\n    max_uses: 12,\r\n    allowed_domains: REGULATOR_DOMAINS,\r\n    user_location: {\r\n      type: \"approximate\", city: \"London\", region: \"England\",\r\n      country: \"GB\", timezone: \"Europe\/London\",\r\n    },\r\n  }],\r\n});<\/code><\/pre>\n<p><code>allowed_domains<\/code> and <code>blocked_domains<\/code> are mutually exclusive \u2014 send both and you get a 400. Entries are bare domains with an optional path (<code>example.com<\/code>, <code>example.com\/blog<\/code>), no scheme.<\/p>\n<hr \/>\n<h2>3. Web Fetch: read the document you have given<\/h2>\n<h3>What it does<\/h3>\n<p>Where web search discovers, web fetch reads. Point it at a URL and it returns full page text or, for PDFs, base64 document content that is processed exactly like a directly attached PDF.<\/p>\n<p>Versions, again meaningful:<\/p>\n<ul>\n<li><code>web_fetch_20250910<\/code> \u2014 basic fetch<\/li>\n<\/ul>\n<p>Use case: third-party risk assessment at a healthcare system<\/p>\n<p>A hospital network onboards roughly 40 SaaS vendors a quarter. Each triggers a security review: read the vendor&#8217;s trust centre, their subprocessor list, their most recent SOC 2 scope summary, their DPA, and their status page history. An analyst spends two to three hours per vendor reading PDFs.<\/p>\n<pre><code class=\"language-python\">VENDOR_DOCS = [\r\n    \"https:\/\/vendor.example.com\/trust\",\r\n    \"https:\/\/vendor.example.com\/legal\/subprocessors\",\r\n    \"https:\/\/vendor.example.com\/security\/soc2-scope.pdf\",\r\n    \"https:\/\/vendor.example.com\/legal\/dpa.pdf\",\r\n]\r\n\r\nresponse = client.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=8192,\r\n    system=(\r\n        \"You are a third-party risk analyst for a healthcare system subject to \"\r\n        \"HIPAA. Assess each vendor against: data residency, subprocessor \"\r\n        \"disclosure, breach notification SLA, encryption at rest and in transit, \"\r\n        \"BAA availability, and SOC 2 scope coverage. Cite the source for every \"\r\n        \"finding. If a control is not addressed in the documents, say so \"\r\n        \"explicitly rather than inferring.\"\r\n    ),\r\n    messages=[{\r\n        \"role\": \"user\",\r\n        \"content\": \"Assess this vendor:\\n\" + \"\\n\".join(VENDOR_DOCS),\r\n    }],\r\n    tools=[{\r\n        \"type\": \"web_fetch_20260318\",\r\n        \"name\": \"web_fetch\",\r\n        \"max_uses\": 8,\r\n        \"allowed_domains\": [\"vendor.example.com\"],\r\n        \"citations\": {\"enabled\": True},\r\n        \"max_content_tokens\": 60000,\r\n    }],\r\n)<\/code><\/pre>\n<pre><code class=\"language-typescript\">const response = await client.messages.create({\r\n  model: \"claude-opus-5\",\r\n  max_tokens: 8192,\r\n  system: \"You are a third-party risk analyst for a healthcare system ...\",\r\n  messages: [{ role: \"user\", content: `Assess this vendor:\\n${VENDOR_DOCS.join(\"\\n\")}` }],\r\n  tools: [{\r\n    type: \"web_fetch_20260318\",\r\n    name: \"web_fetch\",\r\n    max_uses: 8,\r\n    allowed_domains: [\"vendor.example.com\"],\r\n    citations: { enabled: true },\r\n    max_content_tokens: 60000,\r\n  }],\r\n});<\/code><\/pre>\n<p>Three parameters are doing real work here. <code>citations: { enabled: true }<\/code> \u2014 unlike web search, citations are <strong>off by default<\/strong> for fetch, and for a risk assessment that is exactly backwards, so turn them on. <code>allowed_domains<\/code> prevents the model from wandering off to a marketing blog. <code>max_content_tokens<\/code> truncates oversized text content before it enters context \u2014 with one important caveat covered below.<\/p>\n<p>Budget accordingly: an average 10 kB web page is roughly 2,500 tokens, a 100 kB documentation page roughly 25,000, and a 500 kB research-paper PDF roughly 125,000. Four documents of that size will consume a serious fraction of your context window in a single turn.<\/p>\n<h3>Combining search and fetch<\/h3>\n<p>The highest-leverage pattern in this release is enabling both tools together. When a user names a specific document without giving a URL \u2014 &#8220;read the README from the anthropics\/anthropic-sdk-python repo,&#8221; &#8220;pull up the vendor&#8217;s latest DPA&#8221; \u2014 Claude uses search to locate it, then fetch to read it in full:<\/p>\n<pre><code class=\"language-python\">response = client.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=8192,\r\n    messages=[{\r\n        \"role\": \"user\",\r\n        \"content\": (\r\n            \"Find the three most recent independent analyses of hospital \"\r\n            \"ransomware incidents in 2026 and give me a detailed comparison \"\r\n            \"of the attack vectors described.\"\r\n        ),\r\n    }],\r\n    tools=[\r\n        {\"type\": \"web_search_20260318\", \"name\": \"web_search\", \"max_uses\": 5},\r\n        {\r\n            \"type\": \"web_fetch_20260318\",\r\n            \"name\": \"web_fetch\",\r\n            \"max_uses\": 5,\r\n            \"citations\": {\"enabled\": True},\r\n            \"max_content_tokens\": 50000,\r\n        },\r\n    ],\r\n)<\/code><\/pre>\n<p>Claude searches, picks the most promising results, fetches them in full, and analyses with citations. Search gives you breadth cheaply; fetch gives you depth on the handful of sources that matter.<\/p>\n<hr \/>\n<h2>4. MCP Connector: your systems of record, without an MCP client<\/h2>\n<h3>What it does<\/h3>\n<p>Model Context Protocol has become the de facto standard for exposing enterprise systems to models. MCP connector lets you point the Messages API at remote MCP servers directly \u2014 no client implementation, no session management, no tool-schema translation layer. The service performs the connection and the tool calls on your behalf.<\/p>\n<p>The API has two halves: <code>mcp_servers<\/code> defines connections, and an <code>mcp_toolset<\/code> entry in <em>tools<\/em> defines which of that server&#8217;s tools are enabled and how.<\/p>\n<pre><code>response = client.beta.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=4096,\r\n    messages=[{\"role\": \"user\", \"content\": \"What's blocking the payments release?\"}],\r\n    mcp_servers=[{\r\n        \"type\": \"url\",\r\n        \"url\": \"https:\/\/mcp.contoso.com\/jira\/sse\",\r\n        \"name\": \"jira\",\r\n        \"authorization_token\": jira_oauth_token,\r\n    }],\r\n    tools=[{\"type\": \"mcp_toolset\", \"mcp_server_name\": \"jira\"}],\r\n    betas=[\"mcp-client-2025-11-20\"],\r\n)<\/code><\/pre>\n<p>Note <code>client.beta.messages.create<\/code> and the <em>betas<\/em> header \u2014 the MCP connector is in beta on Foundry, as it is on the Claude API.<\/p>\n<h3>Use case: an internal IT support agent<\/h3>\n<p>A manufacturer&#8217;s IT service desk handles 12,000 tickets a month. Roughly 40% are resolvable through a fixed sequence: look up the user in the directory, check their device compliance state, search the knowledge base, and either apply a known fix or escalate with context attached. The team already runs MCP servers for ServiceNow, Intune, and their Confluence knowledge base \u2014 built for their internal Claude Code deployment.<\/p>\n<p>MCP connector lets the same servers back a customer-facing agent with no new integration work. The interesting part is the tool governance:<\/p>\n<pre><code class=\"language-python\">response = client.beta.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=4096,\r\n    system=(\r\n        \"You are an IT support agent. Diagnose using read-only tools first. \"\r\n        \"Never make a change without stating what you are about to do.\"\r\n    ),\r\n    messages=[{\"role\": \"user\", \"content\": user_ticket}],\r\n    mcp_servers=[\r\n        {\"type\": \"url\", \"url\": \"https:\/\/mcp.contoso.com\/servicenow\/sse\",\r\n         \"name\": \"servicenow\", \"authorization_token\": snow_token},\r\n        {\"type\": \"url\", \"url\": \"https:\/\/mcp.contoso.com\/intune\/sse\",\r\n         \"name\": \"intune\", \"authorization_token\": intune_token},\r\n        {\"type\": \"url\", \"url\": \"https:\/\/mcp.contoso.com\/confluence\/sse\",\r\n         \"name\": \"kb\", \"authorization_token\": kb_token},\r\n    ],\r\n    tools=[\r\n        # ServiceNow: everything except the destructive operations\r\n        {\r\n            \"type\": \"mcp_toolset\",\r\n            \"mcp_server_name\": \"servicenow\",\r\n            \"configs\": {\r\n                \"delete_incident\": {\"enabled\": False},\r\n                \"bulk_close_incidents\": {\"enabled\": False},\r\n                \"modify_sla\": {\"enabled\": False},\r\n            },\r\n        },\r\n        # Intune: strict allowlist, read-only\r\n        {\r\n            \"type\": \"mcp_toolset\",\r\n            \"mcp_server_name\": \"intune\",\r\n            \"default_config\": {\"enabled\": False},\r\n            \"configs\": {\r\n                \"get_device_compliance\": {\"enabled\": True},\r\n                \"list_user_devices\": {\"enabled\": True},\r\n            },\r\n        },\r\n        # Knowledge base: large, rarely all needed at once\r\n        {\r\n            \"type\": \"mcp_toolset\",\r\n            \"mcp_server_name\": \"kb\",\r\n            \"default_config\": {\"defer_loading\": True},\r\n        },\r\n    ],\r\n    betas=[\"mcp-client-2025-11-20\"],\r\n)<\/code><\/pre>\n<pre><code class=\"language-typescript\">const response = await client.beta.messages.create({\r\n  model: \"claude-opus-5\",\r\n  max_tokens: 4096,\r\n  system: \"You are an IT support agent. Diagnose using read-only tools first. ...\",\r\n  messages: [{ role: \"user\", content: userTicket }],\r\n  mcp_servers: [\r\n    { type: \"url\", url: \"https:\/\/mcp.contoso.com\/intune\/sse\", name: \"intune\",\r\n      authorization_token: intuneToken },\r\n  ],\r\n  tools: [{\r\n    type: \"mcp_toolset\",\r\n    mcp_server_name: \"intune\",\r\n    default_config: { enabled: false },\r\n    configs: {\r\n      get_device_compliance: { enabled: true },\r\n      list_user_devices: { enabled: true },\r\n    },\r\n  }],\r\n  betas: [\"mcp-client-2025-11-20\"],\r\n});<\/code><\/pre>\n<p>Three patterns are worth naming because they map directly onto how enterprises actually govern agents:<\/p>\n<ul>\n<li><strong>Denylist<\/strong> \u2014 enable everything, disable the destructive operations. Good default when the server is trusted and broad capability is useful.<\/li>\n<li><strong>Allowlist<\/strong> \u2014 <code>default_config: {enabled: false}<\/code>, then enable named tools. The right posture for anything touching identity, endpoints, or money. This is how you build a genuinely read-only agent.<\/li>\n<li><strong>Deferred<\/strong> \u2014 <code>default_config: {defer_loading: true}<\/code>, which hands the server&#8217;s tools to tool search rather than loading them into context. Covered in the next section.<\/li>\n<\/ul>\n<p>Configuration merges with precedence: per-tool <em>configs<\/em> beats set-level <code>default_config<\/code> beats system defaults.<\/p>\n<h3>Response blocks<\/h3>\n<p>MCP tool calls appear as <code>mcp_tool_use<\/code> and <code>mcp_tool_result<\/code> blocks, with <code>server_name<\/code> identifying the source \u2014 useful for per-server audit logging:<\/p>\n<pre><code>for block in response.content:\r\n    if block.type == \"mcp_tool_use\":\r\n        audit_log.record(server=block.server_name, tool=block.name, args=block.input)<\/code><\/pre>\n<hr \/>\n<h2>5. Tool Search: scaling past the point where agents get confused<\/h2>\n<h3>The problem<\/h3>\n<p>Two failure modes appear at the same threshold, and both are unintuitive to teams whose agent works fine with eight tools.<\/p>\n<p><strong>Context bloat.<\/strong> A modest multi-server setup \u2014 GitHub, Slack, Sentry, Grafana, Splunk \u2014 consumes roughly <strong>55,000 tokens in tool definitions before the model does any work<\/strong>. That is context you paid for and cannot use, on every single turn.<\/p>\n<p><strong>Selection accuracy collapse.<\/strong> Claude&#8217;s ability to pick the right tool degrades once you exceed roughly <strong>30\u201350 available tools<\/strong>. Not gracefully. The agent starts calling <code>search_issues<\/code> when it wanted <code>search_pull_requests<\/code>, and your evals get noisy in a way that looks like a prompting problem but is not.<\/p>\n<h3>What it does<\/h3>\n<p>Tool search inverts the loading model. Instead of every definition entering context up front, Claude searches your catalogue and loads only what it needs \u2014 typically 3\u20135 tools per request, cutting definition tokens by <strong>over 85%<\/strong>. Because the working set stays small, selection accuracy stays high across thousands of tools.<\/p>\n<p>Two variants:<\/p>\n<ul>\n<li><code>tool_search_tool_regex_20251119<\/code> \u2014 Claude writes Python <code>re.search()<\/code> patterns (max 200 characters, case-insensitive)<\/li>\n<li><code>tool_search_tool_bm25_20251119<\/code> \u2014 Claude writes natural-language queries (max 500 characters)<\/li>\n<\/ul>\n<p>Both search tool names, descriptions, argument names, <em>and<\/em> argument descriptions.<\/p>\n<pre><code class=\"language-python\">response = client.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=4096,\r\n    messages=[{\"role\": \"user\", \"content\": \"Open a Sev-2 for the checkout latency spike and page the on-call.\"}],\r\n    tools=[\r\n        {\"type\": \"tool_search_tool_regex_20251119\", \"name\": \"tool_search_tool_regex\"},\r\n        # ~3-5 hot tools stay loaded\r\n        {\"name\": \"search_incidents\", \"description\": \"...\", \"input_schema\": {...}},\r\n        # everything else is deferred\r\n        {\"name\": \"create_incident\", \"description\": \"...\", \"input_schema\": {...},\r\n         \"defer_loading\": True},\r\n        {\"name\": \"page_oncall\", \"description\": \"...\", \"input_schema\": {...},\r\n         \"defer_loading\": True},\r\n        # ... 400 more\r\n    ],\r\n)<\/code><\/pre>\n<p>The mental model that trips people up: <strong><code>defer_loading<\/code><\/strong> <strong>controls what enters the context window, not what you send.<\/strong> You still transmit every tool definition in the <em>tools<\/em> array on every request \u2014 the API needs them server-side to run the search and expand <code>tool_reference<\/code> blocks. At least one tool must remain non-deferred; normally that is the tool search tool itself. Never set <code>defer_loading: true<\/code> on the tool search tool, and note that deferring <em>every<\/em> tool returns a 400: <code>At least one tool must have defer_loading=false.<\/code><\/p>\n<h3>Use case: a field-service agent over 600 tools<\/h3>\n<p>An industrial equipment manufacturer runs a field-service agent for 3,000 technicians. It spans nine MCP servers \u2014 parts inventory, warranty, CRM, scheduling, telematics, shipping, billing, a document store, and a diagnostics service \u2014 for a combined 600-plus tools. Loaded eagerly, tool definitions alone consumed most of a 200k context window and the agent&#8217;s tool selection was unreliable enough that the pilot nearly died.<\/p>\n<p>With MCP servers, you do not set <code>defer_loading<\/code> on individual tool definitions. You set it once on the toolset:<\/p>\n<pre><code class=\"language-python\">response = client.beta.messages.create(\r\n    model=\"claude-opus-5\",\r\n    max_tokens=8192,\r\n    system=(\r\n        \"You support field technicians. You can search for tools covering parts \"\r\n        \"inventory, warranty claims, customer records, scheduling, telematics, \"\r\n        \"shipping, billing, service documentation, and diagnostics.\"\r\n    ),\r\n    messages=[{\"role\": \"user\", \"content\": (\r\n        \"Unit SN-44812 is throwing a hydraulic pressure fault. Check whether \"\r\n        \"it's under warranty, find the replacement seal kit, and see if we can \"\r\n        \"get it on site by Thursday.\"\r\n    )}],\r\n    mcp_servers=[\r\n        {\"type\": \"url\", \"url\": \"https:\/\/mcp.contoso.com\/parts\/sse\",\r\n         \"name\": \"parts\", \"authorization_token\": parts_token},\r\n        {\"type\": \"url\", \"url\": \"https:\/\/mcp.contoso.com\/warranty\/sse\",\r\n         \"name\": \"warranty\", \"authorization_token\": warranty_token},\r\n        {\"type\": \"url\", \"url\": \"https:\/\/mcp.contoso.com\/logistics\/sse\",\r\n         \"name\": \"logistics\", \"authorization_token\": logistics_token},\r\n        # ... six more\r\n    ],\r\n    tools=[\r\n        {\"type\": \"tool_search_tool_bm25_20251119\", \"name\": \"tool_search_tool_bm25\"},\r\n        {\"type\": \"mcp_toolset\", \"mcp_server_name\": \"parts\",\r\n         \"default_config\": {\"defer_loading\": True},\r\n         \"configs\": {\"search_parts\": {\"defer_loading\": False}}},\r\n        {\"type\": \"mcp_toolset\", \"mcp_server_name\": \"warranty\",\r\n         \"default_config\": {\"defer_loading\": True}},\r\n        {\"type\": \"mcp_toolset\", \"mcp_server_name\": \"logistics\",\r\n         \"default_config\": {\"defer_loading\": True}},\r\n    ],\r\n    betas=[\"mcp-client-2025-11-20\"],\r\n)<\/code><\/pre>\n<p><code>search_parts<\/code> is the single most-used tool in the system, so it stays hot with <code>defer_loading: false<\/code> while the rest of the parts server defers. That is the pattern: keep your 3\u20135 highest-frequency tools loaded, defer the long tail.<\/p>\n<p>Note the system prompt. Telling the model what <em>categories<\/em> of tools exist measurably improves search quality \u2014 it cannot search for capabilities it does not know to look for.<\/p>\n<h3>The response flow<\/h3>\n<pre><code class=\"language-json\">{\r\n  \"type\": \"server_tool_use\",\r\n  \"id\": \"srvtoolu_01ABC123\",\r\n  \"name\": \"tool_search_tool_bm25\",\r\n  \"input\": { \"query\": \"warranty coverage lookup by serial number\", \"limit\": 10 }\r\n}<\/code><\/pre>\n<p>followed by<\/p>\n<pre><code class=\"language-json\">{\r\n  \"type\": \"tool_search_tool_result\",\r\n  \"tool_use_id\": \"srvtoolu_01ABC123\",\r\n  \"content\": {\r\n    \"type\": \"tool_search_tool_search_result\",\r\n    \"tool_references\": [{ \"type\": \"tool_reference\", \"tool_name\": \"get_warranty_status\" }]\r\n  }\r\n}<\/code><\/pre>\n<p>The API expands <code>tool_reference<\/code> blocks into full definitions before Claude sees them. You never expand them yourself. Never return a <code>tool_result<\/code> for the <code>srvtoolu_...<\/code> ID \u2014 the API rejects it. Pass the assistant&#8217;s content back unchanged on the next turn, along with the same full <em>tools<\/em> array, and Claude can reuse discovered tools in later turns without searching again.<\/p>\n<hr \/>\n<h2>Operational checklist<\/h2>\n<p>Before you ship any of this to production on Foundry:<\/p>\n<ul>\n<li><strong>Deployment.<\/strong> Everything in this post works on both hosting options. Choose Hosted on Azure if your workload needs prompts and completions to remain within Azure, or US Data Zone Standard to keep inference within the United States. Choose Hosted on Anthropic if you need a model that is not yet hosted on Azure.<\/li>\n<li><strong>Auth.<\/strong> Use Entra ID with Azure RBAC rather than API keys. Tokens expire after about an hour \u2014 refresh them.<\/li>\n<li><strong>Cost controls.<\/strong> <code>max_uses<\/code> on search and fetch. <code>max_content_tokens<\/code> on fetch. <code>defer_loading<\/code> on large toolsets. Budget web search at $10 per 1,000 searches; fetch and tool search add no per-call charge. All of it bills as Claude Consumption Units through Azure Marketplace, metered hourly and invoiced monthly in arrears.<\/li>\n<li><strong>Data handling.<\/strong> Structured outputs are ZDR-processed but schemas are cached 24 hours \u2014 no PHI in schemas. MCP connector&#8217;s server exchange is not covered by ZDR. Get both reviewed.<\/li>\n<li><strong>Security.<\/strong> Treat <code>allowed_domains<\/code> on web fetch as a security control against prompt injection. Use allowlist-style <code>mcp_toolset<\/code> configs for anything touching identity, endpoints, or funds. Verify denylists in CI, because unknown tool names warn rather than error.<\/li>\n<li><strong>Resilience.<\/strong> Handle <code>pause_turn<\/code> on search. Echo <code>encrypted_content<\/code> byte-for-byte. Check <code>stop_reason<\/code> before parsing structured output. Implement exponential backoff \u2014 Foundry does not surface Anthropic&#8217;s rate-limit headers.<\/li>\n<li><strong>Observability.<\/strong> Log <code>request-id<\/code> and <em>apim-request-id<\/em>. Route to Azure Monitor and Log Analytics; Anthropic recommends at least a 30-day rolling retention. Track which tools tool search discovers and iterate on descriptions.<\/li>\n<li><strong>Not available on Foundry.<\/strong> Message Batches API, Admin API, Models API, Compliance API, Claude Managed Agents, server-side fallback, and the Advisor tool. Plan around them.<\/li>\n<\/ul>\n<hr \/>\n<h2>Where to start<\/h2>\n<p>If you are picking one thing to try this week, pick the one that matches the pain you already have.<\/p>\n<p>Data pipeline with a retry loop and a dead-letter queue? <strong>Structured outputs.<\/strong> It is the smallest change with the most immediate reliability win \u2014 a schema and one parameter.<\/p>\n<p>Analysts manually reading source documents? <strong>Web search plus web fetch<\/strong>, domain-restricted, citations on.<\/p>\n<p>An MCP server already running for your internal Claude Code deployment? <strong>MCP connector.<\/strong> The integration work is done; you are pointing a new consumer at it.<\/p>\n<p>An agent that works in demos and gets confused in production? Count your tools. Past 30, it is <strong>tool search<\/strong>, not your prompt.<\/p>\n<p>The through-line is that the platform now owns the scaffolding. What is left for you to build is the part that is actually your business.<\/p>\n<hr \/>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/build-with-claude\/structured-outputs\">Structured outputs \u2014 Claude Platform Docs<\/a><\/li>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/tool-use\/web-search-tool\">Web search tool \u2014 Claude Platform Docs<\/a><\/li>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/tool-use\/web-fetch-tool\">Web fetch tool \u2014 Claude Platform Docs<\/a><\/li>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/tool-use\/tool-search-tool\">Tool search tool \u2014 Claude Platform Docs<\/a><\/li>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/mcp-connector\">MCP connector \u2014 Claude Platform Docs<\/a><\/li>\n<li><a href=\"https:\/\/platform.claude.com\/docs\/en\/build-with-claude\/claude-in-microsoft-foundry\">Claude in Microsoft Foundry \u2014 Claude Platform Docs<\/a><\/li>\n<li><a href=\"https:\/\/ai.azure.com\/catalog\/publishers\/anthropic\">Foundry model catalog<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Structured outputs, web search, web fetch, MCP connector, and tool search are now available for Claude models hosted on Azure in Microsoft Foundry, turning a model endpoint into a production agent platform.<\/p>\n","protected":false},"author":218995,"featured_media":2849,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25,32,99,31,7,33,9],"class_list":["post-2848","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-foundry","tag-agents","tag-ai","tag-anthropic","tag-azure","tag-claude","tag-foundry","tag-mcp"],"acf":[],"blog_post_summary":"<p>Structured outputs, web search, web fetch, MCP connector, and tool search are now available for Claude models hosted on Azure in Microsoft Foundry, turning a model endpoint into a production agent platform.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts\/2848","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/users\/218995"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/comments?post=2848"}],"version-history":[{"count":2,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts\/2848\/revisions"}],"predecessor-version":[{"id":2851,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/posts\/2848\/revisions\/2851"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/media\/2849"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/media?parent=2848"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/categories?post=2848"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/foundry\/wp-json\/wp\/v2\/tags?post=2848"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}