Skip to content

Commit 280b2d5

Browse files
authored
[AI Agents] add MCPToolDefinition and sample (Azure#51137)
* [AI Agents] initial generation (doesn't build yet) * update streaming with toolResources in CreateRunRequest and new SubmitToolOutputsToRunRequest constructor * Add convenience code and sample * update samples to remove ambiguous CreateRun calls * MCP sample now works * update snippets and run API extract * fix sample sync * fix ambiguous call * make generated CreateRun internal, add specific ToolResources CreateRun API * Fix test failures * remove tmp files * restore OpenAPI sample to previous * update code to use simpler CreateRun API * fix tests * enabled tracing for createRun call * fix tests
1 parent d0ec068 commit 280b2d5

File tree

60 files changed

+2966
-119
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2966
-119
lines changed

sdk/ai/Azure.AI.Agents.Persistent/api/Azure.AI.Agents.Persistent.net8.0.cs

Lines changed: 123 additions & 2 deletions
Large diffs are not rendered by default.

sdk/ai/Azure.AI.Agents.Persistent/api/Azure.AI.Agents.Persistent.netstandard2.0.cs

Lines changed: 123 additions & 2 deletions
Large diffs are not rendered by default.

sdk/ai/Azure.AI.Agents.Persistent/samples/Sample11_PersistentAgents_Code_Interpreter_Enterprise_File_Search.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ PersistentThreadMessage message = client.Messages.CreateMessage(
6262
);
6363

6464
ThreadRun run = client.Runs.CreateRun(
65-
thread.Id,
66-
agent.Id
65+
thread,
66+
agent
6767
);
6868
do
6969
{
@@ -90,8 +90,8 @@ PersistentThreadMessage message = await client.Messages.CreateMessageAsync(
9090
);
9191

9292
ThreadRun run = await client.Runs.CreateRunAsync(
93-
thread.Id,
94-
agent.Id
93+
thread,
94+
agent
9595
);
9696
do
9797
{
@@ -143,8 +143,8 @@ PersistentThreadMessage message = client.Messages.CreateMessage(
143143
);
144144

145145
ThreadRun run = client.Runs.CreateRun(
146-
thread.Id,
147-
agent.Id
146+
thread,
147+
agent
148148
);
149149
do
150150
{
@@ -171,8 +171,8 @@ PersistentThreadMessage message = await client.Messages.CreateMessageAsync(
171171
);
172172

173173
ThreadRun run = await client.Runs.CreateRunAsync(
174-
thread.Id,
175-
agent.Id
174+
thread,
175+
agent
176176
);
177177
do
178178
{

sdk/ai/Azure.AI.Agents.Persistent/samples/Sample12_PersistentAgents_Code_Interpreter_File_Attachment.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ PersistentThreadMessage message = await client.Messages.CreateMessageAsync(
8484
Synchronous sample:
8585
```C# Snippet:AgentsCodeInterpreterFileAttachmentSync_CreateRun
8686
ThreadRun run = client.Runs.CreateRun(
87-
thread.Id,
88-
agent.Id
87+
thread,
88+
agent
8989
);
9090

9191
do
@@ -104,8 +104,8 @@ Assert.AreEqual(
104104
Asynchronous sample:
105105
```C# Snippet:AgentsCodeInterpreterFileAttachment_CreateRun
106106
ThreadRun run = await client.Runs.CreateRunAsync(
107-
thread.Id,
108-
agent.Id
107+
thread,
108+
agent
109109
);
110110

111111
do

sdk/ai/Azure.AI.Agents.Persistent/samples/Sample15_PersistentAgents_Vector_Store_Batch_Enterprise_File_Search.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ private static string replaceReferences(Dictionary<string, string> fileIds, stri
147147
Synchronous sample:
148148
```C# Snippet:AgentsVectorStoreBatchEnterpriseFileSearch_ThreadRun
149149
ThreadRun run = client.Runs.CreateRun(
150-
thread.Id,
151-
agent.Id
150+
thread,
151+
agent
152152
);
153153

154154
do
@@ -183,8 +183,8 @@ WriteMessages(messages, dtFiles);
183183
Asynchronous sample:
184184
```C# Snippet:AgentsVectorStoreBatchEnterpriseFileSearch_ThreadRun_Async
185185
ThreadRun run = await client.Runs.CreateRunAsync(
186-
thread.Id,
187-
agent.Id
186+
thread,
187+
agent
188188
);
189189

190190
do
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Sample for use of an agent with Model Context Protocol (MCP) tools in Azure.AI.Agents.Persistent.
2+
3+
To enable your Agent to use Model Context Protocol (MCP) tools, you use `MCPToolDefinition` along with server configuration and tool resources.
4+
1. First we need to create an agent client and read the environment variables, which will be used in the next steps.
5+
6+
```C# Snippet:AgentsMCP_CreateProject
7+
var projectEndpoint = System.Environment.GetEnvironmentVariable("PROJECT_ENDPOINT");
8+
var modelDeploymentName = System.Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME");
9+
var mcpServerUrl = System.Environment.GetEnvironmentVariable("MCP_SERVER_URL");
10+
var mcpServerLabel = System.Environment.GetEnvironmentVariable("MCP_SERVER_LABEL");
11+
PersistentAgentsClient agentClient = new(projectEndpoint, new DefaultAzureCredential());
12+
```
13+
14+
2. We will create the MCP tool definition and configure allowed tools.
15+
16+
```C# Snippet:AgentsMCP_CreateMCPTool
17+
// Create MCP tool definition
18+
MCPToolDefinition mcpTool = new(mcpServerLabel, mcpServerUrl);
19+
20+
// Configure allowed tools (optional)
21+
string searchApiCode = "search_azure_rest_api_code";
22+
mcpTool.AllowedTools.Add(searchApiCode);
23+
```
24+
25+
3. We will use the `MCPToolDefinition` during the agent initialization.
26+
27+
Synchronous sample:
28+
```C# Snippet:AgentsMCP_CreateAgent
29+
PersistentAgent agent = agentClient.Administration.CreateAgent(
30+
model: modelDeploymentName,
31+
name: "my-mcp-agent",
32+
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.",
33+
tools: [mcpTool]);
34+
```
35+
36+
Asynchronous sample:
37+
```C# Snippet:AgentsMCPAsync_CreateAgent
38+
PersistentAgent agent = await agentClient.Administration.CreateAgentAsync(
39+
model: modelDeploymentName,
40+
name: "my-mcp-agent",
41+
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.",
42+
tools: [mcpTool]
43+
);
44+
```
45+
46+
4. Now we will create the thread, add the message containing a question for agent and start the run with MCP tool resources.
47+
48+
Synchronous sample:
49+
```C# Snippet:AgentsMCP_CreateThreadMessage
50+
PersistentAgentThread thread = agentClient.Threads.CreateThread();
51+
52+
// Create message to thread
53+
PersistentThreadMessage message = agentClient.Messages.CreateMessage(
54+
thread.Id,
55+
MessageRole.User,
56+
"Please summarize the Azure REST API specifications Readme");
57+
58+
MCPToolResource mcpToolResource = new(mcpServerLabel);
59+
mcpToolResource.UpdateHeader("SuperSecret", "123456");
60+
ToolResources toolResources = mcpToolResource.ToToolResources();
61+
62+
// Run the agent with MCP tool resources
63+
ThreadRun run = agentClient.Runs.CreateRun(thread, agent, toolResources);
64+
65+
// Handle run execution and tool approvals
66+
while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress || run.Status == RunStatus.RequiresAction)
67+
{
68+
Thread.Sleep(TimeSpan.FromMilliseconds(1000));
69+
run = agentClient.Runs.GetRun(thread.Id, run.Id);
70+
71+
if (run.Status == RunStatus.RequiresAction && run.RequiredAction is SubmitToolApprovalAction toolApprovalAction)
72+
{
73+
var toolApprovals = new List<ToolApproval>();
74+
foreach (var toolCall in toolApprovalAction.SubmitToolApproval.ToolCalls)
75+
{
76+
if (toolCall is RequiredMcpToolCall mcpToolCall)
77+
{
78+
Console.WriteLine($"Approving MCP tool call: {mcpToolCall.Name}");
79+
toolApprovals.Add(new ToolApproval(mcpToolCall.Id, approve: true)
80+
{
81+
Headers = { ["SuperSecret"] = "123456" }
82+
});
83+
}
84+
}
85+
86+
if (toolApprovals.Count > 0)
87+
{
88+
run = agentClient.Runs.SubmitToolOutputsToRun(thread.Id, run.Id, toolApprovals: toolApprovals);
89+
}
90+
}
91+
}
92+
93+
Assert.AreEqual(
94+
RunStatus.Completed,
95+
run.Status,
96+
run.LastError?.Message);
97+
```
98+
99+
Asynchronous sample:
100+
```C# Snippet:AgentsMCPAsync_CreateThreadMessage
101+
PersistentAgentThread thread = await agentClient.Threads.CreateThreadAsync();
102+
103+
// Create message to thread
104+
PersistentThreadMessage message = await agentClient.Messages.CreateMessageAsync(
105+
thread.Id,
106+
MessageRole.User,
107+
"Please summarize the Azure REST API specifications Readme");
108+
109+
MCPToolResource mcpToolResource = new(mcpServerLabel);
110+
mcpToolResource.UpdateHeader("SuperSecret", "123456");
111+
ToolResources toolResources = mcpToolResource.ToToolResources();
112+
113+
// Run the agent with MCP tool resources
114+
ThreadRun run = await agentClient.Runs.CreateRunAsync(thread, agent, toolResources);
115+
116+
// Handle run execution and tool approvals
117+
while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress || run.Status == RunStatus.RequiresAction)
118+
{
119+
await Task.Delay(TimeSpan.FromMilliseconds(1000));
120+
run = await agentClient.Runs.GetRunAsync(thread.Id, run.Id);
121+
122+
if (run.Status == RunStatus.RequiresAction && run.RequiredAction is SubmitToolApprovalAction toolApprovalAction)
123+
{
124+
var toolApprovals = new List<ToolApproval>();
125+
foreach (var toolCall in toolApprovalAction.SubmitToolApproval.ToolCalls)
126+
{
127+
if (toolCall is RequiredMcpToolCall mcpToolCall)
128+
{
129+
Console.WriteLine($"Approving MCP tool call: {mcpToolCall.Name}");
130+
toolApprovals.Add(new ToolApproval(mcpToolCall.Id, approve: true)
131+
{
132+
Headers = { ["SuperSecret"] = "123456" }
133+
});
134+
}
135+
}
136+
137+
if (toolApprovals.Count > 0)
138+
{
139+
run = await agentClient.Runs.SubmitToolOutputsToRunAsync(thread.Id, run.Id, toolApprovals: toolApprovals);
140+
}
141+
}
142+
}
143+
144+
Assert.AreEqual(
145+
RunStatus.Completed,
146+
run.Status,
147+
run.LastError?.Message);
148+
```
149+
150+
5. Print the agent messages to console in chronological order.
151+
152+
Synchronous sample:
153+
```C# Snippet:AgentsMCP_Print
154+
Pageable<PersistentThreadMessage> messages = agentClient.Messages.GetMessages(
155+
threadId: thread.Id,
156+
order: ListSortOrder.Ascending
157+
);
158+
159+
foreach (PersistentThreadMessage threadMessage in messages)
160+
{
161+
Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: ");
162+
foreach (MessageContent contentItem in threadMessage.ContentItems)
163+
{
164+
if (contentItem is MessageTextContent textItem)
165+
{
166+
Console.Write(textItem.Text);
167+
}
168+
else if (contentItem is MessageImageFileContent imageFileItem)
169+
{
170+
Console.Write($"<image from ID: {imageFileItem.FileId}>");
171+
}
172+
Console.WriteLine();
173+
}
174+
}
175+
```
176+
177+
Asynchronous sample:
178+
```C# Snippet:AgentsMCPAsync_Print
179+
AsyncPageable<PersistentThreadMessage> messages = agentClient.Messages.GetMessagesAsync(
180+
threadId: thread.Id,
181+
order: ListSortOrder.Ascending
182+
);
183+
184+
await foreach (PersistentThreadMessage threadMessage in messages)
185+
{
186+
Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: ");
187+
foreach (MessageContent contentItem in threadMessage.ContentItems)
188+
{
189+
if (contentItem is MessageTextContent textItem)
190+
{
191+
Console.Write(textItem.Text);
192+
}
193+
else if (contentItem is MessageImageFileContent imageFileItem)
194+
{
195+
Console.Write($"<image from ID: {imageFileItem.FileId}>");
196+
}
197+
Console.WriteLine();
198+
}
199+
}
200+
```
201+
202+
6. Clean up resources by deleting thread and agent.
203+
204+
Synchronous sample:
205+
```C# Snippet:AgentsMCPCleanup
206+
agentClient.Threads.DeleteThread(threadId: thread.Id);
207+
agentClient.Administration.DeleteAgent(agentId: agent.Id);
208+
```
209+
210+
Asynchronous sample:
211+
```C# Snippet:AgentsMCPCleanupAsync
212+
await agentClient.Threads.DeleteThreadAsync(threadId: thread.Id);
213+
await agentClient.Administration.DeleteAgentAsync(agentId: agent.Id);
214+
```

sdk/ai/Azure.AI.Agents.Persistent/samples/Sample5_PersistentAgents_Enterprise_File_Search.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ PersistentThreadMessage message = client.Messages.CreateMessage(
7676
);
7777

7878
ThreadRun run = client.Runs.CreateRun(
79-
thread.Id,
80-
agent.Id
79+
thread,
80+
agent
8181
);
8282

8383
do
@@ -104,8 +104,8 @@ PersistentThreadMessage message = await client.Messages.CreateMessageAsync(
104104
);
105105

106106
ThreadRun run = await client.Runs.CreateRunAsync(
107-
thread.Id,
108-
agent.Id
107+
thread,
108+
agent
109109
);
110110

111111
do

sdk/ai/Azure.AI.Agents.Persistent/samples/Sample7_PersistentAgents_Functions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ do
173173
{
174174
toolOutputs.Add(GetResolvedToolOutput(toolCall));
175175
}
176-
run = client.Runs.SubmitToolOutputsToRun(run, toolOutputs);
176+
run = client.Runs.SubmitToolOutputsToRun(run, toolOutputs, null);
177177
}
178178
}
179179
while (run.Status == RunStatus.Queued
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Azure.AI.Agents.Persistent
8+
{
9+
public partial class MCPToolResource
10+
{
11+
/// <summary> Initializes a new instance of <see cref="MCPToolResource"/>. </summary>
12+
/// <param name="serverLabel"> The label for the MCP server. </param>
13+
/// <exception cref="ArgumentNullException"> <paramref name="serverLabel"/> is null. </exception>
14+
public MCPToolResource(string serverLabel)
15+
{
16+
Argument.AssertNotNull(serverLabel, nameof(serverLabel));
17+
18+
ServerLabel = serverLabel;
19+
Headers = new Dictionary<string, string>();
20+
}
21+
22+
/// <summary>
23+
/// Adds or updates a header in the Headers dictionary.
24+
/// </summary>
25+
/// <param name="key">The header key.</param>
26+
/// <param name="value">The header value.</param>
27+
public void UpdateHeader(string key, string value)
28+
{
29+
Headers[key] = value;
30+
}
31+
32+
/// <summary>
33+
/// Creates a new ToolResources instance with this MCPToolResource added to the Mcp collection.
34+
/// </summary>
35+
/// <returns>A new ToolResources instance containing this MCPToolResource.</returns>
36+
public ToolResources ToToolResources()
37+
{
38+
var toolResources = new ToolResources();
39+
toolResources.Mcp.Add(this);
40+
return toolResources;
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)