@@ -38,7 +38,7 @@ def __init__(self, context: UiPathMcpRuntimeContext):
3838 self ._server : Optional [McpServer ] = None
3939 self ._signalr_client : Optional [SignalRClient ] = None
4040 self ._session_servers : Dict [str , SessionServer ] = {}
41- self ._session_outputs : Dict [str , str ] = {}
41+ self ._session_output : Optional [str ] = None
4242 self ._cancel_event = asyncio .Event ()
4343 self ._uipath = UiPath ()
4444
@@ -59,10 +59,13 @@ async def execute(self) -> Optional[UiPathRuntimeResult]:
5959 return None
6060
6161 # Set up SignalR client
62- signalr_url = f"{ os .environ .get ('UIPATH_URL' )} /mcp_/wsstunnel?slug={ self ._server .name } &sessionId={ self ._server .session_id } "
62+ signalr_url = f"{ os .environ .get ('UIPATH_URL' )} /mcp_/wsstunnel?slug={ self ._server .name } "
63+ if self ._server .session_id :
64+ signalr_url += f"&sessionId={ self ._server .session_id } "
6365
6466 with tracer .start_as_current_span (self ._server .name ) as root_span :
65- root_span .set_attribute ("session_id" , self ._server .session_id )
67+ if self ._server .session_id :
68+ root_span .set_attribute ("session_id" , self ._server .session_id )
6669 root_span .set_attribute ("command" , self ._server .command )
6770 root_span .set_attribute ("args" , self ._server .args )
6871 root_span .set_attribute ("span_type" , "MCP Server" )
@@ -100,13 +103,8 @@ async def execute(self) -> Optional[UiPathRuntimeResult]:
100103 task .cancel ()
101104
102105 output_result = {}
103- if len (self ._session_outputs ) == 1 :
104- # If there's only one session, use a single "content" key
105- single_session_id = next (iter (self ._session_outputs ))
106- output_result ["content" ] = self ._session_outputs [single_session_id ]
107- elif self ._session_outputs :
108- # If there are multiple sessions, use the session_id as the key
109- output_result = self ._session_outputs
106+ if self ._session_output :
107+ output_result ["content" ] = self ._session_output
110108
111109 self .context .result = UiPathRuntimeResult (output = output_result )
112110
@@ -138,6 +136,13 @@ async def validate(self) -> None:
138136
139137 async def cleanup (self ) -> None :
140138 """Clean up all resources."""
139+ for session_id , session_server in self ._session_servers .items ():
140+ try :
141+ await session_server .stop ()
142+ except Exception as e :
143+ logger .error (
144+ f"Error cleaning up session server { session_id } : { str (e )} "
145+ )
141146 if self ._signalr_client and hasattr (self ._signalr_client , "_transport" ):
142147 transport = self ._signalr_client ._transport
143148 if transport and hasattr (transport , "_ws" ) and transport ._ws :
@@ -167,10 +172,14 @@ async def _handle_signalr_session_closed(self, args: list) -> None:
167172 if session_server :
168173 await session_server .stop ()
169174 if session_server .output :
170- self ._session_outputs [session_id ] = session_server .output
171-
175+ if self ._is_ephemeral :
176+ self ._session_output = session_server .output
177+ else :
178+ logger .info (
179+ f"Session { session_id } output: { session_server .output } "
180+ )
172181 # If this is an ephemeral runtime for a specific session, cancel the execution
173- if self ._is_ephemeral () :
182+ if self ._is_ephemeral :
174183 self ._cancel_event .set ()
175184
176185 except Exception as e :
@@ -215,7 +224,7 @@ async def _handle_signalr_open(self) -> None:
215224 """Handle SignalR connection open event."""
216225 logger .info ("Websocket connection established." )
217226 # If this is an ephemeral runtime we need to start the local MCP session
218- if self ._is_ephemeral () :
227+ if self ._is_ephemeral :
219228 try :
220229 # Check if we have a session server for this session_id
221230 # Websocket reconnection may occur, so we need to check if the session server already exists
@@ -254,7 +263,7 @@ async def _register(self) -> None:
254263
255264 # Start a temporary stdio client to get tools
256265 # Use a temporary file to capture stderr
257- with tempfile .TemporaryFile (mode = "w+" ) as stderr_temp :
266+ with tempfile .TemporaryFile (mode = 'w+b' ) as stderr_temp :
258267 async with stdio_client (server_params , errlog = stderr_temp ) as (
259268 read ,
260269 write ,
@@ -274,7 +283,7 @@ async def _register(self) -> None:
274283 logger .error ("Initialization timed out" )
275284 # Capture stderr output here, after the timeout
276285 stderr_temp .seek (0 )
277- server_stderr_output = stderr_temp .read ()
286+ server_stderr_output = stderr_temp .read (). decode ( 'utf-8' , errors = 'replace' )
278287 # We'll handle this after exiting the context managers
279288
280289 # We don't continue with registration here - we'll do it after the context managers
@@ -303,7 +312,7 @@ async def _register(self) -> None:
303312 "Name" : self ._server .name ,
304313 "Slug" : self ._server .name ,
305314 "Version" : "1.0.0" ,
306- "Type" : 1 ,
315+ "Type" : 1 if self . _server . session_id else 3 ,
307316 },
308317 "tools" : [],
309318 }
@@ -339,7 +348,7 @@ async def _on_initialization_failure(self) -> None:
339348 Ephemeral runtimes are triggered by new client connections.
340349 """
341350
342- if self ._is_ephemeral () is False :
351+ if self ._is_ephemeral is False :
343352 return
344353
345354 try :
@@ -350,7 +359,7 @@ async def _on_initialization_failure(self) -> None:
350359 jsonrpc = "2.0" ,
351360 id = 0 ,
352361 result = {
353- "protocolVersion" : "initiliaze -failure" ,
362+ "protocolVersion" : "initialize -failure" ,
354363 "capabilities" : {},
355364 "serverInfo" : {"name" : self ._server .name , "version" : "1.0" },
356365 },
@@ -369,6 +378,7 @@ async def _on_initialization_failure(self) -> None:
369378 f"Error sending session dispose signal to UiPath MCP Server: { e } "
370379 )
371380
381+ @property
372382 def _is_ephemeral (self ) -> bool :
373383 """
374384 Check if the runtime is ephemeral (created on-demand for a single agent execution).
0 commit comments