Generated banner for Microsoft Foundry
Generated logo for Microsoft Foundry

Microsoft Foundry

MicrosoftOfficialCreated by Microsoft

  • Skill
  • Workflow
  • Data and AI
  • MIT
  • Low risk
  • Has scripts

From the author

Build, deploy, evaluate, optimize, fine-tune, and manage Microsoft Foundry agents, models, and resources end to end. USE FOR: foundry, azd ai agent, azd provision/deploy, hosted agent scaffold/develop/run/deploy/troubleshoot, prompt agent, managed harness agent, Copilot harness, voice agent, create/update/invoke/evaluate agent, add tool to agent, agent insights, batch/continuous eval, continuous monitoring, agent CI/CD, optimize/improve prompt, optimize agent instructions, Agent Optimizer, dataset curation from traces, deploy model, model fine-tuning (SFT/DPO/RFT), Foundry project, RBAC, role assignment, permissions, quota, capacity, region, deployment failure, AI Services, create Foundry resource, knowledge index, customize deployment, onboard, availability, training-data, grader, distillation, large file upload. DO NOT USE FOR: Azure Functions, App Service, general Azure deploy (use azure-deploy), general Azure prep (use azure-prepare).
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Microsoft Foundry provides a comprehensive framework for the entire AI agent lifecycle. This skill enables developers to scaffold, provision, deploy, and manage agents while integrating evaluation and optimization workflows. It supports various agent types, including prompt-based, managed harness, and voice agents, and provides tools for CI/CD, continuous monitoring, and fine-tuning models. The skill emphasizes a structured approach using workspace-specific metadata and standard Azure Developer CLI (azd) patterns to ensure consistent environment management and deployment.

What it does

  • Scaffold and create new AI agents with support for various harness types.
  • Manage the full deployment lifecycle including provisioning and CI/CD integration.
  • Execute batch and continuous evaluation workflows to monitor agent quality.
  • Optimize agent prompts and instructions using automated evaluation-driven loops.
  • Fine-tune models using SFT, DPO, or RFT techniques with dataset curation.
  • Troubleshoot agent performance through log analysis, telemetry, and trace inspection.

When to use it

  • When building, deploying, or managing AI agents within the Microsoft Foundry ecosystem.
  • When you need to evaluate agent performance or optimize prompts based on production traces.
  • When setting up CI/CD pipelines for automated agent deployment and monitoring.
  • When fine-tuning models or managing capacity and quotas for AI services.
  • When troubleshooting deployment failures or agent runtime issues.

Advantages

  • Provides a unified, end-to-end workflow for the entire agent development lifecycle.
  • Standardizes project structure and metadata management across different environments.
  • Integrates seamlessly with Azure Developer CLI (azd) for consistent infrastructure management.
  • Enables data-driven improvements through automated evaluation and prompt optimization.
  • Supports advanced model customization including fine-tuning and distillation.

Usage examples

“Create a new hosted Foundry agent from scratch.”
The agent will guide you through the quick-start workflow to scaffold, provision, and deploy a new agent project.
“Optimize the prompt for my existing Python hosted agent.”
The agent will trigger the evaluation-driven optimization workflow to analyze performance and suggest prompt improvements.
“Set up a CI/CD pipeline for my agent.”
The agent will configure the necessary deployment pipeline steps for your Foundry agent project.
“Troubleshoot why my agent deployment is failing.”
The agent will help you inspect logs, telemetry, and quota settings to diagnose and resolve the deployment issue.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/microsoft/microsoft-foundry/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip microsoft-foundry.zip -d .agents/skills

Documentation by Microsoft

Microsoft Foundry Skill

This skill helps developers work with Microsoft Foundry resources, covering model discovery and deployment, complete dev lifecycle of AI agent, evaluation workflows, and troubleshooting.

Pre-Execution Requirements

Follow each applicable subsection below before starting its corresponding action or workflow.

Dependency Check and Setup

MANDATORY: As the first step after this skill loads, run the dependency check and setup script below from this skill's root and wait for it to finish before continuing. The script checks first and installs only missing dependencies; it does not reinstall dependencies that are already available.

You MUST complete this check before reading or entering any sub-skill, workflow, or workflow-specific reference.

./scripts/check-and-setup-dependencies.sh     # macOS / Linux
./scripts/check-and-setup-dependencies.ps1    # Windows (pwsh)

Strictly follow the script output for subsequent actions.

Workflow Guidance

MANDATORY: Before executing ANY workflow-specific steps, you MUST read the corresponding sub-skill document. Do not call workflow-specific MCP tools for a workflow without reading its skill document. This applies even if you already know the MCP tool parameters — the skill document contains required workflow steps, pre-checks, and validation logic that must be followed. This rule applies on every new user message that triggers a different workflow, even if the skill is already loaded.

Foundry MCP

MANDATORY: Before using Foundry MCP operations, call the Azure MCP foundry tool and inspect the available Foundry MCP tools and related parameters. Treat this as the discovery/help step for MCP-based workflows.

azd

MANDATORY: Before executing ANY azd command, you MUST read azd-guidance and strictly follow the shared rules defined in it, especially the AZURE_DEV_USER_AGENT setting rules.

Sub-Skills

This skill includes specialized sub-skills for specific workflows. When a sub-skill matches the task, strictly follow its workflow:

Sub-SkillWhen to UseReference
deployDeploy hosted agents to Foundry, smoke-test a deployment, create or update prompt agents, and manage agent versions and multi-environment deploys.deploy
cicdSet up a CI/CD deployment pipeline for a Foundry agent.cicd
invokeSend messages to an agent, single or multi-turn conversationsinvoke
routineSchedule or event-trigger Foundry agents with routines; use azd for CRUD, enable/disable, manual dispatch, and viewing past runs, or define routines in azure.yaml.routine
invocations-wsBuild, deploy, and connect to custom Hosted agents that use developer-defined duplex real-time or media streams over invocations_ws.invocations-ws
observeEvaluate agent quality, run batch evals, analyze failures, optimize prompts, improve agent instructions, compare versions, set up CI/CD monitoring, and enable continuous production evaluationobserve
insightsPull generated agent insights, evidence, and recommendations from an existing monitor; read-only retrieval, not a new analysis runinsights
traceQuery traces, analyze latency/failures, correlate eval results to specific responses via App Insights customEventstrace
troubleshootView hosted agent logs, query telemetry, diagnose failurestroubleshoot
validateUse only when the user explicitly asks to use this validation sub-skill or to validate Microsoft Foundry hosted-agent code against best practices. Never invoke it proactively or add it to another workflow.validate
create (quick start)Create a new hosted Foundry agent from scratch end-to-end — scaffold, provision or use an existing Foundry project, deploy, and smoke-test. Do not use for any work on existing code. For anything not covered by the quickstart, use create.create/quick-start-hosted.md
createUse when the standard end-to-end happy path (quick start) doesn't fit. Create a new Foundry agent, update code of an existing agent, continue development of an existing agent, wire connections at scaffold time, use advanced setup or A2A (Agent2Agent), or recover from a failed quickstart run.create
create managed harnessCreate or continue developing a Prompt Agent that explicitly uses the Foundry-managed Copilot harness. Uses azd and azure.yaml, not the ordinary Prompt Agent MCP workflow.create-managed-harness
create voiceCreate, configure, or extend a Foundry Voice Agent, defaulting generic requests to managed Prompt Voice and supporting self-deployed Prompt Voice or two-agent Hosted Voice.create-voice
agent-optimizerMake existing Python hosted-agent code optimization-ready, configure eval.yaml, run Agent Optimizer jobs, apply candidates locally, and deploy through azd after review.agent-optimizer
eval-datasetsHarvest production traces into evaluation datasets, manage dataset versions and splits, track evaluation metrics over time, detect regressions, and maintain full lineage from trace to deployment. Use for: create dataset from traces, dataset versioning, evaluation trending, regression detection, dataset comparison, eval lineage.eval-datasets
project/createCreating a new Microsoft Foundry project for hosting agents and models. Use when onboarding to Foundry or setting up new infrastructure.project/create/create-foundry-project.md
resource/createCreating Azure AI Services multi-service resource (Foundry resource) using Azure CLI. Use when manually provisioning AI Services resources with granular control.resource/create/create-foundry-resource.md
private-networkAnswer questions about Foundry network isolation and deploy Foundry with VNet isolation (BYO VNet, Managed VNet, hybrid). Covers architecture concepts, template selection, deployment, and post-deployment validation.resource/private-network/private-network.md
models/deploy-modelUnified model deployment with intelligent routing. Handles quick preset deployments, fully customized deployments (version/SKU/capacity/RAI), and capacity discovery across regions. Routes to sub-skills: preset (quick deploy), customize (full control), capacity (find availability).models/deploy-model/SKILL.md
quotaManaging quotas and capacity for Microsoft Foundry resources. Use when checking quota usage, troubleshooting deployment failures due to insufficient quota, requesting quota increases, or planning capacity.quota/quota.md
rbacManaging RBAC permissions, role assignments, managed identities, and service principals for Microsoft Foundry resources. Use for access control, auditing permissions, and CI/CD setup.rbac/rbac.md
finetuningFine-tune models on Microsoft Foundry — SFT distillation, DPO preference optimization, RFT with graders and tool calling. Dataset preparation, grader calibration, training, checkpoint selection, deployment, evaluation. Use for: fine-tune, SFT, DPO, RFT, training data, grader, distillation, fine-tuned model, large file upload.finetuning/SKILL.md
azd-guidanceProvide shared azd knowledge and guidance for managing Foundry agents. Read this first for any workflows related to azd.azd-guidance

