2929 ListPromptsResult ,
3030 ListResourcesResult ,
3131 ListResourceTemplatesResult ,
32+ ListToolsetsRequestParams ,
33+ ListToolsetsResult ,
34+ ListToolsRequestParams ,
3235 ListToolsResult ,
3336 LoggingLevel ,
3437 PaginatedRequestParams ,
3841 ResourceTemplateReference ,
3942 Result ,
4043 ServerCapabilities ,
44+ ToolsetRef ,
4145)
4246from mcp_types .version import HANDSHAKE_PROTOCOL_VERSIONS , MODERN_PROTOCOL_VERSIONS
4347from typing_extensions import deprecated
@@ -543,10 +547,12 @@ async def _cached_fetch(
543547 cache_mode : CacheMode ,
544548 send : Callable [[], Awaitable [_CacheableT ]],
545549 absorb : Callable [[_CacheableT ], _CacheableT ] | None = None ,
550+ cache_key : str = "" ,
546551 ) -> _CacheableT :
547552 """Serve one of the four list verbs through the response cache.
548553
549554 `absorb` (tools/list only) re-applies session-side derived state to a served cache hit.
555+ `cache_key` disambiguates variants of the same method (e.g. Toolset pins).
550556 """
551557 cache = self ._response_cache
552558 if cache is None or cache_mode == "bypass" :
@@ -564,13 +570,13 @@ async def _cached_fetch(
564570 if e .code == INVALID_PARAMS :
565571 await cache .evict_method (method )
566572 raise
567- if cache_mode == "use" and (hit := await cache .read (method , "" )) is not None :
573+ if cache_mode == "use" and (hit := await cache .read (method , cache_key )) is not None :
568574 # The hit is a private deep copy, so absorption may mutate it freely.
569575 served = cast (_CacheableT , hit )
570576 return served if absorb is None else absorb (served )
571- gen = cache .capture (method , "" )
577+ gen = cache .capture (method , cache_key )
572578 result = await send ()
573- await cache .write (method , "" , result , gen , cache_mode )
579+ await cache .write (method , cache_key , result , gen , cache_mode )
574580 return result
575581
576582 async def list_resources (
@@ -742,6 +748,7 @@ async def call_tool(
742748 input_responses : InputResponses | None = None ,
743749 request_state : str | None = None ,
744750 meta : RequestParamsMeta | None = None ,
751+ toolset : ToolsetRef | None = None ,
745752 ) -> CallToolResult :
746753 """Call a tool on the server.
747754
@@ -768,6 +775,7 @@ async def call_tool(
768775 resuming from a persisted `InputRequiredResult`).
769776 request_state: Opaque state to seed the first call with.
770777 meta: Additional metadata for the request.
778+ toolset: Optional Toolset pin (toolsets extension).
771779
772780 Returns:
773781 The tool result.
@@ -788,6 +796,7 @@ async def retry(r: InputResponses | None, s: str | None) -> CallToolResult | Inp
788796 input_responses = r ,
789797 request_state = s ,
790798 meta = meta ,
799+ toolset = toolset ,
791800 allow_input_required = True ,
792801 # Input rounds resolve before a claimed result, so a claim may end any round.
793802 allow_claimed = True ,
@@ -910,15 +919,27 @@ async def list_tools(
910919 * ,
911920 cursor : str | None = None ,
912921 meta : RequestParamsMeta | None = None ,
922+ toolset : ToolsetRef | None = None ,
913923 cache_mode : CacheMode = "use" ,
914924 ) -> ListToolsResult :
915- """List available tools from the server."""
925+ """List available tools from the server.
926+
927+ Args:
928+ cursor: Pagination cursor.
929+ meta: Request `_meta`.
930+ toolset: Optional Toolset pin (toolsets extension).
931+ cache_mode: Response-cache behaviour.
932+ """
933+ key = "" if toolset is None else f"{ toolset .name } @{ toolset .version } "
916934 return await self ._cached_fetch (
917935 "tools/list" ,
918936 cursor = cursor ,
919937 meta = meta ,
920938 cache_mode = cache_mode ,
921- send = lambda : self .session .list_tools (params = PaginatedRequestParams (cursor = cursor , _meta = meta )),
939+ cache_key = key ,
940+ send = lambda : self .session .list_tools (
941+ params = ListToolsRequestParams (cursor = cursor , toolset = toolset , _meta = meta )
942+ ),
922943 # A cache hit skips session.list_tools, so the session re-absorbs the served
923944 # listing to rebuild its derived per-tool state. Hits are cursorless, but a
924945 # cached page 1 can carry next_cursor - never prune on a partial listing.
@@ -927,6 +948,16 @@ async def list_tools(
927948 ),
928949 )
929950
951+ async def list_toolsets (
952+ self ,
953+ * ,
954+ name : str | None = None ,
955+ status : types .ToolsetStatus | None = None ,
956+ meta : RequestParamsMeta | None = None ,
957+ ) -> ListToolsetsResult :
958+ """List published Toolsets (toolsets extension)."""
959+ return await self .session .list_toolsets (params = ListToolsetsRequestParams (name = name , status = status , _meta = meta ))
960+
930961 @deprecated ("The roots capability is deprecated as of 2026-07-28 (SEP-2577)." , category = MCPDeprecationWarning )
931962 async def send_roots_list_changed (self ) -> None :
932963 """Send a notification that the roots list has changed."""
0 commit comments