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
8 changes: 7 additions & 1 deletion sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,12 @@ class SPANDATA:
Example: 6379
"""

NETWORK_TRANSPORT = "network.transport"
"""
The transport protocol used for the network connection.
Example: "tcp", "udp", "unix"
"""

PROFILER_ID = "profiler_id"
"""
Label identifying the profiler id that the span occurred in. This should be a string.
Expand Down Expand Up @@ -824,7 +830,7 @@ class SPANDATA:
MCP_TRANSPORT = "mcp.transport"
"""
The transport method used for MCP communication.
Example: "pipe" (stdio), "tcp" (HTTP/WebSocket/SSE)
Example: "http", "sse", "stdio"
"""

MCP_SESSION_ID = "mcp.session.id"
Expand Down
44 changes: 29 additions & 15 deletions sentry_sdk/integrations/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,44 @@ def setup_once():
def _get_request_context_data():
# type: () -> tuple[Optional[str], Optional[str], str]
"""
Extract request ID, session ID, and transport type from the MCP request context.
Extract request ID, session ID, and MCP transport type from the request context.

Returns:
Tuple of (request_id, session_id, transport).
Tuple of (request_id, session_id, mcp_transport).
- request_id: May be None if not available
- session_id: May be None if not available
- transport: "tcp" for HTTP-based, "pipe" for stdio
- mcp_transport: "http", "sse", "stdio"
"""
request_id = None # type: Optional[str]
session_id = None # type: Optional[str]
transport = "pipe" # type: str
mcp_transport = "stdio" # type: str

try:
ctx = request_ctx.get()

if ctx is not None:
request_id = ctx.request_id
if hasattr(ctx, "request") and ctx.request is not None:
transport = "tcp"
request = ctx.request
if hasattr(request, "headers"):
# Detect transport type by checking request characteristics
if hasattr(request, "query_params") and request.query_params.get(
"session_id"
):
# SSE transport uses query parameter
mcp_transport = "sse"
session_id = request.query_params.get("session_id")
elif hasattr(request, "headers") and request.headers.get(
"mcp-session-id"
):
# StreamableHTTP transport uses header
mcp_transport = "http"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Misclassified Requests Default to Stdio Transport Mode

The _get_request_context_data function defaults mcp_transport to "stdio" even when a request object is present. This misclassifies network-based HTTP requests as "stdio" if specific session ID indicators are absent.

Fix in Cursor Fix in Web

session_id = request.headers.get("mcp-session-id")

except LookupError:
# No request context available - default to pipe
# No request context available - default to stdio
pass

return request_id, session_id, transport
return request_id, session_id, mcp_transport


def _get_span_config(handler_type, item_name):
Expand Down Expand Up @@ -120,16 +130,20 @@ def _set_span_input_data(
arguments,
request_id,
session_id,
transport,
mcp_transport,
):
# type: (Any, str, str, str, dict[str, Any], Optional[str], Optional[str], str) -> None
"""Set input span data for MCP handlers."""

# Set handler identifier
span.set_data(span_data_key, handler_name)
span.set_data(SPANDATA.MCP_METHOD_NAME, mcp_method_name)

# Set transport type
span.set_data(SPANDATA.MCP_TRANSPORT, transport)
# Set transport/MCP transport type
span.set_data(
SPANDATA.NETWORK_TRANSPORT, "pipe" if mcp_transport == "stdio" else "tcp"
)
span.set_data(SPANDATA.MCP_TRANSPORT, mcp_transport)

# Set request_id if provided
if request_id:
Expand Down Expand Up @@ -331,7 +345,7 @@ async def _async_handler_wrapper(handler_type, func, original_args):
origin=MCPIntegration.origin,
) as span:
# Get request ID, session ID, and transport from context
request_id, session_id, transport = _get_request_context_data()
request_id, session_id, mcp_transport = _get_request_context_data()

# Set input span data
_set_span_input_data(
Expand All @@ -342,7 +356,7 @@ async def _async_handler_wrapper(handler_type, func, original_args):
arguments,
request_id,
session_id,
transport,
mcp_transport,
)

# For resources, extract and set protocol
Expand Down Expand Up @@ -396,7 +410,7 @@ def _sync_handler_wrapper(handler_type, func, original_args):
origin=MCPIntegration.origin,
) as span:
# Get request ID, session ID, and transport from context
request_id, session_id, transport = _get_request_context_data()
request_id, session_id, mcp_transport = _get_request_context_data()

# Set input span data
_set_span_input_data(
Expand All @@ -407,7 +421,7 @@ def _sync_handler_wrapper(handler_type, func, original_args):
arguments,
request_id,
session_id,
transport,
mcp_transport,
)

# For resources, extract and set protocol
Expand Down
Loading