August 17th, 2026
0 reactions

From single call to agents: five new Claude capabilities now available in Microsoft Foundry

Product Manager

From single call to agents: five new Claude capabilities now available in Microsoft Foundry

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.


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 procurement and governance problem. Teams could finally run Claude inside the same subscription, network perimeter, and cost-management surface as the rest of their Azure estate.

But access to a model is not the same as a platform for agents. In the weeks since the generally available launch, we’ve 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.

  1. A retry loop that re-prompts the model because it returned JSON with a trailing comma.
  2. A bespoke search-and-scrape service, with its own crawler, cache, robots.txt handling, and citation plumbing.
  3. A hand-rolled MCP client so the model can reach Jira, ServiceNow, Confluence, and three internal APIs.
  4. A tool router, because once you wire up 300 tools the model starts picking the wrong one.

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.

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.


The part that changes the architecture: these now run hosted on Azure

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.

That trade-off is now resolved. Structured outputs, Web search, Web fetch, MCP connector, and Tool search are available on deployments hosted on Azure.

Hosted on Azure Hosted on Anthropic
Where inference runs Anthropic-operated service on Azure infrastructure Anthropic-operated service on Anthropic infrastructure
Model availability Latest Opus, Sonnet, and Haiku models The full Claude catalogue on Foundry
Deployment types Global Standard, US Data Zone Standard Global Standard
The five features in this post
Recommended for Most workloads Access to models not yet hosted on Azure

For deployments hosted on Azure, prompts and completions remain within Azure; only usage metadata and content flagged by Anthropic’s safety systems egress to Anthropic. Anthropic acts as an independent processor for Microsoft, and customers using Claude through Foundry are subject to Anthropic’s data use terms.

The practical consequence for regulated industries is significant. A US Data Zone Standard deployment keeps inference within the United States, equivalent to setting inference_geo: "us" 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.

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&utm_medium=webinar&utm_campaign=msft-promotion]


1. Structured Outputs: the end of JSON.parse() roulette

The problem

Every team that has put an LLM in a data pipeline has written this code:

for attempt in range(3):
    raw = call_model(prompt)
    try:
        data = json.loads(raw)
        validate(data)
        break
    except (json.JSONDecodeError, ValidationError):
        prompt += "\n\nYour last response was invalid JSON. Try again."

It works most of the time. “Most of the time” 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.

What it does

Structured outputs constrain generation itself. The model’s decoding is restricted by a grammar compiled from your JSON Schema, so the output cannot be malformed. Two complementary features, usable independently or together:

  • JSON outputs (output_config.format) — controls the shape of Claude’s response text.
  • Strict tool use (strict: true on a tool) — guarantees schema-valid tool inputs.

The first governs what Claude says. The second governs how Claude calls your functions.

Use case: claims intake at a specialty insurer

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.

With structured outputs, the extraction contract is the schema:

from pydantic import BaseModel
from typing import Literal
from anthropic import AnthropicFoundry

class ClaimIntake(BaseModel):
    policy_number: str
    claimant_name: str
    loss_date: str                       # ISO 8601
    loss_type: Literal[
        "property_damage", "bodily_injury", "business_interruption",
        "auto_liability", "other",
    ]
    estimated_severity_usd: float
    third_party_involved: bool
    injuries_reported: bool
    summary: str
    escalate_to_adjuster: bool

client = AnthropicFoundry(resource="contoso-ai")

response = client.messages.parse(
    model="claude-opus-5",
    max_tokens=2048,
    system=(
        "You are a claims intake analyst. Extract only what is stated or "
        "clearly implied in the submission. If severity is not stated, "
        "estimate conservatively from comparable losses."
    ),
    messages=[{"role": "user", "content": submission_text}],
    output_format=ClaimIntake,
)

claim = response.parsed_output      # a ClaimIntake instance, already validated
if claim.escalate_to_adjuster:
    enqueue_for_adjuster(claim)

The TypeScript equivalent, using Zod:

import { z } from "zod";
import AnthropicFoundry from "@anthropic-ai/foundry-sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";

