Skip to content

Commit b38750f

Browse files
merge
2 parents 6ddc211 + 678ec71 commit b38750f

1 file changed

Lines changed: 33 additions & 78 deletions

File tree

sentry_sdk/integrations/mcp.py

Lines changed: 33 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"""
99

1010
import inspect
11+
from collections.abc import Iterator
12+
from contextlib import contextmanager
1113
from functools import wraps
1214
from typing import TYPE_CHECKING
1315

@@ -53,7 +55,7 @@
5355

5456

5557
if TYPE_CHECKING:
56-
from typing import Any, Awaitable, Callable, ContextManager, Optional, Tuple, Union
58+
from typing import Any, Awaitable, Callable, Optional, Union
5759

5860
from mcp_types import (
5961
CallToolResult,
@@ -93,29 +95,48 @@ def setup_once() -> None:
9395
_patch_fastmcp()
9496

9597

96-
def _get_active_http_scopes(
97-
ctx: "Optional[Any]" = None,
98-
) -> "Optional[Tuple[Optional[sentry_sdk.Scope], Optional[sentry_sdk.Scope]]]":
98+
@contextmanager
99+
def _with_active_http_scopes(
100+
ctx: "Any" = None,
101+
) -> "Iterator[None]":
102+
"""
103+
Use isolation and current scopes that were stored before the in-memory MCP request queue.
104+
This ensures that MCP spans are nested under the HTTP server span when using the Streamable HTTP transport.
105+
"""
99106
if MCP_PACKAGE_VERSION and MCP_PACKAGE_VERSION < (2, 0, 0):
100107
if ctx is None:
101108
try:
102109
ctx = request_ctx.get()
103110
except LookupError:
104-
return None
111+
yield None
112+
return
105113

106114
if (
107115
ctx is None
108116
or not hasattr(ctx, "request")
109117
or ctx.request is None
110118
or "state" not in ctx.request.scope
111119
):
112-
return None
120+
yield
121+
return
113122

114-
return (
115-
ctx.request.scope["state"].get("sentry_sdk.isolation_scope"),
116-
ctx.request.scope["state"].get("sentry_sdk.current_scope"),
123+
isolation_scope = ctx.request.scope["state"].get("sentry_sdk.isolation_scope")
124+
current_scope = ctx.request.scope["state"].get("sentry_sdk.current_scope")
125+
126+
isolation_scope_context = (
127+
nullcontext()
128+
if isolation_scope is None
129+
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
130+
)
131+
current_scope_context = (
132+
nullcontext()
133+
if current_scope is None
134+
else sentry_sdk.scope.use_scope(current_scope)
117135
)
118136

137+
with isolation_scope_context, current_scope_context:
138+
yield
139+
119140

120141
def _get_request_context_data(
121142
ctx: "Optional[Any]" = None,
@@ -470,35 +491,13 @@ async def _tool_handler_wrapper(
470491
"tool", original_args, original_kwargs
471492
)
472493

473-
scopes = _get_active_http_scopes(ctx=ctx)
474-
475-
isolation_scope_context: "ContextManager[Any]"
476-
current_scope_context: "ContextManager[Any]"
477-
478-
if scopes is None:
479-
isolation_scope_context = nullcontext()
480-
current_scope_context = nullcontext()
481-
else:
482-
isolation_scope, current_scope = scopes
483-
484-
isolation_scope_context = (
485-
nullcontext()
486-
if isolation_scope is None
487-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
488-
)
489-
current_scope_context = (
490-
nullcontext()
491-
if current_scope is None
492-
else sentry_sdk.scope.use_scope(current_scope)
493-
)
494-
495494
# Get request ID, session ID, and transport from context
496495
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
497496

498497
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
499498

500499
# Start span and execute
501-
with isolation_scope_context, current_scope_context:
500+
with _with_active_http_scopes(ctx=ctx):
502501
span_mgr: "Union[Span, StreamedSpan]"
503502
if span_streaming:
504503
span_mgr = sentry_sdk.traces.start_span(
@@ -590,35 +589,13 @@ async def _prompt_handler_wrapper(
590589
"prompt", original_args, original_kwargs
591590
)
592591

593-
scopes = _get_active_http_scopes(ctx=ctx)
594-
595-
isolation_scope_context: "ContextManager[Any]"
596-
current_scope_context: "ContextManager[Any]"
597-
598-
if scopes is None:
599-
isolation_scope_context = nullcontext()
600-
current_scope_context = nullcontext()
601-
else:
602-
isolation_scope, current_scope = scopes
603-
604-
isolation_scope_context = (
605-
nullcontext()
606-
if isolation_scope is None
607-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
608-
)
609-
current_scope_context = (
610-
nullcontext()
611-
if current_scope is None
612-
else sentry_sdk.scope.use_scope(current_scope)
613-
)
614-
615592
# Get request ID, session ID, and transport from context
616593
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
617594

618595
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
619596

620597
# Start span and execute
621-
with isolation_scope_context, current_scope_context:
598+
with _with_active_http_scopes(ctx=ctx):
622599
span_mgr: "Union[Span, StreamedSpan]"
623600
if span_streaming:
624601
span_mgr = sentry_sdk.traces.start_span(
@@ -710,35 +687,13 @@ async def _resource_handler_wrapper(
710687
"resource", original_args, original_kwargs
711688
)
712689

713-
scopes = _get_active_http_scopes(ctx=ctx)
714-
715-
isolation_scope_context: "ContextManager[Any]"
716-
current_scope_context: "ContextManager[Any]"
717-
718-
if scopes is None:
719-
isolation_scope_context = nullcontext()
720-
current_scope_context = nullcontext()
721-
else:
722-
isolation_scope, current_scope = scopes
723-
724-
isolation_scope_context = (
725-
nullcontext()
726-
if isolation_scope is None
727-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
728-
)
729-
current_scope_context = (
730-
nullcontext()
731-
if current_scope is None
732-
else sentry_sdk.scope.use_scope(current_scope)
733-
)
734-
735690
# Get request ID, session ID, and transport from context
736691
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
737692

738693
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
739694

740695
# Start span and execute
741-
with isolation_scope_context, current_scope_context:
696+
with _with_active_http_scopes(ctx=ctx):
742697
span_mgr: "Union[Span, StreamedSpan]"
743698
if span_streaming:
744699
span_mgr = sentry_sdk.traces.start_span(

0 commit comments

Comments
 (0)