microsoft / microsoft/agent-framework
Python: [Feature]: Support the stateless MCP 2026-07-28 revision alongside 2025-era peers
@jpalvarezl is already working on this.
Since Sep 10, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
Description
Summary
MCP 2026-07-28 makes the protocol
stateless: there is no initialize/notifications/initialized handshake and no
Mcp-Session-Id. Every request is self-contained and carries its own protocol version and
capabilities in _meta. The Python MCP integration in agent_framework is built around the
2025-era session model, and the mcp dependency is capped below 2.0, so Agent Framework
Python cannot act as a 2026-07-28 client or serve 2026-07-28 clients today.
The .NET side already moved to the 2.x MCP SDK
(microsoft/agent-framework#7773, microsoft/agent-framework#7774; the request in
microsoft/agent-framework#7824 was closed as addressed). This issue tracks the equivalent work
for Python, with an explicit goal of supporting both protocol eras, not switching to the
new one — existing users pointing at 2025-era servers must keep working.
microsoft/agent-framework#7446 already catalogs the mechanical breakages of moving to
mcp 2.x (McpError → MCPError, camelCase → snake_case on mcp.types,
read_timeout_seconds becoming a float). This issue is the protocol-semantics counterpart and
does not restate that list.
What changed in 2026-07-28
From the changelog, the
items that touch us:
- No protocol-level sessions.
Mcp-Session-Idis gone from Streamable HTTP, and
tools/list/resources/list/prompts/listno longer vary per connection. Cross-call
state becomes server-minted handles passed as ordinary tool arguments (SEP-2567). - No handshake.
initialize/notifications/initializedare removed. Each request
carriesio.modelcontextprotocol/protocolVersionand
io.modelcontextprotocol/clientCapabilitiesin_meta; clients SHOULD send
clientInfoper request and servers SHOULD returnserverInfoper result.
Mismatches yieldUnsupportedProtocolVersionError(SEP-2575). server/discoverreplaces the handshake as the way to learn supported versions,
capabilities and identity. Servers MUST implement it; clients MAY call it up front.subscriptions/listenreplaces the HTTP GET stream and
resources/subscribe/unsubscribewith one opted-in, long-lived POST-response stream for
*ListChangedand resource-change notifications. Request-scoped notifications
(notifications/progress,notifications/message) stay on the originating request's stream.ping,logging/setLevelandnotifications/roots/list_changedare removed. Log level
is now per request viaio.modelcontextprotocol/logLevelin_meta.- Tasks moved out of core into the
io.modelcontextprotocol/tasksextension: polling via
tasks/get, newtasks/updatefor mid-flight input,tasks/resultandtasks/listgone,
and servers may return task handles unsolicited (SEP-2663). - Multi Round-Trip Requests (MRTR) replace server-initiated requests (
roots/list,
sampling/createMessage,elicitation/create). The server returns an
InputRequiredResultand the client retries the original request carrying
inputResponses(SEP-2322). resultTypeis required on every result ("complete"or"input_required"); results
from earlier-protocol servers that omit it MUST be treated as"complete".- No SSE resumability.
Last-Event-IDand event IDs are gone; a broken stream means the
client must re-issue the request with a new request ID. - Notable minor items: required
Mcp-Method/Mcp-Nameheaders plusx-mcp-headerfor
tool-parameter-driven headers,ttlMs/cacheScopeon list and read results, and
deterministictools/listordering. Roots, Sampling and Logging are now deprecated.
Current couplings to the 2025-era model
This is an inventory of the places that assume the old session model, not a proposed design —
it is here to size the work and to invite corrections about anything missed. See open question
5 for the implementation shape.
Client (python/packages/core/agent_framework/_mcp.py):
MCPToolowns aClientSessionwhose lifecycle assumes handshake + reconnect semantics
(connect,_connect_on_owner,_reset_session_state,_set_server_capabilities,
_send_with_one_reconnect). With no session, "connected" stops being a meaningful state and
reconnect-on-lost-session logic loses its purpose.- Liveness uses
session.send_ping()(_ensure_connected) —pingno longer exists. connect()callssession.set_logging_level(...)— replaced by the per-request
io.modelcontextprotocol/logLevel_metafield.sampling_callback,logging_callbackandmessage_handlerimplement server-initiated
flows that MRTR replaces, and Sampling/Logging are deprecated on top of that. The existing
sampling-approval surface (MCPSpecificApproval,_sampling_request_approved) needs a
decision about its future shape.- Long-running tools drive
tools/call→tasks/get→tasks/result
(call_tool_as_task,_poll_task_until_terminal,_fetch_task_result,MCPTaskOptions)
against the 2025-era design; the 2026 extension shares neither API nor wire format. MCPStreamableHTTPToolheader plumbing (_MCPHeaderScopedClient, per-run auth headers)
intersects with the new requiredMcp-Method/Mcp-Nameheaders andx-mcp-header._schedule_reloadreacts to*ListChangednotifications that now arrive only via
subscriptions/listen.
Server/hosting:
AgentProtocol.as_mcp_serverinpython/packages/core/agent_framework/_agents.pybuilds a
low-levelServerand registers aset_logging_levelhandler.agent_framework_hosting_mcpconversion helpers and thepython/samples/04-hosting/mcp
apps useFastMCP/StreamableHTTPSessionManager, both reshaped inmcp2.x.
Dependency pins that block the move:
| Package | Pin |
|---|---|
python/packages/core/pyproject.toml |
mcp>=1.24.0,<2 |
python/packages/foundry_hosting/pyproject.toml |
mcp>=1.24.0,<2 |
python/packages/hosting-mcp/pyproject.toml |
mcp>=1.11.0,<2 |
python/samples/04-hosting/mcp/pyproject.toml |
mcp>=1.27.0,<2 |
Proposal
Support both protocol eras through the mcp 2.x SDK, which
speaks 2026-07-28 and still serves every earlier revision
from the same client and server objects (Client negotiates the version automatically,
FastMCP is now MCPServer).
- Dependencies. Lift the
<2cap across core,foundry_hosting,hosting-mcpand the
MCP samples, and pull through the dependency-floor changes noted in
microsoft/agent-framework#7446. - Client statelessness. Let the SDK client own version selection; keep the
MCPTool/MCPStdioTool/MCPStreamableHTTPTool/MCPWebsocketToolpublic surface and
async withergonomics intact. Retireping-based liveness and handshake-shaped reconnect
on 2026 connections while preserving them for down-level peers. - Logging. Move
set_logging_levelto the per-request_metaopt-in, keeping the
existing "derive the level from the Python logger" behavior. - Notifications. Drive tool/prompt/resource reloads from
subscriptions/listenwith
explicit opt-in, falling back to the 2025 notification path for older servers. - MRTR. Handle
resultType: "input_required"by resolvinginputRequestsand retrying
withinputResponses, reusing the existing approval/consent plumbing so a tool call still
looks like a normal tool call to the agent loop. - Tasks. Re-target long-running tool calls at the
io.modelcontextprotocol/tasks
extension, mirroring what .NET did in microsoft/agent-framework#7774. - Caching. Honor
ttlMs/cacheScopewhen cachingtools/listresults, which also
benefits progressive tool disclosure. - Hosting. Update
as_mcp_server,agent_framework_hosting_mcpand the hosting samples
to the 2.x server API so hosted agents serve both eras.
Cross-language naming parity
To keep the Python and .NET documentation aligned, mirror the .NET surface introduced in
microsoft/agent-framework#7774 (adapted to Python casing):
.NET (Microsoft.Agents.AI.Mcp) |
Proposed Python (agent_framework) |
Notes |
|---|---|---|
McpTaskOptions |
MCPTaskOptions |
Already exists; re-shaped for the extension. |
McpTaskOptions.RemoteCancellationTimeout |
remote_cancellation_timeout |
New. |
McpTaskOptions.MinimumPollingInterval |
minimum_polling_interval |
New. |
McpTaskOptions.MaximumPollingInterval |
maximum_polling_interval |
New. |
McpTaskOptions.CancelRemoteTaskOnLocalCancellation |
cancel_remote_task_on_local_cancellation |
Already exists. |
McpTaskOptions.MaxConsecutiveStuckPolls |
max_consecutive_stuck_polls |
New; bounds stuck input_required polling. |
McpTaskOptions.MaxTotalInputRequests |
max_total_input_requests |
New; bounds unique input requests per task. |
McpTaskStatus.InputRequired |
MCPTaskStatus.INPUT_REQUIRED |
Status surface for MRTR-driven tasks. |
ListAgentToolsWithTasksAsync |
task-aware tool listing on MCPTool |
Python drives tasks inside MCPTool; keep the terminology ("tasks", not "task support"). |
MCPTaskOptions.default_ttl maps to the removed per-request task TTL and would be dropped,
as it was in .NET.
Backward compatibility
- 2025-era servers must keep working unchanged, including stdio and Streamable HTTP.
- Public Python API changes should be additive where possible; anything breaking needs the
breaking changelabel and the usual lifecycle treatment. - Long-running task behavior is the known breaking area: the 2025 and 2026 task designs share
neither API nor wire format, exactly as .NET found.
Acceptance criteria
-
agent-framework-coreinstalls and runs againstmcp2.x. -
MCPStreamableHTTPToollists and calls tools on a 2026-07-28 server with no handshake,
sending the required headers and_meta. - The same code path still works against a 2025-11-25 server.
- Long-running tools work through the
io.modelcontextprotocol/tasksextension, including
cancellation and mid-flight input. -
*ListChangedreloads work throughsubscriptions/listen. - Agents hosted via
as_mcp_server/agent_framework_hosting_mcpserve 2026-07-28 and
2025-era clients. - Samples under
python/samples/02-agents/mcpandpython/samples/04-hosting/mcpare
updated, and MCP docs describe era differences.
Open questions
- Pin strategy. Widen to
mcp>=1.x,<3with a compatibility shim so both SDK majors keep
resolving during a transition, or require 2.x outright? The SDK'sv1.xbranch is in
maintenance mode (security fixes only), which argues for a decisive move, but a shim may be
needed to avoid breaking installs pinned by other packages. - Tasks extension availability. SEP-2663 was listed as a known gap in the Python SDK
2.0.0 release notes; the current status on 2.2.x should be confirmed before scoping item 6. - Sampling and elicitation. Both are deprecated or replaced by MRTR — do we keep the
current callback/approval surface for down-level servers, or deprecate it in lockstep with
the spec? - Issue breakdown. This is intended as an umbrella. Suggested children: dependency +
SDK renames (microsoft/agent-framework#7446), client statelessness, tasks extension,
hosting/server side, samples and docs. - Implementation shape.
mcp2.x replaces v1's transport +ClientSession+
initialize()layering with a singleClientthat negotiates the era itself, so most of
MCPTool's connection management (get_mcp_client,_connect_on_owner,
_reset_session_state,_send_with_one_reconnect, thesend_pingliveness check) is
delegated away rather than rewritten. That suggests adaptingMCPToolin place and keeping
the public surface (MCPStdioTool,MCPStreamableHTTPTool,async with,call_tool)
unchanged, instead of introducing era-specific tool classes. Worth confirming before any
code is written, and likely worth an ADR underdocs/decisions/given the blast radius.
Related
- microsoft/agent-framework#7446 — Python:
mcp2.x pin and SDK renames - microsoft/agent-framework#7824 — .NET request for 2026-07-28 (closed as addressed)
- microsoft/agent-framework#7773 / microsoft/agent-framework#7774 — .NET migration to the
2026-07-28 Tasks extension andModelContextProtocol2.1.0 - MCP 2026-07-28 specification
and changelog
Code Sample
# Today this is capped by `mcp>=1.24.0,<2` and speaks 2025-era semantics.
# Target: the same code works against a 2026-07-28 stateless server, with no
# initialize handshake and no Mcp-Session-Id, and still works against 2025-era servers.
from agent_framework import MCPStreamableHTTPTool
async with MCPStreamableHTTPTool(
name="docs",
url="https://example.com/mcp",
) as tool:
# Each call is a self-contained request carrying its protocol version,
# client capabilities and log level in `_meta`.
result = await tool.call_tool("search_docs", query="stateless MCP")
# Long-running tools go through the io.modelcontextprotocol/tasks extension,
# with options named to match .NET's McpTaskOptions.
from datetime import timedelta
from agent_framework import MCPStreamableHTTPTool, MCPTaskOptions
tool = MCPStreamableHTTPTool(
name="reports",
url="https://example.com/mcp",
task_options=MCPTaskOptions(
remote_cancellation_timeout=timedelta(seconds=5),
minimum_polling_interval=timedelta(milliseconds=10),
cancel_remote_task_on_local_cancellation=True,
max_consecutive_stuck_polls=60,
max_total_input_requests=100,
),
)
Language/SDK
Python
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.