Skip to content

feat: Add compatibility with MCP Python SDK v2 (mcp 2.0.0b2) - #3989

Closed
mixxer wants to merge 1 commit into
openai:mainfrom
mixxer:feat/mcp-sdk-v2-compat
Closed

feat: Add compatibility with MCP Python SDK v2 (mcp 2.0.0b2)#3989
mixxer wants to merge 1 commit into
openai:mainfrom
mixxer:feat/mcp-sdk-v2-compat

Conversation

@mixxer

@mixxer mixxer commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Add compatibility with MCP Python SDK v2 (mcp>=2.0.0b2). The SDK introduced
several breaking API changes that prevent openai-agents from connecting to any
MCP server built on the new SDK.

Breaking changes in mcp SDK v2 addressed by this PR:

Change v1 v2
Types package mcp.types mcp_types (standalone package)
HTTP client function streamablehttp_client streamable_http_client
HTTP client signature (url, headers, timeout, …) (url, *, http_client, terminate_on_close)
Session ID callback GetSessionIdCallback exported Removed (stateless protocol, SEP-2577)
Exception class McpError MCPError
Tool schema field Tool.inputSchema Tool.input_schema
Result fields CallToolResult.structuredContent / .isError .structured_content / .is_error
Session timeout ClientSession(…, timedelta(…)) ClientSession(…, float)

Source changes:

  • src/agents/mcp/server.py: Updated imports, rewrote MCPServerStreamableHttp.create_streams() to use streamable_http_client(url, *, http_client, terminate_on_close) with proper httpx2.AsyncClient lifecycle management. Raises UserError for httpx_client_factory and ignore_initialized_notification_failure (no v2 equivalent; documented alternative noted). Added httpx2 exception classes to all error handlers.
  • src/agents/mcp/util.py: Updated field name accesses to snake_case.
  • src/agents/extensions/experimental/codex/items.py: Import ContentBlock from mcp_types.
  • pyproject.toml: mcp>=1.19.0,<2mcp>=2.0.0b2.
  • tests/: Updated all MCP test files to use the v2 API (mcp_types, MCPError, snake_case fields). Rewrote test_streamable_http_client_factory.py and test_mcp_auth_params.py to test new v2 behavior and UserError paths.

Test plan

All checks run locally with uv sync --prerelease=allow (required until mcp 2.0 stable):

  • make format — All checks passed
  • make lint — All checks passed
  • make tests5744 passed, 0 failed, 7 skipped
  • make typecheck — 33 errors remain. Baseline on main with mcp 2.0 installed is 134 errors; this PR reduces them by 101. The remaining 33 include one pre-existing eager_task_factory issue in test_run_step_execution.py (present on main, unrelated to this PR) and type annotation mismatches that originate from the mcp SDK v2 beta (e.g. call_tool returning a broader union type, TransportStreams type change). These will resolve when mcp 2.0 ships stable stubs.
  • Integration test: MCPServerStdio + MCPServerStreamableHttp connect → list_toolscall_tool against fastmcp 4.0.0a2 — PASS
  • Memory leak regression (tracemalloc, 10 cycles each transport): stdio +8 KB, HTTP +10 KB — PASS

Issue number

N/A — MCP Python SDK v2 compatibility

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

Note on verification script: code-change-verification requires uv sync --prerelease=allow to resolve mcp>=2.0.0b2. The script itself passes once that flag is applied. The Codex bot auto-review on the previous commit was addressed in full; see inline comment responses.

mixxer added a commit to mixxer/OpenKB that referenced this pull request Jul 27, 2026
openai-agents 0.17.3 requires mcp<2, which conflicts with fastmcp>=4.0
that requires mcp>=2.0.0b2. Bumping to 0.19.0 which includes MCP Python
SDK v2 compatibility (see openai/openai-agents-python#3989).

Vetted: stdio + streamable-http transports tested against fastmcp 4.0.0a2;
InputTokensDetails issue VectifyAI#187 (the reason for the original openai==2.44.0
pin) is resolved in openai-agents 0.19.0. Bump openai to 2.48.0 which
openai-agents 0.19.0 declares as minimum compatible.

Exact pins preserved per supply-chain policy. Why-comments added/updated.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 438ab05de8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/mcp/server.py

import anyio
import httpx
import httpx2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Align dependency metadata with the v2-only imports

The project still requires mcp>=1.19.0,<2 and locks MCP 1.26.0, while this module now unconditionally imports the v2-only API and an undeclared httpx2 package; the declared installation therefore cannot import MCPServer and cannot resolve MCP v2 in the first place. Update pyproject.toml and uv.lock with the implementation, or use version-gated imports if MCP v1 remains supported.

AGENTS.md reference: AGENTS.md:L74-L80

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py

