Skip to content

Python: .NET/Python: Isolate declarative MCP client sessions by workflow - #8662

Draft
Vincent Biret (baywet) wants to merge 28 commits into
mainfrom
fix/mcp-workflow-session-isolation
Draft

Vincent Biret (baywet) wants to merge 28 commits into
mainfrom
fix/mcp-workflow-session-isolation

Conversation

@baywet

Copy link
Copy Markdown
Member

Motivation & Context

The default declarative MCP handlers cached connected clients by endpoint, label, connection name, and headers. When one handler instance served multiple workflow sessions, those sessions could therefore reuse the same stateful MCP protocol session. This change fixes the MCP client session isolation/cache scoping bug while preserving session continuity within one workflow session and the existing endpoint, header, label, and connection isolation.

Description & Review Guide

  • What are the major changes? Added framework-owned workflow session identity to the Python invocation contract and a workflow-scoped .NET handler contract, propagated trusted session IDs from workflow execution contexts, and included that identity in default MCP client cache keys. Added regression coverage for separate workflows, same-workflow continuation, cache-key discrimination, and concurrent cache creation.
  • What is the impact of these changes? Separate workflow sessions no longer share a stateful MCP protocol session through a shared default handler. Calls within the same workflow session continue to reuse the cached MCP client when all existing cache identity dimensions also match. Custom handlers using the existing .NET contract remain compatible.
  • What do you want reviewers to focus on? Please review the workflow-owned scope propagation and the compatibility behavior for custom handlers and direct unscoped handler calls.

Related Issue

No public issue is available for this MCP client session isolation/cache scoping bug.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7ed91283-cb21-49a7-98f4-b1c088d0b6b6
Copilot AI balanced review requested due to automatic review settings September 22, 2026 18:08
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python .NET Usage: [Issues, PRs], Target: .Net labels Sep 22, 2026
@agent-framework-automation agent-framework-automation Bot added the workflows Usage: [Issues, PRs], Target: Workflows label Sep 22, 2026
@github-actions github-actions Bot changed the title .NET/Python: Isolate declarative MCP client sessions by workflow .NET: .NET/Python: Isolate declarative MCP client sessions by workflow Sep 22, 2026
@github-actions github-actions Bot changed the title .NET: .NET/Python: Isolate declarative MCP client sessions by workflow Python: .NET/Python: Isolate declarative MCP client sessions by workflow Sep 22, 2026

This comment was marked as outdated.

@github-code-quality

github-code-quality Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Python, C#

Python / code-coverage/python

The overall line coverage in commit 447f8b6 in the fix/mcp-workflow-ses... branch is 92%. Line coverage data for the main branch is not yet available.

Show a line coverage summary of the most covered files.
File main fix/mcp-workflow-ses... 447f8b6 +/-
packages/core/a...ework/_tools.py — 96% —
packages/core/a...ework/_types.py — 95% —
packages/core/a...work/_skills.py — 95% —
packages/openai..._chat_client.py — 94% —
packages/core/a.../_compaction.py — 94% —
packages/core/a...ork/_vectors.py — 93% —
packages/core/a...bservability.py — 93% —
packages/core/a...amework/_mcp.py — 92% —
packages/ag-ui/...i/_agent_run.py — 90% —
packages/core/a...ork/security.py — 89% —

C# / code-coverage/dotnet

The overall line coverage in commit 447f8b6 in the fix/mcp-workflow-ses... branch is 85%. Line coverage data for the main branch is not yet available.

Show a line coverage summary of the most covered files.
File main fix/mcp-workflow-ses... 447f8b6 +/-
/home/runner/wo...valConverter.cs — 100% —
/home/runner/wo...entsProvider.cs — 99% —
/home/runner/wo...egatingAgent.cs — 99% —
/home/runner/wo...nticAnalyzer.cs — 94% —
/home/runner/wo...putConverter.cs — 90% —
/home/runner/wo...kflowBuilder.cs — 90% —
/home/runner/wo...SkillsSource.cs — 89% —
/home/runner/wo...onExtensions.cs — 81% —
/home/runner/wo...CopilotAgent.cs — 76% —
/home/runner/wo...ctionVisitor.cs — 75% —

Updated September 29, 2026 18:36 UTC

@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)): bb9e92b189ed
Model: gpt-5.6-sol-fast

Overview

The change adds workflow identity to both default MCP cache keys while preserving the existing endpoint, label, connection, and header dimensions, and its regression tests cover normal isolation, continuation, and same-key creation coalescing. Provider-backed calls also retain their per-invocation lifecycle and shutdown guards. Two residual gaps remain: the .NET compatibility fallback is scoped to a reusable workflow object rather than an execution session, and Python's completed-entry LRU does not bound the now-per-workflow set of concurrent connection attempts.

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

Affected areas: dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowContext.cs, python/packages/declarative/agent_framework_declarative/_workflows/_mcp_handler.py

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread python/packages/declarative/tests/test_default_mcp_tool_handler.py Fixed
Comment thread python/packages/declarative/tests/test_default_mcp_tool_handler.py Fixed

This comment was marked as outdated.

@baywet

Copy link
Copy Markdown
Member Author

