Skip to content

Python: Allow concurrent MCP calls with matching headers - #10142

Closed
Jose Alvarez (jpalvarezl) wants to merge 5 commits into
mainfrom
jpalvarezl-toolbox-issue-investigation
Closed

Jose Alvarez (jpalvarezl) wants to merge 5 commits into
mainfrom
jpalvarezl-toolbox-issue-investigation

Conversation

@jpalvarezl

@jpalvarezl Jose Alvarez (jpalvarezl) commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Motivation & Context

Concurrent calls through one FoundryToolbox or an MCPStreamableHTTPTool with a
header_provider currently serialize across the complete RPC, even when their
effective headers are identical. This prevents an independent batch scheduled
with asyncio.gather() from executing concurrently.

Allow calls sharing the same effective header identity to overlap while retaining
the existing isolation boundary when headers differ. This is a draft for
maintainer review of the concurrency and lifecycle changes.

Description & Review Guide

  • What are the major changes? Replace whole-call header locking with
    same-identity leases. A short admission lock and idle event let matching-header
    calls overlap; different identities wait outside the lock. Lease release is
    synchronous, including during cancellation. The lifecycle owner handles complete
    connection setup, discovery, recovery, and cleanup. Calls only borrow ready
    connections; retries reprepare arguments and metadata from the refreshed catalog.
    Direct prompts resolve ambient headers, generated prompts retain runtime headers,
    and Toolbox HTTP cleanup runs on the owner after MCP teardown.
  • What is the impact of these changes? Same-header tool batches can execute
    concurrently without changing public signatures, dependencies, header
    precedence, or caller-owned connection ownership. request_timeout also bounds
    admission/reconciliation and recovery as separate budgets; None disables
    those budgets. A waiting caller's timeout does not cancel another caller's
    active operation. Managed discovery remains owner-serialized and restarts a
    complete snapshot after connection replacement. Externally supplied MCP Clients
    and ClientSessions retain caller ownership and do not gain these synchronization
    guarantees. Explicit connection and discovery after a completed close remain
    supported. Close/reset retain teardown semantics; this is not a new graceful
    drain or arbitrary nested cross-identity-call feature.
  • What do you want reviewers to focus on? Same-identity concurrency,
    different-identity draining, nested same-header calls, cancellation cleanup,
    late failures from replaced connections, shutdown during recovery discovery,
    and admission during abandoned setup. Also check direct/detached prompt identity,
    separation of prompt arguments from authentication, and cancelling a Toolbox
    close caller while the owner still needs HTTP for termination. The implementation
    deliberately avoids a separate HTTP exclusive-barrier/ancestry stack.

Local validation:

  • uv run --directory python\packages\core --frozen --group dev --group test --inexact pytest --no-cov -q -o addopts= --disable-warnings --tb=short tests ..\foundry_hosting\tests\test_toolbox.py -m "not integration":
    passed, 9,450 passed, 44 skipped, 2 deselected, 2 xfailed. Existing deprecation
    and telemetry warnings remain; the runner exited successfully.
  • Ruff 0.17.3 lint/format checks and Pyright 1.1.414 targeted source/test checks:
    passed.

Live Toolbox deployment validation has not been run.
The full local run above predates the latest main merge. Its manual conflict
resolution only combined test imports; the full suite was not rerun for that merge.

Related Issue

No linked GitHub issue. Maintainer-directed draft for discussing the implementation;
this change is not presented as a trivial-change exception.

Alternative implementation: #10147.
Both address the same batch bottleneck; this draft explores a lifecycle-owner
readiness boundary rather than per-network-attempt exclusive HTTP transitions.

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: Copilot assisted with implementation, regression tests,
documentation, independent code/design review, and this description.

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 links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • 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.

Copilot AI balanced review requested due to automatic review settings November 6, 2026 10:55
@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 labels Nov 6, 2026
@github-code-quality

github-code-quality Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Code coverage overview

Languages: Python

Python / code-coverage/python

main c04fecd jpalvarezl-toolbox-i... 8dbf27f +/-
92% 92% 0%
View coverage of the 3 most impacted files.
File main c04fecd jpalvarezl-toolbox-i... 8dbf27f +/-
packages/core/a...ework/_tools.py 96% 96% 0%
packages/core/a...amework/_mcp.py 91% 92% +1%
packages/foundr...ing/_toolbox.py 97% 99% +2%

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


Updated November 06, 2026 13:46 UTC

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

Critical cancellation and recovery races can leave active counts stuck, admit calls with stale metadata, reconnect after close, and deadlock concurrent connection attempts.

4 open issues
What changed in this PR

This PR enables concurrent Python MCP operations with matching effective HTTP headers while retaining session isolation when headers differ.

