5555if TYPE_CHECKING :
5656 from typing import Any , Awaitable , Callable , ContextManager , Optional , Tuple , Union
5757
58- from mcp .server .context import CallNext , HandlerResult
5958 from mcp_types import (
6059 CallToolResult ,
6160 GetPromptResult ,
@@ -458,50 +457,6 @@ def _extract_handler_data_from_args(
458457 return handler_name , arguments
459458
460459
461- def _prepare_handler_data (
462- handler_type : str ,
463- original_args : "tuple[Any, ...]" ,
464- original_kwargs : "Optional[dict[str, Any]]" = None ,
465- params : "Optional[Any]" = None ,
466- ) -> "tuple[str, dict[str, Any], str, str, str, Optional[str]]" :
467- """
468- Prepare common handler data for both v1 and v2 MCP SDK.
469-
470- Args:
471- handler_type: "tool", "prompt", or "resource"
472- original_args: Original positional args (v1 path)
473- original_kwargs: Original keyword args (v1 path)
474- params: Typed params object from v2 ServerRequestContext path
475-
476- Returns:
477- Tuple of (handler_name, arguments, span_data_key, span_name, mcp_method_name, result_data_key)
478- """
479- if params is not None :
480- handler_name , arguments = _extract_handler_data_from_params (
481- handler_type , params
482- )
483- elif _is_v2_context (original_args ):
484- handler_name = "unknown"
485- arguments = {}
486- else :
487- handler_name , arguments = _extract_handler_data_from_args (
488- handler_type , original_args , original_kwargs
489- )
490-
491- span_data_key , span_name , mcp_method_name , result_data_key = _get_span_config (
492- handler_type , handler_name
493- )
494-
495- return (
496- handler_name ,
497- arguments ,
498- span_data_key ,
499- span_name ,
500- mcp_method_name ,
501- result_data_key ,
502- )
503-
504-
505460async def _tool_handler_wrapper (
506461 func : "Callable[..., Awaitable[Union[CallToolResult, InputRequiredResult]]]" ,
507462 original_args : "tuple[Any, ...]" ,
@@ -531,16 +486,20 @@ async def _tool_handler_wrapper(
531486 and isinstance (original_args [0 ], ServerRequestContext )
532487 ):
533488 ctx = original_args [0 ]
534- params = original_args [1 ] if len (original_args ) > 1 else None
489+ if len (original_args ) > 1 :
490+ params = original_args [1 ]
491+ handler_name , arguments = _extract_handler_data_from_params ("tool" , params )
492+ else :
493+ handler_name = "unknown"
494+ arguments = {}
495+ else :
496+ handler_name , arguments = _extract_handler_data_from_args (
497+ "tool" , original_args , original_kwargs
498+ )
535499
536- (
537- handler_name ,
538- arguments ,
539- span_data_key ,
540- span_name ,
541- mcp_method_name ,
542- result_data_key ,
543- ) = _prepare_handler_data ("tool" , original_args , original_kwargs , params = params )
500+ span_data_key , span_name , mcp_method_name , result_data_key = _get_span_config (
501+ "tool" , handler_name
502+ )
544503
545504 scopes = _get_active_http_scopes (ctx = ctx )
546505
@@ -642,23 +601,27 @@ async def _prompt_handler_wrapper(
642601
643602 # Detect v1 vs v2: MCP SDK v2 passes (ServerRequestContext, params) to handlers
644603 ctx : "Optional[Any]" = None
645- params : "Optional[Any]" = None
646604 if (
647605 ServerRequestContext is not None
648606 and original_args
649607 and isinstance (original_args [0 ], ServerRequestContext )
650608 ):
651609 ctx = original_args [0 ]
652- params = original_args [1 ] if len (original_args ) > 1 else None
610+ if len (original_args ) > 1 :
611+ handler_name , arguments = _extract_handler_data_from_params (
612+ "prompt" , original_args [1 ]
613+ )
614+ else :
615+ handler_name = "unknown"
616+ arguments = {}
617+ else :
618+ handler_name , arguments = _extract_handler_data_from_args (
619+ "prompt" , original_args , original_kwargs
620+ )
653621
654- (
655- handler_name ,
656- arguments ,
657- span_data_key ,
658- span_name ,
659- mcp_method_name ,
660- result_data_key ,
661- ) = _prepare_handler_data ("prompt" , original_args , original_kwargs , params = params )
622+ span_data_key , span_name , mcp_method_name , result_data_key = _get_span_config (
623+ "prompt" , handler_name
624+ )
662625
663626 scopes = _get_active_http_scopes (ctx = ctx )
664627
@@ -765,16 +728,22 @@ async def _resource_handler_wrapper(
765728 and isinstance (original_args [0 ], ServerRequestContext )
766729 ):
767730 ctx = original_args [0 ]
768- params = original_args [1 ] if len (original_args ) > 1 else None
731+ if len (original_args ) > 1 :
732+ params = original_args [1 ]
733+ handler_name , arguments = _extract_handler_data_from_params (
734+ "resource" , params
735+ )
736+ else :
737+ handler_name = "unknown"
738+ arguments = {}
739+ else :
740+ handler_name , arguments = _extract_handler_data_from_args (
741+ "resource" , original_args , original_kwargs
742+ )
769743
770- (
771- handler_name ,
772- arguments ,
773- span_data_key ,
774- span_name ,
775- mcp_method_name ,
776- result_data_key ,
777- ) = _prepare_handler_data ("resource" , original_args , original_kwargs , params = params )
744+ span_data_key , span_name , mcp_method_name , result_data_key = _get_span_config (
745+ "resource" , handler_name
746+ )
778747
779748 scopes = _get_active_http_scopes (ctx = ctx )
780749
@@ -944,21 +913,6 @@ async def wrapper(*args: "Any") -> "Any":
944913 Server .read_resource = patched_read_resource # type: ignore[attr-defined]
945914
946915
947- async def _sentry_middleware (
948- ctx : "ServerRequestContext[Any, Any]" , call_next : "CallNext"
949- ) -> "HandlerResult" :
950- if ctx .method == "tools/call" :
951- return await _tool_handler_wrapper (call_next , ctx )
952-
953- if ctx .method == "prompts/get" :
954- return await _prompt_handler_wrapper (call_next , ctx )
955-
956- if ctx .method == "resources/read" :
957- return await _resource_handler_wrapper (call_next , ctx )
958-
959- return await call_next (ctx )
960-
961-
962916def _patch_lowlevel_server_v2 () -> None :
963917 """Patches the v2 Server to wrap tool/prompt/resource handlers.
964918
@@ -971,11 +925,113 @@ def _patch_lowlevel_server_v2() -> None:
971925
972926 @wraps (original_init )
973927 def patched_init (self : "Server" , * args : "Any" , ** kwargs : "Any" ) -> None :
928+ on_tool_call = kwargs .get ("on_call_tool" )
929+ if on_tool_call is not None and not getattr (
930+ on_tool_call , "__sentry_mcp_wrapped__" , False
931+ ):
932+
933+ @wraps (on_tool_call )
934+ async def wrapper (* args : "Any" , ** kwargs : "Any" ) -> "Any" :
935+ return await _tool_handler_wrapper (
936+ on_tool_call , args , kwargs , force_await = False
937+ )
938+
939+ wrapper .__sentry_mcp_wrapped__ = True # type: ignore[attr-defined]
940+ kwargs ["on_call_tool" ] = wrapper
941+
942+ on_get_prompt = kwargs .get ("on_get_prompt" )
943+ if on_get_prompt is not None and not getattr (
944+ on_get_prompt , "__sentry_mcp_wrapped__" , False
945+ ):
946+
947+ @wraps (on_get_prompt )
948+ async def wrapper (* args : "Any" , ** kwargs : "Any" ) -> "Any" :
949+ return await _prompt_handler_wrapper (
950+ on_get_prompt , args , kwargs , force_await = False
951+ )
952+
953+ wrapper .__sentry_mcp_wrapped__ = True # type: ignore[attr-defined]
954+ kwargs ["on_get_prompt" ] = wrapper
955+
956+ on_read_resource = kwargs .get ("on_read_resource" )
957+ if on_read_resource is not None and not getattr (
958+ on_read_resource , "__sentry_mcp_wrapped__" , False
959+ ):
960+
961+ @wraps (on_read_resource )
962+ async def wrapper (* args : "Any" , ** kwargs : "Any" ) -> "Any" :
963+ return await _resource_handler_wrapper (
964+ on_read_resource , args , kwargs , force_await = False
965+ )
966+
967+ wrapper .__sentry_mcp_wrapped__ = True # type: ignore[attr-defined]
968+ kwargs ["on_read_resource" ] = wrapper
969+
974970 original_init (self , * args , ** kwargs )
975- self .middleware .append (_sentry_middleware )
976971
977972 Server .__init__ = patched_init # type: ignore[method-assign]
978973
974+ original_add_request_handler = Server .add_request_handler
975+
976+ def patched_add_request_handler (
977+ self : "Server" ,
978+ method : str ,
979+ params_type : "Any" ,
980+ handler : "Callable[..., Any]" ,
981+ * args : "Any" ,
982+ ** kwargs : "Any" ,
983+ ) -> None :
984+ if getattr (handler , "__sentry_mcp_wrapped__" , False ):
985+ return original_add_request_handler (
986+ self , method , params_type , handler , * args , ** kwargs
987+ )
988+
989+ if method == "tools/call" :
990+
991+ @wraps (handler )
992+ async def wrapper (* args : "Any" , ** kwargs : "Any" ) -> "Any" :
993+ return await _tool_handler_wrapper (
994+ handler , args , kwargs , force_await = False
995+ )
996+
997+ wrapper .__sentry_mcp_wrapped__ = True # type: ignore[attr-defined]
998+
999+ return original_add_request_handler (
1000+ self , method , params_type , wrapper , * args , ** kwargs
1001+ )
1002+ if method == "prompts/get" :
1003+
1004+ @wraps (handler )
1005+ async def wrapper (* args : "Any" , ** kwargs : "Any" ) -> "Any" :
1006+ return await _prompt_handler_wrapper (
1007+ handler , args , kwargs , force_await = False
1008+ )
1009+
1010+ wrapper .__sentry_mcp_wrapped__ = True # type: ignore[attr-defined]
1011+
1012+ return original_add_request_handler (
1013+ self , method , params_type , wrapper , * args , ** kwargs
1014+ )
1015+ if method == "resources/read" :
1016+
1017+ @wraps (handler )
1018+ async def wrapper (* args : "Any" , ** kwargs : "Any" ) -> "Any" :
1019+ return await _resource_handler_wrapper (
1020+ handler , args , kwargs , force_await = False
1021+ )
1022+
1023+ wrapper .__sentry_mcp_wrapped__ = True # type: ignore[attr-defined]
1024+
1025+ return original_add_request_handler (
1026+ self , method , params_type , wrapper , * args , ** kwargs
1027+ )
1028+
1029+ original_add_request_handler (
1030+ self , method , params_type , handler , * args , ** kwargs
1031+ )
1032+
1033+ Server .add_request_handler = patched_add_request_handler # type: ignore[method-assign]
1034+
9791035
9801036def _patch_handle_request () -> None :
9811037 original_handle_request = StreamableHTTPServerTransport .handle_request
0 commit comments