@@ -12,7 +12,10 @@ use std::env;
1212use std:: ffi:: OsString ;
1313use std:: path:: PathBuf ;
1414use std:: sync:: Arc ;
15+ use std:: sync:: LazyLock ;
16+ use std:: sync:: Mutex as StdMutex ;
1517use std:: time:: Duration ;
18+ use std:: time:: Instant ;
1619
1720use crate :: mcp:: CODEX_APPS_MCP_SERVER_NAME ;
1821use crate :: mcp:: auth:: McpAuthStatusEntry ;
@@ -83,6 +86,8 @@ pub const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(10);
8386/// Default timeout for individual tool calls.
8487const DEFAULT_TOOL_TIMEOUT : Duration = Duration :: from_secs ( 60 ) ;
8588
89+ const CODEX_APPS_TOOLS_CACHE_TTL : Duration = Duration :: from_secs ( 3600 ) ;
90+
8691/// The Responses API requires tool names to match `^[a-zA-Z0-9_-]+$`.
8792/// MCP server/tool names are user-controlled, so sanitize the fully-qualified
8893/// name we expose to the model by replacing any disallowed character with `_`.
@@ -161,6 +166,15 @@ pub(crate) struct ToolInfo {
161166 pub ( crate ) connector_name : Option < String > ,
162167}
163168
169+ #[ derive( Clone ) ]
170+ struct CachedCodexAppsTools {
171+ expires_at : Instant ,
172+ tools : Vec < ToolInfo > ,
173+ }
174+
175+ static CODEX_APPS_TOOLS_CACHE : LazyLock < StdMutex < Option < CachedCodexAppsTools > > > =
176+ LazyLock :: new ( || StdMutex :: new ( None ) ) ;
177+
164178type ResponderMap = HashMap < ( String , RequestId ) , oneshot:: Sender < ElicitationResponse > > ;
165179
166180#[ derive( Clone , Default ) ]
@@ -465,13 +479,28 @@ impl McpConnectionManager {
465479 #[ instrument( level = "trace" , skip_all) ]
466480 pub async fn list_all_tools ( & self ) -> HashMap < String , ToolInfo > {
467481 let mut tools = HashMap :: new ( ) ;
468- for managed_client in self . clients . values ( ) {
482+ for ( server_name , managed_client) in & self . clients {
469483 let client = managed_client. client ( ) . await . ok ( ) ;
470484 if let Some ( client) = client {
471- tools. extend ( qualify_tools ( filter_tools (
472- client. tools ,
473- client. tool_filter ,
474- ) ) ) ;
485+ let rmcp_client = client. client ;
486+ let tool_timeout = client. tool_timeout ;
487+ let tool_filter = client. tool_filter ;
488+ let mut server_tools = client. tools ;
489+
490+ if server_name == CODEX_APPS_MCP_SERVER_NAME {
491+ match list_tools_for_client ( server_name, & rmcp_client, tool_timeout) . await {
492+ Ok ( fresh_or_cached_tools) => {
493+ server_tools = fresh_or_cached_tools;
494+ }
495+ Err ( err) => {
496+ warn ! (
497+ "Failed to refresh tools for MCP server '{server_name}', using startup snapshot: {err:#}"
498+ ) ;
499+ }
500+ }
501+ }
502+
503+ tools. extend ( qualify_tools ( filter_tools ( server_tools, tool_filter) ) ) ;
475504 }
476505 }
477506 tools
@@ -965,6 +994,50 @@ async fn list_tools_for_client(
965994 server_name : & str ,
966995 client : & Arc < RmcpClient > ,
967996 timeout : Option < Duration > ,
997+ ) -> Result < Vec < ToolInfo > > {
998+ if server_name == CODEX_APPS_MCP_SERVER_NAME
999+ && let Some ( cached_tools) = read_cached_codex_apps_tools ( )
1000+ {
1001+ return Ok ( cached_tools) ;
1002+ }
1003+
1004+ let tools = list_tools_for_client_uncached ( server_name, client, timeout) . await ?;
1005+ if server_name == CODEX_APPS_MCP_SERVER_NAME {
1006+ write_cached_codex_apps_tools ( & tools) ;
1007+ }
1008+ Ok ( tools)
1009+ }
1010+
1011+ fn read_cached_codex_apps_tools ( ) -> Option < Vec < ToolInfo > > {
1012+ let mut cache_guard = CODEX_APPS_TOOLS_CACHE
1013+ . lock ( )
1014+ . unwrap_or_else ( std:: sync:: PoisonError :: into_inner) ;
1015+ let now = Instant :: now ( ) ;
1016+
1017+ if let Some ( cached) = cache_guard. as_ref ( )
1018+ && now < cached. expires_at
1019+ {
1020+ return Some ( cached. tools . clone ( ) ) ;
1021+ }
1022+
1023+ * cache_guard = None ;
1024+ None
1025+ }
1026+
1027+ fn write_cached_codex_apps_tools ( tools : & [ ToolInfo ] ) {
1028+ let mut cache_guard = CODEX_APPS_TOOLS_CACHE
1029+ . lock ( )
1030+ . unwrap_or_else ( std:: sync:: PoisonError :: into_inner) ;
1031+ * cache_guard = Some ( CachedCodexAppsTools {
1032+ expires_at : Instant :: now ( ) + CODEX_APPS_TOOLS_CACHE_TTL ,
1033+ tools : tools. to_vec ( ) ,
1034+ } ) ;
1035+ }
1036+
1037+ async fn list_tools_for_client_uncached (
1038+ server_name : & str ,
1039+ client : & Arc < RmcpClient > ,
1040+ timeout : Option < Duration > ,
9681041) -> Result < Vec < ToolInfo > > {
9691042 let resp = client. list_tools_with_connector_ids ( None , timeout) . await ?;
9701043 Ok ( resp
0 commit comments