diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index ee71fa729a..ff815348c7 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -77,6 +77,7 @@
+
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/05_WorkflowAndAgents.csproj b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/05_WorkflowAndAgents.csproj
new file mode 100644
index 0000000000..517dd323a7
--- /dev/null
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/05_WorkflowAndAgents.csproj
@@ -0,0 +1,42 @@
+
+
+ net10.0
+ v4
+ Exe
+ enable
+ enable
+
+ WorkflowAndAgents
+ WorkflowAndAgents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Executors.cs b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Executors.cs
new file mode 100644
index 0000000000..727379b482
--- /dev/null
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Executors.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace WorkflowAndAgents;
+
+internal sealed class TranslateText() : Executor("TranslateText")
+{
+ public override ValueTask HandleAsync(
+ string message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine($"[Activity] TranslateText: '{message}'");
+ return ValueTask.FromResult(new TranslationResult(message, message.ToUpperInvariant()));
+ }
+}
+
+internal sealed class FormatOutput() : Executor("FormatOutput")
+{
+ public override ValueTask HandleAsync(
+ TranslationResult message,
+ IWorkflowContext context,
+ CancellationToken cancellationToken = default)
+ {
+ Console.WriteLine("[Activity] FormatOutput: Formatting result");
+ return ValueTask.FromResult($"Original: {message.Original} => Translated: {message.Translated}");
+ }
+}
+
+internal sealed record TranslationResult(string Original, string Translated);
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs
new file mode 100644
index 0000000000..51b9fb4d7f
--- /dev/null
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/Program.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates using ConfigureDurableOptions to register BOTH agents AND workflows
+// in a single Azure Functions app. It uses a workflow to translate text and a standalone AI agent
+// accessible via HTTP and MCP tool triggers.
+
+#pragma warning disable IDE0002 // Simplify Member Access
+
+using Azure;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hosting.AzureFunctions;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+using OpenAI.Chat;
+using WorkflowAndAgents;
+
+// Get the Azure OpenAI endpoint and deployment name from environment variables.
+string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
+ ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
+ ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
+
+// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
+string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
+AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
+ ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
+ : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential());
+
+ChatClient chatClient = client.GetChatClient(deploymentName);
+
+// Define a standalone AI agent
+AIAgent assistant = chatClient.AsAIAgent(
+ "You are a helpful assistant. Answer questions clearly and concisely.",
+ "Assistant",
+ description: "A general-purpose helpful assistant.");
+
+// Define workflow executors
+TranslateText translateText = new();
+FormatOutput formatOutput = new();
+
+// Build a workflow: TranslateText -> FormatOutput
+Workflow translateWorkflow = new WorkflowBuilder(translateText)
+ .WithName("Translate")
+ .WithDescription("Translate text to uppercase and format the result")
+ .AddEdge(translateText, formatOutput)
+ .Build();
+
+// Use ConfigureDurableOptions to register both agents and workflows together
+using IHost app = FunctionsApplication
+ .CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableOptions(options =>
+ {
+ // Register the standalone agent with HTTP and MCP tool triggers
+ options.Agents.AddAIAgent(assistant, enableHttpTrigger: true, enableMcpToolTrigger: true);
+
+ // Register the workflow with an HTTP endpoint and MCP tool trigger
+ options.Workflows.AddWorkflow(translateWorkflow, exposeStatusEndpoint: false, exposeMcpToolTrigger: true);
+ })
+ .Build();
+app.Run();
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/README.md b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/README.md
new file mode 100644
index 0000000000..37841777cc
--- /dev/null
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/README.md
@@ -0,0 +1,76 @@
+# Workflow and Agents Sample
+
+This sample demonstrates how to use `ConfigureDurableOptions` to register **both** AI agents **and** workflows in a single Azure Functions app. This is the recommended approach when your application needs both standalone agents and orchestrated workflows.
+
+## Key Concepts Demonstrated
+
+- **Unified Configuration**: Use `ConfigureDurableOptions` to register agents and workflows together
+- **Standalone Agent**: An AI agent accessible via HTTP and MCP tool triggers
+- **Workflow**: A simple text translation workflow also exposed as an MCP tool
+- **Mixed Triggers**: Both agents and workflows coexist in the same Functions host
+
+## Sample Architecture
+
+### Standalone Agent
+
+| Agent | Description |
+|-------|-------------|
+| **Assistant** | A general-purpose AI assistant accessible via HTTP (`/agents/Assistant/run`) and as an MCP tool |
+
+### Translate Workflow
+
+| Executor | Input | Output | Description |
+|----------|-------|--------|-------------|
+| **TranslateText** | `string` | `TranslationResult` | Converts input text to uppercase |
+| **FormatOutput** | `TranslationResult` | `string` | Formats the result into a readable string |
+
+## Environment Setup
+
+See the [README.md](../../README.md) file in the parent directory for complete setup instructions, including:
+
+- Prerequisites installation
+- Durable Task Scheduler setup
+- Storage emulator configuration
+
+This sample also requires Azure OpenAI credentials. Set the following in `local.settings.json`:
+
+- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint URL
+- `AZURE_OPENAI_DEPLOYMENT_NAME`: Your chat model deployment name
+- `AZURE_OPENAI_API_KEY` (optional): If not set, Azure CLI credential is used
+
+## Running the Sample
+
+1. **Start the Function App**:
+
+ ```bash
+ cd dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents
+ func start
+ ```
+
+2. **Expected Functions**: When the app starts, you should see functions for both the agent and the workflow:
+
+ - `dafx-Assistant` (entity trigger for the agent)
+ - `http-Assistant` (HTTP trigger for the agent)
+ - `mcptool-Assistant` (MCP tool trigger for the agent)
+ - `wf-Translate` (orchestration trigger for the workflow)
+ - `mcptool-wf-Translate` (MCP tool trigger for the workflow)
+
+## Invoking the Agent via HTTP
+
+```bash
+curl -X POST http://localhost:7071/agents/Assistant/run \
+ -H "Content-Type: application/json" \
+ -d '{"query": "What is the capital of France?"}'
+```
+
+## Invoking via MCP Inspector
+
+1. Install and run the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector):
+
+ ```bash
+ npx @modelcontextprotocol/inspector
+ ```
+
+2. Connect to `http://localhost:7071/runtime/webhooks/mcp` using **Streamable HTTP** transport.
+
+3. Click **List Tools** to see both the `Assistant` agent tool and the `Translate` workflow tool.
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/host.json b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/host.json
new file mode 100644
index 0000000000..9384a0a583
--- /dev/null
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/host.json
@@ -0,0 +1,20 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Microsoft.Agents.AI.DurableTask": "Information",
+ "Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
+ "DurableTask": "Information",
+ "Microsoft.DurableTask": "Information"
+ }
+ },
+ "extensions": {
+ "durableTask": {
+ "hubName": "default",
+ "storageProvider": {
+ "type": "AzureManaged",
+ "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/local.settings.json b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/local.settings.json
new file mode 100644
index 0000000000..5f6d7d3340
--- /dev/null
+++ b/dotnet/samples/04-hosting/DurableWorkflows/AzureFunctions/05_WorkflowAndAgents/local.settings.json
@@ -0,0 +1,10 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
+ "AZURE_OPENAI_ENDPOINT": "",
+ "AZURE_OPENAI_DEPLOYMENT_NAME": ""
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs
index 2e9cc801c7..959ffab2f6 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs
@@ -67,6 +67,13 @@ public static class FunctionsApplicationBuilderExtensions
builder.Services.ConfigureDurableOptions(configure);
+ if (sharedOptions.Agents.GetAgentFactories().Count > 0)
+ {
+ builder.Services.TryAddSingleton(_ =>
+ new DefaultFunctionsAgentOptionsProvider(DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot()));
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ }
+
if (sharedOptions.Workflows.Workflows.Count > 0)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
@@ -102,6 +109,7 @@ public static class FunctionsApplicationBuilderExtensions
builder.UseWhen(static context =>
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) ||
+ string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) ||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal) ||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrchestrationHttpFunctionEntryPoint, StringComparison.Ordinal) ||
string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrchestrationFunctionEntryPoint, StringComparison.Ordinal) ||
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
index 64e35ecc88..af62436ea4 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs
@@ -293,6 +293,58 @@ public sealed class WorkflowSamplesValidation(ITestOutputHelper outputHelper) :
});
}
+ [Fact]
+ public async Task WorkflowAndAgentsSampleValidationAsync()
+ {
+ string samplePath = Path.Combine(s_samplesPath, "05_WorkflowAndAgents");
+ await this.RunSampleTestAsync(samplePath, requiresOpenAI: true, async (logs) =>
+ {
+ // Connect to the MCP endpoint exposed by the Azure Functions host
+ IClientTransport clientTransport = new HttpClientTransport(new()
+ {
+ Endpoint = new Uri($"http://localhost:{AzureFunctionsPort}/runtime/webhooks/mcp")
+ });
+
+ await using McpClient mcpClient = await McpClient.CreateAsync(clientTransport);
+
+ // Verify both the agent and workflow tools are listed
+ IList tools = await mcpClient.ListToolsAsync();
+ this._outputHelper.WriteLine($"MCP tools found: {string.Join(", ", tools.Select(t => t.Name))}");
+
+ Assert.Single(tools, t => t.Name == "Assistant");
+ Assert.Single(tools, t => t.Name == "Translate");
+
+ // Invoke the Translate workflow via MCP tool
+ this._outputHelper.WriteLine("Invoking MCP tool 'Translate'...");
+ CallToolResult translateResult = await mcpClient.CallToolAsync(
+ "Translate",
+ arguments: new Dictionary { { "input", "hello world" } });
+
+ Assert.NotEmpty(translateResult.Content);
+ string translateResponse = Assert.IsType(translateResult.Content[0]).Text;
+ this._outputHelper.WriteLine($"Translate MCP tool response: {translateResponse}");
+ Assert.Contains("HELLO WORLD", translateResponse);
+
+ // Invoke the Assistant agent via MCP tool
+ this._outputHelper.WriteLine("Invoking MCP tool 'Assistant'...");
+ CallToolResult assistantResult = await mcpClient.CallToolAsync(
+ "Assistant",
+ arguments: new Dictionary { { "query", "What is 2 + 2?" } });
+
+ Assert.NotEmpty(assistantResult.Content);
+ string assistantResponse = Assert.IsType(assistantResult.Content[0]).Text;
+ this._outputHelper.WriteLine($"Assistant MCP tool response: {assistantResponse}");
+ Assert.NotEmpty(assistantResponse);
+
+ // Verify workflow executor activities ran in the logs
+ lock (logs)
+ {
+ Assert.True(logs.Any(log => log.Message.Contains("[Activity] TranslateText:")), "TranslateText activity not found in logs.");
+ Assert.True(logs.Any(log => log.Message.Contains("[Activity] FormatOutput:")), "FormatOutput activity not found in logs.");
+ }
+ });
+ }
+
[Fact]
public async Task ConcurrentWorkflowSampleValidationAsync()
{