Skip to content

Commit 2442fe2

Browse files
authored
Merge pull request #735 from MicrosoftDocs/main
Merge main to live
2 parents 9b3ca67 + 2306880 commit 2442fe2

2 files changed

Lines changed: 213 additions & 0 deletions

File tree

agent-framework/tutorials/agents/agent-as-mcp-tool.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
title: Exposing an agent as an MCP tool
33
description: Learn how to expose an agent as a tool over the MCP protocol
4+
zone_pivot_groups: programming-languages
45
author: westey-m
56
ms.topic: tutorial
67
ms.author: westey
@@ -10,6 +11,8 @@ ms.service: agent-framework
1011

1112
# Expose an agent as an MCP tool
1213

14+
::: zone pivot="programming-language-csharp"
15+
1316
This tutorial shows you how to expose an agent as a tool over the Model Context Protocol (MCP), so it can be used by other systems that support MCP tools.
1417

1518
## Prerequisites
@@ -78,6 +81,73 @@ await builder.Build().RunAsync();
7881

7982
This will start an MCP server that exposes the agent as a tool over the MCP protocol.
8083

84+
::: zone-end
85+
::: zone pivot="programming-language-python"
86+
87+
This tutorial shows you how to expose an agent as a tool over the Model Context Protocol (MCP), so it can be used by other systems that support MCP tools.
88+
89+
## Prerequisites
90+
91+
For prerequisites and installing Python packages, see the [Create and run a simple agent](./run-agent.md) step in this tutorial.
92+
93+
## Expose an agent as an MCP server
94+
95+
You can expose an agent as an MCP server by using the `as_mcp_server()` method. This allows the agent to be invoked as a tool by any MCP-compatible client.
96+
97+
First, create an agent that you'll expose as an MCP server. You can also add tools to the agent:
98+
99+
```python
100+
from typing import Annotated
101+
from agent_framework.openai import OpenAIResponsesClient
102+
103+
def get_specials() -> Annotated[str, "Returns the specials from the menu."]:
104+
return """
105+
Special Soup: Clam Chowder
106+
Special Salad: Cobb Salad
107+
Special Drink: Chai Tea
108+
"""
109+
110+
def get_item_price(
111+
menu_item: Annotated[str, "The name of the menu item."],
112+
) -> Annotated[str, "Returns the price of the menu item."]:
113+
return "$9.99"
114+
115+
# Create an agent with tools
116+
agent = OpenAIResponsesClient().create_agent(
117+
name="RestaurantAgent",
118+
description="Answer questions about the menu.",
119+
tools=[get_specials, get_item_price],
120+
)
121+
```
122+
123+
Turn the agent into an MCP server. The agent name and description will be used as the MCP server metadata:
124+
125+
```python
126+
# Expose the agent as an MCP server
127+
server = agent.as_mcp_server()
128+
```
129+
130+
Setup the MCP server to listen for incoming requests over standard input/output:
131+
132+
```python
133+
import anyio
134+
from mcp.server.stdio import stdio_server
135+
136+
async def run():
137+
async def handle_stdin():
138+
async with stdio_server() as (read_stream, write_stream):
139+
await server.run(read_stream, write_stream, server.create_initialization_options())
140+
141+
await handle_stdin()
142+
143+
if __name__ == "__main__":
144+
anyio.run(run)
145+
```
146+
147+
This will start an MCP server that exposes the agent over the MCP protocol, allowing it to be used by MCP-compatible clients like VS Code GitHub Copilot Agents.
148+
149+
::: zone-end
150+
81151
## Next steps
82152

83153
> [!div class="nextstepaction"]

agent-framework/tutorials/agents/function-tools-approvals.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
title: Using function tools with human in the loop approvals
33
description: Learn how to use function tools with human in the loop approvals
4+
zone_pivot_groups: programming-languages
45
author: westey-m
56
ms.topic: tutorial
67
ms.author: westey
@@ -10,6 +11,7 @@ ms.service: agent-framework
1011

1112
# Using function tools with human in the loop approvals
1213

14+
::: zone pivot="programming-language-csharp"
1315

1416
This tutorial step shows you how to use function tools that require human approval with an agent, where the agent is built on the Azure OpenAI Chat Completion service.
1517

@@ -94,6 +96,147 @@ Console.WriteLine(await agent.RunAsync(approvalMessage, thread));
9496

9597
Whenever you are using function tools with human in the loop approvals, remember to check for `FunctionApprovalRequestContent` instances in the response, after each agent run, until all function calls have been approved or rejected.
9698

