|
2 | 2 | from dataclasses import dataclass |
3 | 3 | from typing import Any, AsyncGenerator, Callable, Collection, Tuple, cast |
4 | 4 | import json |
| 5 | +import logging |
| 6 | +import traceback |
5 | 7 |
|
6 | 8 | from opentelemetry import context, propagate |
7 | 9 | from opentelemetry.instrumentation.instrumentor import BaseInstrumentor |
|
18 | 20 | _instruments = ("mcp >= 1.6.0",) |
19 | 21 |
|
20 | 22 |
|
| 23 | +class Config: |
| 24 | + exception_logger = None |
| 25 | + |
| 26 | + |
| 27 | +def dont_throw(func): |
| 28 | + """ |
| 29 | + A decorator that wraps the passed in function and logs exceptions instead of throwing them. |
| 30 | +
|
| 31 | + @param func: The function to wrap |
| 32 | + @return: The wrapper function |
| 33 | + """ |
| 34 | + # Obtain a logger specific to the function's module |
| 35 | + logger = logging.getLogger(func.__module__) |
| 36 | + |
| 37 | + def wrapper(*args, **kwargs): |
| 38 | + try: |
| 39 | + return func(*args, **kwargs) |
| 40 | + except Exception as e: |
| 41 | + logger.debug( |
| 42 | + "OpenLLMetry failed to trace in %s, error: %s", |
| 43 | + func.__name__, |
| 44 | + traceback.format_exc(), |
| 45 | + ) |
| 46 | + if Config.exception_logger: |
| 47 | + Config.exception_logger(e) |
| 48 | + |
| 49 | + return wrapper |
| 50 | + |
| 51 | + |
21 | 52 | class McpInstrumentor(BaseInstrumentor): |
22 | 53 | def instrumentation_dependencies(self) -> Collection[str]: |
23 | 54 | return _instruments |
@@ -106,6 +137,7 @@ def traced_method( |
106 | 137 | return traced_method |
107 | 138 |
|
108 | 139 | def patch_mcp_client(self, tracer): |
| 140 | + @dont_throw |
109 | 141 | async def traced_method(wrapped, instance, args, kwargs): |
110 | 142 | import mcp.types |
111 | 143 |
|
@@ -206,11 +238,18 @@ async def __aenter__(self) -> Any: |
206 | 238 | async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any: |
207 | 239 | return await self.__wrapped__.__aexit__(exc_type, exc_value, traceback) |
208 | 240 |
|
| 241 | + @dont_throw |
209 | 242 | async def __aiter__(self) -> AsyncGenerator[Any, None]: |
210 | 243 | from mcp.types import JSONRPCMessage, JSONRPCRequest |
| 244 | + from mcp.shared.message import SessionMessage |
211 | 245 |
|
212 | 246 | async for item in self.__wrapped__: |
213 | | - request = cast(JSONRPCMessage, item).root |
| 247 | + if isinstance(item, SessionMessage): |
| 248 | + request = cast(JSONRPCMessage, item.message).root |
| 249 | + elif type(item) is JSONRPCMessage: |
| 250 | + request = cast(JSONRPCMessage, item).root |
| 251 | + else: |
| 252 | + return |
214 | 253 | if not isinstance(request, JSONRPCRequest): |
215 | 254 | yield item |
216 | 255 | continue |
@@ -240,10 +279,17 @@ async def __aenter__(self) -> Any: |
240 | 279 | async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any: |
241 | 280 | return await self.__wrapped__.__aexit__(exc_type, exc_value, traceback) |
242 | 281 |
|
| 282 | + @dont_throw |
243 | 283 | async def send(self, item: Any) -> Any: |
244 | 284 | from mcp.types import JSONRPCMessage, JSONRPCRequest |
| 285 | + from mcp.shared.message import SessionMessage |
245 | 286 |
|
246 | | - request = cast(JSONRPCMessage, item).root |
| 287 | + if isinstance(item, SessionMessage): |
| 288 | + request = cast(JSONRPCMessage, item.message).root |
| 289 | + elif type(item) is JSONRPCMessage: |
| 290 | + request = cast(JSONRPCMessage, item).root |
| 291 | + else: |
| 292 | + return |
247 | 293 |
|
248 | 294 | with self._tracer.start_as_current_span("ResponseStreamWriter") as span: |
249 | 295 | if hasattr(request, "result"): |
@@ -287,6 +333,7 @@ async def __aenter__(self) -> Any: |
287 | 333 | async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any: |
288 | 334 | return await self.__wrapped__.__aexit__(exc_type, exc_value, traceback) |
289 | 335 |
|
| 336 | + @dont_throw |
290 | 337 | async def send(self, item: Any) -> Any: |
291 | 338 | with self._tracer.start_as_current_span("RequestStreamWriter") as span: |
292 | 339 | if hasattr(item, "request_id"): |
|
0 commit comments