💡 Tip: For a complete onboarding flow: project/create (public) or private-network (VNet isolation) → models/deploy-model → agent workflows (create → deploy → invoke).

💡 Fine-Tuning: Use finetuning for all model customization — SFT distillation, DPO preference optimization, and RFT with graders. Includes quickstart, grader calibration, and training curve analysis.

💡 Model Deployment: Use models/deploy-model for all deployment scenarios — it intelligently routes between quick preset deployment, customized deployment with full control, and capacity discovery across regions.

💡 Prompt Optimization: For requests like "optimize my prompt" or "improve my agent instructions," load observe and use the prompt_optimize MCP tool through that eval-driven workflow.

Infrastructure Lifecycle

Match user intent to the correct infrastructure workflow.

User IntentWorkflow
"Create Foundry" / "Set up Foundry" (ambiguous)Use AskUserQuestion: (a) just an AI Services resource, (b) a project with public access, or (c) a project with network isolation? Route: (a) → resource/create, (b) → project/create, (c) → private-network
Set up Foundry with VNet isolationprivate-network
Create a Foundry project (public)project/create
Create a bare Foundry resourceresource/create

Agent Development Lifecycle

Match user intent to the correct agent workflow. Read each sub-skill in order before executing.

User IntentWorkflow (read in order)
Create or develop a Voice Agent (Prompt Voice or Hosted Voice)dependency check and setup → azd-guidance → create-voice → deploy → invoke
Create or develop an explicitly requested Managed Harness Agent / Copilot harness agentdependency check and setup → azd-guidance → create-managed-harness → deploy → invoke
Create a new hosted agent end-to-end (scaffold + deploy + test)dependency check and setup → azd-guidance → quick-start-hosted (self-contained end-to-end)
Anything beyond the standard quickstart (existing code, migration, re-hosting, deployment customization, scaffold-time connections, A2A (Agent2Agent), recovery)dependency check and setup → azd-guidance → create → deploy → invoke
Optimize existing Python hosted agentdependency check and setup → azd-guidance → agent-optimizer → scaffold/review → eval.yaml → optimize → apply candidate → deploy → invoke
Deploy an agent (code already exists)dependency check and setup → azd-guidance → deploy (includes eval-suite setup) → invoke → observe (evaluate/optimize)
Update/redeploy an agent after code changesdependency check and setup → azd-guidance → deploy (includes eval-suite setup) → invoke → observe (evaluate/optimize)
Set up a CI/CD deployment pipeline for a hosted agentdependency check and setup → azd-guidance → cicd
Invoke/test/chat with an agentdependency check and setup → azd-guidance → invoke
Schedule/event-trigger an agent, or CRUD/enable/disable/dispatch a routinedependency check and setup → azd-guidance → routine
Optimize / improve agent prompt or instructionsobserve (Step 4: Optimize)
Evaluate and optimize agent (full loop)observe
Enable continuous evaluation monitoringobserve (Step 6: CI/CD & Monitoring)
Pull agent insights / list generated issues and recommendationsdependency check and setup → insights (all pages with expanded evidence; read-only)
Troubleshoot an agent issuedependency check and setup → azd-guidance → invoke → troubleshoot
Fix a broken agent (troubleshoot + redeploy)dependency check and setup → azd-guidance → invoke → troubleshoot → apply fixes → deploy → invoke

Agent: .foundry Workspace Standard

Every agent source folder can keep Foundry-specific cache and overlay state under .foundry/:

<agent-root>/
  .foundry/
    agent-metadata.yaml
    agent-metadata.prod.yaml
    suites/
    datasets/
    evaluators/
    results/
  • In azd projects, derive deployment context (project endpoint, agent name/version, ACR, App Insights) from azure.yaml plus azd env get-values; do not duplicate those values in metadata when azd already provides them.

  • agent-metadata.yaml is the preferred local/dev overlay for non-azd values, remote Foundry suite references, local cache paths, result summaries, and explicit overrides. Optional sidecar files such as agent-metadata.prod.yaml can hold a single prod or CI-targeted overlay without mixing multiple environments in one file.

  • suites/, datasets/, and evaluators/ are local cache folders. Reuse them when they are current, and ask before refreshing or overwriting them.

  • See Agent Metadata Contract for the canonical schema and workflow rules.

Agent: Setup References

  • Standard Agent Setup — advanced setup for production workloads that need data-residency control (bring-your-own Cosmos DB / Storage / AI Search via a Foundry capability host). The default azd ai agent flow uses Basic Agent Setup and does not provision capabilityHosts/agents — do not flag its absence as a bug. For default post-provision state, see the "Expected env-var fingerprint" section in foundry-agent/create/create-hosted.md.

Agent: Common Project Context Resolution

Agent skills should run this step only when they need configuration values they don't already have. If a value (for example, agent root, environment, project endpoint, or agent name) is already known from the user's message or a previous skill in the same session, skip resolution for that value.

Step 1: Discover Agent Roots and azd Context

First check whether the workspace has azure.yaml with services using host: azure.ai.agent.

  • One azd agent service -> classify it from kind, harness, modelType, and conversationEngine, then use that service's project folder or the azure.yaml directory as the agent root.

  • Multiple azd agent services -> check whether a Voice wrapper references a kind: hosted service via conversationEngine.name:

    • Selecting the Hosted Voice wrapper implies its named kind: hosted target as an upstream dependency (uses: [<target>], conversationEngine.name: <target>); deploy and verification must report both services separately.

    • Selecting the kind: hosted target alone scopes changes/debugging to the target container and does not automatically modify the wrapper.

    • Otherwise -> require the user to choose the target service/folder.

  • No azd agent service -> search the workspace for .foundry/ folders that contain agent-metadata.yaml or agent-metadata.<env>.yaml.

    • One match -> use that agent root.

    • Multiple matches -> require the user to choose the target agent folder.

    • No matches -> for insights, ask only for missing remote inputs without initializing local files; for create/deploy workflows, seed a new .foundry/ folder during setup; for other workflows, stop and ask the user which agent source folder to initialize.

After selecting an agent root, keep all local .foundry cache inspection, source inspection, evaluator suggestions, dataset suggestions, and prompt-optimization context inside that folder only. Do not scan sibling agent folders unless the user explicitly switches roots.

Step 2: Resolve Environment and Deployment Context

If azure.yaml is present, resolve the azd environment first:

  1. Environment explicitly named by the user

  2. AZURE_ENV_NAME from azd env get-values

  3. azd default environment from .azure/config.json

  4. Environment already selected earlier in the session

Run azd env get-values for the selected environment when project/deployment values are not already known. Prefer azd values for deployment context:

