The Hidden Reason Your Foundry Agent Can’t Reach Any of Your Private (Bring-Your-Own) Resources

The setup

You’ve built the deployment the enterprise architecture review asked for. Nothing touches the public internet.

Your Microsoft Foundry resource is private — public network access disabled, a private endpoint fronting it inside your virtual network. Every resource the agent depends on is locked down the same way:

  • Azure AI Search — private endpoint, public access off.
  • Azure Storage — private endpoint, public access off.
  • Azure Cosmos DB — private endpoint, public access off.
  • An MCP server (say on Azure Functions, Container Apps or APIM) — private, reachable only from inside the VNet.

You’ve delegated a subnet for network injection, stood up Private DNS zones for the Foundry account and every data resource, kept the Foundry resource and the VNet in the same region, and configured network injection at account-creation time like the docs require. On paper, this is a textbook private standard agent setup.

Then you run your first agent.

The problem: network errors everywhere

The agent can’t reach anything. And the errors look exactly like what you’d expect from a private-networking problem — connection failures and name-resolution errors reaching Search, Storage, and Cosmos; MCP tool calls timing out. Every message points at the network.

(Some of you will see the quieter version of this bug instead: no errors at all, but your Cosmos container, your blob container, and your search index stay stubbornly empty. Same root cause — hold that thought.)

So, you do the natural thing. You open Network Watcher and start debugging the network.

That’s the trap. In the vast majority of these cases, the network is fine. The failure is happening one layer up, in a resource most people barely touch — and it presents as a network problem because of what it does when it’s misconfigured.

The layer you’re probably staring at

The network layer is the obvious suspect, and to be fair, private standard agent setup has a lot of moving parts that can genuinely break:

  • Private endpoints and Private DNS zones for the Foundry account and for each data resource, so name resolution stays inside the VNet.
  • A dedicated subnet delegated to Microsoft.App/environments for network injection.
  • Foundry resource and VNet in the same region.
  • Network injection configured at account creation — it can’t be added to an existing account afterward.

Here’s the useful distinction, though, and it’s the pivot of this whole post: not every “unreachable resource” belongs to the same layer.

The layer that’s actually failing: the capability host

A capability host is a sub-resource you configure at both the Foundry account scope and the Foundry project scope. Its entire job is to tell Agent Service where to store and process agent data — conversation history, file uploads, and vector stores. Capability hosts let you bring your own Azure resources instead of using the default Microsoft-managed platform resources. This gives you:

  • Data sovereignty – Keep all agent data within your Azure subscription.
  • Security control – Use your own storage accounts, databases, and search services.
  • Compliance – Meet specific regulatory or organizational requirements.

If you don’t create capability hosts, Agent Service doesn’t error out. It quietly uses Microsoft-managed resources for all the communications. That’s a fine default for development. The moment you bring your own resources, you’re in “standard agent setup,” and the contract changes: you now have to declare your resources explicitly, in two places, through connections that resolve correctly — or things break.

And here’s why it masquerades as a network problem. The capability host resolves each of your resources through a connection, and that connection has to carry enough information to bind to the right resource at runtime. Four properties matter:

Property What it must be
authTypeThe auth type, e.g. AAD
categoryThe resource type: AzureStorageAccount, AzureCosmosDb, or CognitiveSearch
targetThe service endpoint URL — not the resource ID
metadata.ResourceIdThe full Azure resource ID of the resource

If target is wrong, or metadata.ResourceId is missing or malformed, the agent tries to reach the wrong endpoint — or can’t resolve the resource at all. The symptom you see is a connection or resolution failure. The symptom looks like DNS or a private endpoint. The cause is a bad connection object. This is a resolution problem wearing a **connectivity** costume.

Hidden reason #1: There is no inheritance from account to project

This is the big one, and it’s the reason most private setups fail. It’s natural to assume that if you configure things at the account level — the account capability host, account-level connections — everything cascades down to the projects underneath. It doesn’t.

The project-level capability host is the only thing Agent Service reads to decide which storage, conversation, and vector store resources a project uses. There is no automatic inheritance of BYO resource configuration from the account capability host to the project. Even if your account capability host references every connection perfectly, Agent Service will not use those resources for a project unless that project has its *own* capability host that explicitly references them.

Account-level connections are inherited by new projects — but a connection and a capability host are different things. Inheriting the connection only means the project can see it. You still have to create a project capability host that points at it.

So the classic failure looks like this: you create the account capability host and your connections, maybe reference them at the account level, and stop — because it feels complete. You never created a project capability host, or created one that doesn’t reference your connections. Now Agent Service has no project-level instruction to use your resources, so it either falls back to managed defaults (the silent, empty-container variant) or fails to resolve your resource entirely (the loud, network-error variant). Either way, from your private resource’s point of view, the agent never correctly reached it.

The fix is the project capability host that ties it all together:

PUT https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}/capabilityHosts/{name}?api-version=2025-06-01

{
“properties”: {
“capabilityHostKind”: “Agents”,
“threadStorageConnections”: [“my-cosmos-db-connection”],
“vectorStoreConnections”: [“my-ai-search-connection”],
“storageConnections”: [“my-storage-account-connection”],
“aiServicesConnections”: [“my-azure-openai-connection”]
}
}

aiServicesConnections is optional — include it only if the project should use models from your own Azure OpenAI resource. And ordering matters: you can’t create a project capability host until an account-level one already exists. Account host first, then project host.

