Repository navigation
.NET: [BREAKING] fix(dotnet): validate declarative MCP destinations - #10192
Vincent Biret (baywet) wants to merge 4 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3333dcdf-889a-4ccc-af0c-4e41fd966d74
There was a problem hiding this comment.
🔵 Needs a closer look
Proposed ADR 0055 introduces a breaking, security-sensitive destination-authorization contract that requires PR approval from its designated architectural decider, westey-m.
No open issues 🎉
What changed in this PR
This PR makes .NET declarative MCP destinations application-controlled, using the same host-and-port matching policy as declarative HTTP requests. The existing constructor remains available but now denies all destinations until callers migrate to the allowlist overload.
Changes:
- Add destination validation before client creation and reject workflow-supplied
Hostheaders. - Share HTTP host normalization and extend tests for denial, matching, and copied configuration.
- Migrate samples and integration tests; update API baselines, guidance, and proposed ADR 0055.
| File | Description |
|---|---|
| skills/agent-framework-builder-dotnet/references/declarative.md | Emphasizes application-owned destination policy. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs | Tests default denial, invalid hosts, and Host header rejection. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerLifetimeTests.cs | Migrates lifetime tests and covers matching and configuration copying. |
| dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/InvokeToolWorkflowTest.cs | Explicitly allows the integration-test destination. |
| dotnet/src/Shared/Http/AllowedHttpHosts.cs | Extracts shared host-and-port validation and matching. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/README.md | Documents destination policy and constructor migration. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/DefaultHttpRequestHandler.cs | Uses the shared matcher without changing matching rules. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt | Records the allowlist constructor for .NET Standard 2.0. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/PublicAPI/net9.0/PublicAPI.Unshipped.txt | Records the allowlist constructor for .NET 9. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/PublicAPI/net8.0/PublicAPI.Unshipped.txt | Records the allowlist constructor for .NET 8. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/PublicAPI/net472/PublicAPI.Unshipped.txt | Records the allowlist constructor for .NET Framework 4.7.2. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/PublicAPI/net10.0/PublicAPI.Unshipped.txt | Records the allowlist constructor for .NET 10. |
| dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs | Adds the allowlist overload and pre-client destination checks. |
| dotnet/samples/03-workflows/Declarative/InvokeMcpTool/Program.cs | Configures explicit sample destinations. |
| dotnet/samples/03-workflows/Declarative/InvokeFoundryToolboxMcp/Program.cs | Allows the configured toolbox endpoint’s authority. |
| docs/decisions/0055-dotnet-declarative-mcp-allowed-hosts.md | Proposes the default-deny policy and documents compatibility trade-offs. |
Review effort: Balanced
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (1 commit(s)): 71fde3437c4d
Model: gpt-5.6-sol
Overview
The PR introduces default-deny, exact host-and-port authorization for declarative MCP calls, rejects workflow-supplied Host overrides, and preserves per-invocation cleanup. Its direct-destination and lifecycle tests provide strong coverage, but the authorization check does not constrain automatic redirects in the application-provided HTTP pipeline. As a result, a redirect can escape the newly introduced destination boundary.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 high) across 1 file. Details are attached to the affected lines below.
Affected areas: dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs
Code coverage overviewLanguages: C# C# / code-coverage/dotnet
View coverage of the 5 most impacted files.
ℹ️ Calculations represent line coverage. Results may change as calculations are updated. Updated |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3333dcdf-889a-4ccc-af0c-4e41fd966d74
There was a problem hiding this comment.
MAF Automated Review — Iteration 2
Result: Findings reported
Scope: 1 net-new commit(s): 5e6ed6ef2406
Model: gpt-5.6-sol
Overview
The incremental change hardens declarative MCP destination enforcement by requiring application-owned handler pipelines with redirects and cookies disabled, while preserving default-deny behavior and per-invocation cleanup. Regression tests cover redirect responses, malformed pipelines, and pooled-handler ownership. One Medium issue remains: transport validation can spin indefinitely on a cyclic delegating-handler chain.
Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
1 verified finding remained after source verification (1 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: dotnet/src/Shared/Http/HttpTransportValidation.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3333dcdf-889a-4ccc-af0c-4e41fd966d74
There was a problem hiding this comment.
🔵 Needs a closer look
ADR 0055 remains proposed, and its breaking destination-authorization and HTTP-factory contract changes require approval from listed decider westey-m for the architectural and security trade-offs.
No open issues 🎉
Review effort: Balanced
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3333dcdf-889a-4ccc-af0c-4e41fd966d74
There was a problem hiding this comment.
🔵 Needs a closer look
The breaking public-constructor replacement and security-sensitive destination-authorization changes require approval from westey-m, the listed decider for proposed ADR 0055.
No open issues 🎉
1 resolved since last review
Review effort: Balanced

Motivation & Context
Make declarative MCP destination handling consistent with application-configured HTTP destinations. Applications should configure the servers their workflows use before the default handler creates a client.
Description & Review Guide
DefaultMcpToolHandler(IHttpClientFactory, string)constructor withDefaultMcpToolHandler(IEnumerable<string>, IHttpMessageHandlerFactory, string), share the existing HTTP host-and-port matcher and transport validation, and reject workflow-supplied Host headers. Update callers, API baselines, guidance, and proposed ADR 0055.IHttpMessageHandlerFactory, and the named-client configuration. An empty list denies both tool calls and discovery. The pipeline must terminate in a supported HTTP transport with automatic redirects and cookies disabled; unknown, incomplete, or cyclic pipelines are rejected before sending. HTTP client wrappers and MCP sessions are disposed per invocation without disposing factory-owned pooled handlers.IHttpClientFactoryclient-configuration callbacks do not apply: move headers and authentication to delegating handlers. Protocol selection and host-matching rules are unchanged.Related Issue
N/A - maintainer-directed bugfix; no public issue. No matching open pull request was found.
AI Assistance
AI assistance details: Copilot assisted with implementation, regression tests, caller updates, documentation, validation, and this description.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.Commands below were run from
dotnet/, except formatting and the diff check, which were run from the repository root.dotnet run --project tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests -f net10.0 --no-restore -- --filter-method '*InvokeToolAsync_WithoutAllowedHosts_ShouldRejectBeforeClientCreationAsync' --no-progress— before: 2 failed because client creation occurred; after: 2 passed.dotnet run --project tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests -f net10.0 --no-restore -- --filter-method '*Factory_InvalidTransportSettings_RejectBeforeInitializationAsync' --progress off— before the transport change: 2 failed because both invocations proceeded; after: 2 passed.dotnet run --project tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests -f net10.0 --no-restore -- --filter-method '*Factory_CyclicPipeline_RejectsBeforeSendingAsync' --progress off— before cycle detection: 2 failed because validation did not complete; after: 2 passed.dotnet run --project tests\Microsoft.Agents.AI.Workflows.Declarative.UnitTests -f net10.0 --no-restore -- --filter-method '*SendAsyncRejectsCyclicPipelineBeforeTransportAsync' --progress off— before cycle detection: 2 failed because validation did not complete; after: 2 passed.dotnet run --project tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests -f net10.0 --no-restore -- --progress off— passed after constructor removal, 97 tests, including cycle rejection, 307/308 redirect-response, and pooled-handler coverage. Existing denial regressions now use an explicit empty allowlist; two duplicate empty-policy cases were consolidated.dotnet run --project tests\Microsoft.Agents.AI.Workflows.Declarative.UnitTests -f net10.0 --no-build -- --progress off— passed before constructor removal, 1,377 tests, including all 108 HTTP-handler cases; this follow-up does not change the declarative HTTP implementation.dotnet build src\Microsoft.Agents.AI.Workflows.Declarative.Mcp -c Release --no-restore --tl:off— passed, all target frameworks and package validation; zero warnings or errors.dotnet build samples\03-workflows\Declarative\InvokeMcpTool -f net10.0 --no-restore --tl:off— passed.dotnet build samples\03-workflows\Declarative\InvokeFoundryToolboxMcp -f net10.0 --no-restore --tl:off— passed.dotnet build tests\Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests -f net10.0 --no-restore --tl:off— passed. Credentialed integration tests and sample execution were not run.dotnet format $project --no-restore --include $changed --verbosity minimal— passed with$projectset to each ofdotnet\src\Microsoft.Agents.AI.Workflows.Declarative,dotnet\src\Microsoft.Agents.AI.Workflows.Declarative.Mcp,dotnet\tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests,dotnet\tests\Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests,dotnet\samples\03-workflows\Declarative\InvokeMcpTool, anddotnet\samples\03-workflows\Declarative\InvokeFoundryToolboxMcp;$changedcontained the absolute changed C# paths, including the new shared HTTP helper.dotnet format $project --no-restore --include $changed --verbosity minimal— passed again after cycle detection fordotnet\src\Microsoft.Agents.AI.Workflows.Declarative,dotnet\src\Microsoft.Agents.AI.Workflows.Declarative.Mcp,dotnet\tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests, anddotnet\tests\Microsoft.Agents.AI.Workflows.Declarative.UnitTests;$changedcontained the shared validator and both changed test files.dotnet format $project --no-restore --include $changed --verbosity minimal— passed after constructor removal fordotnet\src\Microsoft.Agents.AI.Workflows.Declarative.Mcpanddotnet\tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests;$changedcontained the three changed C# files.git diff --check— passed.dotnet build --tl:off— blocked by unavailable, unrelatedA2A/A2A.Grpcversion1.0.0-preview4packages (NU1102; available feed has preview3). The full solution unit suite was not run.dotnet test --project tests\Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests -f net10.0 --filter-query '/*/*/DefaultMcpToolHandlerTests/InvokeToolAsync_WithoutAllowedHosts_ShouldRejectBeforeClientCreationAsync' --no-restore --no-progress --tl:off— blocked by an MTP handshake failure; the documented direct runner above executed the tests successfully.