392 lines
15 KiB
Plaintext
392 lines
15 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b8c57858",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Azure AI Agents with Model Context Protocol (MCP) Support - Python\n",
|
|
"\n",
|
|
"This notebook demonstrates how to use Azure AI Agents with Model Context Protocol (MCP) tools in Python. It shows how to create an intelligent agent that can leverage external MCP servers (like Microsoft Learn) for enhanced capabilities using keyless authentication."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "2e6e4234",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Install Required Python Packages\n",
|
|
"\n",
|
|
"First, we need to install the necessary Python packages:\n",
|
|
"- **azure-ai-projects**: Core Azure AI Projects SDK\n",
|
|
"- **azure-ai-agents**: Azure AI Agents SDK for creating and managing agents\n",
|
|
"- **azure-identity**: Provides keyless authentication using DefaultAzureCredential\n",
|
|
"- **mcp**: Model Context Protocol implementation for Python"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6a2e9a05",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Keyless Authentication Benefits\n",
|
|
"\n",
|
|
"This notebook demonstrates **keyless authentication** which provides several advantages:\n",
|
|
"- ✅ **No API keys to manage** - Uses Azure identity-based authentication\n",
|
|
"- ✅ **Enhanced security** - No secrets stored in code or configuration files\n",
|
|
"- ✅ **Automatic credential rotation** - Azure handles credential lifecycle management\n",
|
|
"- ✅ **Role-based access control** - Uses Azure RBAC for fine-grained permissions\n",
|
|
"- ✅ **Multi-environment support** - Works seamlessly across development and production\n",
|
|
"\n",
|
|
"The `DefaultAzureCredential` automatically selects the best available credential source:\n",
|
|
"1. **Managed Identity** (when running in Azure)\n",
|
|
"2. **Azure CLI** credentials (during local development)\n",
|
|
"3. **Visual Studio** credentials\n",
|
|
"4. **Environment variables** (if configured)\n",
|
|
"5. **Interactive browser** authentication (as fallback)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "43efa94d",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Keyless Authentication Setup\n",
|
|
"\n",
|
|
"**Prerequisites for keyless authentication:**\n",
|
|
"\n",
|
|
"### For Local Development:\n",
|
|
"```bash\n",
|
|
"# Install Azure CLI and login\n",
|
|
"az login\n",
|
|
"# Verify your identity\n",
|
|
"az account show\n",
|
|
"```\n",
|
|
"\n",
|
|
"### For Azure Environments:\n",
|
|
"- Enable **System-assigned Managed Identity** on your Azure resource\n",
|
|
"- Assign appropriate **RBAC roles** to the managed identity:\n",
|
|
" - `Cognitive Services OpenAI User` for Azure OpenAI access\n",
|
|
" - `AI Developer` for Azure AI Projects access\n",
|
|
"\n",
|
|
"### Environment Variables (Optional):\n",
|
|
"```python\n",
|
|
"# These are automatically detected by DefaultAzureCredential\n",
|
|
"# AZURE_CLIENT_ID=<your-client-id>\n",
|
|
"# AZURE_CLIENT_SECRET=<your-client-secret>\n",
|
|
"# AZURE_TENANT_ID=<your-tenant-id>\n",
|
|
"```\n",
|
|
"\n",
|
|
"**No API keys or connection strings needed!** 🔐"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2e21387d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"! pip install azure-ai-projects -U\n",
|
|
"! pip install azure-ai-agents==1.1.0b4 -U\n",
|
|
"! pip install azure-identity -U\n",
|
|
"! pip install mcp==1.11.0 -U"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b31c8873",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Import Required Libraries\n",
|
|
"\n",
|
|
"Import the necessary Python modules:\n",
|
|
"- **os, time**: Standard Python libraries for environment variables and delays\n",
|
|
"- **AIProjectClient**: Main client for Azure AI Projects\n",
|
|
"- **DefaultAzureCredential**: Keyless authentication for Azure services\n",
|
|
"- **MCP-related classes**: For creating and managing MCP tools and handling approvals"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "43667b32",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os, time\n",
|
|
"from azure.ai.projects import AIProjectClient\n",
|
|
"from azure.identity import DefaultAzureCredential\n",
|
|
"from azure.ai.agents.models import McpTool, RequiredMcpToolCall, SubmitToolApprovalAction, ToolApproval\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "721355e5",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Configure MCP Server Settings\n",
|
|
"\n",
|
|
"Set up the MCP server configuration using environment variables with fallback defaults:\n",
|
|
"- **MCP_SERVER_URL**: The URL of the MCP server (defaults to Microsoft Learn API)\n",
|
|
"- **MCP_SERVER_LABEL**: A label to identify the MCP server (defaults to \"mslearn\")\n",
|
|
"\n",
|
|
"This approach allows for flexible configuration across different environments."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "189f3d55",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"mcp_server_url = os.environ.get(\"MCP_SERVER_URL\", \"https://learn.microsoft.com/api/mcp\")\n",
|
|
"mcp_server_label = os.environ.get(\"MCP_SERVER_LABEL\", \"mslearn\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "20612d9a",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Create Azure AI Project Client (Keyless Authentication)\n",
|
|
"\n",
|
|
"Initialize the Azure AI Project client using **keyless authentication**:\n",
|
|
"- **endpoint**: The Azure AI Foundry project endpoint URL\n",
|
|
"- **credential**: Uses `DefaultAzureCredential()` for secure, keyless authentication\n",
|
|
"- **No API keys required**: Automatically discovers and uses the best available credential\n",
|
|
"\n",
|
|
"**Authentication Flow:**\n",
|
|
"1. Checks for Managed Identity (in Azure environments)\n",
|
|
"2. Falls back to Azure CLI credentials (for local development)\n",
|
|
"3. Uses other available credential sources as needed\n",
|
|
"\n",
|
|
"This approach eliminates the need to manage API keys or connection strings in your code."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "36b1dbd8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"project_client = AIProjectClient(\n",
|
|
" endpoint=\"Your Azure AI Foundry Endpoint\",\n",
|
|
" credential=DefaultAzureCredential(),\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cdb6ab8c",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Create MCP Tool Definition\n",
|
|
"\n",
|
|
"Create an MCP tool that connects to the Microsoft Learn MCP server:\n",
|
|
"- **server_label**: Identifier for the MCP server\n",
|
|
"- **server_url**: URL endpoint of the MCP server\n",
|
|
"- **allowed_tools**: Optional list to restrict which tools can be used (empty list allows all tools)\n",
|
|
"\n",
|
|
"This tool will enable the agent to access Microsoft Learn documentation and resources."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "51e7e136",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"mcp_tool = McpTool(\n",
|
|
" server_label=mcp_server_label,\n",
|
|
" server_url=mcp_server_url,\n",
|
|
" allowed_tools=[], # Optional: specify allowed tools\n",
|
|
")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6e894b0b",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Create Agent and Execute Conversation (Keyless Workflow)\n",
|
|
"\n",
|
|
"This comprehensive section demonstrates the complete **keyless agent workflow**:\n",
|
|
"\n",
|
|
"1. **Create AI Agent**: Set up an agent with GPT-4.1 nano model and MCP tools\n",
|
|
"2. **Create Thread**: Establish a conversation thread for communication\n",
|
|
"3. **Send Message**: Ask the agent about Azure OpenAI vs OpenAI differences\n",
|
|
"4. **Handle Tool Approvals**: Automatically approve MCP tool calls when required\n",
|
|
"5. **Monitor Execution**: Track the agent's progress and handle any required actions\n",
|
|
"6. **Display Results**: Show the conversation and tool usage details\n",
|
|
"\n",
|
|
"**Keyless Features:**\n",
|
|
"- ✅ **No hardcoded secrets** - All authentication handled by Azure identity\n",
|
|
"- ✅ **Secure by default** - Uses role-based access control\n",
|
|
"- ✅ **Simplified deployment** - No credential management required\n",
|
|
"- ✅ **Audit-friendly** - All access is tracked through Azure identity\n",
|
|
"\n",
|
|
"The agent will use MCP tools to access Microsoft Learn resources with full security and no API key management."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "68c49af5",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"with project_client:\n",
|
|
" agents_client = project_client.agents\n",
|
|
"\n",
|
|
" # Create a new agent with keyless authentication\n",
|
|
" # NOTE: To reuse existing agent, fetch it with get_agent(agent_id)\n",
|
|
" agent = agents_client.create_agent(\n",
|
|
" model=\"Your Azure OpenAI Model Deployment Name\",\n",
|
|
" name=\"my-mcp-agent\",\n",
|
|
" instructions=\"You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.\",\n",
|
|
" tools=mcp_tool.definitions,\n",
|
|
" )\n",
|
|
" print(f\"Created agent, ID: {agent.id}\")\n",
|
|
" print(f\"MCP Server: {mcp_tool.server_label} at {mcp_tool.server_url}\")\n",
|
|
"\n",
|
|
" # Create thread for communication\n",
|
|
" thread = agents_client.threads.create()\n",
|
|
" print(f\"Created thread, ID: {thread.id}\")\n",
|
|
"\n",
|
|
" # Create message to thread\n",
|
|
" message = agents_client.messages.create(\n",
|
|
" thread_id=thread.id,\n",
|
|
" role=\"user\",\n",
|
|
" content=\"What's difference between Azure OpenAI and OpenAI?\",\n",
|
|
" )\n",
|
|
" print(f\"Created message, ID: {message.id}\")\n",
|
|
"\n",
|
|
" # KEYLESS APPROACH: Handle tool approvals without hardcoded secrets\n",
|
|
" \n",
|
|
" # Option 1: Completely keyless (recommended for Azure identity-enabled MCP servers)\n",
|
|
" # run = agents_client.runs.create(thread_id=thread.id, agent_id=agent.id, tool_resources=mcp_tool.resources)\n",
|
|
" \n",
|
|
" # Option 2: With minimal headers (if MCP server requires specific headers)\n",
|
|
" # For demonstration purposes, using a placeholder header\n",
|
|
" mcp_tool.update_headers(\"SuperSecret\", \"123456\") # Replace with actual auth if needed\n",
|
|
" \n",
|
|
" # Set approval mode - uncomment next line to disable approval requirement completely\n",
|
|
" # mcp_tool.set_approval_mode(\"never\") # Fully automated, no approval needed\n",
|
|
" \n",
|
|
" run = agents_client.runs.create(thread_id=thread.id, agent_id=agent.id, tool_resources=mcp_tool.resources)\n",
|
|
" print(f\"Created run, ID: {run.id}\")\n",
|
|
"\n",
|
|
" while run.status in [\"queued\", \"in_progress\", \"requires_action\"]:\n",
|
|
" time.sleep(1)\n",
|
|
" run = agents_client.runs.get(thread_id=thread.id, run_id=run.id)\n",
|
|
"\n",
|
|
" if run.status == \"requires_action\" and isinstance(run.required_action, SubmitToolApprovalAction):\n",
|
|
" tool_calls = run.required_action.submit_tool_approval.tool_calls\n",
|
|
" if not tool_calls:\n",
|
|
" print(\"No tool calls provided - cancelling run\")\n",
|
|
" agents_client.runs.cancel(thread_id=thread.id, run_id=run.id)\n",
|
|
" break\n",
|
|
"\n",
|
|
" tool_approvals = []\n",
|
|
" for tool_call in tool_calls:\n",
|
|
" if isinstance(tool_call, RequiredMcpToolCall):\n",
|
|
" try:\n",
|
|
" print(f\"Approving tool call: {tool_call}\")\n",
|
|
" \n",
|
|
" # KEYLESS APPROVAL OPTIONS:\n",
|
|
" \n",
|
|
" # Option 1: No headers (fully keyless)\n",
|
|
" # tool_approvals.append(\n",
|
|
" # ToolApproval(\n",
|
|
" # tool_call_id=tool_call.id,\n",
|
|
" # approve=True,\n",
|
|
" # headers={} # No headers needed for keyless\n",
|
|
" # )\n",
|
|
" # )\n",
|
|
" \n",
|
|
" # Option 2: With headers (if MCP server requires them)\n",
|
|
" tool_approvals.append(\n",
|
|
" ToolApproval(\n",
|
|
" tool_call_id=tool_call.id,\n",
|
|
" approve=True,\n",
|
|
" headers=mcp_tool.headers, # Uses configured headers if needed\n",
|
|
" )\n",
|
|
" )\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"Error approving tool_call {tool_call.id}: {e}\")\n",
|
|
"\n",
|
|
" print(f\"tool_approvals: {tool_approvals}\")\n",
|
|
" if tool_approvals:\n",
|
|
" agents_client.runs.submit_tool_outputs(\n",
|
|
" thread_id=thread.id, run_id=run.id, tool_approvals=tool_approvals\n",
|
|
" )\n",
|
|
"\n",
|
|
" print(f\"Current run status: {run.status}\")\n",
|
|
"\n",
|
|
" print(f\"Run completed with status: {run.status}\")\n",
|
|
" if run.status == \"failed\":\n",
|
|
" print(f\"Run failed: {run.last_error}\")\n",
|
|
"\n",
|
|
" # Display run steps and tool calls\n",
|
|
" run_steps = agents_client.run_steps.list(thread_id=thread.id, run_id=run.id)\n",
|
|
"\n",
|
|
" # Loop through each step\n",
|
|
" for step in run_steps:\n",
|
|
" print(f\"Step {step['id']} status: {step['status']}\")\n",
|
|
"\n",
|
|
" # Check if there are tool calls in the step details\n",
|
|
" step_details = step.get(\"step_details\", {})\n",
|
|
" tool_calls = step_details.get(\"tool_calls\", [])\n",
|
|
"\n",
|
|
" if tool_calls:\n",
|
|
" print(\" MCP Tool calls:\")\n",
|
|
" for call in tool_calls:\n",
|
|
" print(f\" Tool Call ID: {call.get('id')}\")\n",
|
|
" print(f\" Type: {call.get('type')}\")\n",
|
|
"\n",
|
|
" print() # add an extra newline between steps\n",
|
|
"\n",
|
|
" # Fetch and log all messages\n",
|
|
" messages = agents_client.messages.list(thread_id=thread.id)\n",
|
|
" print(\"\\nConversation:\")\n",
|
|
" print(\"-\" * 50)\n",
|
|
" for msg in messages:\n",
|
|
" if msg.text_messages:\n",
|
|
" last_text = msg.text_messages[-1]\n",
|
|
" print(f\"{msg.role.upper()}: {last_text.text.value}\")\n",
|
|
" print(\"-\" * 50)\n",
|
|
"\n",
|
|
" # Example of dynamic tool management (keyless)\n",
|
|
" print(f\"\\nDemonstrating keyless dynamic tool management:\")\n",
|
|
" print(f\"Current allowed tools: {mcp_tool.allowed_tools}\")\n",
|
|
" print(\"✅ All operations completed using keyless authentication!\")"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "demo",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.10.15"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|