const ClaimIntake = z.object({
  policy_number: z.string(),
  claimant_name: z.string(),
  loss_date: z.string(),
  loss_type: z.enum([
    "property_damage", "bodily_injury", "business_interruption",
    "auto_liability", "other",
  ]),
  estimated_severity_usd: z.number(),
  third_party_involved: z.boolean(),
  injuries_reported: z.boolean(),
  summary: z.string(),
  escalate_to_adjuster: z.boolean(),
});

const client = new AnthropicFoundry({ resource: "contoso-ai" });

const response = await client.messages.parse({
  model: "claude-opus-5",
  max_tokens: 2048,
  messages: [{ role: "user", content: submissionText }],
  output_config: { format: zodOutputFormat(ClaimIntake) },
});

2. Web Search: current information, with citations, without a crawler

What it does

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.

Version available is web_search_20250305 — basic search

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "What's the current state of the EU AI Act's GPAI obligations?"}],
    tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}],
)

Dynamic filtering: the token economics change

With basic search, every result loads into the context window in full — 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.

With web_search_20260209 and later, Claude instead writes and runs code that filters results before they reach context, keeping only relevant content. Mechanically, search runs from inside the code execution tool: on these versions allowed_callers defaults to ["code_execution_20260120"], and Foundry provisions the code execution the request needs automatically. You do not add code execution to your tools array, and there is no extra charge for those calls beyond standard token costs.

To force direct calls without dynamic filtering, set allowed_callers: ["direct"]. Models that do not support programmatic tool calling require this; without it you get a 400 telling you so.

Use case: regulatory change monitoring at a global bank

A Tier 1 bank’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.

The rebuild is a nightly Azure Container Apps job. The critical design choice is not the prompt — it is allowed_domains. Regulatory monitoring is exactly the case where you cannot afford a secondary source paraphrasing a rule incorrectly:

REGULATOR_DOMAINS = [
    "eba.europa.eu", "esma.europa.eu", "eur-lex.europa.eu",
    "federalreserve.gov", "sec.gov", "occ.gov",
    "bankofengland.co.uk", "fca.org.uk",
    "mas.gov.sg", "apra.gov.au",
]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=8192,
    system=(
        "You monitor prudential and conduct regulation for a global bank. "
        "Report only changes published in the last 7 days. For each change, "
        "state the regulator, the instrument, the effective date, and the "
        "business lines affected. Do not speculate beyond the source text."
    ),
    messages=[{"role": "user", "content": "What changed this week in capital and liquidity rules?"}],
    tools=[{
        "type": "web_search_20260318",
        "name": "web_search",
        "max_uses": 12,
        "allowed_domains": REGULATOR_DOMAINS,
        "user_location": {
            "type": "approximate",
            "city": "London",
            "region": "England",
            "country": "GB",
            "timezone": "Europe/London",
        },
    }],
)
const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 8192,
  system: "You monitor prudential and conduct regulation for a global bank. ...",
  messages: [{ role: "user", content: "What changed this week in capital and liquidity rules?" }],
  tools: [{
    type: "web_search_20260318",
    name: "web_search",
    max_uses: 12,
    allowed_domains: REGULATOR_DOMAINS,
    user_location: {
      type: "approximate", city: "London", region: "England",
      country: "GB", timezone: "Europe/London",
    },
  }],
});

allowed_domains and blocked_domains are mutually exclusive — send both and you get a 400. Entries are bare domains with an optional path (example.com, example.com/blog), no scheme.


3. Web Fetch: read the document you have given

What it does

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.

Versions, again meaningful:

  • web_fetch_20250910 — basic fetch

Use case: third-party risk assessment at a healthcare system

A hospital network onboards roughly 40 SaaS vendors a quarter. Each triggers a security review: read the vendor’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.

