@@ -54,6 +54,9 @@ export class Aggregator {
5454 private readonly promptRoute = new Map < string , { upstreamId : string ; original : string } > ( ) ;
5555 /** uri -> upstreamId for resources (URIs are unique upstream-side; we keep first wins on collision). */
5656 private readonly resourceRoute = new Map < string , string > ( ) ;
57+ /** Short-lived cache for collectTools() to debounce redundant fan-out. */
58+ private toolsCache : { tools : unknown [ ] ; ts : number } | null = null ;
59+ private readonly TOOLS_CACHE_TTL_MS = 2000 ;
5760
5861 constructor ( deps : AggregatorDeps ) {
5962 this . deps = deps ;
@@ -210,12 +213,24 @@ export class Aggregator {
210213 * and return the merged list.
211214 */
212215 async collectTools ( ) : Promise < unknown [ ] > {
216+ // Return cached result if fresh enough to avoid redundant upstream fan-out.
217+ if ( this . toolsCache && Date . now ( ) - this . toolsCache . ts < this . TOOLS_CACHE_TTL_MS ) {
218+ return this . toolsCache . tools ;
219+ }
220+
213221 this . toolRoute . clear ( ) ;
214222 const out : Record < string , unknown > [ ] = [ ] ;
223+ const timeoutMs = this . deps . cfg . upstreamTimeoutMs ?? 30_000 ;
224+
215225 for ( const [ id , conn ] of this . deps . upstream . connections ) {
216226 if ( conn . status !== "connected" || ! conn . capabilities ?. tools ) continue ;
227+ let timer : ReturnType < typeof setTimeout > | undefined ;
217228 try {
218- const result = await conn . client . listTools ( ) ;
229+ const timeout = new Promise < never > ( ( _ , reject ) => {
230+ timer = setTimeout ( ( ) => reject ( new Error ( `upstream "${ id } " tools/list timed out after ${ timeoutMs } ms` ) ) , timeoutMs ) ;
231+ } ) ;
232+ const result = await Promise . race ( [ conn . client . listTools ( ) , timeout ] ) ;
233+ clearTimeout ( timer ) ;
219234 for ( const t of result . tools ?? [ ] ) {
220235 const namespaced = this . namespace ( id , t . name ) ;
221236 if ( ! this . deps . filter . isAllowed ( namespaced , "tool" ) ) continue ;
@@ -246,9 +261,11 @@ export class Aggregator {
246261 } ) ;
247262 }
248263 } catch ( err ) {
264+ clearTimeout ( timer ) ;
249265 this . log . warn ( { id, err : errMsg ( err ) } , "upstream tools/list failed" ) ;
250266 }
251267 }
268+ this . toolsCache = { tools : out , ts : Date . now ( ) } ;
252269 return out ;
253270 }
254271
0 commit comments