Changes:

  • Replace whole-call locking with shared-header coordination for tools, generated prompts, and Foundry Toolbox resource reads.
  • Coordinate connection recovery, shutdown handling, and timeout budgets.
  • Add concurrency and lifecycle regression tests and update guidance.
File Description
skills/​agent-framework-builder-python/​references/​integrations.md Document matching-header concurrency and timeout behavior.
python/​packages/​foundry_hosting/​tests/​test_toolbox.py Test resource reads during identity reconnection.
python/​packages/​foundry_hosting/​agent_framework_foundry_hosting/​_toolbox.py Use shared-header coordination for resource reads.
python/​packages/​core/​tests/​core/​test_mcp.py Adapt recovery tests to transport-only replacement.
python/​packages/​core/​tests/​core/​test_mcp_observability.py Update recovery mocking in logging coverage.
python/​packages/​core/​tests/​core/​test_mcp_http_auth.py Test concurrency, recovery, cancellation, shutdown, and timeouts.
python/​packages/​core/​README.md Explain HTTP concurrency and timeout semantics.
python/​packages/​core/​AGENTS.md Update header-scoping and recovery guidance.
python/​packages/​core/​agent_framework/​_mcp.py Implement shared-header admission and coordinated connection recovery.

Review effort: Balanced

@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)): 9d4978090fcc
Model: gpt-5.6-sol

Overview

The PR replaces whole-call header locking with condition-based admission and an active-operation count, allowing calls with the same effective headers to overlap while draining before an identity switch. Owner-scoped header injection, generation checks around recovery, finally-based release, and extensive cancellation/recovery tests provide strong isolation and lifecycle guardrails. However, the admission predicate has no fairness mechanism, so sustained traffic for the current identity can indefinitely starve a queued caller with different headers.

Reviewed the supplied pull-request 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: python/packages/core/agent_framework/_mcp.py

…sue-investigation

# Conflicts:
#	python/packages/foundry_hosting/tests/test_toolbox.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the latest head, 8dbf27f0b424a85696ae69e4a5606f63420e8c60.

The earlier direct-prompt identity, stale retry arguments/metadata, resource URI conversion, and cancelled Toolbox-close issues are fixed in the offline checks. Same-identity calls overlap. I also ran the selected MCP, HTTP auth, observability, skills, and Toolbox unit suites against an archived copy of this head: 1,474 passed, 2 deselected.

One admission-fairness issue still reproduces; the inline comment gives the scenario.

while True:
await self._wait_for_lifecycle()
async with self._call_headers_lock:
if self._active_call_headers is None or identity == _mcp_header_identity(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Prevent newer matching-identity calls from starving a waiting identity change

This still reproduces on 8dbf27f0, despite the earlier fairness thread being resolved. Once B is waiting for A's leases to drain, this predicate keeps admitting unrelated new A calls. The idle event is therefore never reached under sustained A traffic, so B repeatedly hits its admission timeout, or can wait indefinitely with request_timeout=None.

The offline repro starts one A call, queues B, then admits three successive newer A calls. Each preceding A call is released and completes. The original call and the first two newer calls have all finished, but the third newer A call alone keeps B blocked until its admission deadline. This is not just B waiting for the original batch.

Please account for a queued identity transition when admitting new unrelated calls, while retaining the same-identity nested callbacks needed by an active parent. A regression should verify that later unrelated A calls cannot continually overtake B after the original A calls finish, and that a required nested A callback still completes.

@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 2

Result: No findings
Scope: 19 net-new commit(s): df7d02bdc99c, e9292752c1ea, 18cd5903b902, 2fa7b9ab21be, 0c479365fca9, 83c719065840, 4ffc820cd62d, 082217287cb4, 6e770b95c033, d0e191cf4d3d, 44f5495c0851, 9db7efb0841b, 80d0cbf5bc30, dee716e6150c, 28163f5e23d2, c04fecdfdc2f, d2017c1c5d0e, 1d959983c7b5, 8dbf27f0b424
Model: gpt-5.6-sol

Overview

The change moves managed MCP connection setup, discovery, recovery, and teardown onto a lifecycle owner while allowing operations with the same effective header identity to overlap. Synchronous lease release, owner-tagged header context, origin checks, generation checks, bounded admission/recovery waits, and focused concurrency/cancellation tests provide strong isolation and cleanup guardrails. No new Critical, High, or Medium defect remained after reconciliation against the implementation, tests, and existing feedback.

Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
No publishable findings remained after source verification for this scope.

This branch was successfully deployed

1 active deployment
github-app-auth — 8dbf27f0 Deployed Nov 6, 2026 by jpalvarezl via team_check #8903
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 python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants