Skip to content

Commit 4668762

Browse files
Merge branch 'webb/mcp/use-middleware-4' into webb/mcp/prompt-arguments
2 parents 49b8f07 + b3cacc8 commit 4668762

1 file changed

Lines changed: 34 additions & 79 deletions

File tree

sentry_sdk/integrations/mcp.py

Lines changed: 34 additions & 79 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,
@@ -375,35 +396,13 @@ async def _tool_handler_wrapper(
375396
"tool", original_args, original_kwargs
376397
)
377398

378-
scopes = _get_active_http_scopes(ctx=ctx)
379-
380-
isolation_scope_context: "ContextManager[Any]"
381-
current_scope_context: "ContextManager[Any]"
382-
383-
if scopes is None:
384-
isolation_scope_context = nullcontext()
385-
current_scope_context = nullcontext()
386-
else:
387-
isolation_scope, current_scope = scopes
388-
389-
isolation_scope_context = (
390-
nullcontext()
391-
if isolation_scope is None
392-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
393-
)
394-
current_scope_context = (
395-
nullcontext()
396-
if current_scope is None
397-
else sentry_sdk.scope.use_scope(current_scope)
398-
)
399-
400399
# Get request ID, session ID, and transport from context
401400
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
402401

403402
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
404403

405404
# Start span and execute
406-
with isolation_scope_context, current_scope_context:
405+
with _with_active_http_scopes(ctx=ctx):
407406
span_mgr: "Union[Span, StreamedSpan]"
408407
if span_streaming:
409408
span_mgr = sentry_sdk.traces.start_span(
@@ -515,35 +514,13 @@ async def _prompt_handler_wrapper(
515514
"prompt", original_args, original_kwargs
516515
)
517516

518-
scopes = _get_active_http_scopes(ctx=ctx)
519-
520-
isolation_scope_context: "ContextManager[Any]"
521-
current_scope_context: "ContextManager[Any]"
522-
523-
if scopes is None:
524-
isolation_scope_context = nullcontext()
525-
current_scope_context = nullcontext()
526-
else:
527-
isolation_scope, current_scope = scopes
528-
529-
isolation_scope_context = (
530-
nullcontext()
531-
if isolation_scope is None
532-
else sentry_sdk.scope.use_isolation_scope(isolation_scope)
533-
)
534-
current_scope_context = (
535-
nullcontext()
536-
if current_scope is None
537-
else sentry_sdk.scope.use_scope(current_scope)
538-
)
539-
540517
# Get request ID, session ID, and transport from context
541518
request_id, session_id, mcp_transport = _get_request_context_data(ctx=ctx)
542519

543520
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
544521

545522
# Start span and execute
546-
with isolation_scope_context, current_scope_context:
523+
with _with_active_http_scopes(ctx=ctx):
547524
span_mgr: "Union[Span, StreamedSpan]"
548525
if span_streaming:
549526
span_mgr = sentry_sdk.traces.start_span(
@@ -707,35 +684,13 @@ async def _resource_handler_wrapper(
707684
"resource", original_args, original_kwargs
708685
)
709686

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

735690
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
736691

737692
# Start span and execute
738-
with isolation_scope_context, current_scope_context:
693+
with _with_active_http_scopes(ctx=ctx):
739694
span_mgr: "Union[Span, StreamedSpan]"
740695
if span_streaming:
741696
span_mgr = sentry_sdk.traces.start_span(
@@ -826,7 +781,7 @@ async def wrapper(*args: "Any") -> "Any":
826781
return await _tool_handler_wrapper(func, args, force_await=False)
827782

828783
# Then register it with the original MCP decorator
829-
return original_call_tool(self)(wrapper)
784+
return original_call_tool(self, **kwargs)(wrapper)
830785

831786
return instrumented_decorator
832787

0 commit comments

Comments
 (0)