Skip to content

Commit eb3dfd9

Browse files
committed
fix: handle debug always-on mode
1 parent b4311ba commit eb3dfd9

File tree

4 files changed

+33
-23
lines changed

4 files changed

+33
-23
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-mcp"
3-
version = "0.0.51"
3+
version = "0.0.52"
44
description = "UiPath MCP SDK"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"

src/uipath_mcp/_cli/_runtime/_runtime.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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).

src/uipath_mcp/_cli/_runtime/_session.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ async def _run_server(self, server_params: StdioServerParameters) -> None:
9898
"""Run the local MCP server process."""
9999
logger.info(f"Starting local MCP Server process for session {self._session_id}")
100100
self._server_stderr_output = None
101-
with tempfile.TemporaryFile(mode="w+") as stderr_temp:
101+
with tempfile.TemporaryFile(mode='w+b') as stderr_temp:
102102
try:
103103
async with stdio_client(server_params, errlog=stderr_temp) as (
104104
read,
@@ -129,7 +129,7 @@ async def _run_server(self, server_params: StdioServerParameters) -> None:
129129
)
130130
finally:
131131
stderr_temp.seek(0)
132-
self._server_stderr_output = stderr_temp.read()
132+
self._server_stderr_output = stderr_temp.read().decode('utf-8', errors='replace')
133133
# The context managers will handle cleanup of resources
134134

135135
def _run_server_callback(self, task):

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)