feat: Add compatibility with MCP Python SDK v2 (mcp 2.0.0b2) - #3989
feat: Add compatibility with MCP Python SDK v2 (mcp 2.0.0b2)#3989mixxer wants to merge 1 commit into
Conversation
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.
There was a problem hiding this comment.
💡 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".
|
|
||
| import anyio | ||
| import httpx | ||
| import httpx2 |
There was a problem hiding this comment.
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 👍 / 👎.
|
|
||
| @asynccontextmanager | ||
| async def _managed() -> AsyncGenerator[MCPStreamTransport, None]: | ||
| async with httpx2.AsyncClient( |
There was a problem hiding this comment.
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 👍 / 👎.
|
|
||
| @asynccontextmanager | ||
| async def _managed() -> AsyncGenerator[MCPStreamTransport, None]: | ||
| async with httpx2.AsyncClient( |
There was a problem hiding this comment.
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 👍 / 👎.
| async def _managed() -> AsyncGenerator[MCPStreamTransport, None]: | ||
| async with httpx2.AsyncClient( | ||
| headers=headers, | ||
| timeout=httpx2.Timeout(timeout_sec, read=sse_timeout), |
There was a problem hiding this comment.
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 👍 / 👎.
| if server.use_structured_content and result.structured_content: | ||
| tool_output = json.dumps(result.structured_content) |
There was a problem hiding this comment.
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 👍 / 👎.
| async with httpx2.AsyncClient( | ||
| headers=headers, | ||
| timeout=httpx2.Timeout(timeout_sec, read=sse_timeout), | ||
| auth=auth, |
There was a problem hiding this comment.
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 👍 / 👎.
| if self.params.get("ignore_initialized_notification_failure", False): | ||
| httpx_client_factory = self.params.get("httpx_client_factory") | ||
| return _streamablehttp_client_with_transport( |
There was a problem hiding this comment.
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 👍 / 👎.
438ab05 to
d89758f
Compare
There was a problem hiding this comment.
💡 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".
| ) as transport: | ||
| yield transport |
There was a problem hiding this comment.
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 👍 / 👎.
| | httpx2.HTTPStatusError | ||
| | httpx2.ConnectError | ||
| | httpx2.TimeoutException, |
There was a problem hiding this comment.
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 👍 / 👎.
| "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'", |
There was a problem hiding this comment.
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 👍 / 👎.
d89758f to
ea9bd35
Compare
There was a problem hiding this comment.
💡 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".
| 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." |
There was a problem hiding this comment.
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 👍 / 👎.
| "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'", |
There was a problem hiding this comment.
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 👍 / 👎.
618a7a3 to
53f23f4
Compare
There was a problem hiding this comment.
💡 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".
| 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). |
There was a problem hiding this comment.
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 👍 / 👎.
53f23f4 to
6b0e7da
Compare
There was a problem hiding this comment.
💡 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".
| # To add authentication, pass an httpx2.Auth instance: | ||
| # "auth": httpx2.BasicAuth(username="user", password="secret"), |
There was a problem hiding this comment.
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
6b0e7da to
50a2505
Compare
|
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 It also targets 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. :-) |
Summary
Add compatibility with MCP Python SDK v2 (
mcp>=2.0.0b2). The SDK introducedseveral 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:
mcp.typesmcp_types(standalone package)streamablehttp_clientstreamable_http_client(url, headers, timeout, …)(url, *, http_client, terminate_on_close)GetSessionIdCallbackexportedMcpErrorMCPErrorTool.inputSchemaTool.input_schemaCallToolResult.structuredContent/.isError.structured_content/.is_errorClientSession(…, timedelta(…))ClientSession(…, float)Source changes:
src/agents/mcp/server.py: Updated imports, rewroteMCPServerStreamableHttp.create_streams()to usestreamable_http_client(url, *, http_client, terminate_on_close)with properhttpx2.AsyncClientlifecycle management. RaisesUserErrorforhttpx_client_factoryandignore_initialized_notification_failure(no v2 equivalent; documented alternative noted). Addedhttpx2exception classes to all error handlers.src/agents/mcp/util.py: Updated field name accesses to snake_case.src/agents/extensions/experimental/codex/items.py: ImportContentBlockfrommcp_types.pyproject.toml:mcp>=1.19.0,<2→mcp>=2.0.0b2.tests/: Updated all MCP test files to use the v2 API (mcp_types,MCPError, snake_case fields). Rewrotetest_streamable_http_client_factory.pyandtest_mcp_auth_params.pyto test new v2 behavior andUserErrorpaths.Test plan
All checks run locally with
uv sync --prerelease=allow(required until mcp 2.0 stable):make format— All checks passedmake lint— All checks passedmake tests— 5744 passed, 0 failed, 7 skippedmake typecheck— 33 errors remain. Baseline onmainwith mcp 2.0 installed is 134 errors; this PR reduces them by 101. The remaining 33 include one pre-existingeager_task_factoryissue intest_run_step_execution.py(present onmain, unrelated to this PR) and type annotation mismatches that originate from the mcp SDK v2 beta (e.g.call_toolreturning a broader union type,TransportStreamstype change). These will resolve when mcp 2.0 ships stable stubs.MCPServerStdio+MCPServerStreamableHttpconnect →list_tools→call_toolagainstfastmcp 4.0.0a2— PASSIssue number
N/A — MCP Python SDK v2 compatibility
Checks
.agents/skills/code-change-verification/scripts/run.sh/reviewbefore submitting this PRNote on verification script:
code-change-verificationrequiresuv sync --prerelease=allowto resolvemcp>=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.