Migrate to MCP 2.x

Agent Framework Python 1.23.0 introduces three changes that can require code updates:

  • MCP integrations move to the MCP SDK 2.x high-level client lifecycle.
  • AG-UI endpoint registration requires an explicit authorization ownership mode.
  • DockerShellTool.extra_run_args is renamed to unsafe_extra_run_args.

The release also adds allowlisted A2A run-context forwarding, an opt-in provider compatibility option for sequential agents, and broader Python 3.15 support.

Upgrade the package cohort together

If you use the standard metapackage, upgrade it directly:

python -m pip install --upgrade "agent-framework==1.23.0"

If you install individual packages, upgrade the packages used by the application in one operation. Agent Framework packages don't all share the same numeric version, so don't force every integration package to 1.23.0. Keep --pre only when the command includes an alpha or beta package.

Avoid running old and new workers against the same persisted sessions, histories, snapshots, or checkpoints during the upgrade. Python 1.22 introduced serialized hosted and merge_mode fields that older runtimes can't read.

Migrate MCP connections

Agent Framework 1.23 uses mcp>=2.2.0,<3 and treats the MCP SDK's high-level Client as the primary connection.

Keep simple framework-owned connections

For most applications, continue to pass a URL or command to an Agent Framework MCP tool. Agent Framework creates, enters, reconnects, and closes the MCP client:

from agent_framework import MCPStreamableHTTPTool

mcp_tool = MCPStreamableHTTPTool(
    name="microsoft-learn",
    url="https://learn.microsoft.com/api/mcp",
)

Use the tool as an async context manager when your application controls its lifetime, or attach it to an Agent whose lifetime is managed with async with.

Use a caller-owned high-level client

Use this pattern when the application must configure or reuse the MCP SDK client:

from agent_framework import MCPStreamableHTTPTool
from mcp import Client

async with (
    Client(server="https://learn.microsoft.com/api/mcp") as mcp_client,
    MCPStreamableHTTPTool(
        name="microsoft-learn",
        mcp_client=mcp_client,
    ) as mcp_tool,
):
    result = await agent.run("Find the relevant documentation.", tools=mcp_tool)

The caller must enter the Client before the MCP tool connects and keep it entered for the complete agent or workflow run. Agent Framework doesn't close or replace a caller-owned client.

Don't combine mcp_client= with transport configuration such as url=, command=, headers, or a caller-owned HTTP client. These combinations mix ownership modes and fail early.

Replace low-level session access

Caller-supplied ClientSession support remains as a deprecated compatibility path. It doesn't receive the high-level client's response caching, automatic multi-round-trip handling, or modern subscription behavior.

Replace code that reads MCPTool.session or passes a ClientSession with a caller-owned mcp.Client. For high-level connections, MCPTool.session returns None.

Remove unsupported MCP features

  • Replace MCPWebsocketTool with MCPStreamableHTTPTool. The compatibility symbol raises when used.
  • Remove MCPTaskOptions and task_options. MCP Tasks aren't currently supported by the Python SDK runtime used by Agent Framework.
  • Don't build new integrations around sampling_callback. Server-initiated MCP sampling is deprecated; servers should call model-provider APIs directly.
  • Replace MCP hosting examples based on FastMCP with the MCP SDK 2.x MCPServer API.

For detailed lifecycle behavior, see Local MCP tools.

Declare AG-UI authorization ownership

Every AG-UI endpoint must now declare who owns server-side state.

Local or single-user application

Use single_user=True only when every caller belongs to the same trust boundary and process-local scope is sufficient:

add_agent_framework_fastapi_endpoint(
    app,
    agent,
    "/agent",
    single_user=True,
)

The generated scope changes when the endpoint is registered again. For durable single-user snapshots, checkpoints, or provider state, return an application-owned constant from authorization_scope_resolver instead.

Authenticated or shared application

Resolve a stable scope from authenticated request context:

import os

from fastapi import HTTPException, Security
from fastapi.security import APIKeyHeader

api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
expected_api_key = os.environ["AG_UI_API_KEY"]


async def resolve_authorization_scope(
    api_key: str | None = Security(api_key_header),
) -> str:
    if api_key != expected_api_key:
        raise HTTPException(status_code=403, detail="Invalid API key.")
    return "tenant:contoso:user:alice"


add_agent_framework_fastapi_endpoint(
    app,
    agent,
    "/agent",
    authorization_scope_resolver=resolve_authorization_scope,
)

The resolver can use FastAPI Depends, Security, headers, cookies, or Request. It must return a stable, nonempty scope for the principal or collaboration boundary that can access the same snapshots, sessions, checkpoints, detached runs, and approval state.

Authentication-only dependencies don't provide this scoping. Don't combine them with single_user=True when different authenticated callers can reach the endpoint.

For more information, see AG-UI security considerations.

Rename Docker shell extra arguments

Rename the constructor argument:

Before:

DockerShellTool(extra_run_args=["--some-runtime-option"])

After:

DockerShellTool(
    unsafe_extra_run_args=["--some-runtime-option"],
)

These values are appended verbatim to the Docker or Podman command. They can weaken or bypass the tool's isolation defaults. Keep them developer-controlled, never populate them from user or model output, and prefer a dedicated constructor option when one exists.

Review optional behavior changes

A2A forwarded context

Configure independent allow lists on both sides:

from agent_framework.a2a import A2AAgent, A2AExecutor

client_agent = A2AAgent(
    name="remote",
    url="https://a2a-agent.example.com",
    forwarded_kwargs=["tenant_id", "trace"],
)

executor = A2AExecutor(
    local_agent,
    accepted_function_invocation_kwargs=["tenant_id"],
    accepted_client_kwargs=["trace"],
)

The default is to send and accept nothing. Forwarded values must be JSON-compatible and aren't credentials or authorization by themselves.

Sequential provider compatibility

Some chat-completions providers return an empty response when the input ends with an assistant message. Opt in only for those providers:

workflow = SequentialBuilder(
    participants=[writer, reviewer],
    ensure_trailing_user_turn=True,
).build()

The synthetic user continuation is added only to the invocation copy and isn't stored in the workflow conversation.

Review Python version behavior

Python 1.24 supports Python 3.11 through 3.15. Package metadata still permits Python 3.10 installation, but that version is no longer supported or tested. The remaining Python 3.15 standard-install limitations are:

  • Redis isn't installed because RedisVL requires Python earlier than 3.15.
  • Declarative Power Fx expressions aren't evaluated on Python 3.14 or 3.15.

Migration checklist

  • Upgrade the complete package cohort in one operation.
  • Replace low-level MCP sessions with entered high-level mcp.Client instances where the application owns the connection.
  • Remove MCP WebSocket and task_options usage. Tools that require the Tasks extension are skipped, while tools that declare Tasks as optional continue through ordinary tools/call requests.
  • Remove new server-initiated sampling usage.
  • Add an authorization resolver or single_user=True to every AG-UI endpoint.
  • Rename extra_run_args to unsafe_extra_run_args.
  • Review A2A forwarded context and sequential provider compatibility only when those opt-in behaviors are needed.
  • Test persisted-state, approval, workflow, and hosting paths before a rolling deployment.

For the complete release summary, see Python 2026 significant changes.