@@ -103,7 +103,11 @@ import {
103103} from "../tools/web-search.js" ;
104104import { createUseSkillTool } from "./use-skill.js" ;
105105import { createSkillSearchTool } from "./skill-search.js" ;
106- import { createToolIndex , createToolSearchTool } from "./tool-search.js" ;
106+ import {
107+ createToolIndex ,
108+ createToolSearchTool ,
109+ TOOL_SEARCH_PENDING_WAIT_MS ,
110+ } from "./tool-search.js" ;
107111import { createSearchAgentsTool } from "./agent-search.js" ;
108112import { createReadAgentTraceTool } from "../subagent/trace-tool.js" ;
109113import {
@@ -319,6 +323,10 @@ export interface AgentToolset {
319323 // second add of an active name; failed rows retry through connectMCPServer
320324 // without a second persist. Still true while disable is in progress.
321325 hasMCPServer : ( name : string ) => boolean ;
326+ // Bounded wait for in-flight MCP handshakes; resolves to the remaining
327+ // count. Capped by `timeoutMs` so a hung authorization never hangs the
328+ // caller — the tool_search bound passes briefly by default.
329+ awaitPendingMcpConnections : ( timeoutMs ?: number ) => Promise < number > ;
322330 // Catalog unshadow can change local → global/none without rebuilding the
323331 // toolset; connectOne reads this on every late connect.
324332 setMcpServersSource : ( source : "local" | "global" | "none" ) => void ;
@@ -719,6 +727,12 @@ export async function createAgentToolset(
719727 lookup : ( name ) =>
720728 runnerHolder . current ?. currentDefinitions ( ) . find ( ( d ) => d . name === name ) ,
721729 promote : ( names ) => promoter . promote ( names ) ,
730+ // Misses wait briefly for in-flight MCP handshakes (bounded, so hung
731+ // OAuth cannot hang the call) and re-search before answering. Reads the
732+ // connection map live — declared below, populated by the time any
733+ // search runs.
734+ awaitPendingConnections : ( timeoutMs = TOOL_SEARCH_PENDING_WAIT_MS ) =>
735+ awaitPendingMcpConnections ( timeoutMs ) ,
722736 } ) ,
723737 ) ;
724738
@@ -744,6 +758,26 @@ export async function createAgentToolset(
744758 const connectedClients = new Map < string , MCPClient > ( ) ;
745759 const inFlightConnections = new Map < string , Promise < void > > ( ) ;
746760 const inFlightEpochs = new Map < string , number > ( ) ;
761+ // Bounded wait for in-flight handshakes; resolves to the remaining count.
762+ // Capped by `timeoutMs` so a hung authorization never hangs the caller.
763+ const awaitPendingMcpConnections = async (
764+ timeoutMs = TOOL_SEARCH_PENDING_WAIT_MS ,
765+ ) : Promise < number > => {
766+ if ( inFlightConnections . size === 0 ) return 0 ;
767+ const pending = [ ...inFlightConnections . values ( ) ] ;
768+ let timer : ReturnType < typeof setTimeout > | undefined ;
769+ try {
770+ await Promise . race ( [
771+ Promise . allSettled ( pending ) ,
772+ new Promise < void > ( ( resolve ) => {
773+ timer = setTimeout ( resolve , timeoutMs ) ;
774+ } ) ,
775+ ] ) ;
776+ } finally {
777+ if ( timer !== undefined ) clearTimeout ( timer ) ;
778+ }
779+ return inFlightConnections . size ;
780+ } ;
747781 const disabledNames = new Set < string > ( ) ;
748782 const serverAborts = new Map < string , AbortController > ( ) ;
749783 const serverEpochs = new Map < string , number > ( ) ;
@@ -1218,6 +1252,7 @@ export async function createAgentToolset(
12181252 disconnectMCPServer : publicDisconnectMCPServer ,
12191253 hasMCPServer : ( name ) =>
12201254 connectedClients . has ( name ) || inFlightConnections . has ( name ) ,
1255+ awaitPendingMcpConnections,
12211256 setMcpServersSource : ( source ) => {
12221257 mcpServersSource = source ;
12231258 } ,
0 commit comments