@@ -339,17 +339,22 @@ class MCPServer(AbstractToolset[Any], ABC):
339339
340340 When enabled (default), tools are fetched once and cached until either:
341341 - The server sends a `notifications/tools/list_changed` notification
342- - The connection is closed
342+ - [`MCPServer.__aexit__`][pydantic_ai.mcp.MCPServer.__aexit__] is called (when the last context exits)
343343
344344 Set to `False` for servers that change tools dynamically without sending notifications.
345+
346+ Note: When using durable execution (Temporal, DBOS), tool definitions are additionally cached
347+ at the wrapper level across activities/steps, to avoid redundant MCP connections. This
348+ wrapper-level cache is not invalidated by `tools/list_changed` notifications.
349+ Set to `False` to disable all caching if tools may change during a workflow.
345350 """
346351
347352 cache_resources : bool
348353 """Whether to cache the list of resources.
349354
350355 When enabled (default), resources are fetched once and cached until either:
351356 - The server sends a `notifications/resources/list_changed` notification
352- - The connection is closed
357+ - [`MCPServer.__aexit__`][pydantic_ai.mcp.MCPServer.__aexit__] is called (when the last context exits)
353358
354359 Set to `False` for servers that change resources dynamically without sending notifications.
355360 """
@@ -479,20 +484,18 @@ async def list_tools(self) -> list[mcp_types.Tool]:
479484
480485 Tools are cached by default, with cache invalidation on:
481486 - `notifications/tools/list_changed` notifications from the server
482- - Connection close (cache is cleared in `__aexit__`)
487+ - `__aexit__` when the last context exits
483488
484489 Set `cache_tools=False` for servers that change tools without sending notifications.
485490 """
491+ if self .cache_tools and self ._cached_tools is not None :
492+ return self ._cached_tools
493+
486494 async with self :
495+ result = await self ._client .list_tools ()
487496 if self .cache_tools :
488- if self ._cached_tools is not None :
489- return self ._cached_tools
490- result = await self ._client .list_tools ()
491497 self ._cached_tools = result .tools
492- return result .tools
493- else :
494- result = await self ._client .list_tools ()
495- return result .tools
498+ return result .tools
496499
497500 async def direct_call_tool (
498501 self ,
@@ -600,27 +603,25 @@ async def list_resources(self) -> list[Resource]:
600603
601604 Resources are cached by default, with cache invalidation on:
602605 - `notifications/resources/list_changed` notifications from the server
603- - Connection close (cache is cleared in `__aexit__`)
606+ - `__aexit__` when the last context exits
604607
605608 Set `cache_resources=False` for servers that change resources without sending notifications.
606609
607610 Raises:
608611 MCPError: If the server returns an error.
609612 """
613+ if self .cache_resources and self ._cached_resources is not None :
614+ return self ._cached_resources
615+
610616 async with self :
611617 if not self .capabilities .resources :
612618 return []
613619 try :
620+ result = await self ._client .list_resources ()
621+ resources = [Resource .from_mcp_sdk (r ) for r in result .resources ]
614622 if self .cache_resources :
615- if self ._cached_resources is not None :
616- return self ._cached_resources
617- result = await self ._client .list_resources ()
618- resources = [Resource .from_mcp_sdk (r ) for r in result .resources ]
619623 self ._cached_resources = resources
620- return resources
621- else :
622- result = await self ._client .list_resources ()
623- return [Resource .from_mcp_sdk (r ) for r in result .resources ]
624+ return resources
624625 except mcp_exceptions .McpError as e :
625626 raise MCPError .from_mcp_sdk (e ) from e
626627
0 commit comments