@@ -303,7 +303,11 @@ function registerResourceHandlers(
303303 ctx : DevframeNodeContext ,
304304 exposeSharedState : boolean | ( ( key : string ) => boolean ) ,
305305) : ( ) => Promise < void > {
306- const subscriptions = new Map < string , ( ) => void | Promise < void > > ( )
306+ const stateFilter = sharedStateFilter ( exposeSharedState )
307+ const subscriptions = new Map < string , {
308+ kind : 'agent' | 'state'
309+ cleanup : ( ) => void | Promise < void >
310+ } > ( )
307311 let subscriptionOperations = Promise . resolve ( )
308312 const runSubscriptionOperation = < Result > ( operation : ( ) => Promise < Result > ) : Promise < Result > => {
309313 const result = subscriptionOperations . then ( operation )
@@ -325,10 +329,9 @@ function registerResourceHandlers(
325329 resources . push ( ...listed . resources )
326330 }
327331
328- if ( exposeSharedState !== false ) {
329- const filter = typeof exposeSharedState === 'function' ? exposeSharedState : ( ) => true
332+ if ( stateFilter ) {
330333 for ( const key of ctx . rpc . sharedState . keys ( ) ) {
331- if ( ! filter ( key ) )
334+ if ( ! stateFilter ( key ) )
332335 continue
333336 resources . push ( {
334337 uri : `devframe://state/${ encodeURIComponent ( key ) } ` ,
@@ -370,7 +373,7 @@ function registerResourceHandlers(
370373 }
371374
372375 const parsed = parseResourceUri ( uri )
373- if ( parsed . kind === 'state' ) {
376+ if ( parsed . kind === 'state' && stateFilter ?. ( parsed . key ) && ctx . rpc . sharedState . keys ( ) . includes ( parsed . key ) ) {
374377 const state = await ctx . rpc . sharedState . get ( parsed . key )
375378 return {
376379 contents : [
@@ -393,24 +396,36 @@ function registerResourceHandlers(
393396 return { }
394397
395398 const resource = resolveAgentResource ( ctx , uri )
396- if ( ! resource )
399+ if ( resource ) {
400+ const cleanup = await ctx . agent . subscribeResource ( resource . id , uri )
401+ subscriptions . set ( uri , { kind : 'agent' , cleanup } )
402+ return { }
403+ }
404+
405+ const parsed = parseResourceUri ( uri )
406+ if ( parsed . kind !== 'state' || ! stateFilter ?.( parsed . key ) || ! ctx . rpc . sharedState . keys ( ) . includes ( parsed . key ) )
397407 throw new Error ( `[devframe/mcp] unknown resource URI "${ uri } "` )
398408
399- const cleanup = await ctx . agent . subscribeResource ( resource . id , uri )
400- subscriptions . set ( uri , cleanup )
409+ const state = await ctx . rpc . sharedState . get ( parsed . key )
410+ const cleanup = state . on ( 'updated' , ( ) => {
411+ if ( ! subscriptions . has ( uri ) )
412+ return
413+ void server . sendResourceUpdated ( { uri } ) . catch ( ( ) => { /* ignore transport errors */ } )
414+ } )
415+ subscriptions . set ( uri , { kind : 'state' , cleanup } )
401416 return { }
402417 } )
403418 } )
404419
405420 server . setRequestHandler ( 'resources/unsubscribe' , async ( request ) => {
406421 const { uri } = request . params
407422 return await runSubscriptionOperation ( async ( ) => {
408- const cleanup = subscriptions . get ( uri )
409- if ( ! cleanup )
423+ const subscription = subscriptions . get ( uri )
424+ if ( ! subscription )
410425 return { }
411426
412427 subscriptions . delete ( uri )
413- await cleanup ( )
428+ await subscription . cleanup ( )
414429 return { }
415430 } )
416431 } )
@@ -423,13 +438,23 @@ function registerResourceHandlers(
423438
424439 const offManifest = ctx . agent . events . on ( DEVFRAME_EVENTS . bus . agentManifestChanged , ( ) => {
425440 void runSubscriptionOperation ( async ( ) => {
426- for ( const [ uri , cleanup ] of [ ...subscriptions ] ) {
441+ for ( const [ uri , subscription ] of [ ...subscriptions ] ) {
442+ if ( subscription . kind === 'state' )
443+ continue
444+ try {
445+ await subscription . cleanup ( )
446+ }
447+ catch {
448+ continue
449+ }
427450 subscriptions . delete ( uri )
428- await cleanup ( )
429451 const resource = resolveAgentResource ( ctx , uri )
430452 if ( ! resource )
431453 continue
432- subscriptions . set ( uri , await ctx . agent . subscribeResource ( resource . id , uri ) )
454+ subscriptions . set ( uri , {
455+ kind : 'agent' ,
456+ cleanup : await ctx . agent . subscribeResource ( resource . id , uri ) ,
457+ } )
433458 }
434459 } ) . catch ( ( ) => { /* ignore subscription cleanup errors during reconciliation */ } )
435460 } )
@@ -440,7 +465,7 @@ function registerResourceHandlers(
440465 await runSubscriptionOperation ( async ( ) => {
441466 const active = [ ...subscriptions . values ( ) ]
442467 subscriptions . clear ( )
443- await Promise . all ( active . map ( cleanup => cleanup ( ) ) )
468+ await Promise . all ( active . map ( subscription => subscription . cleanup ( ) ) )
444469 } )
445470 }
446471}
@@ -520,7 +545,13 @@ function parseResourceUri(uri: string): { kind: 'resource', id: string } | { kin
520545 if ( ! match )
521546 return { kind : 'unknown' }
522547 const [ , kind , rest ] = match
523- const decoded = decodeURIComponent ( rest ! )
548+ let decoded : string
549+ try {
550+ decoded = decodeURIComponent ( rest ! )
551+ }
552+ catch {
553+ return { kind : 'unknown' }
554+ }
524555 if ( kind === 'resource' )
525556 return { kind : 'resource' , id : decoded }
526557 return { kind : 'state' , key : decoded }
0 commit comments