Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion python/packages/azure-ai/agent_framework_azure_ai/_client.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Copyright (c) Microsoft. All rights reserved.

import sys
from collections.abc import MutableSequence
from collections.abc import MutableMapping, MutableSequence
from typing import Any, ClassVar, TypeVar

from agent_framework import (
AGENT_FRAMEWORK_USER_AGENT,
ChatMessage,
ChatOptions,
HostedMCPTool,
TextContent,
get_logger,
use_chat_middleware,
Expand All @@ -18,6 +19,7 @@
from agent_framework.openai._responses_client import OpenAIBaseResponsesClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import (
MCPTool,
PromptAgentDefinition,
PromptAgentDefinitionText,
ResponseTextFormatConfigurationJsonSchema,
Expand Down Expand Up @@ -325,3 +327,27 @@ def _update_agent_name(self, agent_name: str | None) -> None:
# to update the agent name in the client.
if agent_name and not self.agent_name:
self.agent_name = agent_name

def get_mcp_tool(self, tool: HostedMCPTool) -> MutableMapping[str, Any]:
"""Get MCP tool from HostedMCPTool."""
mcp: MCPTool = {
"type": "mcp",
"server_label": tool.name.replace(" ", "_"),
"server_url": str(tool.url),
}

if tool.allowed_tools:
mcp["allowed_tools"] = list(tool.allowed_tools)

# TODO (dmytrostruk): Check "always" approval mode
if tool.approval_mode:
match tool.approval_mode:
case str():
mcp["require_approval"] = "always" if tool.approval_mode == "always_require" else "never"
case _:
if always_require_approvals := tool.approval_mode.get("always_require_approval"):
mcp["require_approval"] = {"always": {"tool_names": list(always_require_approvals)}}
if never_require_approvals := tool.approval_mode.get("never_require_approval"):
mcp["require_approval"] = {"never": {"tool_names": list(never_require_approvals)}}

return mcp
47 changes: 22 additions & 25 deletions python/packages/core/agent_framework/openai/_responses_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,31 +187,7 @@ def _tools_to_response_tools(
if isinstance(tool, ToolProtocol):
match tool:
case HostedMCPTool():
mcp: Mcp = {
"type": "mcp",
"server_label": tool.name.replace(" ", "_"),
"server_url": str(tool.url),
"server_description": tool.description,
"headers": tool.headers,
}
if tool.allowed_tools:
mcp["allowed_tools"] = list(tool.allowed_tools)
if tool.approval_mode:
match tool.approval_mode:
case str():
mcp["require_approval"] = (
"always" if tool.approval_mode == "always_require" else "never"
)
case _:
if always_require_approvals := tool.approval_mode.get("always_require_approval"):
mcp["require_approval"] = {
"always": {"tool_names": list(always_require_approvals)}
}
if never_require_approvals := tool.approval_mode.get("never_require_approval"):
mcp["require_approval"] = {
"never": {"tool_names": list(never_require_approvals)}
}
response_tools.append(mcp)
response_tools.append(self.get_mcp_tool(tool))
case HostedCodeInterpreterTool():
tool_args: CodeInterpreterContainerCodeInterpreterToolAuto = {"type": "auto"}
if tool.inputs:
Expand Down Expand Up @@ -305,6 +281,27 @@ def _tools_to_response_tools(
response_tools.append(tool_dict)
return response_tools

def get_mcp_tool(self, tool: HostedMCPTool) -> MutableMapping[str, Any]:
"""Get MCP tool from HostedMCPTool."""
mcp: Mcp = {
"type": "mcp",
"server_label": tool.name.replace(" ", "_"),
"server_url": str(tool.url),
"server_description": tool.description,
"headers": tool.headers,
}
if tool.allowed_tools:
mcp["allowed_tools"] = list(tool.allowed_tools)
if tool.approval_mode:
match tool.approval_mode:
case str():
mcp["require_approval"] = "always" if tool.approval_mode == "always_require" else "never"
case _:
if always_require_approvals := tool.approval_mode.get("always_require_approval"):
mcp["require_approval"] = {"always": {"tool_names": list(always_require_approvals)}}
if never_require_approvals := tool.approval_mode.get("never_require_approval"):
mcp["require_approval"] = {"never": {"tool_names": list(never_require_approvals)}}

Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is missing a return statement. Add return mcp at line 304 to return the constructed MCP tool dictionary.

Suggested change
return mcp

Copilot uses AI. Check for mistakes.
async def prepare_options(
self, messages: MutableSequence[ChatMessage], chat_options: ChatOptions
) -> dict[str, Any]:
Expand Down
1 change: 1 addition & 0 deletions python/samples/getting_started/agents/azure_ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent name and version to the Azure AI client. Demonstrates agent reuse patterns for production scenarios. |
| [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Shows how to work with a pre-existing conversation by providing the conversation ID to continue existing chat sessions. |
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `AzureAIClient` settings, including project endpoint, model deployment, and credentials rather than relying on environment variable defaults. |
| [`azure_ai_with_hosted_mcp.py`](azure_ai_with_hosted_mcp.py) | Shows how to integrate hosted Model Context Protocol (MCP) tools with Azure AI Agent. |
| [`azure_ai_with_response_format.py`](azure_ai_with_response_format.py) | Shows how to use structured outputs (response format) with Azure AI agents using Pydantic models to enforce specific response schemas. |
| [`azure_ai_with_thread.py`](azure_ai_with_thread.py) | Demonstrates thread management with Azure AI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio

from agent_framework import HostedMCPTool
from agent_framework.azure import AzureAIClient
from azure.identity.aio import AzureCliCredential

"""
Azure AI Agent with Hosted MCP Example
This sample demonstrates integrating hosted Model Context Protocol (MCP) tools with Azure AI Agent.
"""


async def run_hosted_mcp() -> None:
# Since no Agent ID is provided, the agent will be automatically created.
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
async with (
AzureCliCredential() as credential,
AzureAIClient(async_credential=credential).create_agent(
name="MyDocsAgent",
instructions="You are a helpful assistant that can help with Microsoft documentation questions.",
tools=HostedMCPTool(
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
# "always_require" mode is not supported yet
approval_mode="never_require",
),
) as agent,
):
query = "How to create an Azure storage account using az cli?"
print(f"User: {query}")
result = await agent.run(query)
print(f"{agent.name}: {result}\n")


async def main() -> None:
print("=== Azure AI Agent with Hosted Mcp Tools Example ===\n")
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent capitalization: 'Mcp' should be 'MCP' to match the standard acronym format used throughout the codebase.

Suggested change
print("=== Azure AI Agent with Hosted Mcp Tools Example ===\n")
print("=== Azure AI Agent with Hosted MCP Tools Example ===\n")

Copilot uses AI. Check for mistakes.

await run_hosted_mcp()


if __name__ == "__main__":
asyncio.run(main())
Loading