azd VariableResolves To
AZURE_AI_PROJECT_ENDPOINT or AZURE_AIPROJECT_ENDPOINTProject endpoint
AGENT_<SERVICE>_NAMEAgent name for the selected azd service
AGENT_<SERVICE>_VERSIONAgent version for the selected azd service
AZURE_CONTAINER_REGISTRY_NAME or AZURE_CONTAINER_REGISTRY_ENDPOINTACR registry name / image URL prefix
APPLICATIONINSIGHTS_CONNECTION_STRINGApp Insights connection string for trace workflows
AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP, AZURE_AI_ACCOUNT_NAME, AZURE_AI_PROJECT_NAMEAzure resource lookup and Playground links

When azd supplies these values, use them as the source of truth and do not copy them into .foundry/agent-metadata*.yaml on metadata writes.

Step 3: Select Metadata Overlay and Resolve Environment

Inside the selected agent root, choose the metadata file in this order:

  1. Metadata filename or path explicitly provided by the user or workflow

  2. If an explicit environment is already known and .foundry/agent-metadata.<env>.yaml exists, use that file

  3. .foundry/agent-metadata.yaml

  4. If multiple metadata files remain and no rule above selects one, prompt the user to choose

Read the selected metadata file and resolve any remaining environment choice in this order:

  1. Environment explicitly named by the user

  2. If the selected metadata file defines exactly one environment, use it

  3. Environment already selected earlier in the session

  4. defaultEnvironment from metadata

If the selected metadata file still contains multiple environments and none of the rules above selects one, prompt the user to choose. Keep the selected agent root, metadata file, environment, and whether context came from azd or metadata visible in every workflow summary.

If the selected environment exposes older testSuites[] metadata but not evaluationSuites[], treat testSuites[] as the source for this session and normalize each entry in memory to the evaluationSuites[] shape before continuing. If the metadata is older still and only exposes legacy testCases[], normalize that list the same way. Preserve dataset and evaluator fields, keep any existing tags, and map legacy priority to tags.tier only when tags.tier is missing: P0 -> smoke, P1 -> regression, P2 -> coverage.

Step 4: Resolve eval.yaml Local Evaluation Intent

If eval.yaml exists in the selected agent root, parse it before generating new suites:

  • agent.name -> target agent candidate; verify it matches the selected azd/metadata agent before using it.

  • dataset.local_uri -> local seed dataset candidate; legacy dataset_file may be normalized in memory.

  • dataset.name / dataset.version -> registered dataset candidate.

  • validation_dataset -> optional validation dataset candidate.

  • evaluators[] -> candidate Foundry evaluator names; verify with evaluator_catalog_get before treating them as remote evaluators.

  • name -> local eval/suite candidate; verify remotely before persisting as suiteName.

  • options.eval_model, options.optimization_model, options.max_candidates, options.optimization_config.model_search_space, options.pass_threshold, max_samples, trace_days, and generation_instruction -> setup defaults.

Treat eval.yaml as local evaluation intent, not proof that a Foundry suite exists. Persist synced suite/dataset/evaluator references to .foundry only after remote lookup or registration succeeds.

Step 5: Resolve Common Configuration

Layer sources in this order:

  1. Explicit user input and values already selected in the session

  2. azd environment values for deployment context

  3. .foundry/agent-metadata*.yaml overlay values and remote suite/cache references

  4. azure.yaml and eval.yaml local source configuration

  5. User prompts for anything still missing

If azd and metadata both provide the same value and they differ, stop and ask which source is authoritative. If they match, use the azd value and avoid rewriting the duplicate on future metadata writes.

Effective ValuePreferred SourceUsed By
Project endpointazd envdeploy, invoke, observe, trace, troubleshoot
Agent name/versionazd agent variables, then azure.yamlinvoke, observe, trace, troubleshoot
ACRazd envdeploy
Evaluation suites and cache paths.foundry/agent-metadata*.yamlobserve, eval-datasets
Local seed dataset/evaluator intenteval.yamlobserve, eval-datasets
Step 6: Write Metadata Overlay (Create/Deploy/Observe Only)

On any metadata write (deploy, auto-setup, dataset refresh, or trace-to-dataset update), persist only non-derivable overlay/cache state in the selected metadata file:

  • azd binding (azd.environmentName, azd.service) when useful for future resolution

  • evaluationSuites[] with remote suite/dataset/evaluator references and local cache paths

  • lastEval, result files, comparison summaries, or explicit non-azd overrides

Do not copy azd-owned deployment values into metadata when azd already provides them. If the selected file is a preferred single-environment file, rewrite only that one environment block. If the selected file is a legacy multi-environment file, rewrite only the selected environment block. Never copy or merge environments across sibling metadata files automatically. If the selected environment still uses older testSuites[] or legacy testCases[], rewrite it to evaluationSuites[] and remove migrated priority fields from the rewritten entries.

Step 7: Collect Missing Values

Use the ask_user or askQuestions tool only for values not resolved from the user's message, session context, metadata, or azd bootstrap. Common values skills may need:

  • Agent root — Target azd service project folder or folder containing .foundry/agent-metadata*.yaml

  • Metadata file — agent-metadata.yaml for local/dev, or an explicit sidecar such as agent-metadata.prod.yaml

  • Environment — azd environment, dev, prod, or another environment key from metadata

  • Project endpoint — Microsoft Foundry project endpoint URL

  • Agent name — Name of the target agent

💡 Tip: If the user already provides the agent path, environment, project endpoint, or agent name, extract it directly — do not ask again.

Agent: Agent Types

Agent workflows distinguish four first-class agent types:

TypeKindDescription
Prompt"prompt"LLM-based agents backed by a model deployment; managed via Foundry MCP or SDK
Hosted"hosted"Container-based agents running custom code; developed and deployed via azd
Managed Harness Agent"prompt" with harness.type: "microsoft_copilot_preview" for new authoringPrompt Agent running on the Foundry-managed Copilot harness; developed and deployed via azd
Voice"voice"Managed real-time Voice Agent — either Prompt Voice (1 agent, modelType: managed or self_deployed) or Hosted Voice (2 agents: kind: voice wrapper with conversationEngine.type: hosted_agent + kind: hosted target over invocations_ws 1.0.0 and Voice Live Bridge 1.0); developed and deployed via azd and verified over the voice WebSocket endpoint

Classify an azure.yaml service by its definition in this exact order, not by host alone:

  1. kind: voice -> Voice. For compatibility, silently classify an existing kind: prompt-voice service as Voice, then normalize it when editing its YAML. Classify the subtype:

    • conversationEngine.type: hosted_agent -> Hosted Voice wrapper (paired with a named kind: hosted target service).

    • modelType: managed or modelType: self_deployed -> Prompt Voice (Foundry infers native speech-to-speech vs cascaded STT -> LLM -> TTS from model.id).

    • Having both modelType/model and conversationEngine is invalid (stop and fix azure.yaml); having neither is incomplete (do not guess). Never use obsolete modelType: hosted_agent, targetAgent, or a nested config: block.

  2. kind: hosted -> Hosted. When it declares protocols: [{ protocol: invocations_ws, version: 1.0.0 }] and metadata.voiceLiveCompatible: "true" alongside a kind: voice wrapper, it is a Hosted Voice target; otherwise treat a standalone invocations_ws service as a custom Hosted real-time/audio agent.

  3. kind: prompt plus harness.type: microsoft_copilot_preview or legacy github_copilot_preview -> Managed Harness Agent.

  4. kind: prompt without that harness -> ordinary Prompt Agent.

Use agent_get only when the type cannot be resolved from project context. Preserve the harness discriminator already authored on an existing Managed Harness Agent; do not migrate it during ordinary edits or redeploys. Do not convert an existing Agent between Prompt, Hosted, Managed Harness Agent, and Voice types; create a new Agent instead. Before creating any Voice Agent, determine the model strategy first per create-voice.

Tool Usage Conventions

  • Use the ask_user or askQuestions tool whenever collecting information from the user

  • Use the task or runSubagent tool to delegate long-running or independent sub-tasks (e.g., env var scanning, status polling, Dockerfile generation)

  • Prefer azd for Hosted Agents, Managed Harness Agents, and Voice Agents; prefer Foundry MCP for ordinary Prompt Agents.

  • Reference official Microsoft documentation URLs instead of embedding CLI command syntax

