feat(mcp): upgrade Python SDK to 2.0 - #1025
Conversation
There was a problem hiding this comment.
Pull request overview
Upgrades the MCP Python SDK integration in uipath_langchain.agent.tools.mcp to SDK 2.0.0, replacing the previously copied Streamable HTTP transport with a thin adapter and updating the client/tool layers (plus tests/docs) to the new SDK APIs while preserving UiPath’s externally persisted SessionInfo behavior.
Changes:
- Bump dependency pin to
mcp==2.0.0(and remove directlangchain-mcp-adapters) with corresponding lockfile updates. - Replace the local forked Streamable HTTP transport with a session-aware adapter around the SDK 2 transport using
httpx2event hooks. - Rework session recovery logic (fresh transport +
ClientSessionreplacement) and update tests/docs to exercise real SDK 2 transport behavior.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock |
Updates resolved dependency graph for MCP SDK 2.0 (httpx2, mcp-types, etc.) and removes langchain-mcp-adapters. |
pyproject.toml |
Pins mcp==2.0.0 and drops the direct langchain-mcp-adapters dependency. |
src/uipath_langchain/agent/tools/mcp/streamable_http.py |
Replaces ~800-line fork with a small adapter that syncs mcp-session-id via httpx2 request/response hooks. |
src/uipath_langchain/agent/tools/mcp/mcp_client.py |
Migrates to SDK 2 (MCPError, httpx2) and replaces recovery with “close connection + reopen transport/session” semantics. |
src/uipath_langchain/agent/tools/mcp/mcp_tool.py |
Updates SDK 2 model field names (input_schema/output_schema) and MCP error mapping. |
src/uipath_langchain/agent/tools/mcp/claude.md |
Updates implementation documentation for the new adapter/recovery model. |
tests/agent/tools/test_mcp/test_mcp_client.py |
Replaces prior mocks with real SDK 2 transport over httpx2.MockTransport to validate negotiation/recovery/cache/disposal. |
tests/agent/tools/test_mcp/test_mcp_tool.py |
Updates imports/types for SDK 2 and aligns schema assertions with snake_case fields. |
tests/agent/tools/test_mcp/claude.md |
Updates test strategy documentation for the new endpoint + transport mocking pattern. |
docs/mcp-sdk-2-upgrade.md |
Adds a focused upgrade review doc explaining SDK 2 changes, compatibility boundaries, and recovery behavior. |
| client.event_hooks["request"].remove(apply_session_id) | ||
| client.event_hooks["response"].remove(capture_session_id) |
| if self._connection_stack is not None: | ||
| await self._connection_stack.aclose() | ||
| self._connection_stack = None | ||
| self._session = None |
| else: | ||
| detail = ( | ||
| f"MCP server '{server_slug}' returned an error for tool " | ||
| f"'{tool_name}': {error.error.message}" | ||
| f"'{tool_name}': {error.message}" | ||
| ) |
| "jsonpath-ng>=1.7.0", | ||
| "mcp==1.26.0", | ||
| "langchain-mcp-adapters==0.2.1", | ||
| "mcp==2.0.0", |
There was a problem hiding this comment.
Restore compatibility with langchain-mcp-adapters before pinning MCP 2.0. The repository’s own simple-local-mcp integration resolves langchain-mcp-adapters==0.3.1 with this pin, then fails at import because the adapter imports RequestContext removed by MCP 2. I reproduced the same failure with uv run --with langchain-mcp-adapters==0.3.1 ..., and the alpha/cloud/staging integration jobs all fail on it. The adapter metadata only says mcp>=1.24.0, so dependency resolution cannot protect downstream users. Land a compatible adapter/migration for this supported path, or prevent the incompatible combination from resolving.
| async with sdk_streamable_http_client( | ||
| url, | ||
| http_client=client, | ||
| terminate_on_close=terminate_on_close, |
There was a problem hiding this comment.
Preserve the full restored-session lifecycle instead of only injecting its ID into requests. The SDK transport delegated to here keeps a separate in-memory session_id; when SessionInfo starts with a persisted ID and initialization is skipped, that SDK field remains None. I reproduced a successful tool call followed by disposal with terminate_on_close=True: no DELETE was sent (delete_count == 0) and the persisted ID remained set, whereas the replaced transport terminated through SessionInfo. The fresh ClientSession also has no adopted negotiation state, so the restored-session request carried no mcp-protocol-version. Explicitly hydrate/adopt the required session state or implement termination/version handling for restored IDs, and cover both the protocol header and DELETE path.
| if self._session_info: | ||
| await self._session_info.set_session_id(None) | ||
| await self._initialize_session() | ||
| await self._open_connection() |
There was a problem hiding this comment.
Keep the client recoverable when opening the replacement connection fails. I reproduced _open_connection() raising MCPError(CONNECTION_CLOSED, ...) here: _open_connection clears _session, but _client_initialized remains True; every later operation then fails in _ensure_session() with RuntimeError("MCP client initialized without a session") instead of rebuilding the client. The handshake failure also escapes from inside the retry handler, so the configured retry loop does not repair this state. Reset the initialized state on reopen failure or make _ensure_session() rebuild when the session is absent, with a focused failed-recovery-then-next-call test.
| [[package]] | ||
| name = "mcp" | ||
| version = "1.26.0" | ||
| version = "2.0.0" |
There was a problem hiding this comment.
Bump the uipath-langchain package version and regenerate the lockfile with that version. This PR changes runtime session/recovery behavior and takes a new major MCP dependency, but pyproject.toml and the local-package lock entry still publish 0.15.3. Every merge publishes immediately, so leaving the package version unchanged makes these changes ride another release and prevents an independent rollback.
43b7ac8 to
ecbeb94
Compare
|



Summary
1.26.0to2.0.0and replace the copied SDK 1.x Streamable HTTP transport with a thin SDK 2 adapterSessionInfo, including restored-session protocol adoption, version headers, idempotent persistence, termination, and recovery from the SDK server's canonicalSession not foundresponsehttpx.TimeoutAPIlangchain-mcp-adapterssample dependency with a tested first-party active-session tool converter, and migrate local and remote samples to MCP 2 public APIshttpx2dependency and release the public raw-model attribute changes asuipath-langchain0.17.0Compatibility
The low-level UiPath client remains compatible with servers negotiating
2025-03-26,2025-06-18, or2025-11-25; all three versions are covered by real-transport tests. Externally restored sessions are validated and adopted without another initialize request, then terminated on disposal when configured.A server that also supports the legacy initialize flow connects in legacy mode. Strictly modern
2026-07-28discovery-only support remains a separate, uncommitted follow-up layer and is not part of this PR.Migration guide
McpClientintentionally continues to return the MCP SDK's raw result models. Their Python attributes changed from camelCase to snake_case in SDK 2, so this PR bumps the package to0.17.0and documents the exact mappings.Validation
uv lock --checkuv run just lintuv run ruff format --check .uv run mypy --config-file pyproject.toml .uv run pytest— 2,672 passeduv run pytest -o addopts='' tests/agent/tools/test_mcp— 79 passedsimple-local-mcpdependency resolution dry run withoutlangchain-mcp-adaptersuv build— 0.17.0 sdist and wheelorigin/main— 93%