Skip to content

.NET: fix: mcp workflow isolation - #9545

Open
Vincent Biret (baywet) wants to merge 1 commit into
mainfrom
fix/mcp-workflow-session-isolation-dotnet
Open

Vincent Biret (baywet) wants to merge 1 commit into
mainfrom
fix/mcp-workflow-session-isolation-dotnet

Conversation

@baywet

Copy link
Copy Markdown
Member

dotnet part of the #8662 PR

Copilot AI balanced review requested due to automatic review settings October 19, 2026 19:20
@agent-framework-automation agent-framework-automation Bot added .NET Usage: [Issues, PRs], Target: .Net workflows Usage: [Issues, PRs], Target: Workflows labels Oct 19, 2026
@github-actions github-actions Bot changed the title fix: mcp workflow isolation .NET: fix: mcp workflow isolation Oct 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cache eviction can terminate unfinished workflows’ MCP sessions, breaking continuity and disrupting pending callers.

1 open issue
What changed in this PR

Implements the .NET portion of #8662, isolating declarative MCP sessions by workflow while preserving compatibility with existing handlers.

Changes:

  • Propagates workflow identity through execution contexts and subworkflows.
  • Adds workflow-scoped MCP caching, capacity limits, and coordinated cleanup.
  • Adds isolation, continuation, cancellation, and lifetime tests.
File Description
dotnet/​tests/​Microsoft.Agents.AI.Workflows.UnitTests/​RepresentationTests.cs Tests subworkflow identity and checkpoint compatibility.
dotnet/​tests/​Microsoft.Agents.AI.Workflows.Declarative.UnitTests/​PowerFx/​WorkflowFormulaStateTests.cs Tests persisted fallback identity.
dotnet/​tests/​Microsoft.Agents.AI.Workflows.Declarative.UnitTests/​ObjectModel/​InvokeMcpToolExecutorTest.cs Tests scoped invocation and approval handling.
dotnet/​tests/​Microsoft.Agents.AI.Workflows.Declarative.UnitTests/​Kit/​RootExecutorTests.cs Updates context setup.
dotnet/​tests/​Microsoft.Agents.AI.Workflows.Declarative.UnitTests/​Kit/​IWorkflowContextExtensionsTests.cs Updates context creation helpers.
dotnet/​tests/​Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/​DefaultMcpToolHandlerTests.cs Tests workflow-aware cache keys.
dotnet/​tests/​Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/​DefaultMcpToolHandlerLifetimeTests.cs Tests cache capacity, concurrency, and cleanup.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​SubworkflowBinding.cs Derives hierarchical session identifiers.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​Specialized/​WorkflowHostExecutor.cs Propagates child workflow identity.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​PublicAPI/​netstandard2.0/​PublicAPI.Unshipped.txt Records session-context API.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​PublicAPI/​net9.0/​PublicAPI.Unshipped.txt Records session-context API.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​PublicAPI/​net8.0/​PublicAPI.Unshipped.txt Records session-context API.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​PublicAPI/​net472/​PublicAPI.Unshipped.txt Records session-context API.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​PublicAPI/​net10.0/​PublicAPI.Unshipped.txt Records session-context API.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​IWorkflowContext.cs Defines optional session identity contract.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​InProc/​InProcessRunnerContext.cs Exposes identity through bound contexts.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​InProc/​InProcessRunner.cs Separates workflow and checkpoint identities.
dotnet/​src/​Microsoft.Agents.AI.Workflows/​GroupChatManager.cs Preserves identity through context wrappers.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​PublicAPI/​netstandard2.0/​PublicAPI.Unshipped.txt Records scoped handler contract.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​PublicAPI/​net9.0/​PublicAPI.Unshipped.txt Records scoped handler contract.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​PublicAPI/​net8.0/​PublicAPI.Unshipped.txt Records scoped handler contract.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​PublicAPI/​net472/​PublicAPI.Unshipped.txt Records scoped handler contract.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​PublicAPI/​net10.0/​PublicAPI.Unshipped.txt Records scoped handler contract.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​ObjectModel/​InvokeMcpToolExecutor.cs Dispatches calls with workflow scope.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​Kit/​RootExecutor.cs Creates session-aware contexts.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​Kit/​ActionExecutor.cs Creates session-aware contexts.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​Interpreter/​DelegateActionExecutor.cs Creates session-aware contexts.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​Interpreter/​DeclarativeWorkflowExecutor.cs Creates session-aware contexts.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​Interpreter/​DeclarativeWorkflowContext.cs Resolves and persists workflow identity.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​Interpreter/​DeclarativeActionExecutor.cs Creates session-aware contexts.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative/​IMcpToolHandler.cs Adds compatible scoped handler interface.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​PublicAPI/​netstandard2.0/​PublicAPI.Unshipped.txt Records scoped invocation API.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​PublicAPI/​net9.0/​PublicAPI.Unshipped.txt Records scoped invocation API.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​PublicAPI/​net8.0/​PublicAPI.Unshipped.txt Records scoped invocation API.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​PublicAPI/​net472/​PublicAPI.Unshipped.txt Records scoped invocation API.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​PublicAPI/​net10.0/​PublicAPI.Unshipped.txt Records scoped invocation API.
dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​DefaultMcpToolHandler.cs Implements scoped caching and resource lifetimes.