Azure Authentication

Additional Resources

Network Isolation Errors

Applies to any call against a Foundry project or its parent Foundry account — Foundry MCP tools, azd, az CLI, curl, REST, or SDK.

If an error matches Public access is disabled / PublicNetworkAccessDisabled / 403 Forbidden from a private endpoint / connection timeout / the project endpoint FQDN resolves to a public IP, this typically means the parent Foundry account has publicNetworkAccess=Disabled or Enabled from selected IP addresses, and the current shell is outside its VNet.

Only if the error is ambiguous, confirm against the Foundry account using a management-plane call (works from anywhere with reader access):

az cognitiveservices account show \
  --name <account> --resource-group <rg> \
  --query "properties.{publicNetworkAccess:publicNetworkAccess, networkAcls:networkAcls, privateEndpointConnections:privateEndpointConnections[].properties.privateLinkServiceConnectionState.status}"

publicNetworkAccess: "Disabled" — or "Enabled" together with non-empty networkAcls.ipRules / virtualNetworkRules — confirms isolation. If publicNetworkAccess: "Enabled" and networkAcls is empty, the failure is a caller-side network issue (e.g. Private DNS resolving the FQDN to a public IP from inside a VNet with a private endpoint), not an account-config issue.

If it's indeed a network isolation issue, supported connection options are documented in Choose a secure connection method to Foundry.

ℹ️ Foundry MCP tools cannot reach a VNet-isolated project even from inside the VNet.

Files

foundry-agent/create/references/foundry-tool-catalog.md

Foundry Tool Catalog — Project Connections for Remote Tools

Reference for wiring a remote tool (catalog tile or generic MCP server) into a Foundry project as a RemoteTool project connection, so a toolbox can attach to it.

🚦 Toolbox creation gate: before creating a toolbox/connection, you MUST read the boundary rules in create-hosted.md → Toolbox creation boundary and follow them, then continue with the rest of this file.

Three catalog backends cooperate: the asset-gallery index discovers connectors, the Logic Apps managedApis GET supplies OAuth metadata, and the Logic Apps apiOperations GET supplies the operation list and input schemas. Skip these calls only for fully BYO generic_mcp servers — every catalog-MCP or connector-namespace flow needs all three.

📘 For the toolbox MCP endpoint, protocol, and testing, see use-toolbox-in-hosted-agent.md. 📘 For prompt-agent MCP wiring (without a toolbox), see tool-mcp.md.

When to use this reference

Use when the user mentions any of:

  • Build → Tools → Connect a tool (any subtab — Configured, Catalog, Custom)

  • "Tool connection", "Remote MCP", "Catalog tile", "Custom · Preview"

  • A specific catalog tile (GitHub, Box, Pipedrive, monday.com, Microsoft Learn, …)

  • RemoteTool connection, gateway_connector, catalog_MCP, generic_mcp

  • Connector Namespace / managed MCP server (powered by the Connector Namespace)

  • "Bring my own OAuth App" (BYO client_id + client_secret) for a catalog connector

  • Discovering connector operations (x-ms-operations / Logic Apps apiOperations) or trigger support (x-ms-trigger) via the catalog APIs

Do not use for: non-tool connections (Azure OpenAI, AI Search account, Storage), or general toolbox CRUD beyond the attach-and-verify recipe below.

Inputs to gather upfront

Before generating any PUT body, ask the user in one batched question for:

  1. Subscription id

  2. Resource group

  3. Cognitive Services account name (the Foundry account)

  4. Project name (under the account)

  5. Connection name — lowercase, [a-z0-9-], ≤ 24 chars (e.g. box-1, gh-byo)

  6. Tool scenario in plain language — e.g. "list my files in Box", "create issues on GitHub". Map this onto operations from the connector's apiOperations catalog for gateway_connector, or onto the catalog MCP server's tools/list for catalog_MCP / BYO.

  7. Toolbox name to attach into for verification (defaults to default-tb)

  8. Secrets (BYO clientId / clientSecret, CustomKeys header value, …) — ask the user to type these directly into the terminal, never via tooling that echoes them

The caller's AAD oid / tid (needed only for the consent-link step) are auto-discovered via az ad signed-in-user show --query id -o tsv and az account show --query tenantId -o tsv. For a service-principal caller, use az ad sp show --id <appId> instead. These values can also be read from the oid / tid claims on the ARM bearer token; the gateway validates the caller principal owns them.

ARM endpoint (shared by every variant)

PUT https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}
    /providers/Microsoft.CognitiveServices/accounts/{acct}
    /projects/{proj}/connections/{name}?api-version=2025-04-01-preview
Preflight RBAC

Caller needs Foundry Project Manager or Cognitive Services Contributor on the project scope. Run this before the first PUT to surface 403s early:

