Skip to content

Commit 678ec71

Browse files
Merge branch 'webb/mcp/seperate-handlers' into webb/mcp/use-middleware
2 parents ebf64d3 + 93ea189 commit 678ec71

2 files changed

Lines changed: 36 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,
@@ -501,35 +522,13 @@ async def _tool_handler_wrapper(
501522
"tool", handler_name
502523
)
503524

504-
scopes = _get_active_http_scopes(ctx=ctx)
505-
506-
isolation_scope_context: "ContextManager[Any]"
507-
current_scope_context: "ContextManager[Any]"
508-
509-
if scopes is None:
510-
isolation_scope_context = nullcontext()
511-
current_scope_context = nullcontext()
512-
else:
513-
isolation_scope, current_scope = scopes
514-
515-
isolation_scope_context = (
516-
nullcontext()
517-
if isolation_scope is None
518-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
519-
)
520-
current_scope_context = (
521-
nullcontext()
522-
if current_scope is None
523-
else sentry_sdk.scope.use_scope(current_scope)
524-
)
525-
526525
# Get request ID, session ID, and transport from context
527526
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
528527

529528
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
530529

531530
# Start span and execute
532-
with isolation_scope_context, current_scope_context:
531+
with _with_active_http_scopes(ctx=ctx):
533532
span_mgr: "Union[Span, StreamedSpan]"
534533
if span_streaming:
535534
span_mgr = sentry_sdk.traces.start_span(
@@ -623,35 +622,13 @@ async def _prompt_handler_wrapper(
623622
"prompt", handler_name
624623
)
625624

626-
scopes = _get_active_http_scopes(ctx=ctx)
627-
628-
isolation_scope_context: "ContextManager[Any]"
629-
current_scope_context: "ContextManager[Any]"
630-
631-
if scopes is None:
632-
isolation_scope_context = nullcontext()
633-
current_scope_context = nullcontext()
634-
else:
635-
isolation_scope, current_scope = scopes
636-
637-
isolation_scope_context = (
638-
nullcontext()
639-
if isolation_scope is None
640-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
641-
)
642-
current_scope_context = (
643-
nullcontext()
644-
if current_scope is None
645-
else sentry_sdk.scope.use_scope(current_scope)
646-
)
647-
648625
# Get request ID, session ID, and transport from context
649626
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
650627

651628
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
652629

653630
# Start span and execute
654-
with isolation_scope_context, current_scope_context:
631+
with _with_active_http_scopes(ctx=ctx):
655632
span_mgr: "Union[Span, StreamedSpan]"
656633
if span_streaming:
657634
span_mgr = sentry_sdk.traces.start_span(
@@ -745,35 +722,13 @@ async def _resource_handler_wrapper(
745722
"resource", handler_name
746723
)
747724

748-
scopes = _get_active_http_scopes(ctx=ctx)
749-
750-
isolation_scope_context: "ContextManager[Any]"
751-
current_scope_context: "ContextManager[Any]"
752-
753-
if scopes is None:
754-
isolation_scope_context = nullcontext()
755-
current_scope_context = nullcontext()
756-
else:
757-
isolation_scope, current_scope = scopes
758-
759-
isolation_scope_context = (
760-
nullcontext()
761-
if isolation_scope is None
762-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
763-
)
764-
current_scope_context = (
765-
nullcontext()
766-
if current_scope is None
767-
else sentry_sdk.scope.use_scope(current_scope)
768-
)
769-
770725
# Get request ID, session ID, and transport from context
771726
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
772727

773728
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
774729

775730
# Start span and execute
776-
with isolation_scope_context, current_scope_context:
731+
with _with_active_http_scopes(ctx=ctx):
777732
span_mgr: "Union[Span, StreamedSpan]"
778733
if span_streaming:
779734
span_mgr = sentry_sdk.traces.start_span(

uv.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)