Addressed the latest review summary item about .NET global client lock contention in current branch changes: cache misses now reserve/coalesce an in-flight creation under _clientLock, run CreateClientAsync outside the global lock, and publish/evict under the lock afterward. Added NoProvider_ConcurrentWorkflowSessionCreations_DoNotSerializeHandshakeAsync to cover concurrent workflow handshakes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 89548cb9-9583-47c1-a00f-675aebf810f8
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 936efea1-d581-4dea-813f-1edcfd2ea3bf

This comment was marked as outdated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 936efea1-d581-4dea-813f-1edcfd2ea3bf
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 936efea1-d581-4dea-813f-1edcfd2ea3bf
Comment thread python/packages/declarative/tests/test_default_mcp_tool_handler.py Fixed
Comment thread python/packages/declarative/tests/test_default_mcp_tool_handler.py Fixed
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 936efea1-d581-4dea-813f-1edcfd2ea3bf

This comment was marked as outdated.

Ensure cleanup-originated cancellation is surfaced only after all claimed MCP sessions and completion signals have drained in both implementations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0

This comment was marked as outdated.

Use awaited task results instead of Task.IsCompletedSuccessfully so the regression test compiles on .NET Framework.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0
Track each cache client creator through its final semaphore release so handler disposal cannot tear down synchronization resources while creation is still exiting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

This comment was marked as outdated.

Give subworkflows stable hierarchical cache scopes and ensure Python cached-entry leases release fully despite repeated caller cancellation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0
Comment thread python/packages/declarative/tests/test_default_mcp_tool_handler.py Fixed
Await the cancelled invocation through asyncio.gather so the test's coordination effect is explicit to code-quality analysis.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0

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.

Copilot review overview

🔵 Needs a closer look

Unresolved checkpoint compatibility and cancellation behavior affect session continuity and shutdown across both implementations.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity

Open (3)
Resolved since last review (2)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Propagate cached client disposal failures during shutdown

dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​DefaultMcpToolHandler.cs:656

When DisposeAsync encounters a client still in use, it waits on client.Disposed.Task rather than disposing it itself. The final invocation then runs DisposeCachedClientAsync; if session cleanup throws OperationCanceledException, this line marks the task successful, so handler disposal can report success despite failed cleanup. Signal the actual disposal failure through the completion task and let shutdown wait for all clients before reporting it.

Medium severity Avoid rotating session IDs on continued agent turns

python/​packages/​declarative/​agent_framework_declarative/​_workflows/​_executors_control_flow.py:416

A second Workflow.as_agent() turn also enters this Entry action, but _ensure_state_initialized preserves the existing declarative state for a list[Message] continuation. Unconditionally rotating the ID here makes that conversation open a new MCP protocol session on every turn. Check whether a message-list turn already has initialized state before calling _ensure_state_initialized, and rotate only on a fresh run; add a two-turn agent regression.

Comment thread dotnet/src/Microsoft.Agents.AI.Workflows/SubworkflowBinding.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: aafca87b-cf2e-4c72-b5cb-ffcad6e1b1e0

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.

Copilot review overview

🔵 Needs a closer look

Cross-language cancellation and shutdown paths still have cleanup-reporting gaps and warrant final human validation.

Review effort: Balanced
Findings: None

Resolved since last review (3)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Report orphan connection cleanup failures during disposal

dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​DefaultMcpToolHandler.cs:611

If handler disposal wins after CreateClientAsync succeeds, the creator disposes its new, uncached connection. A cancellation from that disposal reaches the creator, but the creation-lifetime signal is still completed successfully here. DisposeAsync only awaits that signal and can return without reporting the failed orphan cleanup. Record the cleanup failure in the lifetime result, drain all creator lifetimes before reporting it, and cover this disposal race in a test.

Medium severity Report active invocation cleanup failures during shutdown

dotnet/​src/​Microsoft.Agents.AI.Workflows.Declarative.Mcp/​DefaultMcpToolHandler.cs:658

If DisposeAsync starts while a cached client has an active invocation, that invocation disposes the client after it releases its lease. A cancellation during session cleanup propagates to the invocation, but this line marks the client's completion as successful, so DisposeAsync can return without reporting the failed cleanup. Complete Disposed with the cleanup exception after removing the retired client, and test shutdown while an active invocation's disposal throws OperationCanceledException.

Medium severity Surface client creation cleanup failures during shutdown

python/​packages/​declarative/​agent_framework_declarative/​_workflows/​_mcp_handler.py:653

If aclose() starts while an MCP client is being created, the creator closes the newly connected entry in this branch. When that close raises CancelledError, the creator reports it, but the shared future is completed with only the expected closed-handler error. _drain_shutdown ignores that error, so shutdown can report success without reporting the failed orphan cleanup. Preserve the cleanup outcome separately from the error sent to waiters and surface it after draining all entries; add a shutdown-during-creation cleanup-cancellation test.

This branch was successfully deployed

2 active deployments
github-app-auth — 447f8b68 Deployed Sep 29, 2026 by baywet via add_label #24036
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs .NET Usage: [Issues, PRs], Target: .Net python Usage: [Issues, PRs], Target: Python workflows Usage: [Issues, PRs], Target: Workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants