Skip to content

Commit dda7cba

Browse files
authored
Merge pull request #2 from nourshaker-msft/main
Changes to ensure inter-oprability with WSL and missing items from the VM Start up
2 parents 9e47c08 + 4e22f00 commit dda7cba

File tree

4 files changed

+87
-34
lines changed

4 files changed

+87
-34
lines changed

labs/ai-foundry-private-mcp/agent/load_env_from_kv.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ def load_secrets_from_keyvault(vault_url: str):
2121
env_var_name = secret_name.replace("-", "_")
2222
os.environ[env_var_name] = secret.value
2323
except Exception as e:
24+
print(f"Error loading secret {secret_name}: {e}")
2425

2526
print("Environment variables loaded successfully!")

labs/ai-foundry-private-mcp/clean-up-resources.ipynb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
"import utils\n",
2121
"\n",
2222
"deployment_name = os.path.basename(os.path.dirname(globals()['__vsc_ipynb_file__']))\n",
23-
"utils.cleanup_resources(deployment_name)"
23+
"resource_group = f\"lab-{deployment_name}\"\n",
24+
"\n",
25+
"utils.cleanup_resources(deployment_name, resource_group_name=resource_group)"
2426
]
2527
}
2628
],
2729
"metadata": {
2830
"kernelspec": {
29-
"display_name": ".venv",
31+
"display_name": "base",
3032
"language": "python",
3133
"name": "python3"
3234
},
@@ -40,7 +42,7 @@
4042
"name": "python",
4143
"nbconvert_exporter": "python",
4244
"pygments_lexer": "ipython3",
43-
"version": "3.11.0rc2"
45+
"version": "3.12.2"
4446
}
4547
},
4648
"nbformat": 4,