VENDOR_DOCS = [
    "https://vendor.example.com/trust",
    "https://vendor.example.com/legal/subprocessors",
    "https://vendor.example.com/security/soc2-scope.pdf",
    "https://vendor.example.com/legal/dpa.pdf",
]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=8192,
    system=(
        "You are a third-party risk analyst for a healthcare system subject to "
        "HIPAA. Assess each vendor against: data residency, subprocessor "
        "disclosure, breach notification SLA, encryption at rest and in transit, "
        "BAA availability, and SOC 2 scope coverage. Cite the source for every "
        "finding. If a control is not addressed in the documents, say so "
        "explicitly rather than inferring."
    ),
    messages=[{
        "role": "user",
        "content": "Assess this vendor:\n" + "\n".join(VENDOR_DOCS),
    }],
    tools=[{
        "type": "web_fetch_20260318",
        "name": "web_fetch",
        "max_uses": 8,
        "allowed_domains": ["vendor.example.com"],
        "citations": {"enabled": True},
        "max_content_tokens": 60000,
    }],
)
const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 8192,
  system: "You are a third-party risk analyst for a healthcare system ...",
  messages: [{ role: "user", content: `Assess this vendor:\n${VENDOR_DOCS.join("\n")}` }],
  tools: [{
    type: "web_fetch_20260318",
    name: "web_fetch",
    max_uses: 8,
    allowed_domains: ["vendor.example.com"],
    citations: { enabled: true },
    max_content_tokens: 60000,
  }],
});

Three parameters are doing real work here. citations: { enabled: true } — unlike web search, citations are off by default for fetch, and for a risk assessment that is exactly backwards, so turn them on. allowed_domains prevents the model from wandering off to a marketing blog. max_content_tokens truncates oversized text content before it enters context — with one important caveat covered below.

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.

Combining search and fetch

The highest-leverage pattern in this release is enabling both tools together. When a user names a specific document without giving a URL — “read the README from the anthropics/anthropic-sdk-python repo,” “pull up the vendor’s latest DPA” — Claude uses search to locate it, then fetch to read it in full:

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=8192,
    messages=[{
        "role": "user",
        "content": (
            "Find the three most recent independent analyses of hospital "
            "ransomware incidents in 2026 and give me a detailed comparison "
            "of the attack vectors described."
        ),
    }],
    tools=[
        {"type": "web_search_20260318", "name": "web_search", "max_uses": 5},
        {
            "type": "web_fetch_20260318",
            "name": "web_fetch",
            "max_uses": 5,
            "citations": {"enabled": True},
            "max_content_tokens": 50000,
        },
    ],
)

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.


4. MCP Connector: your systems of record, without an MCP client

What it does

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 — no client implementation, no session management, no tool-schema translation layer. The service performs the connection and the tool calls on your behalf.

The API has two halves: mcp_servers defines connections, and an mcp_toolset entry in tools defines which of that server’s tools are enabled and how.

response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "What's blocking the payments release?"}],
    mcp_servers=[{
        "type": "url",
        "url": "https://mcp.contoso.com/jira/sse",
        "name": "jira",
        "authorization_token": jira_oauth_token,
    }],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "jira"}],
    betas=["mcp-client-2025-11-20"],
)

Note client.beta.messages.create and the betas header — the MCP connector is in beta on Foundry, as it is on the Claude API.

Use case: an internal IT support agent

A manufacturer’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 — built for their internal Claude Code deployment.

MCP connector lets the same servers back a customer-facing agent with no new integration work. The interesting part is the tool governance:

response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=4096,
    system=(
        "You are an IT support agent. Diagnose using read-only tools first. "
        "Never make a change without stating what you are about to do."
    ),
    messages=[{"role": "user", "content": user_ticket}],
    mcp_servers=[
        {"type": "url", "url": "https://mcp.contoso.com/servicenow/sse",
         "name": "servicenow", "authorization_token": snow_token},
        {"type": "url", "url": "https://mcp.contoso.com/intune/sse",
         "name": "intune", "authorization_token": intune_token},
        {"type": "url", "url": "https://mcp.contoso.com/confluence/sse",
         "name": "kb", "authorization_token": kb_token},
    ],
    tools=[
        # ServiceNow: everything except the destructive operations
        {
            "type": "mcp_toolset",
            "mcp_server_name": "servicenow",
            "configs": {
                "delete_incident": {"enabled": False},
                "bulk_close_incidents": {"enabled": False},
                "modify_sla": {"enabled": False},
            },
        },
        # Intune: strict allowlist, read-only
        {
            "type": "mcp_toolset",
            "mcp_server_name": "intune",
            "default_config": {"enabled": False},
            "configs": {
                "get_device_compliance": {"enabled": True},
                "list_user_devices": {"enabled": True},
            },
        },
        # Knowledge base: large, rarely all needed at once
        {
            "type": "mcp_toolset",
            "mcp_server_name": "kb",
            "default_config": {"defer_loading": True},
        },
    ],
    betas=["mcp-client-2025-11-20"],
)
const response = await client.beta.messages.create({
  model: "claude-opus-5",
  max_tokens: 4096,
  system: "You are an IT support agent. Diagnose using read-only tools first. ...",
  messages: [{ role: "user", content: userTicket }],
  mcp_servers: [
    { type: "url", url: "https://mcp.contoso.com/intune/sse", name: "intune",
      authorization_token: intuneToken },
  ],
  tools: [{
    type: "mcp_toolset",
    mcp_server_name: "intune",
    default_config: { enabled: false },
    configs: {
      get_device_compliance: { enabled: true },
      list_user_devices: { enabled: true },
    },
  }],
  betas: ["mcp-client-2025-11-20"],
});

