Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 43 additions & 65 deletions sentry_sdk/integrations/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@
import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
has_data_collection_enabled,
package_version,
safe_serialize,
)
Expand Down Expand Up @@ -245,15 +243,9 @@ async def _instrument_tool_call(
client = sentry_sdk.get_client()
handler_name = ctx.params["name"]
arguments = ctx.params.get("arguments")
if arguments is None:
if arguments is None or not client.options["data_collection"]["gen_ai"]["inputs"]:
arguments = {}

if has_data_collection_enabled(client.options):
if not client.options["data_collection"]["gen_ai"]["inputs"]:
# Arguments can contain sensitive data and shouldn't be added to the span
# if the user has opted out via this config.
arguments = {}

# Get request ID, session ID, and transport from context
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)

Expand Down Expand Up @@ -287,36 +279,32 @@ async def _instrument_tool_call(
if not isinstance(result, dict):
return result

# Get integration to check PII settings
integration = client.get_integration(MCPIntegration)
if integration is None:
return result

# Check if we should include sensitive data
should_include_result_data = False
if has_data_collection_enabled(client.options):
if client.options["data_collection"]["gen_ai"]["outputs"]:
should_include_result_data = True
elif should_send_default_pii():
should_include_result_data = True

result_content = result
if "structuredContent" in result:
result_content = result["structuredContent"]
elif isinstance(result.get("content"), list):
result_content = _extract_text_from_content_blocks(result["content"])

if result_content is not None and should_include_result_data:
if (
result_content is None
or not client.options["data_collection"]["gen_ai"]["outputs"]
):
return result

span.set_attribute(
SPANDATA.MCP_TOOL_RESULT_CONTENT,
safe_serialize(result_content),
)
# Set content count if result is a dict
if isinstance(result_content, dict):
span.set_attribute(
SPANDATA.MCP_TOOL_RESULT_CONTENT,
safe_serialize(result_content),
SPANDATA.MCP_TOOL_RESULT_CONTENT_COUNT,
len(result_content),
)
# Set content count if result is a dict
if isinstance(result_content, dict):
span.set_attribute(
SPANDATA.MCP_TOOL_RESULT_CONTENT_COUNT,
len(result_content),
)

return result

Expand All @@ -337,15 +325,9 @@ async def _instrument_prompt_get(

arguments = ctx.params.get("arguments")

if arguments is None:
if arguments is None or not client.options["data_collection"]["gen_ai"]["inputs"]:
arguments = {}

if has_data_collection_enabled(client.options):
if not client.options["data_collection"]["gen_ai"]["inputs"]:
# Arguments can contain sensitive data and shouldn't be added to the span
# if the user has opted out via this config.
arguments = {}

# Get request ID, session ID, and transport from context
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)

Expand Down Expand Up @@ -379,19 +361,10 @@ async def _instrument_prompt_get(
if not isinstance(result, dict):
return result

# Get integration to check PII settings
integration = client.get_integration(MCPIntegration)
if integration is None:
return result

# Check if we should include sensitive data
should_include_result_data = False
if has_data_collection_enabled(client.options):
if client.options["data_collection"]["gen_ai"]["inputs"]:
should_include_result_data = True
elif should_send_default_pii():
should_include_result_data = True

# For prompts, count messages and set role/content only for single-message prompts
try:
messages: "Optional[list[dict[str, Any]]]" = None
Expand All @@ -407,28 +380,33 @@ async def _instrument_prompt_get(
SPANDATA.MCP_PROMPT_RESULT_MESSAGE_COUNT, message_count
)

# Only set role and content for single-message prompts if PII is allowed
if message_count == 1 and should_include_result_data and messages:
first_message = messages[0]
# Extract role
role = None
if "role" in first_message:
role = first_message["role"]

if role:
span.set_attribute(SPANDATA.MCP_PROMPT_RESULT_MESSAGE_ROLE, role)

content_text = None
if "content" in first_message:
msg_content = first_message["content"]
if "text" in msg_content:
content_text = msg_content["text"]

if content_text:
span.set_attribute(
SPANDATA.MCP_PROMPT_RESULT_MESSAGE_CONTENT,
content_text,
)
if (
message_count != 1
or not client.options["data_collection"]["gen_ai"]["inputs"]
or not messages
):
return result

first_message = messages[0]
# Extract role
role = None
if "role" in first_message:
role = first_message["role"]

if role:
span.set_attribute(SPANDATA.MCP_PROMPT_RESULT_MESSAGE_ROLE, role)

content_text = None
if "content" in first_message:
msg_content = first_message["content"]
if "text" in msg_content:
content_text = msg_content["text"]

if content_text:
span.set_attribute(
SPANDATA.MCP_PROMPT_RESULT_MESSAGE_CONTENT,
content_text,
)
except Exception:
# Silently ignore if we can't extract message info
pass
Expand Down
Loading
Loading