labs/ai-foundry-private-mcp/foundry-private-mcp.ipynb

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"## Microsoft Foundry Private connectivity lab\n",
1010
"![flow](./architecture.png)\n",
1111
"\n",
12-
"Playground to show how to create a private network for consuming REST API managed as MCPs using `Foundry Agents V1`. This lab demonstrates how to create a private network for consuming MCPs from `Foundry Agents V1` using `Private Link Services`, `Azure API Management (APIM)`, and `Azure Front Door`.\n",
12+
"Playground to show how to create a private network for consuming REST API managed as MCPs using `Foundry Agents Classic`. This lab demonstrates how to create a private network for consuming MCPs from `Foundry Agents Classic` using `Private Link Services`, `Azure API Management (APIM)`, and `Azure Front Door`.\n",
1313
"\n",
1414
"Notes:\n",
1515
"- `Foundry Agents` are only accessible through `Private Endpoints`. Public network access is disabled.\n",
@@ -19,6 +19,7 @@
1919
"- **`Azure Front Door` is the only publicly-accessible service.**\n",
2020
"\n",
2121
"### Prerequisites\n",
22+
"- **important** Admin access to be able to deploy and configure Enterprise Applications in Entra ID\n",
2223
"- [Python 3.12 or later version](https://www.python.org/) installed\n",
2324
"- [Pandas Library](https://pandas.pydata.org/) and matplotlib installed\n",
2425
"- [VS Code](https://code.visualstudio.com/) installed with the [Jupyter notebook extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) enabled\n",
@@ -160,10 +161,10 @@
160161
"if output.success and output.json_data:\n",
161162
" apim_resource_id = utils.get_deployment_output(output, 'apimResourceId', 'apimResourceId')\n",
162163
"\n",
163-
" outputPls = utils.run(f\"az network private-endpoint-connection list --id {apim_resource_id} --query [?properties.privateLinkServiceConnectionState.status=='Pending'].id --output tsv\")\n",
164+
" outputPls = utils.run(f\"az network private-endpoint-connection list --id {apim_resource_id} --query \\\"[?properties.privateLinkServiceConnectionState.status=='Pending'].id\\\" --output tsv\")\n",
164165
"\n",
165166
" if outputPls.success:\n",
166-
" pls_connection_id = outputPls.text\n",
167+
" pls_connection_id = outputPls.text.strip()\n",
167168
" print(pls_connection_id)\n",
168169
" utils.run(f\"az network private-endpoint-connection approve --id {pls_connection_id} --description 'Approved'\", f\"Private Link Connection approved.\", f\"Failed to approve Private Link Connection: {pls_connection_id}\")"
169170
]
@@ -219,7 +220,9 @@
219220
" apim_resource_gateway_url = utils.get_deployment_output(output, 'apimResourceGatewayURL', 'APIM API Gateway URL')\n",
220221
" apim_subscription_key = utils.get_deployment_output(output, 'apimSubscriptionKey', 'APIM Subscription Key (masked)', True)\n",
221222
" ai_foundry_project_endpoint = utils.get_deployment_output(output, 'ai_project_endpoint', 'AI Foundry Project Endpoint')\n",
222-
" ai_deployment_name = utils.get_deployment_output(output, 'ai_model_deployment_name', 'AI Model Deployment Name')"
223+
" ai_deployment_name = utils.get_deployment_output(output, 'ai_model_deployment_name', 'AI Model Deployment Name')\n",
224+
" vm_resource_id = utils.get_deployment_output(output, 'vmResourceId', 'VM Resource ID')\n",
225+
" key_vault_url = utils.get_deployment_output(output, 'keyVaultUrl', 'Key Vault URL')"
223226
]
224227
},
225228
{
@@ -229,7 +232,9 @@
229232
"<a id='6'></a>\n",
230233
"### 6️⃣ 🧪 Test the API using a direct HTTP call through Frontdoor\n",
231234
"\n",
232-
"Requests is an elegant and simple HTTP library for Python that will be used here to make raw API requests and inspect the responses. "
235+
"Requests is an elegant and simple HTTP library for Python that will be used here to make raw API requests and inspect the responses.\n",
236+
"\n",
237+
"**After a successful deployment it may take up to 45 mins for this cell to run correctly**"
233238
]
234239
},
235240
{
@@ -245,10 +250,11 @@
245250
" request = {\"method\":\"tools/call\",\"params\":{\"name\":\"PlaceOrder-invoke\",\"arguments\":{\"request-PlaceOrder\":{\"sku\":\"sku-123\",\"quantity\":5}}},\"jsonrpc\":\"2.0\",\"id\":1}\n",
246251
" print(f\"Calling MCP endpoint at URL: {url}\")\n",
247252
" utils.print_info(\"Calling MCP endpoint WITH authorization...\")\n",
248-
" output = utils.run(\"az account get-access-token --resource \\\"https://azure-api.net/authorization-manager\\\"\")\n",
253+
" output = utils.run('az account get-access-token --resource \"https://azure-api.net/authorization-manager\"')\n",
249254
"\n",
250255
" access_token = output.json_data['accessToken']\n",
251256
" response = requests.post(url, stream=True, headers={\"Content-Type\": \"application/json\", \"agent-id\": \"Agent1\", \"Authorization\": \"Bearer \" + str(access_token)}, json=request)\n",
257+
" print(response.status_code)\n",
252258
"\n",
253259
" print(f\"Response status code: {response.status_code}\")\n",
254260
"\n",
@@ -265,7 +271,11 @@
265271
" data = json.loads(line.strip()[5:])\n",
266272
" print(json.dumps(json.loads(data[\"result\"][\"content\"][0][\"text\"]), indent=4))\n",
267273
" else:\n",
268-
" utils.print_error(f\"Unexpected status code: {response.status_code}. Response text: {response.text}\")\n",
274+
" try:\n",
275+
" error_text = response.text\n",
276+
" except Exception:\n",
277+
" error_text = \"[Unable to read response body]\"\n",
278+
" utils.print_error(f\"Unexpected status code: {response.status_code}.\")\n",
269279
"\n",
270280
"call_mcp_api(f\"https://{frontdoor_endpoint}\")"
271281
]
@@ -304,12 +314,28 @@
304314
"\n"
305315
]
306316
},
317+
{
318+
"cell_type": "code",
319+
"execution_count": null,
320+
"metadata": {},
321+
"outputs": [],
322+
"source": [
323+
"utils.print_info(f\"\"\"Run the following command to connect to the VM via Bastion: \n",
324+
"az network bastion ssh \\\n",
325+
"--name bastion-host \\\n",
326+
"--resource-group {resource_group_name} \\\n",
327+
"--target-resource-id {vm_resource_id} \\\n",
328+
"--auth-type password \\\n",
329+
"--username azureuser\n",
330+
"\"\"\")"
331+
]
332+
},
307333
{
308334
"cell_type": "markdown",
309335
"metadata": {},
310336
"source": [
311337
"##### 1. Connect to Jumpbox\n",
312-
"Use Azure Bastion to connect to the VM:\n",
338+
"Use Azure Bastion to connect to the VM (full command is the output from previos cell):\n",
313339
"\n",
314340
"```bash\n",
315341
"az network bastion ssh \\\n",
@@ -320,35 +346,28 @@
320346
" --username azureuser\n",
321347
"```\n",
322348
"\n",
349+
"***Password:***\n",
350+
"```bash\n",
351+
"@Aa123456789\n",
352+
"```\n",
353+
"\n",
323354
"Or connect via the Azure Portal: Navigate to the VM → Connect → Bastion\n",
324355
"\n",
325356
"Once connected to the jumpbox, create the required scripts:\n",
326357
"\n",
327-
"##### 2. Create `scripts/load_env_from_kv.py`\n",
358+
"##### 2. Download needed files\n",
328359
"```bash \n",
329-
"cat > ~/scripts/load_env_from_kv.py << 'EOF'\n",
330-
"# Paste content of agent/load_env_from_kv.py here\n",
331-
"EOF\n",
360+
"wget https://raw.githubusercontent.com/pablocast/AI-Gateway/refs/heads/main/labs/ai-foundry-private-mcp/agent/load_env_from_kv.py\n",
361+
"wget https://raw.githubusercontent.com/pablocast/AI-Gateway/refs/heads/main/labs/ai-foundry-private-mcp/agent/sample_agents_mcp.py\n",
332362
"```\n",
333363
"\n",
334-
"\n",
335-
"##### 3. Create `scripts/sample_agents_mcp.py`\n",
336-
"```bash \n",
337-
"cat > ~/scripts/sample_agents_mcp.py << 'EOF'\n",
338-
"# Paste content of agent/sample_agents_mcp.py here\n",
339-
"EOF\n",
340-
"```\n",
341-
"\n",
342-
"##### 4. Run the agent\n",
364+
"##### 3. Run the agent\n",
343365
"```bash\n",
344-
"# Activate virtual environment\n",
345-
"source ~/venv/bin/activate\n",
346-
"\n",
347366
"# Get Key Vault URL from deployment outputs\n",
348-
"KEY_VAULT_URL=\"https://kv-xxxxx.vault.azure.net/\"\n",
367+
"KEY_VAULT_URL=\"<KEY-VAULT-URL>\"\n",
349368
"\n",
350369
"# Run the MCP agent\n",
351-
"python3 ~/scripts/sample_agents_mcp.py $KEY_VAULT_URL\n",
370+
"python3 ~/sample_agents_mcp.py $KEY_VAULT_URL\n",
352371
"```\n",
353372
"\n"
354373
]
@@ -367,7 +386,7 @@
367386
],
368387
"metadata": {
369388
"kernelspec": {
370-
"display_name": ".venv",
389+
"display_name": "base",
371390
"language": "python",
372391
"name": "python3"
373392
},
@@ -381,7 +400,7 @@
381400
"name": "python",
382401
"nbconvert_exporter": "python",
383402
"pygments_lexer": "ipython3",
384-
"version": "3.12.10"
403+
"version": "3.12.2"
385404
}
386405
},
387406
"nbformat": 4,

