Skip to content

Commit 40b6def

Browse files
authored
.NET: [Feature Branch] Migrate state schema updates and support for agents as MCP tools (#1979)
1 parent 754491c commit 40b6def

File tree

59 files changed

+2781
-253
lines changed

Some content is hidden

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

59 files changed

+2781
-253
lines changed

dotnet/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.0" />
106106
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
107107
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
108+
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
108109
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.5" />
109110
<!-- Community -->
110111
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />

dotnet/agent-framework-dotnet.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<Project Path="samples/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj" />
2828
<Project Path="samples/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
2929
<Project Path="samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj" />
30+
<Project Path="samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj" />
3031
</Folder>
3132
<Folder Name="/Samples/GettingStarted/">
3233
<File Path="samples/GettingStarted/README.md" />

dotnet/samples/AzureFunctions/01_SingleAgent/host.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
}
1818
}
1919
}
20-
}
20+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<!-- The Functions build tools don't like namespaces that start with a number -->
9+
<AssemblyName>AgentAsMcpTool</AssemblyName>
10+
<RootNamespace>AgentAsMcpTool</RootNamespace>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
15+
</ItemGroup>
16+
17+
<!-- Azure Functions packages -->
18+
<ItemGroup>
19+
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
20+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
21+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
22+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
23+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<PackageReference Include="Azure.AI.OpenAI" />
28+
<PackageReference Include="Azure.Identity" />
29+
</ItemGroup>
30+
31+
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
32+
<!--
33+
<ItemGroup>
34+
<PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
35+
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
36+
</ItemGroup>
37+
-->
38+
<ItemGroup>
39+
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
40+
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
41+
</ItemGroup>
42+
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
// This sample demonstrates how to configure AI agents to be accessible as MCP tools.
4+
// When using AddAIAgent and enabling MCP tool triggers, the Functions host will automatically
5+
// generate a remote MCP endpoint for the app at /runtime/webhooks/mcp with a agent-specific
6+
// query tool name.
7+
8+
using Azure;
9+
using Azure.AI.OpenAI;
10+
using Azure.Identity;
11+
using Microsoft.Agents.AI;
12+
using Microsoft.Agents.AI.DurableTask;
13+
using Microsoft.Agents.AI.Hosting.AzureFunctions;
14+
using Microsoft.Azure.Functions.Worker.Builder;
15+
using Microsoft.Extensions.Hosting;
16+
using OpenAI;
17+
18+
// Get the Azure OpenAI endpoint and deployment name from environment variables.
19+
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
20+
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
21+
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")
22+
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT is not set.");
23+
24+
// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
25+
string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
26+
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
27+
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
28+
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
29+
30+
// Define three AI agents we are going to use in this application.
31+
AIAgent agent1 = client.GetChatClient(deploymentName).CreateAIAgent("You are good at telling jokes.", "Joker");
32+
33+
AIAgent agent2 = client.GetChatClient(deploymentName)
34+
.CreateAIAgent("Check stock prices.", "StockAdvisor");
35+
36+
AIAgent agent3 = client.GetChatClient(deploymentName)
37+
.CreateAIAgent("Recommend plants.", "PlantAdvisor", description: "Get plant recommendations.");
38+
39+
using IHost app = FunctionsApplication
40+
.CreateBuilder(args)
41+
.ConfigureFunctionsWebApplication()
42+
.ConfigureDurableAgents(options =>
43+
{
44+
options
45+
.AddAIAgent(agent1) // Enables HTTP trigger by default.
46+
.AddAIAgent(agent2, enableHttpTrigger: false, enableMcpToolTrigger: true) // Disable HTTP trigger, enable MCP Tool trigger.
47+
.AddAIAgent(agent3, agentOptions =>
48+
{
49+
agentOptions.McpToolTrigger.IsEnabled = true; // Enable MCP Tool trigger.
50+
});
51+
})
52+
.Build();
53+
app.Run();
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Agent as MCP Tool Sample
2+
3+
This sample demonstrates how to configure AI agents to be accessible as both HTTP endpoints and [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) tools, enabling flexible integration patterns for AI agent consumption.
4+
5+
## Key Concepts Demonstrated
6+
7+
- **Multi-trigger Agent Configuration**: Configure agents to support HTTP triggers, MCP tool triggers, or both
8+
- **Microsoft Agent Framework Integration**: Use the framework to define AI agents with specific roles and capabilities
9+
- **Flexible Agent Registration**: Register agents with customizable trigger configurations
10+
- **MCP Server Hosting**: Expose agents as MCP tools for consumption by MCP-compatible clients
11+
12+
## Sample Architecture
13+
14+
This sample creates three agents with different trigger configurations:
15+
16+
| Agent | Role | HTTP Trigger | MCP Tool Trigger | Description |
17+
|-------|------|--------------|------------------|-------------|
18+
| **Joker** | Comedy specialist | ✅ Enabled | ❌ Disabled | Accessible only via HTTP requests |
19+
| **StockAdvisor** | Financial data | ❌ Disabled | ✅ Enabled | Accessible only as MCP tool |
20+
| **PlantAdvisor** | Indoor plant recommendations | ✅ Enabled | ✅ Enabled | Accessible via both HTTP and MCP |
21+
22+
## Environment Setup
23+
24+
See the [README.md](../README.md) file in the parent directory for complete setup instructions, including:
25+
26+
- Prerequisites installation
27+
- Azure OpenAI configuration
28+
- Durable Task Scheduler setup
29+
- Storage emulator configuration
30+
31+
For this sample, you'll also need to install [node.js](https://nodejs.org/en/download) in order to use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) tool.
32+
33+
## Configuration
34+
35+
Update your `local.settings.json` with your Azure OpenAI credentials:
36+
37+
```json
38+
{
39+
"Values": {
40+
"AZURE_OPENAI_ENDPOINT": "https://your-resource.openai.azure.com/",
41+
"AZURE_OPENAI_DEPLOYMENT": "your-deployment-name",
42+
"AZURE_OPENAI_KEY": "your-api-key-if-not-using-rbac"
43+
}
44+
}
45+
```
46+
47+
## Running the Sample
48+
49+
1. **Start the Function App**:
50+
51+
```bash
52+
cd dotnet/samples/AzureFunctions/07_AgentAsMcpTool
53+
func start
54+
```
55+
56+
2. **Note the MCP Server Endpoint**: When the app starts, you'll see the MCP server endpoint in the terminal output. It will look like:
57+
58+
```text
59+
MCP server endpoint: http://localhost:7071/runtime/webhooks/mcp
60+
```
61+
62+
## Testing MCP Tool Integration
63+
64+
Any MCP-compatible client can connect to the server endpoint and utilize the exposed agent tools. The agents will appear as callable tools within the MCP protocol.
65+
66+
### Using MCP Inspector
67+
68+
1. Run the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) from the command line:
69+
70+
```bash
71+
npx @modelcontextprotocol/inspector
72+
```
73+
74+
1. Connect using the MCP server endpoint from your terminal output
75+
76+
- For **Transport Type**, select **"Streamable HTTP"**
77+
- For **URL**, enter the MCP server endpoint `http://localhost:7071/runtime/webhooks/mcp`
78+
- Click the **Connect** button
79+
80+
1. Click the **List Tools** button to see the available MCP tools. You should see the `StockAdvisor` and `PlantAdvisor` tools.
81+
82+
1. Test the available MCP tools:
83+
84+
- **StockAdvisor** - Set "MSFT ATH" (ATH is "all time high") as the query and click the **Run Tool** button.
85+
- **PlantAdvisor** - Set "Low light in Seattle" as the query and click the **Run Tool** button.
86+
87+
You'll see the results of the tool calls in the MCP Inspector interface under the **Tool Results** section. You should also see the results in the terminal where you ran the `func start` command.
88+
89+
## Learn More
90+
91+
- [Model Context Protocol Documentation](https://modelcontextprotocol.io/)
92+
- [Microsoft Agent Framework Documentation](https://github.com/Azure/durable-agent-framework)
93+
- [Azure Functions Documentation](https://learn.microsoft.com/azure/azure-functions/)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "2.0",
3+
"logging": {
4+
"logLevel": {
5+
"Microsoft.Azure.Functions.DurableAgents": "Information",
6+
"DurableTask": "Information",
7+
"Microsoft.DurableTask": "Information"
8+
}
9+
},
10+
"extensions": {
11+
"durableTask": {
12+
"hubName": "default",
13+
"storageProvider": {
14+
"type": "AzureManaged",
15+
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
16+
}
17+
}
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"IsEncrypted": false,
3+
"Values": {
4+
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
5+
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
6+
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
7+
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
8+
"AZURE_OPENAI_DEPLOYMENT": "<AZURE_OPENAI_DEPLOYMENT>"
9+
}
10+
}

dotnet/samples/AzureFunctions/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This directory contains samples for Azure Functions.
88
- **[04_AgentOrchestration_Conditionals](04_AgentOrchestration_Conditionals)**: A sample that demonstrates how to host multiple agents in an Azure Functions app and run them sequentially using a durable orchestration with conditionals.
99
- **[05_AgentOrchestration_HITL](05_AgentOrchestration_HITL)**: A sample that demonstrates how to implement a human-in-the-loop workflow using durable orchestration, including external event handling for human approval.
1010
- **[06_LongRunningTools](06_LongRunningTools)**: A sample that demonstrates how agents can start and interact with durable orchestrations from tool calls to enable long-running tool scenarios.
11+
- **[07_AgentAsMcpTool](07_AgentAsMcpTool)**: A sample that demonstrates how to configure durable AI agents to be accessible as Model Context Protocol (MCP) tools.
1112

1213
## Running the Samples
1314

dotnet/src/Microsoft.Agents.AI.DurableTask/AgentEntity.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using Microsoft.Agents.AI.DurableTask.State;
34
using Microsoft.DurableTask.Client;
45
using Microsoft.DurableTask.Entities;
56
using Microsoft.Extensions.AI;
@@ -40,13 +41,11 @@ public async Task<AgentRunResponse> RunAgentAsync(RunRequest request)
4041
logger.LogInformation("Ignoring empty request");
4142
}
4243

43-
// TODO: Get state from optional state store
44-
DurableAgentState state = this.State;
44+
this.State.Data.ConversationHistory.Add(DurableAgentStateRequest.FromRunRequest(request));
4545

4646
foreach (ChatMessage msg in request.Messages)
4747
{
4848
logger.LogAgentRequest(sessionId, msg.Role, msg.Text);
49-
state.AddChatMessage(msg);
5049
}
5150

5251
// Set the current agent context for the duration of the agent run. This will be exposed
@@ -62,7 +61,7 @@ public async Task<AgentRunResponse> RunAgentAsync(RunRequest request)
6261
{
6362
// Start the agent response stream
6463
IAsyncEnumerable<AgentRunResponseUpdate> responseStream = agentWrapper.RunStreamingAsync(
65-
state.EnumerateChatMessages(),
64+
this.State.Data.ConversationHistory.SelectMany(e => e.Messages).Select(m => m.ToChatMessage()),
6665
agentWrapper.GetNewThread(),
6766
options: null,
6867
this._cancellationToken);
@@ -98,7 +97,8 @@ async IAsyncEnumerable<AgentRunResponseUpdate> StreamResultsAsync()
9897
}
9998

10099
// Persist the agent response to the entity state for client polling
101-
state.AddAgentResponse(response, request.CorrelationId);
100+
this.State.Data.ConversationHistory.Add(
101+
DurableAgentStateResponse.FromRunResponse(request.CorrelationId, response));
102102

103103
string responseText = response.Text;
104104

@@ -113,8 +113,6 @@ async IAsyncEnumerable<AgentRunResponseUpdate> StreamResultsAsync()
113113
response.Usage?.TotalTokenCount);
114114
}
115115

116-
this.UpdateEntityState(state);
117-
118116
return response;
119117
}
120118
finally
@@ -123,11 +121,4 @@ async IAsyncEnumerable<AgentRunResponseUpdate> StreamResultsAsync()
123121
DurableAgentContext.ClearCurrent();
124122
}
125123
}
126-
127-
private void UpdateEntityState(DurableAgentState state)
128-
{
129-
// This method is called to update the state of the entity.
130-
// It can be used to persist the state to a durable store if needed.
131-
this.State = state;
132-
}
133124
}

0 commit comments

Comments
 (0)