@@ -26,6 +26,7 @@ import type * as RpcMessage from "effect/unstable/rpc/RpcMessage"
2626import * as RpcServer from "effect/unstable/rpc/RpcServer"
2727import { makeHttpHarness } from "./TestUtils/McpHttpHarness.ts"
2828import { makeServerLayer } from "./TestUtils/McpServerLayer.ts"
29+ import { makeMcpStdioHarness } from "./TestUtils/McpStdioHarness.ts"
2930
3031const OptionalStringTool = Tool . make ( "OptionalStringTool" , {
3132 parameters : Schema . Struct ( { signature : Schema . optional ( Schema . String ) } ) ,
@@ -583,6 +584,47 @@ describe("McpServer", () => {
583584 } ) )
584585 } )
585586
587+ describe ( "list-change notification scheduling" , ( ) => {
588+ it . effect ( "should coalesce notifications when one registration kind changes repeatedly in a scheduling window" , ( ) =>
589+ Effect . gen ( function * ( ) {
590+ const fixture = yield * makeMcpStdioHarness ( McpProtocol . v2026_07_28 )
591+ const makeTool = ( name : string ) => ( {
592+ tool : new McpSchema . Tool ( { name, inputSchema : { type : "object" , properties : { } } } ) ,
593+ annotations : Context . empty ( ) ,
594+ handle : ( ) => Effect . succeed ( new McpSchema . CallToolResult ( { content : [ ] } ) )
595+ } )
596+ const makePrompt = ( name : string ) => ( {
597+ prompt : new McpSchema . Prompt ( { name } ) ,
598+ annotations : Context . empty ( ) ,
599+ completions : { } ,
600+ handle : ( ) =>
601+ Effect . succeed (
602+ new McpSchema . GetPromptResult ( {
603+ messages : [ { role : "user" , content : { type : "text" , text : name } } ]
604+ } )
605+ )
606+ } )
607+
608+ yield * fixture . server . addTool ( makeTool ( "baseline-tool" ) )
609+ yield * fixture . server . addPrompt ( makePrompt ( "baseline-prompt" ) )
610+ yield * fixture . flushListChanged
611+ yield * fixture . initialize ( )
612+ const subscription = yield * fixture . startRequest ( "subscriptions/listen" , {
613+ notifications : { toolsListChanged : true , promptsListChanged : true }
614+ } , "coalesced-list-change" )
615+ assert . strictEqual ( ( yield * fixture . takeMessage ) . method , "notifications/subscriptions/acknowledged" )
616+
617+ yield * fixture . server . addTool ( makeTool ( "coalesced-tool-first" ) )
618+ yield * fixture . server . addTool ( makeTool ( "coalesced-tool-second" ) )
619+ yield * fixture . server . addPrompt ( makePrompt ( "coalescing-sentinel" ) )
620+ yield * fixture . flushListChanged
621+
622+ assert . strictEqual ( ( yield * fixture . takeMessage ) . method , "notifications/tools/list_changed" )
623+ assert . strictEqual ( ( yield * fixture . takeMessage ) . method , "notifications/prompts/list_changed" )
624+ yield * subscription . cancel ( )
625+ } ) )
626+ } )
627+
586628 describe ( "resource subscriptions" , ( ) => {
587629 it . effect ( "should isolate resource subscriptions and clear disconnected sessions" , ( ) =>
588630 Effect . gen ( function * ( ) {
0 commit comments