.NET: Support InvokeMcpTool for declarative workflows#4204
.NET: Support InvokeMcpTool for declarative workflows#4204
Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-class support for invoking MCP (Model Context Protocol) server tools directly from .NET declarative workflows (without going through an AI agent tool-selection step), including approval handling, result parsing, integration tests, and a Getting Started sample.
Changes:
- Introduces
InvokeMcpToolExecutorplusIMcpToolHandlerand aDefaultMcpToolHandlerimplementation based on the MCP C# SDK. - Wires MCP tool handling through workflow options and factory plumbing; adds workflow interpreter support for
InvokeMcpTool. - Adds unit + integration tests and a new declarative workflow sample demonstrating direct MCP tool invocation.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/InvokeMcpToolExecutor.cs | New executor implementing InvokeMcpTool execution + approval + output assignment. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/IMcpToolHandler.cs | New abstraction for MCP tool invocation used by declarative workflows. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/DefaultMcpToolHandler.cs | Default MCP SDK-based handler implementation with client caching and content mapping. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/DeclarativeWorkflowOptions.cs | Adds McpToolHandler option to enable MCP tool invocation in workflows. |
| dotnet/src/Shared/Workflows/Execution/WorkflowFactory.cs | Plumbs McpToolHandler through factory-created workflow options. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/WorkflowActionVisitor.cs | Adds interpreter support to build workflow graph nodes/ports for InvokeMcpTool (incl. approval flow). |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/WorkflowTemplateVisitor.cs | Marks InvokeMcpTool as not supported for template generation (consistent with other tool-invocation actions). |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/InvokeFunctionToolExecutor.cs | Refactors array schema inference to use GetListTypeFromJson extension. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/JsonDocumentExtensions.cs | Adds GetListTypeFromJson and improves nested/empty-array parsing behavior. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/InvokeMcpToolExecutorTest.cs | New unit tests covering InvokeMcpTool executor behavior and result parsing scenarios. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/JsonDocumentExtensionsTests.cs | Adds tests for list-type inference from JSON arrays. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Framework/IntegrationTest.cs | Extends test options creation to optionally provide an MCP tool handler. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/InvokeToolWorkflowTest.cs | New integration tests covering InvokeFunctionTool + InvokeMcpTool workflows and approval paths. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/InvokeFunctionToolWorkflowTest.cs | Removes older InvokeFunctionTool-only integration test (superseded by combined test). |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Workflows/InvokeMcpTool.yaml | New workflow YAML for invoking an MCP tool without approval. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Workflows/InvokeMcpToolWithApproval.yaml | New workflow YAML for invoking an MCP tool with approval required. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj | Adds ModelContextProtocol package dependency for MCP support. |
| dotnet/Directory.Packages.props | Updates workflow-related package versions (and central MCP package version already present). |
| dotnet/samples/GettingStarted/Workflows/Declarative/InvokeMcpTool/Program.cs | New sample showing how to configure McpToolHandler and run an MCP-enabled workflow. |
| dotnet/samples/GettingStarted/Workflows/Declarative/InvokeMcpTool/InvokeMcpTool.yaml | New sample workflow chaining MCP calls and an agent summarization step. |
| dotnet/samples/GettingStarted/Workflows/Declarative/InvokeMcpTool/InvokeMcpTool.csproj | New sample project for the InvokeMcpTool workflow demo. |
| dotnet/agent-framework-dotnet.slnx | Adds the new InvokeMcpTool sample project to the solution. |
Comments suppressed due to low confidence (2)
dotnet/samples/GettingStarted/Workflows/Declarative/InvokeMcpTool/Program.cs:63
- This sample's DefaultAzureCredential warning is less specific than the standard warning used elsewhere in the repo (latency, unintended credential probing, and security risks from fallback mechanisms). Consider aligning the comment text with the established wording so readers get consistent production guidance.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
DefaultAzureCredential credential = new();
DefaultMcpToolHandler mcpToolHandler = new(
dotnet/samples/GettingStarted/Workflows/Declarative/InvokeMcpTool/Program.cs:121
- There is a second DefaultAzureCredential warning here that also doesn’t include the repo-standard details (latency, unintended credential probing, and security risks from fallback mechanisms). Consider using the same warning wording used in other GettingStarted samples for consistency.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential());
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/IMcpToolHandler.cs
Outdated
Show resolved
Hide resolved
...t/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/InvokeToolWorkflowTest.cs
Outdated
Show resolved
Hide resolved
|
|
||
| /// <inheritdoc/> | ||
| [SendsMessage(typeof(ExternalInputRequest))] | ||
| protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Question to make sure I understand this properly: ExecuteAsync resolves serverUrl, toolName, arguments, etc. and builds the approval request from them. Then in CaptureResponseAsync (after the user approves), we re-call GetServerUrl(), GetToolName(), GetArguments(), etc. instead of reusing the already-resolved values? If a workflow expression like =Local.SomeValue resolved differently between those two points, the user would have approved parameters that differ from what actually executes. Could this scenario happen?
| CancellationToken cancellationToken = default) | ||
| { | ||
| //TODO: Handle connectionName and server label appropriately when Hosted scenario supports them. For now, ignore | ||
| McpServerToolResultContent resultContent = new("McpServerToolcallId"); |
There was a problem hiding this comment.
Should this be a hard-coded string?
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public async Task<McpServerToolResultContent> InvokeToolAsync( |
There was a problem hiding this comment.
Do we need exception handling around this?
| /// <summary> | ||
| /// Creates a VariableType.List with schema inferred from the first object element in the array. | ||
| /// </summary> | ||
| internal static VariableType GetListTypeFromJson(this JsonElement arrayElement) |
There was a problem hiding this comment.
nit - This can be public as the class is internal (or private?)
| JsonValueKind.True => typeof(bool), | ||
| JsonValueKind.False => typeof(bool), | ||
| JsonValueKind.Number => typeof(decimal), | ||
| JsonValueKind.Array => (VariableType)VariableType.ListType, // Add support for nested arrays |
There was a problem hiding this comment.
I wonder about this. As a private method isnt ParseRecord only invoked when the object type is Record/Object?
| /// Integration tests for InvokeFunctionTool and InvokeMcpTool actions. | ||
| /// </summary> | ||
| public sealed class InvokeFunctionToolWorkflowTest(ITestOutputHelper output) : IntegrationTest(output) | ||
| public sealed class InvokeToolWorkflowTest(ITestOutputHelper output) : IntegrationTest(output) |
| /// <summary> | ||
| /// Tests for <see cref="InvokeMcpToolExecutor"/>. | ||
| /// </summary> | ||
| public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output) |
| <PackageReference Include="Microsoft.PowerFx.Interpreter" /> | ||
| <PackageReference Include="Microsoft.Extensions.Configuration" /> | ||
| <PackageReference Include="Microsoft.Extensions.Logging" /> | ||
| <PackageReference Include="ModelContextProtocol" /> |
There was a problem hiding this comment.
nit: Would it make sense to have a Microsoft.Agents.AI.Workflows.Declarative.Mcp package so that people only pull in ModelContextProtocol when they need it?
Motivation and Context
This implementation supports the
InvokeMcpToolaction in a declarative workflow to call configured mcp servers directly from the workflow without going through an AI agent first. This is different from the standardInvokeAzureAgentpattern where the AI decides which functions to call based on the conversation.Includes tests and samples for the new action type and sample yaml implementations.
Fixes #3415
Fixes #3738
Contribution Checklist