Three patterns are worth naming because they map directly onto how enterprises actually govern agents:

  • Denylist — enable everything, disable the destructive operations. Good default when the server is trusted and broad capability is useful.
  • Allowlistdefault_config: {enabled: false}, then enable named tools. The right posture for anything touching identity, endpoints, or money. This is how you build a genuinely read-only agent.
  • Deferreddefault_config: {defer_loading: true}, which hands the server’s tools to tool search rather than loading them into context. Covered in the next section.

Configuration merges with precedence: per-tool configs beats set-level default_config beats system defaults.

Response blocks

MCP tool calls appear as mcp_tool_use and mcp_tool_result blocks, with server_name identifying the source — useful for per-server audit logging:

for block in response.content:
    if block.type == "mcp_tool_use":
        audit_log.record(server=block.server_name, tool=block.name, args=block.input)

5. Tool Search: scaling past the point where agents get confused

The problem

Two failure modes appear at the same threshold, and both are unintuitive to teams whose agent works fine with eight tools.

Context bloat. A modest multi-server setup — GitHub, Slack, Sentry, Grafana, Splunk — consumes roughly 55,000 tokens in tool definitions before the model does any work. That is context you paid for and cannot use, on every single turn.

Selection accuracy collapse. Claude’s ability to pick the right tool degrades once you exceed roughly 30–50 available tools. Not gracefully. The agent starts calling search_issues when it wanted search_pull_requests, and your evals get noisy in a way that looks like a prompting problem but is not.

What it does

Tool search inverts the loading model. Instead of every definition entering context up front, Claude searches your catalogue and loads only what it needs — typically 3–5 tools per request, cutting definition tokens by over 85%. Because the working set stays small, selection accuracy stays high across thousands of tools.

Two variants:

  • tool_search_tool_regex_20251119 — Claude writes Python re.search() patterns (max 200 characters, case-insensitive)
  • tool_search_tool_bm25_20251119 — Claude writes natural-language queries (max 500 characters)

Both search tool names, descriptions, argument names, and argument descriptions.

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "Open a Sev-2 for the checkout latency spike and page the on-call."}],
    tools=[
        {"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"},
        # ~3-5 hot tools stay loaded
        {"name": "search_incidents", "description": "...", "input_schema": {...}},
        # everything else is deferred
        {"name": "create_incident", "description": "...", "input_schema": {...},
         "defer_loading": True},
        {"name": "page_oncall", "description": "...", "input_schema": {...},
         "defer_loading": True},
        # ... 400 more
    ],
)

The mental model that trips people up: defer_loading controls what enters the context window, not what you send. You still transmit every tool definition in the tools array on every request — the API needs them server-side to run the search and expand tool_reference blocks. At least one tool must remain non-deferred; normally that is the tool search tool itself. Never set defer_loading: true on the tool search tool, and note that deferring every tool returns a 400: At least one tool must have defer_loading=false.

Use case: a field-service agent over 600 tools

An industrial equipment manufacturer runs a field-service agent for 3,000 technicians. It spans nine MCP servers — parts inventory, warranty, CRM, scheduling, telematics, shipping, billing, a document store, and a diagnostics service — for a combined 600-plus tools. Loaded eagerly, tool definitions alone consumed most of a 200k context window and the agent’s tool selection was unreliable enough that the pilot nearly died.

With MCP servers, you do not set defer_loading on individual tool definitions. You set it once on the toolset:

