@@ -41,7 +41,7 @@ from .mcp_tool import (
4141
4242` McpClient ` implements ` UiPathDisposableProtocol ` and manages the lifecycle of MCP connections for tool invocations with ** two distinct initialization phases** :
4343
44- 1 . ** Client Initialization** (first call): Full stack creation
44+ 1 . ** Client Initialization** (first call): Retrieves MCP server URL via SDK, then full stack creation
45452 . ** Session Reinitialization** (on 404): Lightweight, reuses existing client
4646
4747```
@@ -50,11 +50,15 @@ from .mcp_tool import (
5050├─────────────────────────────────────────────────────────────┤
5151│ Configuration (immutable after __init__) │
5252│ ───────────────────────────────────────── │
53- │ _url: str │
54- │ _headers: dict[str, str] │
53+ │ _config: AgentMcpResourceConfig # Contains slug, folder │
5554│ _timeout: httpx.Timeout │
5655│ _max_retries: int │
5756├─────────────────────────────────────────────────────────────┤
57+ │ Lazy-Resolved State (set during _initialize_client) │
58+ │ ─────────────────────────────────────────────────── │
59+ │ _url: str | None # Retrieved from SDK │
60+ │ _headers: dict[str, str] # Auth header from SDK │
61+ ├─────────────────────────────────────────────────────────────┤
5862│ Synchronization │
5963│ ─────────────── │
6064│ _lock: asyncio.Lock # Protects both init phases │
@@ -82,7 +86,7 @@ from .mcp_tool import (
8286├─────────────────────────────────────────────────────────────┤
8387│ Private Methods │
8488│ ─────────────── │
85- │ - _initialize_client() -> None # Full init (once) │
89+ │ - _initialize_client() -> None # SDK + full init (once) │
8690│ - _initialize_session() -> None # MCP handshake only │
8791│ - _ensure_session() -> ClientSession │
8892│ - _reinitialize_session() -> None │
@@ -105,8 +109,8 @@ async def create_mcp_tools_from_agent(
105109 Iterates over all MCP resources in the agent definition and creates tools
106110 for each enabled MCP server. Each MCP server gets its own McpClient instance.
107111
108- The UiPath SDK is lazily initialized inside this function using environment
109- variables (UIPATH_URL, UIPATH_ACCESS_TOKEN).
112+ The MCP server URL is loaded lazily on first tool call via the UiPath SDK,
113+ using environment variables (UIPATH_URL, UIPATH_ACCESS_TOKEN).
110114
111115 Returns:
112116 A tuple of (tools, mcp_clients) where:
@@ -143,6 +147,11 @@ The key design principle is separating **client initialization** from **session
143147```
144148Phase 1: Client Initialization (expensive, done once)
145149──────────────────────────────────────────────────────
150+ ┌─────────────────┐
151+ │ UiPath SDK │ ─── Retrieves MCP server URL
152+ │ mcp.retrieve() │ and auth token (Bearer)
153+ └─────────────────┘
154+
146155┌─────────────────┐
147156│ httpx.AsyncClient │ ─┐
148157└─────────────────┘ │
@@ -179,8 +188,9 @@ Phase 2: Session Initialization (lightweight, can repeat)
179188 │ Initializing │
180189 │ (Phase 1) │
181190 └──────┬───────┘
182- │ creates HTTP client, streams, session
183- │ then calls _initialize_session()
191+ │ 1. UiPath SDK retrieves MCP URL
192+ │ 2. creates HTTP client, streams, session
193+ │ 3. calls _initialize_session()
184194 ▼
185195 ┌──────────────┐
186196 │ Session │
@@ -251,7 +261,42 @@ The following error codes trigger automatic session reinitialization:
251261
252262## Key Implementation Details
253263
254- ### 1. HTTP Client Configuration
264+ ### 1. Lazy SDK Loading
265+
266+ The MCP server URL and authorization headers are loaded lazily on first tool call:
267+
268+ ``` python
269+ async def _initialize_client (self ) -> None :
270+ # Lazy import to improve cold start time
271+ from uipath.platform import UiPath
272+
273+ # Retrieve MCP server URL from SDK
274+ sdk = UiPath()
275+ mcp_server = await sdk.mcp.retrieve_async(
276+ slug = self ._config.slug, folder_path = self ._config.folder_path
277+ )
278+
279+ if mcp_server.mcp_url is None :
280+ raise ValueError (f " MCP server ' { self ._config.slug} ' has no URL configured " )
281+
282+ self ._url = mcp_server.mcp_url
283+ self ._headers = {" Authorization" : f " Bearer { sdk._config.secret} " }
284+ ```
285+
286+ ** Why lazy loading is required:**
287+
288+ The ` uipath debug ` command loads resource bindings (which can override MCP server URLs)
289+ ** after** the LangGraph agent graph is built. This means bindings are only available at
290+ execution time, not at graph construction time. By deferring the SDK call to the first
291+ tool invocation, we ensure the bindings are properly loaded and applied.
292+
293+ ** Benefits:**
294+ - Bindings are correctly applied (loaded after graph construction)
295+ - No SDK calls during tool creation (only during first use)
296+ - Faster agent startup time
297+ - Errors surface only when tools are actually used
298+
299+ ### 2. HTTP Client Configuration
255300
256301The HTTP client MUST use ` get_httpx_client_kwargs() ` for proper SSL/proxy configuration:
257302
@@ -268,7 +313,7 @@ self._http_client = await self._stack.enter_async_context(
268313)
269314```
270315
271- ### 2 . Single Lock for Both Phases
316+ ### 3 . Single Lock for Both Phases
272317
273318One ` asyncio.Lock ` protects both client initialization and session reinitialization:
274319
@@ -289,7 +334,7 @@ async def _reinitialize_session(self) -> None:
289334 await self ._initialize_session() # Lightweight!
290335```
291336
292- ### 3 . No ` with ` Statement for AsyncExitStack
337+ ### 4 . No ` with ` Statement for AsyncExitStack
293338
294339Manual lifecycle management:
295340
@@ -305,7 +350,7 @@ async with AsyncExitStack() as stack:
305350 ... # Stack closes here!
306351```
307352
308- ### 4 . Reinitialization Reuses Client
353+ ### 5 . Reinitialization Reuses Client
309354
310355The key optimization - on 404, only ` _initialize_session() ` is called:
311356
0 commit comments