1212from typing import TYPE_CHECKING
1313
1414import sentry_sdk
15- from sentry_sdk .ai .utils import get_start_span_function
15+ from sentry_sdk .ai .utils import _set_span_data_attribute , get_start_span_function
1616from sentry_sdk .consts import OP , SPANDATA
1717from sentry_sdk .integrations import Integration , DidNotEnable
18+ from sentry_sdk .traces import StreamedSpan
19+ from sentry_sdk .tracing_utils import has_span_streaming_enabled
1820from sentry_sdk .utils import safe_serialize
1921from sentry_sdk .scope import should_send_default_pii
2022from sentry_sdk .integrations ._wsgi_common import nullcontext
3335
3436
3537if TYPE_CHECKING :
36- from typing import Any , Callable , Optional , Tuple , ContextManager
38+ from typing import Any , Callable , Optional , Tuple , Union , ContextManager
3739
40+ from sentry_sdk .tracing import Span
41+ from sentry_sdk .traces import StreamedSpan
3842 from starlette .types import Receive , Scope , Send # type: ignore[import-not-found]
3943
4044
@@ -156,7 +160,7 @@ def _get_span_config(
156160
157161
158162def _set_span_input_data (
159- span : "Any " ,
163+ span : "Union[StreamedSpan, Span] " ,
160164 handler_name : str ,
161165 span_data_key : str ,
162166 mcp_method_name : str ,
@@ -168,26 +172,28 @@ def _set_span_input_data(
168172 """Set input span data for MCP handlers."""
169173
170174 # Set handler identifier
171- span . set_data ( span_data_key , handler_name )
172- span . set_data ( SPANDATA .MCP_METHOD_NAME , mcp_method_name )
175+ _set_span_data_attribute ( span , span_data_key , handler_name )
176+ _set_span_data_attribute ( span , SPANDATA .MCP_METHOD_NAME , mcp_method_name )
173177
174178 # Set transport/MCP transport type
175- span .set_data (
176- SPANDATA .NETWORK_TRANSPORT , "pipe" if mcp_transport == "stdio" else "tcp"
179+ _set_span_data_attribute (
180+ span ,
181+ SPANDATA .NETWORK_TRANSPORT ,
182+ "pipe" if mcp_transport == "stdio" else "tcp" ,
177183 )
178- span . set_data ( SPANDATA .MCP_TRANSPORT , mcp_transport )
184+ _set_span_data_attribute ( span , SPANDATA .MCP_TRANSPORT , mcp_transport )
179185
180186 # Set request_id if provided
181187 if request_id :
182- span . set_data ( SPANDATA .MCP_REQUEST_ID , request_id )
188+ _set_span_data_attribute ( span , SPANDATA .MCP_REQUEST_ID , request_id )
183189
184190 # Set session_id if provided
185191 if session_id :
186- span . set_data ( SPANDATA .MCP_SESSION_ID , session_id )
192+ _set_span_data_attribute ( span , SPANDATA .MCP_SESSION_ID , session_id )
187193
188194 # Set request arguments (excluding common request context objects)
189195 for k , v in arguments .items ():
190- span . set_data ( f"mcp.request.argument.{ k } " , safe_serialize (v ))
196+ _set_span_data_attribute ( span , f"mcp.request.argument.{ k } " , safe_serialize (v ))
191197
192198
193199def _extract_tool_result_content (result : "Any" ) -> "Any" :
@@ -231,7 +237,10 @@ def _extract_tool_result_content(result: "Any") -> "Any":
231237
232238
233239def _set_span_output_data (
234- span : "Any" , result : "Any" , result_data_key : "Optional[str]" , handler_type : str
240+ span : "Union[StreamedSpan, Span]" ,
241+ result : "Any" ,
242+ result_data_key : "Optional[str]" ,
243+ handler_type : str ,
235244) -> None :
236245 """Set output span data for MCP handlers."""
237246 if result is None :
@@ -248,11 +257,17 @@ def _set_span_output_data(
248257 # For tools, extract the meaningful content
249258 if handler_type == "tool" :
250259 extracted = _extract_tool_result_content (result )
251- if extracted is not None and should_include_data :
252- span .set_data (result_data_key , safe_serialize (extracted ))
260+ if (
261+ extracted is not None
262+ and should_include_data
263+ and result_data_key is not None
264+ ):
265+ _set_span_data_attribute (span , result_data_key , safe_serialize (extracted ))
253266 # Set content count if result is a dict
254267 if isinstance (extracted , dict ):
255- span .set_data (SPANDATA .MCP_TOOL_RESULT_CONTENT_COUNT , len (extracted ))
268+ _set_span_data_attribute (
269+ span , SPANDATA .MCP_TOOL_RESULT_CONTENT_COUNT , len (extracted )
270+ )
256271 elif handler_type == "prompt" :
257272 # For prompts, count messages and set role/content only for single-message prompts
258273 try :
@@ -270,7 +285,9 @@ def _set_span_output_data(
270285
271286 # Always set message count if we found messages
272287 if message_count > 0 :
273- span .set_data (SPANDATA .MCP_PROMPT_RESULT_MESSAGE_COUNT , message_count )
288+ _set_span_data_attribute (
289+ span , SPANDATA .MCP_PROMPT_RESULT_MESSAGE_COUNT , message_count
290+ )
274291
275292 # Only set role and content for single-message prompts if PII is allowed
276293 if message_count == 1 and should_include_data and messages :
@@ -283,7 +300,9 @@ def _set_span_output_data(
283300 role = first_message ["role" ]
284301
285302 if role :
286- span .set_data (SPANDATA .MCP_PROMPT_RESULT_MESSAGE_ROLE , role )
303+ _set_span_data_attribute (
304+ span , SPANDATA .MCP_PROMPT_RESULT_MESSAGE_ROLE , role
305+ )
287306
288307 # Extract content text
289308 content_text = None
@@ -303,8 +322,8 @@ def _set_span_output_data(
303322 elif isinstance (msg_content , str ):
304323 content_text = msg_content
305324
306- if content_text :
307- span . set_data ( result_data_key , content_text )
325+ if content_text and result_data_key is not None :
326+ _set_span_data_attribute ( span , result_data_key , content_text )
308327 except Exception :
309328 # Silently ignore if we can't extract message info
310329 pass
@@ -434,14 +453,28 @@ async def _handler_wrapper(
434453 # Get request ID, session ID, and transport from context
435454 request_id , session_id , mcp_transport = _get_request_context_data ()
436455
456+ span_streaming = has_span_streaming_enabled (sentry_sdk .get_client ().options )
457+
437458 # Start span and execute
438459 with isolation_scope_context :
439460 with current_scope_context :
440- with get_start_span_function ()(
441- op = OP .MCP_SERVER ,
442- name = span_name ,
443- origin = MCPIntegration .origin ,
444- ) as span :
461+ span_mgr : "Union[Span, StreamedSpan]"
462+ if span_streaming :
463+ span_mgr = sentry_sdk .traces .start_span (
464+ name = span_name ,
465+ attributes = {
466+ "sentry.op" : OP .MCP_SERVER ,
467+ "sentry.origin" : MCPIntegration .origin ,
468+ },
469+ )
470+ else :
471+ span_mgr = get_start_span_function ()(
472+ op = OP .MCP_SERVER ,
473+ name = span_name ,
474+ origin = MCPIntegration .origin ,
475+ )
476+
477+ with span_mgr as span :
445478 # Set input span data
446479 _set_span_input_data (
447480 span ,
@@ -467,7 +500,9 @@ async def _handler_wrapper(
467500 elif handler_name and "://" in handler_name :
468501 protocol = handler_name .split ("://" )[0 ]
469502 if protocol :
470- span .set_data (SPANDATA .MCP_RESOURCE_PROTOCOL , protocol )
503+ _set_span_data_attribute (
504+ span , SPANDATA .MCP_RESOURCE_PROTOCOL , protocol
505+ )
471506
472507 try :
473508 # Execute the async handler
@@ -481,7 +516,9 @@ async def _handler_wrapper(
481516 except Exception as e :
482517 # Set error flag for tools
483518 if handler_type == "tool" :
484- span .set_data (SPANDATA .MCP_TOOL_RESULT_IS_ERROR , True )
519+ _set_span_data_attribute (
520+ span , SPANDATA .MCP_TOOL_RESULT_IS_ERROR , True
521+ )
485522 sentry_sdk .capture_exception (e )
486523 raise
487524
0 commit comments