Skip to content

Commit 975f9b5

Browse files
committed
fix: create tracing spans
1 parent dfa5653 commit 975f9b5

File tree

5 files changed

+298
-90
lines changed

5 files changed

+298
-90
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.21"
3+
version = "0.0.22"
44
description = "UiPath MCP SDK"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
import logging
2-
import sys
1+
import os
32

43

54
class LoggerAdapter:
6-
def __init__(self, logger, level=logging.INFO):
7-
self.logger = logger
8-
self.level = level
5+
def __init__(self):
6+
self.devnull = open(os.devnull, "w")
97

108
def write(self, message):
11-
if message and not message.isspace():
12-
self.logger.log(self.level, message.rstrip())
139
return len(message)
1410

1511
def flush(self):
16-
pass
12+
self.devnull.flush()
1713

1814
def fileno(self):
19-
return sys.stderr.fileno()
15+
return self.devnull.fileno()
16+
17+
def close(self):
18+
self.devnull.close()

src/uipath_mcp/_cli/_runtime/_runtime.py

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from mcp import ClientSession, StdioServerParameters
88
from mcp.client.stdio import stdio_client
9+
from opentelemetry import trace
910
from pysignalr.client import SignalRClient
1011
from uipath import UiPath
1112
from uipath._cli._runtime._contracts import (
@@ -20,6 +21,7 @@
2021
from ._session import SessionServer
2122

2223
logger = logging.getLogger(__name__)
24+
tracer = trace.get_tracer(__name__)
2325

2426

2527
class UiPathMcpRuntime(UiPathBaseRuntime):
@@ -56,41 +58,48 @@ async def execute(self) -> Optional[UiPathRuntimeResult]:
5658

5759
self.cancel_event = asyncio.Event()
5860

59-
self.signalr_client = SignalRClient(
60-
signalr_url,
61-
headers={
62-
"X-UiPath-Internal-TenantId": self.context.trace_context.tenant_id,
63-
"X-UiPath-Internal-AccountId": self.context.trace_context.org_id,
64-
},
65-
)
66-
self.signalr_client.on("MessageReceived", self.handle_signalr_message)
67-
self.signalr_client.on("SessionClosed", self.handle_signalr_session_closed)
68-
self.signalr_client.on_error(self.handle_signalr_error)
69-
self.signalr_client.on_open(self.handle_signalr_open)
70-
self.signalr_client.on_close(self.handle_signalr_close)
71-
72-
# Register the server with UiPath MCP Server
73-
await self._register()
74-
75-
# Keep the runtime alive
76-
# Start SignalR client and keep it running (this is a blocking call)
77-
logger.info("Starting websocket client...")
78-
79-
run_task = asyncio.create_task(self.signalr_client.run())
80-
81-
# Set up a task to wait for cancellation
82-
cancel_task = asyncio.create_task(self.cancel_event.wait())
83-
84-
# Wait for either the run to complete or cancellation
85-
done, pending = await asyncio.wait(
86-
[run_task, cancel_task], return_when=asyncio.FIRST_COMPLETED
87-
)
88-
89-
# Cancel any pending tasks
90-
for task in pending:
91-
task.cancel()
92-
93-
return UiPathRuntimeResult()
61+
with tracer.start_as_current_span("MCP Server") as root_span:
62+
root_span.set_attribute("session_id", self.server.session_id)
63+
root_span.set_attribute("command", self.server.command)
64+
root_span.set_attribute("args", self.server.args)
65+
root_span.set_attribute("type", self.server.type)
66+
self.signalr_client = SignalRClient(
67+
signalr_url,
68+
headers={
69+
"X-UiPath-Internal-TenantId": self.context.trace_context.tenant_id,
70+
"X-UiPath-Internal-AccountId": self.context.trace_context.org_id,
71+
},
72+
)
73+
self.signalr_client.on("MessageReceived", self.handle_signalr_message)
74+
self.signalr_client.on(
75+
"SessionClosed", self.handle_signalr_session_closed
76+
)
77+
self.signalr_client.on_error(self.handle_signalr_error)
78+
self.signalr_client.on_open(self.handle_signalr_open)
79+
self.signalr_client.on_close(self.handle_signalr_close)
80+
81+
# Register the server with UiPath MCP Server
82+
await self._register()
83+
84+
# Keep the runtime alive
85+
# Start SignalR client and keep it running (this is a blocking call)
86+
logger.info("Starting websocket client...")
87+
88+
run_task = asyncio.create_task(self.signalr_client.run())
89+
90+
# Set up a task to wait for cancellation
91+
cancel_task = asyncio.create_task(self.cancel_event.wait())
92+
93+
# Wait for either the run to complete or cancellation
94+
done, pending = await asyncio.wait(
95+
[run_task, cancel_task], return_when=asyncio.FIRST_COMPLETED
96+
)
97+
98+
# Cancel any pending tasks
99+
for task in pending:
100+
task.cancel()
101+
102+
return UiPathRuntimeResult()
94103

95104
except Exception as e:
96105
if isinstance(e, UiPathMcpRuntimeError):

src/uipath_mcp/_cli/_runtime/_session.py

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
from mcp.client.stdio import stdio_client
77
from opentelemetry import trace
88
from uipath import UiPath
9-
from uipath.tracing import traced, wait_for_tracers
9+
from uipath.tracing import wait_for_tracers
1010

1111
from .._utils._config import McpServer
1212
from ._logger import LoggerAdapter
13+
from ._tracer import McpTracer
1314

1415
logger = logging.getLogger(__name__)
1516
tracer = trace.get_tracer(__name__)
@@ -28,6 +29,7 @@ def __init__(self, server_config: McpServer, session_id: str):
2829
self.context_task = None
2930
self._message_queue = asyncio.Queue()
3031
self._uipath = UiPath()
32+
self._mcp_tracer = McpTracer(tracer, logger)
3133

3234
async def start(self) -> None:
3335
"""Start the server process in a separate task."""
@@ -59,39 +61,33 @@ async def _run_server(self, server_params: StdioServerParameters) -> None:
5961
"""Run the server in proper context managers."""
6062
logger.info(f"Starting server process for session {self.session_id}")
6163
try:
62-
stderr_adapter = LoggerAdapter(logger)
63-
with tracer.start_as_current_span(self.server_config.name) as root_span:
64-
root_span.set_attribute("session_id", self.session_id)
65-
root_span.set_attribute(
66-
"server_params", server_params.model_dump_json()
67-
)
68-
async with stdio_client(server_params, errlog=stderr_adapter) as (
69-
read,
70-
write,
71-
):
72-
self.read_stream, self.write_stream = read, write
73-
logger.info(f"Session {self.session_id} - stdio client started")
64+
stderr_null = LoggerAdapter(logger)
7465

75-
logger.info(f"Session {self.session_id} - MCP session initialized")
66+
async with stdio_client(server_params, errlog=stderr_null) as (
67+
read,
68+
write,
69+
):
70+
self.read_stream, self.write_stream = read, write
71+
logger.info(f"Session {self.session_id} - stdio client started")
7672

77-
# Start the message consumer task
78-
consumer_task = asyncio.create_task(self._consume_messages())
73+
# Start the message consumer task
74+
consumer_task = asyncio.create_task(self._consume_messages())
7975

80-
# Process incoming messages from the server
76+
# Process incoming messages from the server
77+
try:
78+
while True:
79+
print("Waiting for messages...")
80+
message = await self.read_stream.receive()
81+
json_str = message.model_dump_json()
82+
print(f"Received message from local server: {json_str}")
83+
await self.send_outgoing_message(message)
84+
finally:
85+
# Cancel the consumer when we exit the loop
86+
consumer_task.cancel()
8187
try:
82-
while True:
83-
print("Waiting for messages...")
84-
message = await self.read_stream.receive()
85-
json_str = message.model_dump_json()
86-
print(f"Received message from local server: {json_str}")
87-
await self.send_outgoing_message(message)
88-
finally:
89-
# Cancel the consumer when we exit the loop
90-
consumer_task.cancel()
91-
try:
92-
await consumer_task
93-
except asyncio.CancelledError:
94-
pass
88+
await consumer_task
89+
except asyncio.CancelledError:
90+
pass
9591

9692
except Exception as e:
9793
logger.error(
@@ -163,7 +159,6 @@ async def send_message(self, message: types.JSONRPCMessage) -> None:
163159
await self._message_queue.put(message)
164160
logger.debug(f"Session {self.session_id} - message queued for processing")
165161

166-
@traced()
167162
async def get_incoming_messages(self) -> None:
168163
"""Get new messages from UiPath MCP Server."""
169164
response = self._uipath.api_client.request(
@@ -175,23 +170,37 @@ async def get_incoming_messages(self) -> None:
175170
for message in messages:
176171
logger.info(f"Incoming message from UiPath MCP Server: {message}")
177172
json_message = types.JSONRPCMessage.model_validate(message)
178-
logger.info(f"Forwarding message to local MCP Server: {message}")
179-
await self.send_message(json_message)
173+
with self._mcp_tracer.create_span_for_message(
174+
json_message,
175+
session_id=self.session_id,
176+
server_name=self.server_config.name
177+
) as _:
178+
logger.info(f"Forwarding message to local MCP Server: {message}")
179+
await self.send_message(json_message)
180180

181-
@traced()
182181
async def send_outgoing_message(self, message: types.JSONRPCMessage) -> None:
183182
"""Send new message to UiPath MCP Server."""
184-
response = self._uipath.api_client.request(
185-
"POST",
186-
f"mcp_/mcp/{self.server_config.name}/out/message?sessionId={self.session_id}",
187-
json=message.model_dump(),
188-
)
189-
if response.status_code == 202:
190-
logger.info(f"Outgoing message sent to UiPath MCP Server: {message}")
191-
else:
192-
logger.error(
193-
f"Failed to send outgoing message to UiPath MCP Server: {response.status_code} {response.text}"
194-
)
183+
with self._mcp_tracer.create_span_for_message(
184+
message,
185+
session_id=self.session_id,
186+
server_name=self.server_config.name
187+
) as span:
188+
try:
189+
response = self._uipath.api_client.request(
190+
"POST",
191+
f"mcp_/mcp/{self.server_config.name}/out/message?sessionId={self.session_id}",
192+
json=message.model_dump(),
193+
)
194+
195+
span.set_attribute("http.status_code", response.status_code)
196+
197+
if response.status_code == 202:
198+
logger.info(f"Outgoing message sent to UiPath MCP Server: {message}")
199+
else:
200+
self._mcp_tracer.record_http_error(span, response.status_code, response.text)
201+
except Exception as e:
202+
self._mcp_tracer.record_exception(span, e)
203+
raise
195204

196205
async def cleanup(self) -> None:
197206
"""Clean up resources and stop the server."""

0 commit comments

Comments
 (0)