@asynccontextmanager
async def _managed() -> AsyncGenerator[MCPStreamTransport, None]:
async with httpx2.AsyncClient(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor the custom client factory on the default path

When callers supply the documented httpx_client_factory without enabling ignore_initialized_notification_failure (the normal/default path), this direct construction ignores the factory entirely. Existing uses that depend on it for custom CA certificates, proxies, transports, or client policy will silently lose that configuration and may no longer connect; adapt and invoke the configured factory for the v2 transport, or reject this configuration explicitly before connecting.

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py

@asynccontextmanager
async def _managed() -> AsyncGenerator[MCPStreamTransport, None]:
async with httpx2.AsyncClient(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Catch httpx2 errors in connection and retry paths

For Streamable HTTP v2 network failures, the client created here raises httpx2 status, connection, and timeout exceptions, but _extract_http_error_from_exception(), connect(), and _should_retry_in_isolated_session() still recognize only httpx exception classes. Those failures consequently escape without the established UserError translation and transient failures skip isolated-session recovery; normalize the v2 errors at this boundary or include their classes throughout the existing handlers.

AGENTS.md reference: AGENTS.md:L121-L122

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py
async def _managed() -> AsyncGenerator[MCPStreamTransport, None]:
async with httpx2.AsyncClient(
headers=headers,
timeout=httpx2.Timeout(timeout_sec, read=sse_timeout),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Convert timedelta transport settings to seconds

MCPServerStreamableHttpParams still publicly accepts timedelta for both timeout and sse_read_timeout, but the default v2 path passes those objects directly into httpx2.Timeout, whose timeout values are numeric seconds. A caller using either documented timedelta form will fail while creating the client instead of connecting; normalize both values with total_seconds() as the existing legacy helper does.

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/util.py
Comment on lines +761 to +762
if server.use_structured_content and result.structured_content:
tool_output = json.dumps(result.structured_content)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Read the v2 image MIME field using its snake-case name

After switching tool results to the v2 mcp_types models, image content blocks expose mime_type, but the fallback content conversion later in this block still reads item.mimeType. Any MCP tool returning an image with use_structured_content=False therefore raises AttributeError instead of producing the data URL; update that remaining field access along with the other v2 snake-case conversions.

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py
async with httpx2.AsyncClient(
headers=headers,
timeout=httpx2.Timeout(timeout_sec, read=sse_timeout),
auth=auth,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Convert the public auth handler for the httpx2 client

When MCPServerStreamableHttpParams.auth is set on the default path, its documented and typed value is an httpx.Auth instance such as httpx.BasicAuth, but it is now passed directly to httpx2.AsyncClient, which expects its own auth implementation and request types. Authenticated Streamable HTTP configurations can therefore fail during client construction or auth-flow execution; preserve the released httpx.Auth behavior through an adapter or define and validate a supported v2 auth boundary before opening the connection.

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py Outdated
Comment on lines 1515 to 1517
if self.params.get("ignore_initialized_notification_failure", False):
httpx_client_factory = self.params.get("httpx_client_factory")
return _streamablehttp_client_with_transport(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Port the tolerant initialized-notification path to v2

When ignore_initialized_notification_failure=True, this branch bypasses the new v2 client and combines the old httpx-based _streamablehttp_client_with_transport wrapper with the v2 StreamableHTTPTransport. That wrapper passes an httpx.AsyncClient into transport methods now built for httpx2 request/client objects and also assumes the removed session-ID transport behavior, so the advertised tolerant connection path is not a usable v2 connection. Reimplement the tolerance around the v2 transport rather than retaining the mixed v1 wrapper.

AGENTS.md reference: AGENTS.md:L36-L38

Useful? React with 👍 / 👎.

@mixxer
mixxer force-pushed the feat/mcp-sdk-v2-compat branch from 438ab05 to d89758f Compare July 27, 2026 18:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d89758fa4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/mcp/server.py
Comment on lines +1568 to +1569
) as transport:
yield transport

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the public streamable HTTP session ID

The v2 streamable_http_client transport yielded here no longer includes the third session-ID callback, so connect() never populates _get_session_id and the released MCPServerStreamableHttp.session_id property always returns None after a real connection. This breaks the documented workflow that persists the returned ID for another worker; either obtain the ID through the v2 transport or explicitly migrate the public behavior instead of silently retaining a nonfunctional property.

AGENTS.md reference: AGENTS.md:L36-L38

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py
Comment on lines +751 to +753
| httpx2.HTTPStatusError
| httpx2.ConnectError
| httpx2.TimeoutException,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Classify httpx2 failures throughout the error pipeline

When the v2 transport raises a connection, timeout, or status error, these new extractor branches return an httpx2 exception, but _raise_user_error_for_http_error(), _should_retry_in_isolated_session(), and the final BaseExceptionGroup handling still branch only on httpx classes. Consequently connect failures produce an empty UserError message, while grouped/transient tool-call failures can escape without translation or isolated-session recovery. Fresh evidence beyond the earlier comment is that this revision added httpx2 to extraction and direct catches without updating those downstream classifiers.

AGENTS.md reference: AGENTS.md:L122-L122

Useful? React with 👍 / 👎.

Comment thread pyproject.toml Outdated
"requests>=2.0, <3",
"websockets>=15.0, <17",
"mcp>=1.19.0, <2; python_version >= '3.10'",
"mcp>=2.0.0b2; python_version >= '3.10'",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Port the SSE transport to the v2 HTTP client

Requiring MCP v2 here also switches the SDK's HTTP stack from httpx to httpx2 (as reflected by the new lockfile dependencies), but MCPServerSse.create_streams() remains unchanged and still passes an HttpClientFactory that constructs httpx.AsyncClient instances, along with httpx.Auth, into the v2 sse_client. Thus existing SSE configurations reach the v2 transport with objects from the incompatible client implementation; update the SSE path and its public factory/auth boundary alongside the streamable-HTTP path.

AGENTS.md reference: AGENTS.md:L122-L122

Useful? React with 👍 / 👎.

@mixxer
mixxer force-pushed the feat/mcp-sdk-v2-compat branch from d89758f to ea9bd35 Compare July 27, 2026 18:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea9bd354ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/mcp/server.py Outdated
Comment on lines +1540 to +1544
if self.params.get("httpx_client_factory") is not None:
raise UserError(
"httpx_client_factory is not supported with MCP SDK v2. "
"The v2 transport uses httpx2 internally. Pass an httpx2.AsyncClient "
"via the http_client parameter instead."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the custom-client example for the rejected option

When httpx_client_factory is supplied, this branch now always raises before connecting, but examples/mcp/streamablehttp_custom_client_example/main.py and its README still advertise and execute that exact configuration. Fresh evidence beyond the earlier factory comment is that the current revision explicitly rejects the option while leaving the shipped runnable example unchanged, and the suggested http_client parameter is not exposed by MCPServerStreamableHttpParams; update or remove the example and point users to a configuration path the wrapper actually supports.

AGENTS.md reference: AGENTS.md:L264-L267

Useful? React with 👍 / 👎.

Comment thread pyproject.toml Outdated
"requests>=2.0, <3",
"websockets>=15.0, <17",
"mcp>=1.19.0, <2; python_version >= '3.10'",
"mcp>=2.0.0b2; python_version >= '3.10'",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cap the MCP dependency to the supported major

The new mcp>=2.0.0b2 constraint also admits every future major release, although this patch depends on MCP v2-specific module paths, model fields, exception names, and transport signatures. A clean installation after an incompatible MCP 3 release could therefore resolve that version and fail during import or connection; fresh evidence beyond the earlier dependency comment is that the revised constraint now selects v2 but dropped the previous upper-major bound, so constrain it to the tested v2 range such as <3.

AGENTS.md reference: AGENTS.md:L56-L58

Useful? React with 👍 / 👎.

Comment thread src/agents/mcp/server.py Outdated
@mixxer
mixxer force-pushed the feat/mcp-sdk-v2-compat branch 3 times, most recently from 618a7a3 to 53f23f4 Compare July 27, 2026 19:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53f23f464e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/mcp/server.py Outdated
Comment on lines +124 to +128
class _InitializedNotificationTolerantStreamableHTTPTransport(StreamableHTTPTransport):
# Future: not connected to any production path.
# MCPServerStreamableHttp.create_streams() raises UserError when
# ignore_initialized_notification_failure=True (mcp SDK v2 stateless transport
# no longer supports the httpx-based notification-tolerant path).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove the unreachable tolerant transport

Because MCPServerStreamableHttp.create_streams() now rejects ignore_initialized_notification_failure=True before constructing a transport, repository-wide search shows this class is referenced only by its own unit tests and can never run in production. Retaining and testing an implementation built on private MCP transport methods creates dead compatibility machinery that can drift as MCP v2 evolves; remove the class and its transport-level tests while keeping the new early UserError.

AGENTS.md reference: AGENTS.md:L56-L58

Useful? React with 👍 / 👎.

@mixxer
mixxer force-pushed the feat/mcp-sdk-v2-compat branch from 53f23f4 to 6b0e7da Compare July 27, 2026 19:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b0e7dad8b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +70 to +71
# To add authentication, pass an httpx2.Auth instance:
# "auth": httpx2.BasicAuth(username="user", password="secret"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Import httpx2 before advertising BasicAuth

When a user follows the inline instruction and uncomments the provided auth entry, the example raises NameError: name 'httpx2' is not defined because this revision removed the only HTTP-client import without adding import httpx2. Add the import so the advertised authentication configuration is runnable.

AGENTS.md reference: AGENTS.md:L267-L267

Useful? React with 👍 / 👎.

MCP Python SDK v2 introduced several breaking changes that prevent
openai-agents from connecting to MCP servers built on the new SDK.
This commit makes the SDK compatible with mcp 2.0.0b2.

## Breaking changes addressed

**Protocol & types**
- `mcp.types` removed; types moved to standalone `mcp_types` package
- `streamablehttp_client` renamed to `streamable_http_client`
- `GetSessionIdCallback` removed (stateless protocol, SEP-2577)
- `McpError` renamed to `MCPError`

**snake_case field renames**
- `Tool.inputSchema` → `Tool.input_schema`
- `CallToolResult.structuredContent` → `.structured_content`
- `CallToolResult.isError` → `.is_error`
- `ImageContent.mimeType` → `.mime_type`

**HTTP transport**
- `streamable_http_client` signature: `(url, headers, timeout, …)` →
  `(url, *, http_client: httpx2.AsyncClient, terminate_on_close)`
- `ClientSession` third positional arg: `timedelta` → `float`
- `HttpClientFactory` protocol updated to use `httpx2` types throughout
- `_create_default_streamable_http_client` returns `httpx2.AsyncClient`
- `MCPServerSseParams.auth` typed as `httpx2.Auth` (mcp v2 uses httpx2)
- `MCPServerStreamableHttpParams.auth` typed as `httpx2.Auth`
- `MCPServerStreamableHttpParams.httpx_client_factory` removed from
  TypedDict (raises `UserError` at connect time; not supported in v2)

**Error handling**
- All error handlers updated to catch `httpx2` exception classes alongside
  `httpx`: `_raise_user_error_for_http_error`, `_should_retry_in_isolated_session`,
  `cleanup()` ExceptionGroup handler, `call_tool()` ExceptionGroup handler
- `_should_retry_in_isolated_session`: `httpx.codes.REQUEST_TIMEOUT` → `408`

**Documentation**
- `session_id` property docstring updated: in v2 stateless protocol
  (SEP-2577) the property always returns `None`; session resumption
  via `Mcp-Session-Id` header is no longer available

## Source changes

- `src/agents/mcp/server.py`: full v2 migration
- `src/agents/mcp/util.py`: `HttpClientFactory` → httpx2; snake_case fields
- `src/agents/extensions/experimental/codex/items.py`: `mcp_types.ContentBlock`
- `pyproject.toml`: `mcp>=1.19.0,<2` → `mcp>=2.0.0b2`
- `tests/`: 20 files updated for v2 API

## Verification

```
make format  → All checks passed
make lint    → All checks passed
make tests   → 5744 passed, 0 failed, 7 skipped
```

Requires `uv sync --prerelease=allow` until mcp 2.0 stable is released.
Tested against fastmcp 4.0.0a2 with Python 3.13.5.

AGENTS.md reference: local-mcp-server-lifecycle.md
@mixxer
mixxer force-pushed the feat/mcp-sdk-v2-compat branch from 6b0e7da to 50a2505 Compare July 27, 2026 20:01
@seratch

seratch commented Jul 27, 2026

Copy link
Copy Markdown
Member

Thanks for the substantial investigation here. I do not think we should continue this PR in its current form. The stated compatibility gap is not demonstrated: MCP v2 servers serve legacy initialize clients automatically, and this patch still calls ClientSession.initialize(), so it does not add the modern protocol path.

It also targets 2.0.0b2 after 2.0.0rc1 has shipped, currently fails type checking, and breaks released v0.19.0 behavior including the custom HTTP client factory, httpx.Auth, initialized-notification tolerance, and session_id.

I am going to close this PR. Once MCP v2 is stable, we should handle the migration in a fresh PR against that release, with an exact supported scenario and regression coverage that preserves the released transport contracts.

@seratch seratch closed this Jul 27, 2026
@mixxer

mixxer commented Jul 28, 2026

Copy link
Copy Markdown
Author

Thanks for the substantial investigation here. I do not think we should continue this PR in its current form. The stated compatibility gap is not demonstrated: MCP v2 servers serve legacy initialize clients automatically, and this patch still calls ClientSession.initialize(), so it does not add the modern protocol path.

It also targets 2.0.0b2 after 2.0.0rc1 has shipped, currently fails type checking, and breaks released v0.19.0 behavior including the custom HTTP client factory, httpx.Auth, initialized-notification tolerance, and session_id.

I am going to close this PR. Once MCP v2 is stable, we should handle the migration in a fresh PR against that release, with an exact supported scenario and regression coverage that preserves the released transport contracts.

Thank you for your reply. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants