2424except ImportError :
2525 raise DidNotEnable ("MCP SDK not installed" )
2626
27+ try :
28+ from fastmcp import FastMCP # type: ignore[import-not-found]
29+ except ImportError :
30+ FastMCP = None
31+
2732
2833if TYPE_CHECKING :
2934 from typing import Any , Callable , Optional
@@ -50,6 +55,9 @@ def setup_once() -> None:
5055 """
5156 _patch_lowlevel_server ()
5257
58+ if FastMCP is not None :
59+ _patch_fastmcp ()
60+
5361
5462def _get_request_context_data () -> "tuple[Optional[str], Optional[str], str]" :
5563 """
@@ -280,26 +288,53 @@ def _set_span_output_data(
280288
281289
282290def _prepare_handler_data (
283- handler_type : str , original_args : "tuple[Any, ...]"
291+ handler_type : str ,
292+ original_args : "tuple[Any, ...]" ,
293+ original_kwargs : "Optional[dict[str, Any]]" = None ,
284294) -> "tuple[str, dict[str, Any], str, str, str, Optional[str]]" :
285295 """
286296 Prepare common handler data for both async and sync wrappers.
287297
288298 Returns:
289299 Tuple of (handler_name, arguments, span_data_key, span_name, mcp_method_name, result_data_key)
290300 """
301+ original_kwargs = original_kwargs or {}
302+
291303 # Extract handler-specific data based on handler type
292304 if handler_type == "tool" :
293- handler_name = original_args [0 ] # tool_name
294- arguments = original_args [1 ] if len (original_args ) > 1 else {}
305+ if original_args :
306+ handler_name = original_args [0 ]
307+ elif original_kwargs .get ("name" ):
308+ handler_name = original_kwargs ["name" ]
309+
310+ arguments = {}
311+ if len (original_args ) > 1 :
312+ arguments = original_args [1 ]
313+ elif original_kwargs .get ("arguments" ):
314+ arguments = original_kwargs ["arguments" ]
315+
295316 elif handler_type == "prompt" :
296- handler_name = original_args [0 ] # name
297- arguments = original_args [1 ] if len (original_args ) > 1 else {}
317+ if original_args :
318+ handler_name = original_args [0 ]
319+ elif original_kwargs .get ("name" ):
320+ handler_name = original_kwargs ["name" ]
321+
322+ arguments = {}
323+ if len (original_args ) > 1 :
324+ arguments = original_args [1 ]
325+ elif original_kwargs .get ("arguments" ):
326+ arguments = original_kwargs ["arguments" ]
327+
298328 # Include name in arguments dict for span data
299329 arguments = {"name" : handler_name , ** (arguments or {})}
330+
300331 else : # resource
301- uri = original_args [0 ]
302- handler_name = str (uri ) if uri else "unknown"
332+ handler_name = "unknown"
333+ if original_args :
334+ handler_name = str (original_args [0 ])
335+ elif original_kwargs .get ("uri" ):
336+ handler_name = str (original_kwargs ["uri" ])
337+
303338 arguments = {}
304339
305340 # Get span configuration
@@ -318,7 +353,11 @@ def _prepare_handler_data(
318353
319354
320355async def _async_handler_wrapper (
321- handler_type : str , func : "Callable[..., Any]" , original_args : "tuple[Any, ...]"
356+ handler_type : str ,
357+ func : "Callable[..., Any]" ,
358+ original_args : "tuple[Any, ...]" ,
359+ original_kwargs : "Optional[dict[str, Any]]" = None ,
360+ self : "Optional[Any]" = None ,
322361) -> "Any" :
323362 """
324363 Async wrapper for MCP handlers.
@@ -327,15 +366,20 @@ async def _async_handler_wrapper(
327366 handler_type: "tool", "prompt", or "resource"
328367 func: The async handler function to wrap
329368 original_args: Original arguments passed to the handler
369+ original_kwargs: Original keyword arguments passed to the handler
370+ self: Optional instance for bound methods
330371 """
372+ if original_kwargs is None :
373+ original_kwargs = {}
374+
331375 (
332376 handler_name ,
333377 arguments ,
334378 span_data_key ,
335379 span_name ,
336380 mcp_method_name ,
337381 result_data_key ,
338- ) = _prepare_handler_data (handler_type , original_args )
382+ ) = _prepare_handler_data (handler_type , original_args , original_kwargs )
339383
340384 # Start span and execute
341385 with get_start_span_function ()(
@@ -360,7 +404,11 @@ async def _async_handler_wrapper(
360404
361405 # For resources, extract and set protocol
362406 if handler_type == "resource" :
363- uri = original_args [0 ]
407+ if original_args :
408+ uri = original_args [0 ]
409+ else :
410+ uri = original_kwargs .get ("uri" )
411+
364412 protocol = None
365413 if hasattr (uri , "scheme" ):
366414 protocol = uri .scheme
@@ -371,7 +419,9 @@ async def _async_handler_wrapper(
371419
372420 try :
373421 # Execute the async handler
374- result = await func (* original_args )
422+ if self is not None :
423+ original_args = (self , * original_args )
424+ result = await func (* original_args , ** original_kwargs )
375425 except Exception as e :
376426 # Set error flag for tools
377427 if handler_type == "tool" :
@@ -566,3 +616,48 @@ def patched_read_resource(
566616 )(func )
567617
568618 Server .read_resource = patched_read_resource
619+
620+
621+ def _patch_fastmcp ():
622+ # type: () -> None
623+ """
624+ Patches the standalone fastmcp package's FastMCP class.
625+
626+ The standalone fastmcp package (v2.14.0+) registers its own handlers for
627+ prompts and resources directly, bypassing the Server decorators we patch.
628+ This function patches the _get_prompt_mcp and _read_resource_mcp methods
629+ to add instrumentation for those handlers.
630+ """
631+ if hasattr (FastMCP , "_get_prompt_mcp" ):
632+ original_get_prompt_mcp = FastMCP ._get_prompt_mcp
633+
634+ @wraps (original_get_prompt_mcp )
635+ async def patched_get_prompt_mcp (
636+ self : "Any" , * args : "Any" , ** kwargs : "Any"
637+ ) -> "Any" :
638+ return await _async_handler_wrapper (
639+ "prompt" ,
640+ original_get_prompt_mcp ,
641+ args ,
642+ kwargs ,
643+ self ,
644+ )
645+
646+ FastMCP ._get_prompt_mcp = patched_get_prompt_mcp
647+
648+ if hasattr (FastMCP , "_read_resource_mcp" ):
649+ original_read_resource_mcp = FastMCP ._read_resource_mcp
650+
651+ @wraps (original_read_resource_mcp )
652+ async def patched_read_resource_mcp (
653+ self : "Any" , * args : "Any" , ** kwargs : "Any"
654+ ) -> "Any" :
655+ return await _async_handler_wrapper (
656+ "resource" ,
657+ original_read_resource_mcp ,
658+ args ,
659+ kwargs ,
660+ self ,
661+ )
662+
663+ FastMCP ._read_resource_mcp = patched_read_resource_mcp
0 commit comments