@@ -14,7 +14,8 @@ import {
14
14
LoggingMessageNotificationSchema ,
15
15
Notification ,
16
16
TextContent ,
17
- ElicitRequestSchema
17
+ ElicitRequestSchema ,
18
+ SetLevelRequestSchema
18
19
} from '../types.js' ;
19
20
import { ResourceTemplate } from './mcp.js' ;
20
21
import { completable } from './completable.js' ;
@@ -319,6 +320,62 @@ describe('Context', () => {
319
320
expect ( parsed . userId ) . toBe ( 'user-123' ) ;
320
321
expect ( parsed . attempt ) . toBe ( 1 ) ;
321
322
} ) ;
323
+
324
+ const logLevelsThroughContext = [ 'debug' , 'info' , 'warning' , 'error' ] as const ;
325
+
326
+ //it.each for each log level, test that logging message is sent to client
327
+ it . each ( logLevelsThroughContext ) ( 'should send logging message to client for %s level from Context' , async ( level ) => {
328
+ const mcpServer = new McpServer ( { name : 'ctx-test' , version : '1.0' } , {
329
+ capabilities : {
330
+ logging : { }
331
+ }
332
+ } ) ;
333
+ const client = new Client ( { name : 'ctx-client' , version : '1.0' } , {
334
+ capabilities : {
335
+ logging : { }
336
+ }
337
+ } ) ;
338
+
339
+ let seen = 0 ;
340
+
341
+ client . setNotificationHandler ( LoggingMessageNotificationSchema , ( notification ) => {
342
+ seen ++ ;
343
+ expect ( notification . params . level ) . toBe ( level ) ;
344
+ expect ( notification . params . data ) . toBe ( 'Test message' ) ;
345
+ expect ( notification . params . test ) . toBe ( 'test' ) ;
346
+ expect ( notification . params . sessionId ) . toBe ( 'sample-session-id' ) ;
347
+ return ;
348
+ } ) ;
349
+
350
+
351
+ mcpServer . tool ( 'ctx-log-test' , { name : z . string ( ) } , async ( _args : { name : string } , extra ) => {
352
+ await extra [ level ] ( 'Test message' , { test : 'test' } , 'sample-session-id' ) ;
353
+ await extra . log ( {
354
+ level,
355
+ data : 'Test message' ,
356
+ logger : 'test-logger-namespace'
357
+ } , 'sample-session-id' ) ;
358
+ return { content : [ { type : 'text' , text : 'ok' } ] } ;
359
+ } ) ;
360
+
361
+ const [ clientTransport , serverTransport ] = InMemoryTransport . createLinkedPair ( ) ;
362
+ await Promise . all ( [ client . connect ( clientTransport ) , mcpServer . server . connect ( serverTransport ) ] ) ;
363
+
364
+ const result = await client . request (
365
+ {
366
+ method : 'tools/call' ,
367
+ params : { name : 'ctx-log-test' , arguments : { name : 'ctx-log-test-name' } }
368
+ } ,
369
+ CallToolResultSchema
370
+ ) ;
371
+
372
+ // two messages should have been sent - one from the .log method and one from the .debug/info/warning/error method
373
+ expect ( seen ) . toBe ( 2 ) ;
374
+
375
+ expect ( result . content ) . toHaveLength ( 1 ) ;
376
+ expect ( result . content [ 0 ] . type ) . toBe ( 'text' ) ;
377
+ expect ( result . content [ 0 ] . text ) . toBe ( 'ok' ) ;
378
+ } ) ;
322
379
} ) ;
323
380
324
381
describe ( 'ResourceTemplate' , ( ) => {
@@ -4132,6 +4189,115 @@ describe('elicitInput()', () => {
4132
4189
) ;
4133
4190
} ) ;
4134
4191
4192
+ test ( 'should be able to call elicit from Context' , async ( ) => {
4193
+ mcpServer . tool (
4194
+ 'elicit-through-ctx-tool' ,
4195
+ {
4196
+ restaurant : z . string ( ) ,
4197
+ date : z . string ( ) ,
4198
+ partySize : z . number ( )
4199
+ } ,
4200
+ async ( { restaurant, date, partySize } , extra ) => {
4201
+ // Check availability
4202
+ const available = await checkAvailability ( restaurant , date , partySize ) ;
4203
+
4204
+ if ( ! available ) {
4205
+ // Ask user if they want to try alternative dates
4206
+ const result = await extra . elicit ( {
4207
+ message : `No tables available at ${ restaurant } on ${ date } . Would you like to check alternative dates?` ,
4208
+ requestedSchema : {
4209
+ type : 'object' ,
4210
+ properties : {
4211
+ checkAlternatives : {
4212
+ type : 'boolean' ,
4213
+ title : 'Check alternative dates' ,
4214
+ description : 'Would you like me to check other dates?'
4215
+ } ,
4216
+ flexibleDates : {
4217
+ type : 'string' ,
4218
+ title : 'Date flexibility' ,
4219
+ description : 'How flexible are your dates?' ,
4220
+ enum : [ 'next_day' , 'same_week' , 'next_week' ] ,
4221
+ enumNames : [ 'Next day' , 'Same week' , 'Next week' ]
4222
+ }
4223
+ } ,
4224
+ required : [ 'checkAlternatives' ]
4225
+ }
4226
+ } ) ;
4227
+
4228
+ if ( result . action === 'accept' && result . content ?. checkAlternatives ) {
4229
+ const alternatives = await findAlternatives ( restaurant , date , partySize , result . content . flexibleDates as string ) ;
4230
+ return {
4231
+ content : [
4232
+ {
4233
+ type : 'text' ,
4234
+ text : `Found these alternatives: ${ alternatives . join ( ', ' ) } `
4235
+ }
4236
+ ]
4237
+ } ;
4238
+ }
4239
+
4240
+ return {
4241
+ content : [
4242
+ {
4243
+ type : 'text' ,
4244
+ text : 'No booking made. Original date not available.'
4245
+ }
4246
+ ]
4247
+ } ;
4248
+ }
4249
+
4250
+ await makeBooking ( restaurant , date , partySize ) ;
4251
+ return {
4252
+ content : [
4253
+ {
4254
+ type : 'text' ,
4255
+ text : `Booked table for ${ partySize } at ${ restaurant } on ${ date } `
4256
+ }
4257
+ ]
4258
+ } ;
4259
+ }
4260
+ ) ;
4261
+
4262
+ checkAvailability . mockResolvedValue ( false ) ;
4263
+ findAlternatives . mockResolvedValue ( [ '2024-12-26' , '2024-12-27' , '2024-12-28' ] ) ;
4264
+
4265
+ // Set up client to accept alternative date checking
4266
+ client . setRequestHandler ( ElicitRequestSchema , async request => {
4267
+ expect ( request . params . message ) . toContain ( 'No tables available at ABC Restaurant on 2024-12-25' ) ;
4268
+ return {
4269
+ action : 'accept' ,
4270
+ content : {
4271
+ checkAlternatives : true ,
4272
+ flexibleDates : 'same_week'
4273
+ }
4274
+ } ;
4275
+ } ) ;
4276
+
4277
+ const [ clientTransport , serverTransport ] = InMemoryTransport . createLinkedPair ( ) ;
4278
+
4279
+ await Promise . all ( [ client . connect ( clientTransport ) , mcpServer . server . connect ( serverTransport ) ] ) ;
4280
+
4281
+ // Call the tool
4282
+ const result = await client . callTool ( {
4283
+ name : 'book-restaurant' ,
4284
+ arguments : {
4285
+ restaurant : 'ABC Restaurant' ,
4286
+ date : '2024-12-25' ,
4287
+ partySize : 2
4288
+ }
4289
+ } ) ;
4290
+
4291
+ expect ( checkAvailability ) . toHaveBeenCalledWith ( 'ABC Restaurant' , '2024-12-25' , 2 ) ;
4292
+ expect ( findAlternatives ) . toHaveBeenCalledWith ( 'ABC Restaurant' , '2024-12-25' , 2 , 'same_week' ) ;
4293
+ expect ( result . content ) . toEqual ( [
4294
+ {
4295
+ type : 'text' ,
4296
+ text : 'Found these alternatives: 2024-12-26, 2024-12-27, 2024-12-28'
4297
+ }
4298
+ ] ) ;
4299
+ } ) ;
4300
+
4135
4301
test ( 'should successfully elicit additional information' , async ( ) => {
4136
4302
// Mock availability check to return false
4137
4303
checkAvailability . mockResolvedValue ( false ) ;
0 commit comments