11import json
22import logging
3- from typing import Optional , TypeVar
3+ from typing import Dict , Optional , TypeVar
44
55import mcp .types as types
66from opentelemetry import trace
@@ -19,55 +19,89 @@ def __init__(
1919 ):
2020 self .tracer = tracer or trace .get_tracer (__name__ )
2121 self .logger = logger or logging .getLogger (__name__ )
22+ # Dictionary to store active request spans
23+ self ._active_request_spans : Dict [str , Span ] = {}
2224
23- def create_span_for_message (
24- self , message : types .JSONRPCMessage , ** context
25- ) -> Span :
25+ def create_span_for_message (self , message : types .JSONRPCMessage , ** context ) -> Span :
2626 """Create and configure a span for a message.
2727
2828 Args:
2929 message: The JSON-RPC message
30- span_name: The name to use for the span
3130 **context: Additional context attributes to add to the span
3231
3332 Returns:
3433 A configured OpenTelemetry span
3534 """
3635 root_value = message .root
3736
38- if isinstance (root_value , types .JSONRPCRequest ):
39- span = self .tracer .start_span (f"{ root_value .method } [{ root_value .id } ]" )
40- span .set_attribute ("type" , "request" )
41- span .set_attribute ("span_type" , "request" )
42- span .set_attribute ("id" , str (root_value .id ))
43- span .set_attribute ("method" , root_value .method )
44- self ._add_request_attributes (span , root_value )
45-
46- elif isinstance (root_value , types .JSONRPCNotification ):
47- span = self .tracer .start_span (root_value .method )
48- span .set_attribute ("type" , "notification" )
49- span .set_attribute ("span_type" , "notification" )
50- span .set_attribute ("method" , root_value .method )
51- self ._add_notification_attributes (span , root_value )
52-
53- elif isinstance (root_value , types .JSONRPCResponse ):
54- span = self .tracer .start_span (f"response [{ root_value .id } ]" )
55- span .set_attribute ("type" , "response" )
56- span .set_attribute ("span_type" , "response" )
57- span .set_attribute ("id" , str (root_value .id ))
58- self ._add_response_attributes (span , root_value )
59-
60- elif isinstance (root_value , types .JSONRPCError ):
61- span = self .tracer .start_span (f"error [{ root_value .id } ]" )
62- span .set_attribute ("type" , "error" )
63- span .set_attribute ("span_type" , "error" )
64- span .set_attribute ("id" , str (root_value .id ))
65- span .set_attribute ("error_code" , root_value .error .code )
66- span .set_attribute ("error_message" , root_value .error .message )
37+ # Check if this is a response to a tracked request
38+ parent_span = None
39+ if (
40+ isinstance (root_value , types .JSONRPCResponse )
41+ or isinstance (root_value , types .JSONRPCError )
42+ ) and hasattr (root_value , "id" ):
43+ request_id = str (root_value .id )
44+ parent_span = self ._active_request_spans .get (request_id )
45+
46+ # Create span with appropriate context
47+ if parent_span :
48+ # This is a response to a tracked request - create as child span
49+ span_context = trace .set_span_in_context (parent_span )
50+
51+ if isinstance (root_value , types .JSONRPCResponse ):
52+ span = self .tracer .start_span ("response" , context = span_context )
53+ span .set_attribute ("type" , "response" )
54+ span .set_attribute ("span_type" , "response" )
55+ span .set_attribute ("id" , str (root_value .id ))
56+ self ._add_response_attributes (span , root_value )
57+ else : # JSONRPCError
58+ span = self .tracer .start_span ("error" , context = span_context )
59+ span .set_attribute ("type" , "error" )
60+ span .set_attribute ("span_type" , "error" )
61+ span .set_attribute ("id" , str (root_value .id ))
62+ span .set_attribute ("error_code" , root_value .error .code )
63+ span .set_attribute ("error_message" , root_value .error .message )
64+
65+ # Remove the request from active tracking
66+ self ._active_request_spans .pop (request_id , None )
6767 else :
68- span = self .tracer .start_span ("unknown" )
69- span .set_attribute ("span_type" , str (type (root_value ).__name__ ))
70- span .set_attribute ("type" , str (type (root_value ).__name__ ))
68+ # Create standard span based on message type
69+ if isinstance (root_value , types .JSONRPCRequest ):
70+ span = self .tracer .start_span (f"{ root_value .method } " )
71+ span .set_attribute ("type" , "request" )
72+ span .set_attribute ("span_type" , "request" )
73+ span .set_attribute ("id" , str (root_value .id ))
74+ span .set_attribute ("method" , root_value .method )
75+ self ._add_request_attributes (span , root_value )
76+
77+ # Store for future response correlation
78+ self ._active_request_spans [str (root_value .id )] = span
79+
80+ elif isinstance (root_value , types .JSONRPCNotification ):
81+ span = self .tracer .start_span (root_value .method )
82+ span .set_attribute ("type" , "notification" )
83+ span .set_attribute ("span_type" , "notification" )
84+ span .set_attribute ("method" , root_value .method )
85+ self ._add_notification_attributes (span , root_value )
86+
87+ elif isinstance (root_value , types .JSONRPCResponse ):
88+ span = self .tracer .start_span ("response" )
89+ span .set_attribute ("type" , "response" )
90+ span .set_attribute ("span_type" , "response" )
91+ span .set_attribute ("id" , str (root_value .id ))
92+ self ._add_response_attributes (span , root_value )
93+
94+ elif isinstance (root_value , types .JSONRPCError ):
95+ span = self .tracer .start_span ("error" )
96+ span .set_attribute ("type" , "error" )
97+ span .set_attribute ("span_type" , "error" )
98+ span .set_attribute ("id" , str (root_value .id ))
99+ span .set_attribute ("error_code" , root_value .error .code )
100+ span .set_attribute ("error_message" , root_value .error .message )
101+ else :
102+ span = self .tracer .start_span ("unknown" )
103+ span .set_attribute ("span_type" , str (type (root_value ).__name__ ))
104+ span .set_attribute ("type" , str (type (root_value ).__name__ ))
71105
72106 # Add context attributes
73107 for key , value in context .items ():
@@ -88,7 +122,7 @@ def _add_request_attributes(
88122 if request .method == "tools/call" and isinstance (request .params , dict ):
89123 if "name" in request .params :
90124 span .set_attribute ("tool_name" , request .params ["name" ])
91- span .update_name (f"{ request .method } /{ request .params ['name' ]} [ { request . id } ] " )
125+ span .update_name (f"{ request .method } /{ request .params ['name' ]} " )
92126 if "arguments" in request .params and isinstance (
93127 request .params ["arguments" ], dict
94128 ):
0 commit comments