Skip to content

Commit 6a638fb

Browse files
ferantiveroCopilot
andauthored
feat (iac): [networking] add MCP subnet (#106)
* reserve private MCP server subnet Prepare the workload virtual network for privately hosted MCP servers: - reserve a dedicated /24 subnet - delegate the subnet to Microsoft.App/environments - expose the subnet name and resource ID Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * secure private MCP server subnet Apply deny-by-default controls for a future Container Apps environment: - allow required platform, DNS, load-balancer, and intra-subnet traffic - allow access to workload private endpoints - attach the NSG to the reserved MCP subnet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * allow private agent MCP traffic Enable the private MCP request path within the workload virtual network: - allow Foundry Agent outbound HTTPS to the MCP subnet - allow matching inbound HTTPS on the MCP subnet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * route MCP egress through firewall Control private MCP server outbound traffic through Azure Firewall: - associate the MCP subnet with the egress route table - allow required Container Apps platform dependencies - wire the MCP subnet into the firewall module Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * document private MCP network path Clarify how agents reach public and private MCP servers: - describe firewall-controlled public MCP egress - identify the reserved private MCP subnet - note that the Container Apps environment is not deployed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR Feedback: clarify probe ports Document the Container Apps load-balancer probe rule lifecycle: - retain the required dynamic allocation range initially - narrow the NSG rule after Azure assigns the immutable ports Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e0d3fda commit 6a638fb

4 files changed

Lines changed: 314 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Microsoft Foundry hosts Foundry Agent Service as a capability. Foundry Agent ser
3939
1. When the web application receives a message, it passes it along with the conversation ID to the Foundry prompt-based agent through the [Microsoft Agent Framework](https://github.com/microsoft/agent-framework), which calls the [OpenAI Conversations](https://ai.azure.com/api-reference/conversations/create-conversation/) and [Responses](https://ai.azure.com/api-reference/responses/create-response/) APIs under the hood. The web application calls the agent over a private endpoint and authenticates to Foundry by using its managed identity.
4040
1. The agent processes the user's request based on the instructions in its system prompt. To fulfill the user's intent, the agent uses a configured language model and connected tools and knowledge stores.
4141
1. The agent connects to the knowledge store (Azure AI Search) in the private network via a private endpoint.
42-
1. Requests to most external knowledge stores or tools, such as Wikipedia, traverse Azure Firewall for inspection and egress policy enforcement. Some of Foundry's built-in connections might not support egressing through your subnet.
42+
1. Requests to approved public tools, such as public MCP servers, traverse Azure Firewall for inspection and egress policy enforcement. Calls to private MCP servers use private addressing to reach a dedicated subnet in the workload virtual network. The implementation reserves and secures this delegated subnet but doesn't deploy an MCP server or Azure Container Apps environment.
4343
1. The agent connects to its configured language model and passes relevant context.
4444
1. Before the agent returns the response to the UI, it persists the request, the generated response, and a list of consulted knowledge stores into a dedicated memory database. This database maintains the complete conversation history, which enables context-aware interactions and allows users to resume conversations with the agent without losing prior context.
4545

infra-as-code/bicep/azure-firewall.bicep

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ param logAnalyticsWorkspaceName string
1616
@minLength(8)
1717
param agentsEgressSubnetName string
1818

19+
@description('The name of the subnet reserved for private MCP servers. Must be in the same virtual network that is provided.')
20+
@minLength(8)
21+
param mcpServersSubnetName string
22+
1923
@description('The name of the subnet containing your jump boxes. Must be in the same virtual network that is provided.')
2024
@minLength(8)
2125
param jumpBoxesSubnetName string
@@ -34,6 +38,10 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-07-01' existing
3438
name: agentsEgressSubnetName
3539
}
3640

41+
resource mcpServersSubnet 'subnets' existing = {
42+
name: mcpServersSubnetName
43+
}
44+
3745
resource jumpBoxesSubnet 'subnets' existing = {
3846
name: jumpBoxesSubnetName
3947
}
@@ -98,6 +106,29 @@ resource azureFirewallPolicy 'Microsoft.Network/firewallPolicies@2025-07-01' = {
98106
properties: {
99107
priority: 200
100108
ruleCollections: [
109+
{
110+
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
111+
name: 'mcp-platform-egress'
112+
priority: 900
113+
action: {
114+
type: 'Allow'
115+
}
116+
rules: [
117+
{
118+
ruleType: 'NetworkRule'
119+
name: 'allow-container-apps-platform'
120+
ipProtocols: ['TCP']
121+
sourceAddresses: ['${virtualNetwork::mcpServersSubnet.properties.addressPrefix}']
122+
destinationAddresses: [
123+
'MicrosoftContainerRegistry'
124+
'AzureFrontDoor.FirstParty'
125+
'AzureActiveDirectory'
126+
'AzureMonitor'
127+
]
128+
destinationPorts: ['443']
129+
}
130+
]
131+
}
101132
{
102133
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
103134
name: 'jump-box-egress'
@@ -126,6 +157,39 @@ resource azureFirewallPolicy 'Microsoft.Network/firewallPolicies@2025-07-01' = {
126157
properties: {
127158
priority: 300
128159
ruleCollections: [
160+
{
161+
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
162+
name: 'mcp-server-egress'
163+
priority: 900
164+
action: {
165+
type: 'Allow'
166+
}
167+
rules: [
168+
{
169+
ruleType: 'ApplicationRule'
170+
name: 'allow-container-apps-dependencies'
171+
protocols: [
172+
{
173+
protocolType: 'Https'
174+
port: 443
175+
}
176+
]
177+
fqdnTags: []
178+
webCategories: []
179+
targetFqdns: [
180+
'packages.aks.azure.com'
181+
'acs-mirror.azureedge.net'
182+
'*.identity.azure.net'
183+
// 'api.example.org' // Add the public FQDNs that your MCP servers require.
184+
]
185+
targetUrls: []
186+
terminateTLS: false
187+
sourceAddresses: ['${virtualNetwork::mcpServersSubnet.properties.addressPrefix}']
188+
destinationAddresses: []
189+
httpHeadersToInsert: []
190+
}
191+
]
192+
}
129193
{
130194
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
131195
name: 'agent-egress'

infra-as-code/bicep/main.bicep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ module deployAzureFirewall 'azure-firewall.bicep' = {
8686
logAnalyticsWorkspaceName: logAnalyticsWorkspace.name
8787
virtualNetworkName: deployVirtualNetwork.outputs.virtualNetworkName
8888
agentsEgressSubnetName: deployVirtualNetwork.outputs.agentsEgressSubnetName
89+
mcpServersSubnetName: deployVirtualNetwork.outputs.mcpServersSubnetName
8990
jumpBoxesSubnetName: deployVirtualNetwork.outputs.jumpBoxesSubnetName
9091
}
9192
}

0 commit comments

Comments
 (0)