How to diagnose this in five minutes

Before you touch Network Watcher again, run these checks. Capability hosts are managed through the REST API today (no SDK surface yet), so this is all ARM. If required, you can use Azure Cloud Shell:

  • Get Access Token:
$TOKEN=$(az account get-access-token –resource https://management.azure.com –query accessToken -o tsv)
  • Confirm the account capability host exists:
curl -X GET “https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/capabilityHosts?api-version=2025-06-01” -H “Authorization: Bearer $TOKEN” -H “Content-Type: application/json” | jq .
  • If account capability host doesn’t exit, create one:
curl -X PUT
“https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/capabilityHosts/{capabilityHostName}?api-version=2025-06-01”
-H “Authorization: Bearer $TOKEN”
-H “Content-Type: application/json”
-d ‘{
“properties”: {
“capabilityHostKind”: “Agents”,
“customerSubnet”: “{subnetId}”,
“threadStorageConnections”: [
“my-cosmos-db-connection”
],
“vectorStoreConnections”: [
“my-ai-search-connection”
],
“storageConnections”: [
“my-storage-account-connection”
],
“aiServicesConnections”: [
“my-azure-openai-connection”
]
}
}’
  • Confirm the project capability host exists — and references the connections you expect. This is the one people are missing.
curl -X GET “https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}/capabilityHosts?api-version=2025-06-01” -H “Authorization: Bearer $TOKEN” -H “Content-Type: application/json” | jq .

Look for threadStorageConnections (Cosmos), vectorStoreConnections (AI Search), and storageConnections (Storage). Empty or missing? That’s your bug – the agent has no instruction to use your resources.

  • If project capability host doesn’t exit, create one:
curl -X PUT
“https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CognitiveServices/accounts/{accountName}/projects/{projectName}/capabilityHosts/{capabilityHostName}?api-version=2025-06-01”
-H “Authorization: Bearer $TOKEN”
-H “Content-Type: application/json”
-d ‘{
“properties”: {
“capabilityHostKind”: “Agents”,
“threadStorageConnections”: [
“my-cosmos-db-connection”
],
“vectorStoreConnections”: [
“my-ai-search-connection”
],
“storageConnections”: [
“my-storage-account-connection”
],
“aiServicesConnections”: [
“my-azure-openai-connection”
]
}
}’
  • Inspect each referenced connection. Verify authTyp, category, target, and especially metadata.ResourceId. Watch the classic swap: target is the endpoint URL, metadata.ResourceId is the ARM resource ID. Reversing them is an easy hand-authoring mistake, and it produces failures that read as network errors. A correct Storage connection looks like this:
{
“properties”: {
“authType”: “AAD”,
“category”: “AzureStorageAccount”,
“target”: “https://{storageAccountName}.blob.core.windows.net/”,
“metadata”: {
“ResourceId”: “/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}”
}
}
}
  • Run a test conversation and check where the data actually landed. Conversations should appear in your Cosmos DB, uploaded files in your Storage account, and vector data in your AI Search index and communication to private MCP server should work.

The gotchas that keep you stuck

Even once you know the fix, a few constraints conspire to make this harder than it should be:

  • Capability hosts can’t be updated. Wrong connection name? You can’t PATCH it — you delete and recreate. Build your automation around delete-then-create, not an idempotent upsert.
  • One capability host per scope. A second one with a different name at the same scope returns 409 Conflict. Same name + same config is idempotent; same name + different config returns a 400.
  • Deleting a capability host affects every dependent agent. Delete the project and account hosts and your agents lose access to the files, conversations, and vector stores they were using. Treat deletion as a breaking change.
  • Permissions. You need Contributor on the Foundry account to create capability hosts, and User Access Administrator or Owner to grant the agent access to your Azure resources.

The mental model to take away

Split the question in two.

  • Connectivity asks: can the agent reach this resource? That’s the network layer — private endpoints, DNS, subnet delegation, the data proxy. It owns your MCP server and any tool egress.
  • Resolution asks: is the project even told to use this resource, and is the connection pointing at the right place? That’s the capability host layer. It owns your Search, Storage, and Cosmos DB.

They fail in completely different ways, and – this is the whole point – a resolution failure can look like a connectivity failure. When all of your data resources go dark at once, that’s your signal it’s resolution, not connectivity. No amount of DNS troubleshooting fixes a project that was never wired to your resources in the first place.

Match the symptom to the layer, and you’ll stop losing days to the wrong one.

A 60-second checklist

[] Account-level capability host exists.

[] Project-level capability host exists (the one everyone misses).

[] The project capability host references your Cosmos, AI Search, and Storage connections by name.

[] Each referenced connection has a valid metadata.ResourceId.

[] target is the endpoint URL and metadata.ResourceId is the ARM resource ID — not swapped.

[] authType and category are correct for each resource type.

If all of these pass and it still doesn’t work — *then* go back to the network.

Further reading

Closing

Private-by-default is the right posture for enterprise agents — but it moves the failure modes around. The instinct to blame the network is strong precisely because the network is where you spent all your effort. Next time your agent can’t reach its private resources, resist that instinct for five minutes and check the project capability host first. More often than not, that’s where the day was hiding.

  This article was originally published by Microsoft's Azure AI Foundry Blog. You can find the original article here.