response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=8192,
    system=(
        "You support field technicians. You can search for tools covering parts "
        "inventory, warranty claims, customer records, scheduling, telematics, "
        "shipping, billing, service documentation, and diagnostics."
    ),
    messages=[{"role": "user", "content": (
        "Unit SN-44812 is throwing a hydraulic pressure fault. Check whether "
        "it's under warranty, find the replacement seal kit, and see if we can "
        "get it on site by Thursday."
    )}],
    mcp_servers=[
        {"type": "url", "url": "https://mcp.contoso.com/parts/sse",
         "name": "parts", "authorization_token": parts_token},
        {"type": "url", "url": "https://mcp.contoso.com/warranty/sse",
         "name": "warranty", "authorization_token": warranty_token},
        {"type": "url", "url": "https://mcp.contoso.com/logistics/sse",
         "name": "logistics", "authorization_token": logistics_token},
        # ... six more
    ],
    tools=[
        {"type": "tool_search_tool_bm25_20251119", "name": "tool_search_tool_bm25"},
        {"type": "mcp_toolset", "mcp_server_name": "parts",
         "default_config": {"defer_loading": True},
         "configs": {"search_parts": {"defer_loading": False}}},
        {"type": "mcp_toolset", "mcp_server_name": "warranty",
         "default_config": {"defer_loading": True}},
        {"type": "mcp_toolset", "mcp_server_name": "logistics",
         "default_config": {"defer_loading": True}},
    ],
    betas=["mcp-client-2025-11-20"],
)

search_parts is the single most-used tool in the system, so it stays hot with defer_loading: false while the rest of the parts server defers. That is the pattern: keep your 3–5 highest-frequency tools loaded, defer the long tail.

Note the system prompt. Telling the model what categories of tools exist measurably improves search quality — it cannot search for capabilities it does not know to look for.

The response flow

{
  "type": "server_tool_use",
  "id": "srvtoolu_01ABC123",
  "name": "tool_search_tool_bm25",
  "input": { "query": "warranty coverage lookup by serial number", "limit": 10 }
}

followed by

{
  "type": "tool_search_tool_result",
  "tool_use_id": "srvtoolu_01ABC123",
  "content": {
    "type": "tool_search_tool_search_result",
    "tool_references": [{ "type": "tool_reference", "tool_name": "get_warranty_status" }]
  }
}

The API expands tool_reference blocks into full definitions before Claude sees them. You never expand them yourself. Never return a tool_result for the srvtoolu_... ID — the API rejects it. Pass the assistant’s content back unchanged on the next turn, along with the same full tools array, and Claude can reuse discovered tools in later turns without searching again.


Operational checklist

Before you ship any of this to production on Foundry:

  • Deployment. 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.
  • Auth. Use Entra ID with Azure RBAC rather than API keys. Tokens expire after about an hour — refresh them.
  • Cost controls. max_uses on search and fetch. max_content_tokens on fetch. defer_loading 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.
  • Data handling. Structured outputs are ZDR-processed but schemas are cached 24 hours — no PHI in schemas. MCP connector’s server exchange is not covered by ZDR. Get both reviewed.
  • Security. Treat allowed_domains on web fetch as a security control against prompt injection. Use allowlist-style mcp_toolset configs for anything touching identity, endpoints, or funds. Verify denylists in CI, because unknown tool names warn rather than error.
  • Resilience. Handle pause_turn on search. Echo encrypted_content byte-for-byte. Check stop_reason before parsing structured output. Implement exponential backoff — Foundry does not surface Anthropic’s rate-limit headers.
  • Observability. Log request-id and apim-request-id. 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.
  • Not available on Foundry. Message Batches API, Admin API, Models API, Compliance API, Claude Managed Agents, server-side fallback, and the Advisor tool. Plan around them.

Where to start

If you are picking one thing to try this week, pick the one that matches the pain you already have.

Data pipeline with a retry loop and a dead-letter queue? Structured outputs. It is the smallest change with the most immediate reliability win — a schema and one parameter.

Analysts manually reading source documents? Web search plus web fetch, domain-restricted, citations on.

An MCP server already running for your internal Claude Code deployment? MCP connector. The integration work is done; you are pointing a new consumer at it.

An agent that works in demos and gets confused in production? Count your tools. Past 30, it is tool search, not your prompt.

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.


References

Author

Haoran Cheng
Product Manager

I am a Product Manager in Microsoft Foundry focused on Claude API

0 comments