Review effort: Balanced

Comment on lines +454 to +459
clientToDispose = this._clients[idleNode.Value];
this._clients.Remove(idleNode.Value);
this._clientLru.Remove(idleNode);
clientToDispose.Evicted = true;
clientToDispose.DisposalClaimed = true;
this._retiredClients.Add(clientToDispose);
@github-code-quality

Copy link
Copy Markdown
Contributor

Code coverage overview

Languages: C#

C# / code-coverage/dotnet

main c01db29 fix/mcp-workflow-ses... 96cbf3c +/-
86% 86% 0%
View coverage of the 8 most impacted files.
File main c01db29 fix/mcp-workflow-ses... 96cbf3c +/-
/home/runner/wo...SessionStore.cs 100% 53% -47%
/home/runner/wo...unnerContext.cs 92% 92% 0%
/home/runner/wo...kflowContext.cs 81% 82% +1%
/home/runner/wo...pToolHandler.cs 88% 91% +3%
/home/runner/wo...rocessRunner.cs 91% 94% +3%
/home/runner/wo...pChatManager.cs 61% 64% +3%
/home/runner/wo...HostExecutor.cs 83% 86% +3%
/home/runner/wo...kflowBinding.cs 90% 93% +3%

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (1 commit(s)): 96cbf3c5c41f
Model: gpt-5.6-sol

Overview

The PR propagates a framework-owned workflow session identifier into MCP caching and adds substantial concurrency, cancellation, disposal, provider-isolation, and credential-origin guardrails. The new bounded cache coalesces same-key creation and carefully drains active and retired clients. Residual risks remain for legacy workflow contexts that initialize identity through queued state, for stateful sessions evicted while their workflows are still live, and for unrelated tenants sharing the fixed active-session limit.

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
3 verified findings remained after source verification (3 medium) across 2 files. Details are attached to the affected lines below.

Affected areas: dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs, dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs

string generatedSessionId = Guid.NewGuid().ToString("N");
string sessionId = source is IWorkflowSessionContext sessionContext
? sessionContext.SessionId
: await source.ReadOrInitStateAsync(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a legacy/custom IWorkflowContext that does not implement IWorkflowSessionContext, multiple declarative executors reaching this branch in one superstep each generate a different value. The IWorkflowContext contract permits initialization to be queued and warns that multiple executor initializations conflict, so this can split one workflow across MCP sessions or fail when state is published. Please make the workflow session ID an intrinsic shared runtime value, or require and propagate IWorkflowSessionContext, rather than deriving it through per-executor queued initialization.

throw new InvalidOperationException("MCP client capacity is exhausted; retry when an active invocation completes.");
}

clientToDispose = this._clients[idleNode.Value];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An idle client is only between calls; it does not mean its workflow session has ended. After 32 other keys are used, this evicts a still-live workflow's stateful MCP session, so its next tool call creates a fresh protocol session and silently loses server-side state. Please add a workflow-session completion/release lifecycle and evict only ended or expired scopes, or otherwise preserve continuity for live workflow sessions.


if (!this._clientCreations.TryGetValue(clientCacheKey, out clientCreation))
{
if (this._clients.Count + this._retiredClients.Count + this._clientCreations.Count >= this._clientCacheMaxSize)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixed handler-wide quota lets 32 active distinct scopes consume every slot, after which unrelated workflows using a new session/server/header key fail immediately with InvalidOperationException until one call completes or is cancelled. Because the public constructor cannot tune or partition the limit, one tenant's slow MCP calls can deny MCP access to every other tenant sharing the handler. Please isolate capacity per tenant/workflow owner or add fair backpressure and a configurable limit so one tenant cannot exhaust the shared pool.

private readonly HashSet<CachedClient> _retiredClients = [];
private readonly Dictionary<string, OwnedHttpClient> _ownedHttpClients = [];
private readonly SemaphoreSlim _clientLock = new(1, 1);
private readonly SemaphoreSlim _clientCreationSemaphore;

This branch was successfully deployed

2 active deployments
integration — 96cbf3c5 Deployed Oct 19, 2026 by baywet via dotnet-foundry-hosted-it #30717
github-app-auth — 96cbf3c5 Deployed Oct 19, 2026 by baywet via team_check #7080
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NET Usage: [Issues, PRs], Target: .Net workflows Usage: [Issues, PRs], Target: Workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants