@@ -64,6 +64,7 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
6464 private connections = new Map < string , ServerConnection > ( ) ;
6565 private resourceCache = new Map < string , McpUiResource > ( ) ;
6666 private toolAssociations = new Map < string , McpToolUiAssociation > ( ) ;
67+ private appVisibleTools = new Set < string > ( ) ;
6768 private toolDefinitions = new Map < string , Tool > ( ) ;
6869 private serverConfigs = new Map < string , McpServerConnectionConfig > ( ) ;
6970 private configResolver ?: ( serverName : string ) => Promise < void > ;
@@ -83,6 +84,11 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
8384 this . log = rootLogger . scope ( "mcp-apps-service" ) ;
8485 }
8586
87+ // Resource URIs are server-chosen and collide across servers.
88+ private cacheKey ( serverName : string , resourceUri : string ) : string {
89+ return `${ serverName } \u0000${ resourceUri } ` ;
90+ }
91+
8692 /**
8793 * Store server configs for lazy connections later.
8894 * No connections are created at this point.
@@ -180,9 +186,14 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
180186 }
181187
182188 const uiMeta = ( tool as McpToolUiMeta ) . _meta ?. ui ;
189+ const toolKey = `mcp__${ serverName } __${ tool . name } ` ;
190+
191+ if ( uiMeta ?. visibility ?. includes ( "app" ) ) {
192+ this . appVisibleTools . add ( toolKey ) ;
193+ }
194+
183195 if ( ! uiMeta ?. resourceUri ) continue ;
184196
185- const toolKey = `mcp__${ serverName } __${ tool . name } ` ;
186197 this . toolAssociations . set ( toolKey , {
187198 toolKey,
188199 serverName,
@@ -198,7 +209,10 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
198209 for ( const resource of resourcesList . resources ) {
199210 const meta = resource as McpResourceUiMeta ;
200211 if ( meta . _meta ?. ui ) {
201- this . resourceMetaCache . set ( resource . uri , meta ) ;
212+ this . resourceMetaCache . set (
213+ this . cacheKey ( serverName , resource . uri ) ,
214+ meta ,
215+ ) ;
202216 }
203217 }
204218 }
@@ -335,7 +349,8 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
335349 serverName : string ,
336350 resourceUri : string ,
337351 ) : Promise < McpUiResource | null > {
338- const cached = this . resourceCache . get ( resourceUri ) ;
352+ const key = this . cacheKey ( serverName , resourceUri ) ;
353+ const cached = this . resourceCache . get ( key ) ;
339354 if ( cached ) {
340355 this . log . debug ( "fetchUiResourceByUri: cache hit" , {
341356 serverName,
@@ -344,7 +359,7 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
344359 return cached ;
345360 }
346361
347- const pendingFetch = this . pendingFetches . get ( resourceUri ) ;
362+ const pendingFetch = this . pendingFetches . get ( key ) ;
348363 if ( pendingFetch ) {
349364 this . log . debug ( "fetchUiResourceByUri: joining pending fetch" , {
350365 serverName,
@@ -358,11 +373,11 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
358373 resourceUri,
359374 } ) ;
360375 const fetchPromise = this . doFetchUiResource ( serverName , resourceUri ) ;
361- this . pendingFetches . set ( resourceUri , fetchPromise ) ;
376+ this . pendingFetches . set ( key , fetchPromise ) ;
362377 try {
363378 return await fetchPromise ;
364379 } finally {
365- this . pendingFetches . delete ( resourceUri ) ;
380+ this . pendingFetches . delete ( key ) ;
366381 }
367382 }
368383
@@ -409,7 +424,9 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
409424 return null ;
410425 }
411426
412- const resourceMeta = this . resourceMetaCache . get ( resourceUri ) ;
427+ const resourceMeta = this . resourceMetaCache . get (
428+ this . cacheKey ( serverName , resourceUri ) ,
429+ ) ;
413430
414431 const resource : McpUiResource = {
415432 uri : resourceUri ,
@@ -421,7 +438,7 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
421438 serverName,
422439 } ;
423440
424- this . resourceCache . set ( resourceUri , resource ) ;
441+ this . resourceCache . set ( this . cacheKey ( serverName , resourceUri ) , resource ) ;
425442 this . log . info ( "Lazily fetched and cached UI resource" , {
426443 serverName,
427444 uri : resourceUri ,
@@ -456,6 +473,10 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
456473 ) ;
457474 }
458475
476+ if ( ! association && ! this . appVisibleTools . has ( toolKey ) ) {
477+ throw new Error ( `Tool "${ toolName } " is not exposed to apps` ) ;
478+ }
479+
459480 const conn = await this . getOrCreateConnection ( serverName ) ;
460481 const result = await conn . client . callTool ( {
461482 name : toolName ,
@@ -540,6 +561,7 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
540561 this . resourceCache . clear ( ) ;
541562 this . resourceMetaCache . clear ( ) ;
542563 this . toolAssociations . clear ( ) ;
564+ this . appVisibleTools . clear ( ) ;
543565 this . toolDefinitions . clear ( ) ;
544566 this . pendingConnections . clear ( ) ;
545567 this . pendingFetches . clear ( ) ;
@@ -578,13 +600,17 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
578600 }
579601 }
580602
581- // Only evict cached resources not referenced by remaining associations
582- const stillReferenced = new Set (
583- [ ...this . toolAssociations . values ( ) ] . map ( ( a ) => a . resourceUri ) ,
584- ) ;
585603 for ( const uri of urisToEvict ) {
586- if ( ! stillReferenced . has ( uri ) ) {
587- this . resourceCache . delete ( uri ) ;
604+ const key = this . cacheKey ( serverName , uri ) ;
605+ this . resourceCache . delete ( key ) ;
606+ this . resourceMetaCache . delete ( key ) ;
607+ this . pendingFetches . delete ( key ) ;
608+ }
609+
610+ const toolKeyPrefix = `mcp__${ serverName } __` ;
611+ for ( const toolKey of this . appVisibleTools ) {
612+ if ( toolKey . startsWith ( toolKeyPrefix ) ) {
613+ this . appVisibleTools . delete ( toolKey ) ;
588614 }
589615 }
590616 }
@@ -597,6 +623,7 @@ export class McpAppsService extends TypedEventEmitter<McpAppsServiceEvents> {
597623 this . resourceCache . clear ( ) ;
598624 this . resourceMetaCache . clear ( ) ;
599625 this . toolAssociations . clear ( ) ;
626+ this . appVisibleTools . clear ( ) ;
600627 this . toolDefinitions . clear ( ) ;
601628 this . serverConfigs . clear ( ) ;
602629 this . pendingConnections . clear ( ) ;
0 commit comments