labs/ai-foundry-private-mcp/main.bicep

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ resource nsgApim 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
8181
location: resourceGroup().location
8282
}
8383

84+
// NSG for VM Subnet
85+
resource nsgVm 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
86+
name: 'nsg-vm'
87+
location: resourceGroup().location
88+
properties: {
89+
securityRules: [
90+
{
91+
name: 'AllowSSH'
92+
properties: {
93+
priority: 100
94+
direction: 'Inbound'
95+
access: 'Allow'
96+
protocol: 'Tcp'
97+
sourcePortRange: '*'
98+
destinationPortRange: '22'
99+
sourceAddressPrefix: '*'
100+
destinationAddressPrefix: '*'
101+
}
102+
}
103+
]
104+
}
105+
}
106+
84107
// VNET and Subnets
85108
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' = {
86109
name: virtualNetworkName
@@ -117,6 +140,9 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' = {
117140
name: subnetVmName
118141
properties: {
119142
addressPrefix: '10.0.2.0/24'
143+
networkSecurityGroup: {
144+
id: nsgVm.id
145+
}
120146
}
121147
}
122148
{
@@ -710,15 +736,19 @@ resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2023-09-01' =
710736
apt-get update
711737
712738
# Install Python and venv
713-
apt-get install -y python3 python3-pip python3-venv python3-full
739+
apt-get install -y python3 python3-pip python3-venv python3-full vim wget
714740
715741
# Create a virtual environment for the azureuser
716742
mkdir -p /home/azureuser/scripts
717743
python3 -m venv /home/azureuser/venv
744+
745+
wget -O /home/azureuser/requirements.txt https://raw.githubusercontent.com/Azure-Samples/AI-Gateway/refs/heads/main/requirements.txt
718746
719747
# Install packages in the virtual environment
720748
/home/azureuser/venv/bin/pip install --upgrade pip
721-
/home/azureuser/venv/bin/pip install requests azure-identity azure-ai-projects azure-ai-agents==1.2.0b6 requests jsonref python-dotenv azure-keyvault-secrets
749+
/home/azureuser/venv/bin/pip install -r /home/azureuser/requirements.txt
750+
751+
echo "source /home/azureuser/venv/bin/activate" >> /home/azureuser/.bashrc
722752
723753
# Set ownership
724754
chown -R azureuser:azureuser /home/azureuser/venv
@@ -846,7 +876,7 @@ module keyVaultModule './modules/keyvault.bicep' = {
846876
privateEndpointSubnetId: subnetPe.id
847877
virtualNetworkId: virtualNetwork.id
848878
secrets: {
849-
'MCP-SERVER-URL': 'https://${frontDoorEndpoint.properties.hostName}/order-mcp/mcp'
879+
'MCP-SERVER-URL': '${frontDoorEndpoint.properties.hostName}/order-mcp/mcp'
850880
'MCP-SERVER-LABEL': 'order_mcp'
851881
'AZURE-AI-PROJECT-ENDPOINT': foundryAccountModule.outputs.aiFoundryProjectEndpoint
852882
'AZURE-AI-MODEL-DEPLOYMENT-NAME': modelsConfig[0].name
@@ -870,3 +900,4 @@ output frontDoorEndpointHostName string = frontDoorEndpoint.properties.hostName
870900
output ai_project_endpoint string = foundryAccountModule.outputs.aiFoundryProjectEndpoint
871901
output ai_model_deployment_name string = modelsConfig[0].name
872902
output keyVaultUrl string = keyVaultModule.outputs.keyVaultUrl
903+
output vmResourceId string = vm.id

0 commit comments

Comments
 (0)