11import path from "node:path"
2+ import { pathToFileURL } from "node:url"
23import { expect , mock , beforeEach } from "bun:test"
3- import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
4+ import { ListRootsRequestSchema , ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
45import { Cause , Effect , Exit } from "effect"
56import type { MCP as MCPNS } from "../../src/mcp/index"
67import { testEffect } from "../lib/effect"
@@ -38,6 +39,8 @@ interface MockClientState {
3839 { resources : Array < { name : string ; uri : string ; description ?: string } > ; nextCursor ?: string }
3940 >
4041 closed : boolean
42+ clientOptions ?: { capabilities ?: { roots ?: { listChanged ?: boolean } } }
43+ requestHandlers : Map < unknown , ( ...args : any [ ] ) => Promise < any > >
4144 notificationHandlers : Map < unknown , ( ...args : any [ ] ) => any >
4245}
4346
@@ -75,6 +78,7 @@ function getOrCreateClientState(name?: string): MockClientState {
7578 promptPages : { } ,
7679 resourcePages : { } ,
7780 closed : false ,
81+ requestHandlers : new Map ( ) ,
7882 notificationHandlers : new Map ( ) ,
7983 }
8084 clientStates . set ( key , state )
@@ -149,8 +153,10 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
149153 _state ! : MockClientState
150154 transport : any
151155
152- constructor ( _opts : any ) {
156+ constructor ( _info : any , options ?: MockClientState [ "clientOptions" ] ) {
153157 clientCreateCount ++
158+ this . _state = getOrCreateClientState ( lastCreatedClientName )
159+ this . _state . clientOptions = options
154160 }
155161
156162 async connect ( transport : { start : ( ) => Promise < void > } ) {
@@ -160,6 +166,10 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
160166 this . _state = getOrCreateClientState ( lastCreatedClientName )
161167 }
162168
169+ setRequestHandler ( schema : unknown , handler : ( ...args : any [ ] ) => Promise < any > ) {
170+ this . _state . requestHandlers . set ( schema , handler )
171+ }
172+
163173 setNotificationHandler ( schema : unknown , handler : ( ...args : any [ ] ) => any ) {
164174 this . _state ?. notificationHandlers . set ( schema , handler )
165175 }
@@ -251,6 +261,28 @@ function statusName(status: Record<string, MCPNS.Status> | MCPNS.Status, server:
251261 return status [ server ] ?. status
252262}
253263
264+ it . instance (
265+ "advertises and lists the instance directory as its root" ,
266+ ( ) =>
267+ MCP . Service . use ( ( mcp : MCPNS . Interface ) =>
268+ Effect . gen ( function * ( ) {
269+ const { directory } = yield * TestInstance
270+ lastCreatedClientName = "roots"
271+ yield * mcp . add ( "roots" , { type : "local" , command : [ "echo" , "test" ] } )
272+
273+ const state = getOrCreateClientState ( "roots" )
274+ expect ( state . clientOptions ?. capabilities ?. roots ) . toEqual ( { } )
275+ expect ( state . clientOptions ?. capabilities ?. roots ?. listChanged ) . toBeUndefined ( )
276+
277+ const handler = state . requestHandlers . get ( ListRootsRequestSchema )
278+ expect ( handler ) . toBeDefined ( )
279+ const result = yield * Effect . promise ( ( ) => handler ?.( ) ?? Promise . reject ( new Error ( "roots handler missing" ) ) )
280+ expect ( result ) . toEqual ( { roots : [ { uri : pathToFileURL ( directory ) . href } ] } )
281+ } ) ,
282+ ) ,
283+ { config : { mcp : { } } } ,
284+ )
285+
254286it . instance (
255287 "local mcp cwd resolves relative paths against instance directory" ,
256288 ( ) =>
0 commit comments