$oid = az ad signed-in-user show --query id -o tsv
$projId = "/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.CognitiveServices/accounts/$acct/projects/$proj"
az role assignment list --assignee $oid --scope $projId --include-inherited --all `
  --query "[?roleDefinitionName=='Foundry Project Manager' || roleDefinitionName=='Cognitive Services Contributor'].roleDefinitionName" -o tsv

Empty output → caller lacks the required role; expect 403 AuthorizationFailed on PUT until granted.

Common request template
$tok = az account get-access-token --resource "https://management.azure.com" --query accessToken -o tsv
$h   = @{ Authorization = "Bearer $tok"; "Content-Type" = "application/json" }
$uri = "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.CognitiveServices/accounts/$acct/projects/$proj/connections/${connName}?api-version=2025-04-01-preview"
Invoke-WebRequest -Method PUT -Headers $h -UseBasicParsing -Body $body -Uri $uri
Body invariants
  • properties.target is required for every authType (validation rejects empty). The exact value depends on the variant — see each body shape. For gateway_connector specifically, the literal string "https://placeholder" is the correct value on PUT #1 and is rewritten by the platform on PUT #2 to the real gateway URL.

  • properties.group is server-filled (GenericProtocol for RemoteTool).

  • properties.credentials is scrubbed to null on GET.

  • properties.peRequirement defaults to "NotRequired".

Allowed authType for category=RemoteTool (per api-version=2025-04-01-preview): None, CustomKeys, OAuth2, ProjectManagedIdentity, DeveloperConnection, UserEntraToken, AgentUserImpersonation, AgenticIdentityToken, AgenticUser, UserTokenAndProjectManagedIdentity. ApiKey is rejected for RemoteTool. The authoritative list is whatever the Cognitive Services projects API reference returns for the current API version — if you hit invalid_payload: unsupported authType, re-check against the schema for the version you're calling.

Decision tree

User scenarioauthTypemetadata.typeNotes
Catalog tile tagged "Custom · Preview" (Box, Pipedrive, GitHub, Salesforce, Outlook, …)OAuth2gateway_connectorConnector-namespace managed MCP. Powered by the Connector Namespace in your Foundry account; the namespace handles OAuth, token storage, and per-user passthrough. Needs two PUTs plus listConsentLinks per caller (see Gateway connector full flow).
Catalog MCP tile with Microsoft-managed OAuth (no client_id needed)OAuth2catalog_MCPFoundry brokers the OAuth app for you. The Catalog API tile prepopulates target (server URL); listConsentLinks flow same as gateway.
Catalog MCP tile with your own OAuth AppOAuth2(omit)Supply your own client_id + client_secret + raw authorizationUrl / tokenUrl / scopes. Do not mix BYO credentials with metadata.type=catalog_MCP. See BYO OAuth caveats.
Remote MCP, Azure-side identity (project MI calls the server)ProjectManagedIdentitycatalog_MCP (when listed) or generic_mcpFor catalog-listed MCP servers, prefer catalog_MCP so target is prepopulated. Requires audience in metadata. See PMI limitations.
Remote MCP, static shared secret / header keyCustomKeyscatalog_MCP (when listed) or generic_mcpHeader name and format are NOT always Authorization: Bearer .... Read the required header name from the Catalog API entry's x-ms-connection-parameters and use that exact name in credentials.keys.
Remote MCP, user's Entra token forwardedUserEntraTokengeneric_mcpPer-user identity passthrough. Not supported when the agent is published to Teams. Pair with metadata.audience for the upstream resource URI.
Custom OpenAPI / A2A tool (no MCP)variesn/aUse the Custom subtab shapes; outside the MCP toolbox path. See Custom subtab — OpenAPI / A2A.

Catalog APIs — three backends, three calls

There are three read endpoints the portal hits to populate a connection form. Programmatic callers should use the same three.

POST https://eastus.api.azureml.ms/asset-gallery/v1.0/tools
Headers:
  Authorization: Bearer <token for https://management.azure.com>
  Content-Type: application/json
Body:
{
  "freeTextSearch": "*",
  "filters": [
    { "field": "entityContainerId", "operator": "eq",       "values": ["connectors-registry-prod-bl"] },
    { "field": "type",              "operator": "eq",       "values": ["tools"] },
    { "field": "annotations/name",  "operator": "contains", "values": ["<name>"] }
  ],
  "pageSize": 20
}
  • Catalog lives only in eastus. westus2.api.azureml.ms returns totalCount=0 for the same body. entityIds are portable across project regions.

  • Use this only to discover the connector's entityId — pull objectId out of the returned entityId (e.g. …/objectId/github). That objectId is the connectorName you pass to PUTs and the next two catalog calls.

  • The response is a thin index. properties.remotes[], xMsSecuritySchemes, OAuth endpoints, scopes, and operation schemas are not included. Direct GET /asset-gallery/v1.0/tools/{entityId} returns 404. There is no expand/projection flag that surfaces these fields — fetch them from calls 2 and 3 below.

Two registries are indexed here — distinguished by entityContainerId:

RegistryentityContainerIdContentsPair with
Public catalogconnectors-registry-prod-blCatalog connector definitions (GitHub, Box, Salesforce, …).metadata.type=catalog_MCP or gateway_connector
Private MCP entriesregistry-prod-blMCP-server entries used by the portal Connections UI (e.g. github-mcp-server). Sometimes carries a canonical MCP URL when the public-catalog row lacks remotes[].metadata.type=catalog_MCP

Always query both when surfacing "available tools" to a user — the private MCP entries can fill gaps in the public catalog row.

2. Logic Apps managedApis — OAuth source-of-truth
GET https://management.azure.com/subscriptions/{sub}
    /providers/Microsoft.Web/locations/{region}/managedApis/{connectorName}
    ?api-version=2016-06-01

connectorName is the objectId from the asset-gallery entityId. Verified response shape for github (2026-05-21):

{
  "properties": {
    "displayName": "GitHub",
    "runtimeUrls": ["https://logic-apis-eastus.azure-apim.net/apim/github"],
    "connectionParameters": {
      "token": {
        "type": "oauthSetting",
        "oAuthSettings": {
          "identityProvider": "GitHub",
          "clientId": "faa5f56b825cbc649ae1",          // Microsoft's default OAuth-App id
          "scopes": ["repo","workflow","read:org","admin:org"],
          "redirectMode": "Direct",
          "redirectUrl": "https://logic-apis-eastus.consent.azure-apim.net/redirect"
        }
      }
    }
  }
}

Raw authorizationUrl / tokenUrl are NOT in this response. Logic Apps abstracts them via the identityProvider string and resolves them inside the gateway. For BYO you must map identityProvider → endpoints yourself. Known mappings:

identityProviderauthorizationUrltokenUrl
GitHubhttps://github.com/login/oauth/authorizehttps://github.com/login/oauth/access_token
Googlehttps://accounts.google.com/o/oauth2/v2/authhttps://oauth2.googleapis.com/token
Boxhttps://account.box.com/api/oauth2/authorizehttps://api.box.com/oauth2/token
AzureActiveDirectory / aad3rdPartySNIhttps://login.microsoftonline.com/common/oauth2/v2.0/authorizehttps://login.microsoftonline.com/common/oauth2/v2.0/token

For identityProvider values not in this table (dynamicscrmonlinecertificate, salesforce, dropbox, oauth2generic, …), look the provider's well-known OAuth endpoints up in its developer docs — the catalog API does not surface them.

Use the scopes array from this response as the default scopes list. The catalog clientId is Microsoft's default OAuth App; replace it with your own only when going BYO.

Derive authType from connectionParameters:

  • Any parameter with type: oauthSetting → authType = OAuth2.

  • Else any parameter with type: securestring → authType = CustomKeys.

  • Else → authType = None (anonymous) or ProjectManagedIdentity if the connector explicitly supports MI.

3. Logic Apps apiOperations — operation catalog (gateway_connector only)

For gateway_connector you need the list of operations the connector exposes plus each operation's parameter schema, because that's what gets serialized into metadata.mcpserverConfigProperties on PUT #2. Asset-gallery does not carry this.

GET https://management.azure.com/subscriptions/{sub}
    /providers/Microsoft.Web/locations/{region}/managedApis/{connectorName}
    /apiOperations?api-version=2016-06-01

Returns value[] of operations with name, properties.summary (display name), properties.description, properties.annotation.family, and properties.visibility (important / advanced / internal). Verified 2026-05-21: Box returns 14 operations including ListRootFolder, ListFolder, GetFileMetadata, GetFileContent, DeleteFile, CreateFile, plus several On* triggers (not agent-callable).

To get parameter schemas, fetch a single operation with $expand=properties/inputsDefinition:

GET .../managedApis/{connectorName}/apiOperations/{operationName}
    ?api-version=2016-06-01&$expand=properties/inputsDefinition

properties.inputsDefinition is a JSON-Schema-shaped object with type:"object", properties:{...}, and required:[...]. Map each entry to one agentParameters entry:

inputsDefinition.properties[name] field→ agentParameters[].schema field
typetype
descriptiondescription
titlex-ms-summary
defaultdefault (omit if absent)

If inputsDefinition.properties is empty / missing, the operation takes no arguments and agentParameters is [] (e.g. Box ListRootFolder).

Skip any operation whose properties.isWebhook or isNotification is true — these are Logic Apps triggers, not agent-callable actions.

Picking ops from a plain-language scenario. Match the user's words against properties.summary and properties.description, then prefer the simplest variant (fewest required parameters) and the one whose annotation.family aligns with the user intent. For Box "list my files", ListRootFolder (zero params) wins over ListFolder (requires id); if the user asks to list a specific folder, register both.

Gateway connector full flow

For Catalog tiles tagged Custom · Preview (Box, Pipedrive, GitHub, Salesforce, Outlook, iManage Work, PDF4me, Qdrant, Medallia, Fulcrum, monday.com, SuperMCP, IA-Connect JML, iMIS, Huddo Boards, The Events Calendar, PUG Gamified Engagement, Nitro Sign Enterprise Verified, Soft1, Elfsquad Product Configurator, MintNFT, …).

Step 1 — Discover

Query the asset-gallery (call #1) for the connector. Extract:

  • objectId from entityId → connectorName

  • Full entityId → metadata.toolEntityId

Then call managedApis (call #2) and apiOperations (call #3) for OAuth and operation metadata.

Step 2 — PUT #1 (create connection)

Verbatim PUT body (captured from the portal's Box wizard, 2026-05-21):

{
  "properties": {
    "authType": "OAuth2",
    "category": "RemoteTool",
    "target": "https://placeholder",
    "credentials": {},
    "connectorName": "box",
    "metadata": {
      "type": "gateway_connector",
      "toolEntityId": "azureml://location/eastus/apiCenter/connectors-registry-prod-bl/type/tools/objectId/box/version/1",
      "connectionproperties": "{\"connectorName\":\"box\"}"
    }
  }
}

Spelling traps (case-sensitive):

  • toolEntityId — NOT entityId.

  • connectionproperties — lowercase, value is a stringified JSON object, not a nested object. "{\"connectorName\":\"box\"}" is correct; {"connectorName":"box"} is rejected.

  • connectorName appears at top-level under properties and inside metadata and inside connectionproperties.

target = "https://placeholder" is the persisted value on PUT #1, not a stub. There is no follow-up call that rewrites it before PUT #2. Runtime dispatch keys off metadata.toolEntityId + metadata.connectionproperties.connectorName + OAuth consent state. PUT #2 (register-actions) rewrites target to the real gateway URL https://app-XX.<region>.logic.azure.com/api/connectorGateways/{envId}/mcpServerConfigs/{connectionName}/mcp.

For every distinct end-user (or service principal), call listConsentLinks:

POST .../connections/{name}/listConsentLinks?api-version=2025-04-01-preview

Verbatim portal body:

{
  "parameters": [{
    "objectId":      "<caller AAD oid>",
    "parameterName": "token",
    "redirectUrl":   "https://ai.azure.com/nextgen/authConsentPopup",
    "tenantId":      "<caller AAD tid>"
  }]
}

Notes:

  • The portal sends redirectUrl=https://int.ai.azure.com/... from the INT environment; for production (ai.azure.com) use https://ai.azure.com/nextgen/authConsentPopup. The redirect URL only gates which Foundry origin the OAuth popup closes back into — it does not affect what tokens are minted.

  • Returns a per-user OAuth authorization URL (e.g. a box.com/api/oauth2/authorize?... link). User navigates → consents → gateway stores the token.

  • Cross-tenant calls return InvalidConsentLinkParameter (objectId + tenantId must match the caller principal).

Each listConsentLinks response mints a short-lived signed token (≈ 1 hour TTL based on ExpirationTime in the base64 payload). A 500 from the consent host when clicking the link is most often caused by an expired or stale link, not a server outage. Fix: call listConsentLinks again to get a fresh link and use it immediately. Do not reuse a link from a previous step or previous session.

Portal popup lifecycle (pending-true happy path)

The portal pre-opens a blank popup (about:blank) before calling listConsentLinks, then drives the flow as follows once the consent URL is in hand. Code-first callers should replicate this:

  1. Register listeners on window.postMessage and BroadcastChannel('connector-oauth-callback') to receive completion signals.

  2. Navigate the popup to the consent URL.

  3. Poll popup.closed every 1 second to detect finish / dismiss.

  4. When the popup closes, wait 500 ms grace for any in-flight postMessage / BroadcastChannel messages.

  5. If a { pending: true } signal arrives (consent completed server-side but no authorization code returned to the opener):

    • Issue a PUT to the connection (same body as the original create PUT) to prompt the backend to finalise auth state.

    • If overallStatus is Connected in the response, done ✅.

    • Otherwise poll GET .../connections/{name} every 2 seconds, up to 15 attempts, until overallStatus flips to Connected.

  6. If no signal before popup close, treat as user-cancelled and surface an error.

  7. Cleanup: remove listeners, clear polling, force-close the popup if still open.

The { pending: true } path is the normal happy-path because the provider closes the popup by redirecting to ai.azure.com/nextgen/authConsentPopup, which has no JavaScript opener to post back to. Don't assume consent is done just because the popup closed. The "blank Foundry page" seen after authorising in a detached tab is this same redirect arriving without an opener — the gateway token is still stored; retry PUT #2 to confirm.

Links served from logic-apis-df.consent.azure-apim.net are the dogfood / INT consent host (DF = dogfood). Production region traffic goes through logic-apis-{region}.consent.azure-apim.net (e.g. logic-apis-eastus.consent.azure-apim.net). Either host can return DF links depending on which Logic Apps environment the connector is deployed in; the caller cannot force the host.

Dogfood OAuth-app runtime allowlist trap

Some connectors (Spotify spotifyip confirmed) are backed by a dogfood-env Microsoft OAuth app registered in provider "development mode" with a hard-coded test-user allowlist. Consent + Connected status work fine code-first for any caller, but tools/call at runtime returns:

{ "error": { "code": 403, "source": "...logic-df.azure-apihub.net",
  "innerError": "Check settings on https://developer.spotify.com/dashboard, the user may not be registered." } }

Detect by inspecting the consent URL's first 302: if the redirect_uri is https://global-test.consent.azure-apim.net/redirect (rather than global.consent...), the connector is on the dogfood OAuth app. The connection will still go Connected and tools/list will work; only the actual API invocation fails. Not fixable client-side; requires Microsoft to promote the app or add the caller's email to the provider-side allowlist.

$consentUrl = ($r.Content | ConvertFrom-Json).value[0].link
try { Invoke-WebRequest -Uri $consentUrl -MaximumRedirection 0 -ErrorAction Stop | Out-Null }
catch { $loc = $_.Exception.Response.Headers.Location.ToString() }
if ($loc -match 'global-test\.consent\.azure-apim\.net') {
  Write-Warning "Connector uses dogfood OAuth app; tools/call may 403 with 'user may not be registered' even after Connected."
}
Step 4 — PUT #2 (register actions)

After OAuth, the portal issues a second PUT against the same connection name to register which connector operations the agent can invoke. Without this PUT the runtime has no actions to dispatch even though overallStatus shows Authenticated.

The body is identical to PUT #1 plus an additional metadata.mcpserverConfigProperties field (stringified JSON). Verbatim example for Box connection box-5:

{
  "properties": {
    "authType": "OAuth2",
    "category": "RemoteTool",
    "target": "https://placeholder",
    "credentials": {},
    "connectorName": "box",
    "metadata": {
      "type": "gateway_connector",
      "toolEntityId": "azureml://location/eastus/apiCenter/connectors-registry-prod-bl/type/tools/objectId/box/version/1",
      "connectionproperties": "{\"connectorName\":\"box\"}",
      "mcpserverConfigProperties": "{\"description\":\"\",\"state\":\"Enabled\",\"connectors\":[{\"name\":\"box\",\"connectionName\":\"box-5\",\"displayName\":\"box\",\"description\":\"\",\"operations\":[{\"name\":\"GetFileMetadata\",\"displayName\":\"Get file metadata using id\",\"description\":\"\",\"userParameters\":[],\"agentParameters\":[{\"name\":\"id\",\"schema\":{\"type\":\"string\",\"description\":\"The unique identifier of the file in Box.\",\"x-ms-summary\":\"File Id\"}}]}]}]}"
    }
  }
}

Decoded mcpserverConfigProperties schema:

{
  "description": "",
  "state": "Enabled",
  "connectors": [
    {
      "name":           "<connectorName>",       // same as properties.connectorName
      "connectionName": "<this connection name>",
      "displayName":    "<connectorName>",
      "description":    "",
      "operations": [
        {
          "name":            "<OperationId>",    // operation id from apiOperations
          "displayName":     "<friendly>",
          "description":     "",
          "userParameters":  [],                   // bound at connection time (rare for Custom·Preview)
          "agentParameters": [                     // parameters the agent fills at call time
            {
              "name": "<paramName>",
              "schema": {
                "type":         "string|number|boolean",
                "description":  "...",
                "x-ms-summary": "...",
                "default":      "..."              // optional
              }
            }
          ]
        }
      ]
    }
  ]
}

Each operation in operations[] corresponds 1:1 to one apiOperations entry; agentParameters[].schema is translated from inputsDefinition.properties per the mapping in Catalog APIs §3.

The portal lets the user multi-select via checkboxes in the wizard's "Configure actions" page; the selection is serialized into this string. When the selection changes later, the portal replaces mcpserverConfigProperties wholesale — no merge. Your code must do the same: any time the agent-callable op list changes, re-run PUT #2 with the full new list.

Step 5 — overallStatus flip semantics

Two independent conditions must BOTH be true for overallStatus to flip Unauthenticated → Connected:

  1. PUT #2 issued with non-empty metadata.mcpserverConfigProperties (rewrites target to the real gateway URL; target rewrite is visible immediately on PUT #2 regardless of consent state).

  2. OAuth consent completed (user followed the listConsentLinks URL and clicked Authorize). Gateway then stores the token.

Order-independent observations:

  • PUT #2 before consent → target rewrites, status stays Unauthenticated.

  • Consent before PUT #2 → status stays Unauthenticated until PUT #2 fires; PUT #2 then flips to Connected in the same response.

Body shape — OAuth2 + catalog_MCP (Microsoft-managed OAuth)

Use when the catalog entry is an MCP server and you accept Microsoft's managed OAuth App + consent flow (no BYO secret):

{
  "properties": {
    "authType": "OAuth2",
    "category": "RemoteTool",
    "target": "https://api.githubcopilot.com/mcp",
    "credentials": {},
    "metadata": {
      "type": "catalog_MCP",
      "toolEntityId": "azureml://location/eastus/apiCenter/connectors-registry-prod-bl/type/tools/objectId/github/version/1"
    },
    "peRequirement": "NotRequired"
  }
}

For MCP URL discovery when connectors-registry-prod-bl lacks remotes[], look up the peer entry in registry-prod-bl (e.g. github-mcp-server) — its asset-gallery row sometimes carries the canonical MCP URL. Consent uses the same listConsentLinks flow as gateway_connector.

BYO OAuth App against a catalog MCP server

When the user has their own OAuth App (e.g. GitHub https://github.com/organizations/<org>/settings/applications/<app-id>) and wants the connection to mint tokens via their app instead of Microsoft's managed one. Verified shape, 2026-05-21:

{
  "properties": {
    "authType": "OAuth2",
    "category": "RemoteTool",
    "target": "<MCP server URL>",
    "credentials": { "clientId": "<your client id>", "clientSecret": "<your client secret>" },
    "authorizationUrl": "https://github.com/login/oauth/authorize",
    "tokenUrl":         "https://github.com/login/oauth/access_token",
    "scopes":           ["repo","workflow","read:org","admin:org"],
    "peRequirement":    "NotRequired"
  }
}
Filling the OAuth fields from the catalog APIs
  1. Find the connector entityId — asset-gallery POST with annotations/name contains <name>. Pull objectId out of the returned entityId.

  2. Look up OAuth metadata — GET .../managedApis/<objectId>?api-version=2016-06-01. From properties.connectionParameters.token.oAuthSettings:

    • identityProvider → look up authorizationUrl / tokenUrl in the mapping table in Catalog APIs §2.

    • scopes → use as the default scopes array (override only if the user explicitly needs different scopes).

  3. Supply your own clientId / clientSecret in credentials. Do not reuse the catalog clientId from step 2 — that's Microsoft's managed OAuth App and you cannot mint with it.

  4. PUT the body above.

Hard rules verified by probe (2026-05-21)
  • scopes MUST be a JSON array. A space-separated string returns 400 "Error when parsing request; unable to deserialize request body".

  • DO NOT send useCustomConnector. It is ignored on input; server fills false.

  • DO NOT send metadata.{type=catalog_MCP, toolEntityId, ...} for BYO. Those fields anchor the connection to the catalog's managed OAuth App and conflict with your supplied credentials.

  • DO NOT call listConsentLinks for BYO — the gateway handles consent via the standard authorization_code flow using the server-filled redirectUrl. Calling listConsentLinks against a fresh BYO connection returns 404 AIGatewayConnectionNotFound.

Server-filled response fields
  • credentials → null (scrubbed).

  • connectorName → <gatewayId>-<connectionName> (your input ignored).

  • redirectUrl → https://global.consent.azure-apim.net/redirect/<32-hex> — the OAuth callback URL the provider (e.g. GitHub OAuth App) must allow-list. Generated per-connection on first PUT. Two-pass flow:

    1. PUT with placeholder client_secret.

    2. Read properties.redirectUrl from the response.

    3. Register it as the "Authorization callback URL" on the OAuth App.

    4. PUT again with the real client_secret.

Caveat: api.githubcopilot.com/mcp rejects BYO OAuth-App tokens

The GitHub Copilot MCP server requires GitHub-App-minted Copilot tokens (the microsoft-foundry-agent-service GitHub App). A token from a user OAuth App will be rejected at runtime even if the connection PUT is 200. For real BYO testing point target at a self-hosted GitHub MCP server, or use an OpenAPI tool against api.github.com instead.

ProjectManagedIdentity Remote MCP

For MCP servers that accept Azure-side identity (the project's system MI calls the MCP server's bearer endpoint):

{
  "properties": {
    "authType": "ProjectManagedIdentity",
    "category": "RemoteTool",
    "target": "<MCP server URL with required query string>",
    "metadata": { "type": "generic_mcp", "audience": "<upstream resource URI>" }
  }
}

For catalog-listed MCP servers, prefer metadata.type = catalog_MCP with toolEntityId so target is prepopulated. audience is required for MI auth — it tells Foundry which resource URI to request a token for. Read the required audience from the connector's catalog entry or its documentation (typical values: an app ID URI like api://contoso-mcp, or an Azure service resource ID like https://cognitiveservices.azure.com). If you omit audience, the MCP server rejects the call with 401.

ProjectManagedIdentity limitations

Verified end-to-end against Azure Language /language/mcp, 2026-05-21:

  1. Forwarder drops the query string. The connection target's ?api-version=... is not preserved on the upstream call. If the upstream MCP requires a query parameter, PMI fails with 401/404 even when RBAC is correct.

  2. Forwarder mints the wrong audience. The MI token Foundry sends does not have aud=https://cognitiveservices.azure.com or https://ai.azure.com. Setting properties.audience on the connection is accepted but does not change what is minted.

  3. Endpoints not on the trust list reject the forwarded MI token with -32007 PERMISSION_DENIED "Cannot pass Microsoft token to untrusted MCP endpoint" (e.g. api.githubcopilot.com/mcp). This is the expected security gate.

CustomKeys Remote MCP

Static header(s) injected on every upstream call. Minimum body:

{
  "properties": {
    "authType": "CustomKeys",
    "category": "RemoteTool",
    "target": "<MCP server URL>",
    "credentials": { "keys": { "Ocp-Apim-Subscription-Key": "<value>" } },
    "metadata": { "type": "generic_mcp" }
  }
}

Verified PUT 200 / GET 200 / DELETE 200 round-trip. The header name is arbitrary — it is forwarded as-is to the MCP server. Different connectors require different header shapes:

  • GitHub PAT: Authorization: Bearer <pat> or Authorization: token <pat> — catalog dictates.

  • API-key services: x-api-key: <key> or Ocp-Apim-Subscription-Key: <key>.

  • Multi-header schemes: e.g. X-Account-Id: <id> + X-Account-Secret: <secret>.

Always read the canonical header set from the connector's connectionParameters (each securestring parameter names the header it maps to) before writing the keys block. Do not default to Authorization: Bearer — it's wrong for many connectors.

For catalog-listed servers, swap metadata.type to catalog_MCP and add toolEntityId.

UserEntraToken Remote MCP

For MCP servers that consume the caller's Entra token directly. Body includes metadata.audience so the platform mints the correct token for the upstream:

{
  "properties": {
    "authType": "UserEntraToken",
    "category": "RemoteTool",
    "target": "<MCP server URL>",
    "metadata": { "type": "generic_mcp", "audience": "<upstream resource URI>" }
  }
}

Not available when the agent is published to Teams (Teams agents use the project MI).

Custom subtab — OpenAPI / A2A

Not catalog-driven — the user provides the spec themselves. Each Save in this subtab maps to a single PUT against the same connections endpoint:

TileauthType optionstargetNotes
OpenAPINone, CustomKeys, ApiKey, OAuth2OpenAPI spec URL or upstream API baseAgent gets tools[].openapi.auth.security_scheme.connection_id.
A2A (Preview)None / CustomKeys / UserEntraToken / AAD (mapped from UI)A2A endpointmetadata.agentCardPath default /.well-known/agent-card.json; agent gets tools=[A2APreviewTool(project_connection_id=...)]; runtime emits a2a_preview_call / a2a_preview_call_output events.
MCPcovered above—This tile is just a router to the catalog / BYO flows.

Toolbox attach — gateway_connector tool naming

Attach the same as generic_mcp — the tool block uses type:"mcp" and project_connection_id set to the full ARM resource id of the connection (NOT just the name):

{
  "tools": [{
    "type": "mcp",
    "server_label": "box5",
    "project_connection_id":
      "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{acct}/projects/{proj}/connections/{connName}"
  }]
}

tools/list returns one MCP tool per registered operation. Tool names follow the verified pattern (probed 2026-05-22 against Box):

<server_label>___<connectorName>_<OperationName>

Note ___ (three underscores) between server_label and the rest, then a single _ between connectorName and operation name. Example for Box attached with server_label="box5":

mcpserverConfigProperties optools/list namedescription
ListRootFolderbox5___box_ListRootFolderbox - List files and folders in root folder
GetFileMetadatabox5___box_GetFileMetadatabox - Get file metadata using id

The MCP tool's inputSchema is exactly the JSON schema derived from apiOperations/{op}?$expand=properties/inputsDefinition (the agentParameters[].schema values, re-keyed by parameter name). For an operation with no agent parameters, inputSchema is {"type":"object"}.

Worked tools/call for "list my files in Box" — verified end-to-end:

POST {dp}/toolboxes/{tb}/mcp?api-version=v1
{
  "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": { "name": "box5___box_ListRootFolder", "arguments": {} }
}
→ 200
{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "content": [{ "type": "text", "text": "[]" }],
    "isError": false
  }
}

(text carries a JSON-stringified array of Box file/folder objects; empty [] means the root folder is empty.)

outlook connector — verified end-to-end (2026-05-22)

Uses identityProvider: oauth2generic (MSA / consumers tenant). connectorName = "outlook", toolEntityId objectId = outlook. tools/call response wraps in { "value": [...] } (not a bare array like Box):

POST {dp}/toolboxes/{tb}/mcp?api-version=v1
{
  "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": { "name": "outlook-1___outlook_GetEmailsV2",
              "arguments": { "folderPath": "Inbox", "top": 3 } }
}
→ 200
{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "content": [{ "type": "text",
      "text": "{\n  \"value\": [\n    { \"Subject\": \"...\", \"From\": \"...\", ... }\n  ]\n}" }],
    "isError": false
  }
}

Operations registered for the test: GetEmailsV2 (read emails with folderPath / top / fetchOnlyUnread agent parameters) and SendEmailV2 (send with emailMessage object param containing required To, Subject, Body). SendEmailV2's top-level schema is object — pass it as a single nested agentParameters entry; the gateway flattens into the Logic Apps emailMessage envelope internally. The follow-up PUT after popup close (pending-true path) immediately returned overallStatus: Connected without needing the GET poll loop — outlook's MSA consent round-trips are fast.

Minimum attach + verify recipe

Verifying a fresh connection is the only toolbox operation in scope of this reference. Toolboxes are upserted implicitly by POST /versions; no separate container create is needed.

The $dp value below is the project's data-plane endpoint, in the same {project_endpoint} form used elsewhere in these references — https://<account>.services.ai.azure.com/api/projects/<project>. The host segment varies by Foundry account/region; read it from a non-FOUNDRY_-prefixed env var (see use-toolbox-in-hosted-agent.md § Set TOOLBOX_ENDPOINT) rather than hardcoding. The bearer-token resource is https://ai.azure.com, NOT ARM.

# 0. Constants.
$dp   = $env:PROJECT_ENDPOINT   # https://<account>.services.ai.azure.com/api/projects/<project>
$tb   = "default-tb"
$lbl  = "box5"                  # becomes the "<label>___" prefix on tool names
$connId = "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<acct>/projects/<proj>/connections/<connName>"
$tok  = az account get-access-token --resource "https://ai.azure.com" --query accessToken -o tsv
$hdr  = @{ Authorization      = "Bearer $tok"
           "Content-Type"     = "application/json"
           Accept             = "application/json, text/event-stream" }

# 1. Create a toolbox version with the connection attached.
$body = @{ tools = @(@{
   type = "mcp"
   server_label = $lbl
   project_connection_id = $connId
}) } | ConvertTo-Json -Depth 6 -Compress
$v = Invoke-WebRequest -Method POST -Headers $hdr -UseBasicParsing -Body $body `
        -Uri "$dp/toolboxes/$tb/versions?api-version=v1"