99+
::: zone-end
100+
::: zone pivot="programming-language-python"
101+
102+
This tutorial step shows you how to use function tools that require human approval with an agent.
103+
104+
When agents require any user input, for example to approve a function call, this is referred to as a human-in-the-loop pattern.
105+
An agent run that requires user input, will complete with a response that indicates what input is required from the user, instead of completing with a final answer.
106+
The caller of the agent is then responsible for getting the required input from the user, and passing it back to the agent as part of a new agent run.
107+
108+
## Prerequisites
109+
110+
For prerequisites and installing Python packages, see the [Create and run a simple agent](./run-agent.md) step in this tutorial.
111+
112+
## Create the agent with function tools requiring approval
113+
114+
When using functions, it's possible to indicate for each function, whether it requires human approval before being executed.
115+
This is done by setting the `approval_mode` parameter to `"always_require"` when using the `@ai_function` decorator.
116+
117+
Here is an example of a simple function tool that fakes getting the weather for a given location.
118+
119+
```python
120+
from typing import Annotated
121+
from agent_framework import ai_function
122+
123+
@ai_function
124+
def get_weather(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str:
125+
"""Get the current weather for a given location."""
126+
return f"The weather in {location} is cloudy with a high of 15°C."
127+
```
128+
129+
To create a function that requires approval, you can use the `approval_mode` parameter:
130+
131+
```python
132+
@ai_function(approval_mode="always_require")
133+
def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str:
134+
"""Get detailed weather information for a given location."""
135+
return f"The weather in {location} is cloudy with a high of 15°C, humidity 88%."
136+
```
137+
138+
When creating the agent, you can now provide the approval requiring function tool to the agent, by passing a list of tools to the `ChatAgent` constructor.
139+
140+
```python
141+
from agent_framework import ChatAgent
142+
from agent_framework.openai import OpenAIResponsesClient
143+
144+
async with ChatAgent(
145+
chat_client=OpenAIResponsesClient(),
146+
name="WeatherAgent",
147+
instructions="You are a helpful weather assistant.",
148+
tools=[get_weather, get_weather_detail],
149+
) as agent:
150+
# Agent is ready to use
151+
```
152+
153+
Since you now have a function that requires approval, the agent might respond with a request for approval, instead of executing the function directly and returning the result.
154+
You can check the response for any user input requests, which indicates that the agent requires user approval for a function.
155+
156+
```python
157+
result = await agent.run("What is the detailed weather like in Amsterdam?")
158+
159+
if result.user_input_requests:
160+
for user_input_needed in result.user_input_requests:
161+
print(f"Function: {user_input_needed.function_call.name}")
162+
print(f"Arguments: {user_input_needed.function_call.arguments}")
163+
```
164+
165+
If there are any function approval requests, the detail of the function call including name and arguments can be found in the `function_call` property on the user input request.
166+
This can be shown to the user, so that they can decide whether to approve or reject the function call.
167+
168+
Once the user has provided their input, you can create a response using the `create_response` method on the user input request.
169+
Pass `True` to approve the function call, or `False` to reject it.
170+
171+
The response can then be passed to the agent in a new `ChatMessage`, to get the result back from the agent.
172+
173+
```python
174+
from agent_framework import ChatMessage, Role
175+
176+
# Get user approval (in a real application, this would be interactive)
177+
user_approval = True # or False to reject
178+
179+
# Create the approval response
180+
approval_message = ChatMessage(
181+
role=Role.USER,
182+
contents=[user_input_needed.create_response(user_approval)]
183+
)
184+
185+
# Continue the conversation with the approval
186+
final_result = await agent.run([
187+
"What is the detailed weather like in Amsterdam?",
188+
ChatMessage(role=Role.ASSISTANT, contents=[user_input_needed]),
189+
approval_message
190+
])
191+
print(final_result.text)
192+
```
193+
194+
## Handling approvals in a loop
195+
196+
When working with multiple function calls that require approval, you may need to handle approvals in a loop until all functions are approved or rejected:
197+
198+
```python
199+
async def handle_approvals(query: str, agent) -> str:
200+
"""Handle function call approvals in a loop."""
201+
current_input = query
202+
203+
while True:
204+
result = await agent.run(current_input)
205+
206+
if not result.user_input_requests:
207+
# No more approvals needed, return the final result
208+
return result.text
209+
210+
# Build new input with all context
211+
new_inputs = [query]
212+
213+
for user_input_needed in result.user_input_requests:
214+
print(f"Approval needed for: {user_input_needed.function_call.name}")
215+
print(f"Arguments: {user_input_needed.function_call.arguments}")
216+
217+
# Add the assistant message with the approval request
218+
new_inputs.append(ChatMessage(role=Role.ASSISTANT, contents=[user_input_needed]))
219+
220+
# Get user approval (in practice, this would be interactive)
221+
user_approval = True # Replace with actual user input
222+
223+
# Add the user's approval response
224+
new_inputs.append(
225+
ChatMessage(role=Role.USER, contents=[user_input_needed.create_response(user_approval)])
226+
)
227+
228+
# Continue with all the context
229+
current_input = new_inputs
230+
231+
# Usage
232+
result_text = await handle_approvals("Get detailed weather for Seattle and Portland", agent)
233+
print(result_text)
234+
```
235+
236+
Whenever you are using function tools with human in the loop approvals, remember to check for user input requests in the response, after each agent run, until all function calls have been approved or rejected.
237+
238+
::: zone-end
239+
97240
## Next steps
98241

99242
> [!div class="nextstepaction"]

0 commit comments

Comments
 (0)