Skip to content

Commit 44c9ef6

Browse files
howieleungCopilot
andauthored
Sample for MCP in streaming (#42879)
* Sample for MCP in streaming * Update sdk/ai/azure-ai-agents/samples/agents_tools/sample_agents_mcp_stream_eventhandler.py Co-authored-by: Copilot <[email protected]> * resolved cmment * Fix capitalization in sample description * Fix comment typo in sample_agents_mcp_stream_iteration.py * Clarify description for MCP stream event handler * Resolved comments --------- Co-authored-by: Copilot <[email protected]>
1 parent 1f70a59 commit 44c9ef6

File tree

2 files changed

+388
-0
lines changed

2 files changed

+388
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
"""
8+
DESCRIPTION:
9+
This sample demonstrates how to use Agent operations with the
10+
Model Context Protocol (MCP) tool in stream with an event handler from the Azure Agents service using a synchronous client.
11+
To learn more about Model Context Protocol, visit https://modelcontextprotocol.io/
12+
13+
USAGE:
14+
python sample_agents_mcp_stream_eventhandler.py
15+
16+
Before running the sample:
17+
18+
pip install azure-ai-projects azure-ai-agents>=1.2.0b3 azure-identity --pre
19+
20+
Set these environment variables with your own values:
21+
1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
22+
page of your Azure AI Foundry portal.
23+
2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in
24+
the "Models + endpoints" tab in your Azure AI Foundry project.
25+
3) MCP_SERVER_URL - The URL of your MCP server endpoint.
26+
4) MCP_SERVER_LABEL - A label for your MCP server.
27+
"""
28+
29+
import os
30+
from azure.ai.agents import AgentsClient
31+
from azure.ai.agents.models._models import RunStep, ThreadRun
32+
from azure.ai.projects import AIProjectClient
33+
from azure.identity import DefaultAzureCredential
34+
from azure.ai.agents.models import (
35+
AgentEventHandler,
36+
ListSortOrder,
37+
McpTool,
38+
MessageDeltaChunk,
39+
RequiredMcpToolCall,
40+
RunStepActivityDetails,
41+
SubmitToolApprovalAction,
42+
ThreadMessage,
43+
ToolApproval,
44+
ToolSet,
45+
)
46+
47+
# Get MCP server configuration from environment variables
48+
mcp_server_url = os.environ.get("MCP_SERVER_URL", "https://gitmcp.io/Azure/azure-rest-api-specs")
49+
mcp_server_label = os.environ.get("MCP_SERVER_LABEL", "github")
50+
51+
project_client = AIProjectClient(
52+
endpoint=os.environ["PROJECT_ENDPOINT"],
53+
credential=DefaultAzureCredential(),
54+
)
55+
56+
# Initialize Agent MCP tool
57+
mcp_tool = McpTool(
58+
server_label=mcp_server_label,
59+
server_url=mcp_server_url,
60+
allowed_tools=[], # Optional: specify allowed tools
61+
)
62+
toolset = ToolSet()
63+
toolset.add(mcp_tool)
64+
65+
# You can also add or remove allowed tools dynamically
66+
search_api_code = "search_azure_rest_api_code"
67+
mcp_tool.allow_tool(search_api_code)
68+
print(f"Allowed tools: {mcp_tool.allowed_tools}")
69+
70+
class MyEventHandler(AgentEventHandler):
71+
72+
def __init__(self, agents_client: AgentsClient) -> None:
73+
super().__init__()
74+
self.agents_client = agents_client
75+
76+
def on_message_delta(self, delta: "MessageDeltaChunk") -> None:
77+
print(f"Text delta received: {delta.text}")
78+
79+
def on_thread_message(self, message: "ThreadMessage") -> None:
80+
print(f"ThreadMessage created. ID: {message.id}, Status: {message.status}")
81+
82+
def on_thread_run(self, run: "ThreadRun") -> None:
83+
if isinstance(run.required_action, SubmitToolApprovalAction):
84+
tool_calls = run.required_action.submit_tool_approval.tool_calls
85+
if not tool_calls:
86+
print("No tool calls provided - cancelling run")
87+
agents_client.runs.cancel(thread_id=run.thread_id, run_id=run.id)
88+
return
89+
90+
tool_approvals = []
91+
for tool_call in tool_calls:
92+
if isinstance(tool_call, RequiredMcpToolCall):
93+
try:
94+
print(f"Approving tool call: {tool_call}")
95+
tool_approvals.append(
96+
ToolApproval(
97+
tool_call_id=tool_call.id,
98+
approve=True,
99+
headers=mcp_tool.headers,
100+
)
101+
)
102+
except Exception as e:
103+
print(f"Error approving tool_call {tool_call.id}: {e}")
104+
105+
print(f"tool_approvals: {tool_approvals}")
106+
if tool_approvals:
107+
self.agents_client.runs.submit_tool_outputs_stream(
108+
thread_id=run.thread_id, run_id=run.id, tool_approvals=tool_approvals, event_handler=self
109+
)
110+
111+
112+
def on_run_step(self, step: "RunStep") -> None:
113+
print(f"Step {step.id} status: {step.status}")
114+
115+
# Check if there are tool calls in the step details
116+
step_details = step.get("step_details", {})
117+
tool_calls = step_details.get("tool_calls", [])
118+
119+
if tool_calls:
120+
print(" MCP Tool calls:")
121+
for call in tool_calls:
122+
print(f" Tool Call ID: {call.get('id')}")
123+
print(f" Type: {call.get('type')}")
124+
125+
if isinstance(step_details, RunStepActivityDetails):
126+
for activity in step_details.activities:
127+
for function_name, function_definition in activity.tools.items():
128+
print(
129+
f' The function {function_name} with description "{function_definition.description}" will be called.:'
130+
)
131+
if len(function_definition.parameters) > 0:
132+
print(" Function parameters:")
133+
for argument, func_argument in function_definition.parameters.properties.items():
134+
print(f" {argument}")
135+
print(f" Type: {func_argument.type}")
136+
print(f" Description: {func_argument.description}")
137+
else:
138+
print("This function has no parameters")
139+
140+
print() # add an extra newline between steps
141+
142+
143+
# Create Agent with MCP tool and process Agent run
144+
with project_client:
145+
agents_client = project_client.agents
146+
147+
# Create a new Agent.
148+
# NOTE: To reuse existing Agent, fetch it with get_agent(agent_id)
149+
agent = agents_client.create_agent(
150+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
151+
name="my-mcp-agent",
152+
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.",
153+
toolset=toolset,
154+
)
155+
156+
print(f"Created Agent, ID: {agent.id}")
157+
print(f"MCP Server: {mcp_tool.server_label} at {mcp_tool.server_url}")
158+
159+
# Create thread for communication
160+
thread = agents_client.threads.create()
161+
print(f"Created thread, ID: {thread.id}")
162+
163+
# Create message to thread
164+
message = agents_client.messages.create(
165+
thread_id=thread.id,
166+
role="user",
167+
content="Please summarize the Azure REST API specifications Readme",
168+
)
169+
print(f"Created message, ID: {message.id}")
170+
171+
# Create and process Agent run in thread with MCP tools
172+
mcp_tool.update_headers("SuperSecret", "123456")
173+
# mcp_tool.set_approval_mode("never") # Uncomment to disable approval requirement
174+
event_handler = MyEventHandler(agents_client=agents_client)
175+
with agents_client.runs.stream(thread_id=thread.id, agent_id=agent.id, event_handler=event_handler) as stream:
176+
stream.until_done()
177+
178+
# Fetch and log all messages
179+
messages = agents_client.messages.list(thread_id=thread.id, order=ListSortOrder.ASCENDING)
180+
print("\nConversation:")
181+
print("-" * 50)
182+
for msg in messages:
183+
if msg.text_messages:
184+
last_text = msg.text_messages[-1]
185+
print(f"{msg.role.upper()}: {last_text.text.value}")
186+
print("-" * 50)
187+
188+
# Example of dynamic tool management
189+
print(f"\nDemonstrating dynamic tool management:")
190+
print(f"Current allowed tools: {mcp_tool.allowed_tools}")
191+
192+
# Remove a tool
193+
try:
194+
mcp_tool.disallow_tool(search_api_code)
195+
print(f"After removing {search_api_code}: {mcp_tool.allowed_tools}")
196+
except ValueError as e:
197+
print(f"Error removing tool: {e}")
198+
199+
# Clean-up and delete the Agent once the run is finished.
200+
# NOTE: Comment out this line if you plan to reuse the Agent later.
201+
agents_client.delete_agent(agent.id)
202+
print("Deleted Agent")
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
"""
8+
DESCRIPTION:
9+
This sample demonstrates how to use Agent operations with the
10+
Model Context Protocol (MCP) tool in stream with iteration from the Azure Agents service using a synchronous client.
11+
To learn more about Model Context Protocol, visit https://modelcontextprotocol.io/
12+
13+
USAGE:
14+
python sample_agents_mcp_stream_iteration.py
15+
16+
Before running the sample:
17+
18+
pip install azure-ai-projects azure-ai-agents>=1.2.0b3 azure-identity --pre
19+
20+
Set these environment variables with your own values:
21+
1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
22+
page of your Azure AI Foundry portal.
23+
2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in
24+
the "Models + endpoints" tab in your Azure AI Foundry project.
25+
3) MCP_SERVER_URL - The URL of your MCP server endpoint.
26+
4) MCP_SERVER_LABEL - A label for your MCP server.
27+
"""
28+
29+
import os
30+
from azure.ai.agents.models._models import RunStep, ThreadRun
31+
from azure.ai.projects import AIProjectClient
32+
from azure.identity import DefaultAzureCredential
33+
from azure.ai.agents.models import (
34+
ListSortOrder,
35+
McpTool,
36+
MessageDeltaChunk,
37+
RequiredMcpToolCall,
38+
RunStepActivityDetails,
39+
SubmitToolApprovalAction,
40+
ThreadMessage,
41+
ToolApproval,
42+
ToolSet,
43+
)
44+
45+
# Get MCP server configuration from environment variables
46+
mcp_server_url = os.environ.get("MCP_SERVER_URL", "https://gitmcp.io/Azure/azure-rest-api-specs")
47+
mcp_server_label = os.environ.get("MCP_SERVER_LABEL", "github")
48+
49+
project_client = AIProjectClient(
50+
endpoint=os.environ["PROJECT_ENDPOINT"],
51+
credential=DefaultAzureCredential(),
52+
)
53+
54+
# Initialize Agent MCP tool
55+
mcp_tool = McpTool(
56+
server_label=mcp_server_label,
57+
server_url=mcp_server_url,
58+
allowed_tools=[], # Optional: specify allowed tools
59+
)
60+
toolset = ToolSet()
61+
toolset.add(mcp_tool)
62+
63+
# You can also add or remove allowed tools dynamically
64+
search_api_code = "search_azure_rest_api_code"
65+
mcp_tool.allow_tool(search_api_code)
66+
print(f"Allowed tools: {mcp_tool.allowed_tools}")
67+
68+
# Create Agent with MCP tool and process Agent run
69+
with project_client:
70+
agents_client = project_client.agents
71+
72+
# Create a new Agent.
73+
# NOTE: To reuse existing Agent, fetch it with get_agent(agent_id)
74+
agent = agents_client.create_agent(
75+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
76+
name="my-mcp-agent",
77+
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.",
78+
toolset=toolset,
79+
)
80+
81+
print(f"Created Agent, ID: {agent.id}")
82+
print(f"MCP Server: {mcp_tool.server_label} at {mcp_tool.server_url}")
83+
84+
# Create thread for communication
85+
thread = agents_client.threads.create()
86+
print(f"Created thread, ID: {thread.id}")
87+
88+
# Create message to thread
89+
message = agents_client.messages.create(
90+
thread_id=thread.id,
91+
role="user",
92+
content="Please summarize the Azure REST API specifications Readme",
93+
)
94+
print(f"Created message, ID: {message.id}")
95+
96+
# Create and process Agent run in thread with MCP tools
97+
mcp_tool.update_headers("SuperSecret", "123456")
98+
# mcp_tool.set_approval_mode("never") # Uncomment to disable approval requirement
99+
with agents_client.runs.stream(thread_id=thread.id, agent_id=agent.id) as stream:
100+
for event_type, event_data, _ in stream:
101+
102+
if isinstance(event_data, MessageDeltaChunk):
103+
print(f"Text delta received: {event_data.text}")
104+
elif isinstance(event_data, ThreadRun):
105+
if isinstance(event_data.required_action, SubmitToolApprovalAction):
106+
tool_calls = event_data.required_action.submit_tool_approval.tool_calls
107+
if not tool_calls:
108+
print("No tool calls provided - cancelling run")
109+
agents_client.runs.cancel(thread_id=thread.id, run_id=event_data.id)
110+
break
111+
112+
tool_approvals = []
113+
for tool_call in tool_calls:
114+
if isinstance(tool_call, RequiredMcpToolCall):
115+
try:
116+
print(f"Approving tool call: {tool_call}")
117+
tool_approvals.append(
118+
ToolApproval(
119+
tool_call_id=tool_call.id,
120+
approve=True,
121+
headers=mcp_tool.headers,
122+
)
123+
)
124+
except Exception as e:
125+
print(f"Error approving tool_call {tool_call.id}: {e}")
126+
127+
print(f"tool_approvals: {tool_approvals}")
128+
if tool_approvals:
129+
run = agents_client.runs.submit_tool_outputs_stream(
130+
thread_id=thread.id, run_id=event_data.id, tool_approvals=tool_approvals, event_handler=stream
131+
)
132+
elif isinstance(event_data, RunStep):
133+
print(f"Step {event_data.id} status: {event_data.status}")
134+
135+
# Check if there are tool calls in the step details
136+
step_details = event_data.get("step_details", {})
137+
tool_calls = step_details.get("tool_calls", [])
138+
139+
if tool_calls:
140+
print(" MCP Tool calls:")
141+
for call in tool_calls:
142+
print(f" Tool Call ID: {call.get('id')}")
143+
print(f" Type: {call.get('type')}")
144+
145+
if isinstance(step_details, RunStepActivityDetails):
146+
for activity in step_details.activities:
147+
for function_name, function_definition in activity.tools.items():
148+
print(
149+
f' The function {function_name} with description "{function_definition.description}" will be called.:'
150+
)
151+
if len(function_definition.parameters) > 0:
152+
print(" Function parameters:")
153+
for argument, func_argument in function_definition.parameters.properties.items():
154+
print(f" {argument}")
155+
print(f" Type: {func_argument.type}")
156+
print(f" Description: {func_argument.description}")
157+
else:
158+
print("This function has no parameters")
159+
160+
print() # add an extra newline between steps
161+
162+
# Fetch and log all messages
163+
messages = agents_client.messages.list(thread_id=thread.id, order=ListSortOrder.ASCENDING)
164+
print("\nConversation:")
165+
print("-" * 50)
166+
for msg in messages:
167+
if msg.text_messages:
168+
last_text = msg.text_messages[-1]
169+
print(f"{msg.role.upper()}: {last_text.text.value}")
170+
print("-" * 50)
171+
172+
# Example of dynamic tool management
173+
print(f"\nDemonstrating dynamic tool management:")
174+
print(f"Current allowed tools: {mcp_tool.allowed_tools}")
175+
176+
# Remove a tool
177+
try:
178+
mcp_tool.disallow_tool(search_api_code)
179+
print(f"After removing {search_api_code}: {mcp_tool.allowed_tools}")
180+
except ValueError as e:
181+
print(f"Error removing tool: {e}")
182+
183+
# Clean-up and delete the Agent once the run is finished.
184+
# NOTE: Comment out this line if you plan to reuse the Agent later.
185+
agents_client.delete_agent(agent.id)
186+
print("Deleted Agent")

0 commit comments

Comments
 (0)