Skip to content

Python: Add progressive MCP disclosure#6850

Open
eavanvalkenburg wants to merge 3 commits into
microsoft:mainfrom
eavanvalkenburg:mcp-progressive-disclosure
Open

Python: Add progressive MCP disclosure#6850
eavanvalkenburg wants to merge 3 commits into
microsoft:mainfrom
eavanvalkenburg:mcp-progressive-disclosure

Conversation

@eavanvalkenburg

Copy link
Copy Markdown
Member

Motivation & Context

Large MCP servers can expose many tools, which frontloads a lot of schema into the model context even when only a small subset is needed. This adds progressive disclosure support for MCP tools so agents can discover and load MCP tool schemas on demand while keeping the existing allowed_tools boundary intact.

Description & Review Guide

  • What are the major changes?
    • Added use_progressive_disclosure and always_load constructor options to all Python MCP tool classes immediately after allowed_tools.
    • Progressive mode exposes list_mcp_tools and load_tool loader tools, plus any allowed tools selected by always_load; other allowed tools are added through FunctionInvocationContext.add_tools(...) when loaded.
    • Added tests for transport constructors, filtering, prefixed loader names, idempotent loading, approval preservation, runtime kwargs/header-provider behavior, and load_tools=False validation.
    • Added a runnable MCP sample demonstrating progressive disclosure with a self-spawned stdio MCP server.
  • What is the impact of these changes?
    • Existing MCP tool behavior remains unchanged unless progressive disclosure is explicitly enabled.
    • Agents can keep initial MCP tool context smaller while still allowing the model to discover and load permitted tools during the run.
  • What do you want reviewers to focus on?
    • The progressive disclosure semantics around allowed_tools, always_load, prefixed loader names, and the use of the existing progressive tool exposure mechanism.

Related Issue

Fixes #5821

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 AI review requested due to automatic review settings July 1, 2026 07:03
@giles17 giles17 added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _mcp.py12809792%234, 240, 349, 368, 581, 660–661, 793, 848, 937, 939, 976–977, 994, 1007, 1031–1032, 1051–1054, 1056–1057, 1061, 1087, 1121–1123, 1125, 1178–1180, 1239–1240, 1509, 1550–1551, 1564, 1567, 1576–1577, 1582–1583, 1589, 1643–1644, 1664–1665, 1674–1675, 1680–1681, 1687, 1780, 1783, 1810, 1833–1837, 1860–1862, 1867, 1871–1872, 1974, 1981, 1983, 2054, 2069–2070, 2077–2078, 2083–2084, 2089, 2093, 2108, 2170, 2353, 2355, 2377, 2379–2382, 2395–2396, 2440, 2502, 2919–2920, 3151–3152, 3170
TOTAL43210514888% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8533 37 💤 0 ❌ 0 🔥 2m 14s ⏱️

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.

Pull request overview

Adds progressive disclosure support for Python MCP tools so agents can start with a minimal model-facing tool surface (stable list/load “loader” tools + optionally always-loaded tools) and progressively load additional allowed MCP tool schemas on demand, reducing upfront context bloat while preserving allowed_tools boundaries and existing MCP behaviors.

Changes:

  • Added use_progressive_disclosure and always_load options across Python MCP tool classes and implemented loader tools (list_mcp_tools / load_tool, prefix-aware) that progressively add tools via FunctionInvocationContext.add_tools(...).
  • Added unit tests covering constructor behavior, filtering semantics, prefix behavior, idempotent loading, approval preservation, and runtime kwargs/header-provider behavior.
  • Added documentation updates and a runnable sample demonstrating progressive disclosure with a self-spawned stdio MCP server.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
python/packages/core/agent_framework/_mcp.py Implements progressive disclosure options, loader tools, and progressive tool resolution/loading logic.
python/packages/core/tests/core/test_mcp.py Adds test coverage validating progressive disclosure behavior and invariants.
python/packages/core/AGENTS.md Documents progressive MCP disclosure semantics and how it interacts with allowed-tools, approvals, and tool exposure.
python/samples/02-agents/mcp/mcp_progressive_disclosure.py New sample showing progressive disclosure against a self-spawned MCP stdio server.
python/samples/02-agents/mcp/README.md Adds the progressive disclosure sample to the MCP samples index and notes no extra server setup is needed.

Comment thread python/packages/core/agent_framework/_mcp.py
Comment thread python/packages/core/agent_framework/_mcp.py
Comment thread python/packages/core/agent_framework/_mcp.py
Comment thread python/samples/02-agents/mcp/mcp_progressive_disclosure.py Outdated
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Flagged issue

MCPTool.functions prepends synthetic loader tools without checking for collisions with server-advertised tool names, but the repo already treats duplicate local tool names as invalid. A server tool named list_mcp_tools, load_tool, or the prefixed equivalent will either make Agent.run() fail with a duplicate-name error or make that remote tool impossible to load. Reject reserved-name collisions up front (the same way colliding local MCP names are rejected today) or otherwise guarantee these synthetic names cannot overlap remote tools.


Source: automated DevFlow PR review

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

Automated Code Review

Reviewers: 5 | Confidence: 87%

✓ Correctness

The progressive MCP disclosure implementation is well-structured and correct. The loader tools properly leverage existing FunctionTool context injection and the FunctionInvocationContext.add_tools API. The always_load boundary is correctly gated by allowed_tools via _filtered_functions(). The identity-based loaded checks are safe because the same FunctionTool objects are reused throughout. No correctness, security, or data-loss issues found.

✓ Security Reliability

The progressive MCP disclosure implementation is well-designed from a security and reliability perspective. The allowed_tools boundary is consistently enforced: _resolve_progressive_function validates the model-supplied tool parameter against _filtered_functions(), _list_progressive_mcp_tools only reveals allowed tools, and load_tool correctly rejects filtered tools. Loaded tools preserve their approval modes, argument filtering, and header-provider behavior. The loader tools appropriately use approval_mode="never_require" since they only make tools available without executing remote code. The idempotent load check prevents duplicate tool additions. No injection risks, resource leaks, or unhandled failure modes were identified.

✓ Test Coverage

The progressive MCP disclosure feature has good test coverage for the main happy paths and key security boundary (allowed_tools filtering). However, there are a few untested error/edge-case branches in the new implementation: the ctx.tools is None guard in _load_progressive_mcp_tool, the ambiguous tool name resolution path, and the loaded: True state reporting from list_mcp_tools after a tool has been loaded. These are minor gaps that don't block the PR but would strengthen confidence in the feature.

✓ Failure Modes

The progressive disclosure implementation is well-structured and integrates cleanly with the existing progressive tool exposure mechanism. However, _load_progressive_mcp_tool is a synchronous function, which means it gets dispatched to a thread via asyncio.to_thread during agent execution. Since the framework's function-calling loop executes batch tool calls concurrently via asyncio.gather (line 1788 of _tools.py), two load_tool calls in the same model batch will race on ctx.add_tools(), potentially silently losing one tool's addition. Making the function async would keep it in the event loop thread where cooperative scheduling prevents the race.

✗ Design Approach

I found one design issue in the progressive disclosure implementation: it introduces synthetic loader tool names without reserving them against remote MCP tool names, even though the existing MCP loading and agent tool-merging paths already enforce unique local tool names. That means a valid server exposing list_mcp_tools, load_tool, or a prefixed equivalent can no longer be used safely with progressive disclosure.

Flagged Issues

  • MCPTool.functions prepends synthetic loader tools without checking for collisions with server-advertised tool names, but the repo already treats duplicate local tool names as invalid. A server tool named list_mcp_tools, load_tool, or the prefixed equivalent will either make Agent.run() fail with a duplicate-name error or make that remote tool impossible to load. Reject reserved-name collisions up front (the same way colliding local MCP names are rejected today) or otherwise guarantee these synthetic names cannot overlap remote tools.

Suggestions

  • Add a test for _load_progressive_mcp_tool when ctx.tools is None — this exercises the guard at _mcp.py:940-941 that raises ToolExecutionException.
  • Add a test for the ambiguous tool name resolution path in _resolve_progressive_function (_mcp.py:905-906) where len(matches) > 1.

Automated review by eavanvalkenburg's agents

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

Copy link
Copy Markdown
Member Author

Addressed the DevFlow flagged issue in d039c1b: progressive disclosure now treats the loader names as reserved in the progressive surface, filters always_load/discovery collisions, and returns a clear load_tool error that points users to tool_name_prefix or excluding the colliding MCP tool. Added regression tests for these cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

Python: add progressive MCP discovery/dispatch mode for large MCP servers

3 participants