@@ -261,21 +261,22 @@ export const layer = (options: {
261261 ) ;
262262 // Calls replace an expired session themselves; this covers a background request seeing it first.
263263 live . connection . onSessionExpired ( current ( retire ( server , live ) ) ) ;
264+ // Listed without the server's lock, which calls and closing need: the listing can take a while.
264265 live . connection . onToolsChanged (
265- current (
266- live . connection . tools ( ) . pipe (
267- Effect . tap ( ( tools ) =>
268- Effect . sync ( ( ) => {
269- server . tools = tools ;
270- } ) ,
271- ) ,
272- Effect . catch ( ( error ) =>
273- Effect . sync ( ( ) => {
274- server . error = `failed to refresh tools: ${ error . message } ` ;
266+ Effect . sync ( ( ) => {
267+ fork (
268+ live . connection . tools ( ) . pipe (
269+ Effect . match ( {
270+ onSuccess : ( tools ) => {
271+ if ( server . live === live ) server . tools = tools ;
272+ } ,
273+ onFailure : ( error ) => {
274+ if ( server . live === live ) server . error = `failed to refresh tools: ${ error . message } ` ;
275+ } ,
275276 } ) ,
276277 ) ,
277- ) ,
278- ) ,
278+ ) ;
279+ } ) ,
279280 ) ;
280281 live . connection . onLog ( ( entry ) => log . write ( server . name , entry ) ) ;
281282 } ;
@@ -453,7 +454,8 @@ export const layer = (options: {
453454 new ToolCallError ( {
454455 server : name ,
455456 tool : "resources" ,
456- message : `MCP server "${ name } " failed: ${ message } ` ,
457+ // Listings name the server beside each failure already.
458+ message,
457459 } ) ,
458460 ) ;
459461 return {
@@ -585,6 +587,11 @@ export const layer = (options: {
585587 } ;
586588 instances . set ( key , server ) ;
587589 start ( server ) ;
590+ } else if ( server . state === "failed" && server . attempt === undefined ) {
591+ // A server that could not connect declares no tools, so no call would retry it, and
592+ // shared instances would keep every later session on it. Retry in the background;
593+ // waiting would hold up every prompt while it is down.
594+ start ( server ) ;
588595 }
589596 const entry : Entry = { config, server } ;
590597 session . servers . set ( name , entry ) ;
@@ -635,6 +642,10 @@ export const layer = (options: {
635642 ) ;
636643 }
637644 const output = { save : McpOutput . save } ;
645+ // Resource tools follow their servers' exposure: declared when one of them is `direct`.
646+ const resourceTools = [ ...session . servers . values ( ) ] . some (
647+ ( entry ) => withResources ( entry ) && exposureOf ( entry . config ) === "direct" ,
648+ ) ;
638649 for ( const [ name , entry ] of session . servers ) {
639650 const server = entry . server ;
640651 if ( server === undefined || server . tools . length === 0 ) continue ;
@@ -650,23 +661,26 @@ export const layer = (options: {
650661 tool,
651662 name : names . get ( tool . name ) ! ,
652663 call : call ( session , name , tool . name ) ,
653- content : { ...output , readable : withResources ( entry ) } ,
664+ // A resource link points to `read_mcp_resource` only when the model has it.
665+ content : { ...output , readable : resourceTools && withResources ( entry ) } ,
654666 } ) ,
655667 ) ;
656668 }
657669 }
658- // Resource tools follow their servers' exposure: declared when one of them is `direct`.
659- if (
660- [ ...session . servers . values ( ) ] . some (
661- ( entry ) => withResources ( entry ) && exposureOf ( entry . config ) === "direct" ,
662- )
663- )
670+ if ( resourceTools )
664671 tools . push (
665672 ...McpResources . make ( {
666673 servers : ( ) =>
667674 [ ...session . servers ]
668675 . filter ( ( [ , entry ] ) => withResources ( entry ) )
669676 . map ( ( [ name ] ) => resourceServer ( session , name ) ) ,
677+ // A server still connecting may offer resources once it has.
678+ ready : ( ) =>
679+ Effect . forEach (
680+ [ ...session . servers . values ( ) ] ,
681+ ( { server } ) => ( server ?. ready === undefined ? Effect . void : Deferred . await ( server . ready ) ) ,
682+ { discard : true } ,
683+ ) ,
670684 content : output ,
671685 } ) ,
672686 ) ;
@@ -685,6 +699,9 @@ export const layer = (options: {
685699 Effect . sync ( ( ) => {
686700 session . leases -= 1 ;
687701 for ( const server of used ( session ) ) hold ( server , - 1 ) ;
702+ // Every session drains, MCP or not; one with no servers has nothing worth keeping.
703+ if ( session . leases === 0 && session . servers . size === 0 && sessions . get ( session . id ) === session )
704+ sessions . delete ( session . id ) ;
688705 } ) ,
689706 ) . pipe ( Effect . asVoid ) ;
690707
0 commit comments