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.


Summary
1.26.0to2.0.0and remove the unused directlangchain-mcp-adaptersdependencySessionInfoClientSession, including a concurrency guard and recovery for expired persisted session IDsCompatibility
The current low-level UiPath client remains compatible with servers negotiating
2025-03-26,2025-06-18, or2025-11-25; all three are covered by tests. A server that also supports the legacy initialize flow connects in legacy mode. Strictly modern2026-07-28discovery-only servers require a separate adoption of the SDK 2 high-level auto-discovery client and are explicitly documented as outside this upgrade.Validation
uv lock --checkuv run ruff check .uv run mypy src/uipath_langchain/agent/tools/mcpuv run pytest -qAll checks pass locally.