$ver = ($v.Content | ConvertFrom-Json).version

# 2. Promote the new version to default. default_version MUST be a JSON STRING, not a number.
#    Use ${tb} to terminate the variable name unambiguously before the literal '?'.
Invoke-WebRequest -Method PATCH -Headers $hdr -UseBasicParsing `
   -Body (@{ default_version = "$ver" } | ConvertTo-Json) `
   -Uri "$dp/toolboxes/${tb}?api-version=v1" | Out-Null

# 3. tools/list → expect one entry per registered op, named "<server_label>___<connectorName>_<OpName>".
$req = '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Invoke-WebRequest -Method POST -Headers $hdr -UseBasicParsing -Body $req `
   -Uri "$dp/toolboxes/$tb/mcp?api-version=v1"

# 4. tools/call → the prefixed name and arguments per inputSchema.
$call = @{ jsonrpc="2.0"; id=2; method="tools/call"; params=@{
   name="$lbl`___box_ListRootFolder"; arguments=@{} } } | ConvertTo-Json -Depth 5 -Compress
Invoke-WebRequest -Method POST -Headers $hdr -UseBasicParsing -Body $call `
   -Uri "$dp/toolboxes/$tb/mcp?api-version=v1"

The response body for /mcp is plain JSON (no SSE data: framing) despite the text/event-stream Accept.

Required RBAC summary

OperationRole
PUT any connection aboveFoundry Project Manager on the project (or Cognitive Services Contributor on the account)
Drive OAuth consent (gateway_connector, catalog_MCP managed-OAuth)The end-user themselves, signed in to the subscription's tenant
ProjectManagedIdentity against a Cognitive Services upstreamProject MI needs the upstream's data-plane role (e.g. Cognitive Services Language Owner for /language/mcp)

Pitfalls / common mistakes

  • Do not forget PUT #2 for gateway_connector (Step 4). The first PUT + OAuth flips status to Authenticated but the runtime has no actions to dispatch until you PUT again with metadata.mcpserverConfigProperties.

  • Do not invent a "real" target URL for the gateway_connector flow on PUT #1. "https://placeholder" is correct on PUT #1; PUT #2 rewrites it.

  • Do not mix BYO credentials with metadata.type=catalog_MCP in the BYO body. They conflict; the server accepts the PUT but the runtime uses the catalog's managed app and ignores your secret — or fails with consent confusion.

  • Do not send scopes as a space-separated string anywhere. Always an array.

  • Do not call listConsentLinks for BYO OAuth. Use only for gateway_connector and managed-OAuth catalog_MCP.

  • Do not assume the asset-gallery search response contains OAuth metadata — it does not. Always pair it with the Logic Apps managedApis GET (or hardcode the identityProvider mapping) to get scopes and to derive authorizationUrl / tokenUrl.

  • Use exact field spelling for gateway_connector: toolEntityId (NOT entityId), connectionproperties (lowercase, stringified JSON).

  • Sign in to the subscription's tenant before calling listConsentLinks — it validates the caller principal owns the supplied objectId + tenantId.

  • Toolbox PATCH default_version must be a JSON STRING, not a number. Sending {"default_version": 1} returns 400 invalid_payload "requires an element of type 'String', but the target element has type 'Number'". Use {"default_version": "1"}.

  • metadata.audience is required for ProjectManagedIdentity. Without it the MCP server returns 401.

  • Header names for CustomKeys come from the catalog, not from a default Authorization: Bearer template.

  • ApiKey is rejected for category=RemoteTool. Use CustomKeys for static secrets.

  • OAuth consent is per-user, per-connection, per-project. Each new caller hits CONSENT_REQUIRED once (returned as a nested string code inside an outer -32006 error) and must open the URL the toolbox returns.

  • api.githubcopilot.com/mcp rejects user OAuth-App tokens. Use a self-hosted MCP or fall back to OpenAPI.

  • PMI forwarder drops target query strings and mints a fixed audience. Setting properties.audience is accepted but does not change what is sent.

  • Network-secured Foundry projects cannot use private-endpoint-only MCP servers — only public endpoints reachable from the Foundry data plane and the Connector Namespace.

References

Requirements and compatibility

Written for: Claude Code

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Names Claude Code tools, Needs a command-line tool, Needs Python

Security

What our scanner noticed· Low risk

  • Phrasing that asks for actions without confirmation
  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/skills · .github/plugins/azure-skills/skills/microsoft-foundry · commit 0696600
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from .github/plugins/azure-skills/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Also distributed in
microsoft/azure-skills

AutoGineer is not affiliated with or endorsed by Microsoft.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download microsoft-foundry.zip