Skip to content

Commit 41cca06

Browse files
ref(mcp): Inline _set_span_output_data() (#6762)
Remove unnecessary indirection by inlining `_set_span_output_data()`.
1 parent d55cfd3 commit 41cca06

1 file changed

Lines changed: 96 additions & 100 deletions

File tree

sentry_sdk/integrations/mcp.py

Lines changed: 96 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -267,100 +267,6 @@ def _extract_text_from_content_blocks(content_blocks: "Any") -> "Any":
267267
return " ".join(texts) if texts else content_blocks
268268

269269

270-
def _set_span_output_data(
271-
span: "Union[StreamedSpan, Span]",
272-
result: "Any",
273-
result_data_key: "Optional[str]",
274-
handler_type: str,
275-
) -> None:
276-
"""Set output span data for MCP handlers."""
277-
if result is None:
278-
return
279-
280-
# Get integration to check PII settings
281-
integration = sentry_sdk.get_client().get_integration(MCPIntegration)
282-
if integration is None:
283-
return
284-
285-
# Check if we should include sensitive data
286-
should_include_data = should_send_default_pii() and integration.include_prompts
287-
288-
# For tools, extract the meaningful content
289-
if handler_type == "tool":
290-
extracted = _extract_tool_result_content(result)
291-
if (
292-
extracted is not None
293-
and should_include_data
294-
and result_data_key is not None
295-
):
296-
_set_span_data_attribute(span, result_data_key, safe_serialize(extracted))
297-
# Set content count if result is a dict
298-
if isinstance(extracted, dict):
299-
_set_span_data_attribute(
300-
span, SPANDATA.MCP_TOOL_RESULT_CONTENT_COUNT, len(extracted)
301-
)
302-
elif handler_type == "prompt":
303-
# For prompts, count messages and set role/content only for single-message prompts
304-
try:
305-
messages: "Optional[list[str]]" = None
306-
message_count = 0
307-
308-
# Check if result has messages attribute (GetPromptResult)
309-
if hasattr(result, "messages") and result.messages:
310-
messages = result.messages
311-
message_count = len(messages)
312-
# Also check if result is a dict with messages
313-
elif isinstance(result, dict) and result.get("messages"):
314-
messages = result["messages"]
315-
message_count = len(messages)
316-
317-
# Always set message count if we found messages
318-
if message_count > 0:
319-
_set_span_data_attribute(
320-
span, SPANDATA.MCP_PROMPT_RESULT_MESSAGE_COUNT, message_count
321-
)
322-
323-
# Only set role and content for single-message prompts if PII is allowed
324-
if message_count == 1 and should_include_data and messages:
325-
first_message = messages[0]
326-
# Extract role
327-
role = None
328-
if hasattr(first_message, "role"):
329-
role = first_message.role
330-
elif isinstance(first_message, dict) and "role" in first_message:
331-
role = first_message["role"]
332-
333-
if role:
334-
_set_span_data_attribute(
335-
span, SPANDATA.MCP_PROMPT_RESULT_MESSAGE_ROLE, role
336-
)
337-
338-
# Extract content text
339-
content_text = None
340-
if hasattr(first_message, "content"):
341-
msg_content = first_message.content
342-
# Content can be a TextContent object or similar
343-
if hasattr(msg_content, "text"):
344-
content_text = msg_content.text
345-
elif isinstance(msg_content, dict) and "text" in msg_content:
346-
content_text = msg_content["text"]
347-
elif isinstance(msg_content, str):
348-
content_text = msg_content
349-
elif isinstance(first_message, dict) and "content" in first_message:
350-
msg_content = first_message["content"]
351-
if isinstance(msg_content, dict) and "text" in msg_content:
352-
content_text = msg_content["text"]
353-
elif isinstance(msg_content, str):
354-
content_text = msg_content
355-
356-
if content_text and result_data_key is not None:
357-
_set_span_data_attribute(span, result_data_key, content_text)
358-
except Exception:
359-
# Silently ignore if we can't extract message info
360-
pass
361-
# Resources don't capture result content (result_data_key is None)
362-
363-
364270
# Handler data preparation and wrapping
365271

366272

@@ -532,10 +438,30 @@ async def _tool_handler_wrapper(
532438
sentry_sdk.capture_exception(e)
533439
raise
534440

535-
_set_span_output_data(
536-
span, result, SPANDATA.MCP_TOOL_RESULT_CONTENT, "tool"
441+
if result is None:
442+
return result
443+
444+
# Get integration to check PII settings
445+
integration = sentry_sdk.get_client().get_integration(MCPIntegration)
446+
if integration is None:
447+
return result
448+
449+
# Check if we should include sensitive data
450+
should_include_data = (
451+
should_send_default_pii() and integration.include_prompts
537452
)
538453

454+
extracted = _extract_tool_result_content(result)
455+
if extracted is not None and should_include_data:
456+
_set_span_data_attribute(
457+
span, SPANDATA.MCP_TOOL_RESULT_CONTENT, safe_serialize(extracted)
458+
)
459+
# Set content count if result is a dict
460+
if isinstance(extracted, dict):
461+
_set_span_data_attribute(
462+
span, SPANDATA.MCP_TOOL_RESULT_CONTENT_COUNT, len(extracted)
463+
)
464+
539465
return result
540466

541467

@@ -628,10 +554,82 @@ async def _prompt_handler_wrapper(
628554
sentry_sdk.capture_exception(e)
629555
raise
630556

631-
_set_span_output_data(
632-
span, result, SPANDATA.MCP_PROMPT_RESULT_MESSAGE_CONTENT, "prompt"
557+
if result is None:
558+
return result
559+
560+
# Get integration to check PII settings
561+
integration = sentry_sdk.get_client().get_integration(MCPIntegration)
562+
if integration is None:
563+
return result
564+
565+
# Check if we should include sensitive data
566+
should_include_data = (
567+
should_send_default_pii() and integration.include_prompts
633568
)
634569

570+
# For prompts, count messages and set role/content only for single-message prompts
571+
try:
572+
messages: "Optional[list[str]]" = None
573+
message_count = 0
574+
575+
# Check if result has messages attribute (GetPromptResult)
576+
if hasattr(result, "messages") and result.messages:
577+
messages = result.messages # type: ignore[assignment]
578+
message_count = len(messages) # type: ignore[arg-type]
579+
# Also check if result is a dict with messages
580+
elif isinstance(result, dict) and result.get("messages"):
581+
messages = result["messages"]
582+
message_count = len(messages)
583+
584+
# Always set message count if we found messages
585+
if message_count > 0:
586+
_set_span_data_attribute(
587+
span, SPANDATA.MCP_PROMPT_RESULT_MESSAGE_COUNT, message_count
588+
)
589+
590+
# Only set role and content for single-message prompts if PII is allowed
591+
if message_count == 1 and should_include_data and messages:
592+
first_message = messages[0]
593+
# Extract role
594+
role = None
595+
if hasattr(first_message, "role"):
596+
role = first_message.role
597+
elif isinstance(first_message, dict) and "role" in first_message:
598+
role = first_message["role"]
599+
600+
if role:
601+
_set_span_data_attribute(
602+
span, SPANDATA.MCP_PROMPT_RESULT_MESSAGE_ROLE, role
603+
)
604+
605+
# Extract content text
606+
content_text = None
607+
if hasattr(first_message, "content"):
608+
msg_content = first_message.content
609+
# Content can be a TextContent object or similar
610+
if hasattr(msg_content, "text"):
611+
content_text = msg_content.text
612+
elif isinstance(msg_content, dict) and "text" in msg_content:
613+
content_text = msg_content["text"]
614+
elif isinstance(msg_content, str):
615+
content_text = msg_content
616+
elif isinstance(first_message, dict) and "content" in first_message:
617+
msg_content = first_message["content"]
618+
if isinstance(msg_content, dict) and "text" in msg_content:
619+
content_text = msg_content["text"]
620+
elif isinstance(msg_content, str):
621+
content_text = msg_content
622+
623+
if content_text:
624+
_set_span_data_attribute(
625+
span,
626+
SPANDATA.MCP_PROMPT_RESULT_MESSAGE_CONTENT,
627+
content_text,
628+
)
629+
except Exception:
630+
# Silently ignore if we can't extract message info
631+
pass
632+
635633
return result
636634

637635

@@ -745,8 +743,6 @@ async def _resource_handler_wrapper(
745743
sentry_sdk.capture_exception(e)
746744
raise
747745

748-
_set_span_output_data(span, result, None, "resource")
749-
750746
return result
751747

752748

0 commit comments

Comments
 (0)