@@ -296,6 +296,188 @@ func TestMCPServer_AddSessionTool(t *testing.T) {
296
296
assert .Contains (t , session .GetSessionTools (), "session-tool-helper" )
297
297
}
298
298
299
+ func TestMCPServer_AddSessionToolsUninitialized (t * testing.T ) {
300
+ // This test verifies that adding tools to an uninitialized session works correctly.
301
+ //
302
+ // This scenario can occur when tools are added during the session registration hook,
303
+ // before the session is fully initialized. In this case, we should:
304
+ // 1. Successfully add the tools to the session
305
+ // 2. Not attempt to send a notification (since the session isn't ready)
306
+ // 3. Have the tools available once the session is initialized
307
+ // 4. Not trigger any error hooks when adding tools to uninitialized sessions
308
+
309
+ // Set up error hook to track if it's called
310
+ errorChan := make (chan error )
311
+ hooks := & Hooks {}
312
+ hooks .AddOnError (
313
+ func (ctx context.Context , id any , method mcp.MCPMethod , message any , err error ) {
314
+ errorChan <- err
315
+ },
316
+ )
317
+
318
+ server := NewMCPServer ("test-server" , "1.0.0" ,
319
+ WithToolCapabilities (true ),
320
+ WithHooks (hooks ),
321
+ )
322
+ ctx := context .Background ()
323
+
324
+ // Create an uninitialized session
325
+ sessionChan := make (chan mcp.JSONRPCNotification , 1 )
326
+ session := & sessionTestClientWithTools {
327
+ sessionID : "uninitialized-session" ,
328
+ notificationChannel : sessionChan ,
329
+ initialized : false ,
330
+ }
331
+
332
+ // Register the session
333
+ err := server .RegisterSession (ctx , session )
334
+ require .NoError (t , err )
335
+
336
+ // Add session-specific tools to the uninitialized session
337
+ err = server .AddSessionTools (session .SessionID (),
338
+ ServerTool {Tool : mcp .NewTool ("uninitialized-tool" )},
339
+ )
340
+ require .NoError (t , err )
341
+
342
+ // Verify no errors
343
+ select {
344
+ case err := <- errorChan :
345
+ t .Error ("Expected no errors, but OnError called with: " , err )
346
+ case <- time .After (25 * time .Millisecond ): // no errors
347
+ }
348
+
349
+ // Verify no notification was sent (channel should be empty)
350
+ select {
351
+ case <- sessionChan :
352
+ t .Error ("Expected no notification to be sent for uninitialized session" )
353
+ default : // no notifications
354
+ }
355
+
356
+ // Verify tool was added to session
357
+ assert .Len (t , session .GetSessionTools (), 1 )
358
+ assert .Contains (t , session .GetSessionTools (), "uninitialized-tool" )
359
+
360
+ // Initialize the session
361
+ session .Initialize ()
362
+
363
+ // Now verify that subsequent tool additions will send notifications
364
+ err = server .AddSessionTools (session .SessionID (),
365
+ ServerTool {Tool : mcp .NewTool ("initialized-tool" )},
366
+ )
367
+ require .NoError (t , err )
368
+
369
+ // Verify no errors
370
+ select {
371
+ case err := <- errorChan :
372
+ t .Error ("Expected no errors, but OnError called with:" , err )
373
+ case <- time .After (200 * time .Millisecond ): // No errors
374
+ }
375
+
376
+ // Verify notification was sent for the initialized session
377
+ select {
378
+ case notification := <- sessionChan :
379
+ assert .Equal (t , "notifications/tools/list_changed" , notification .Method )
380
+ case <- time .After (100 * time .Millisecond ):
381
+ t .Error ("Timeout waiting for expected notifications/tools/list_changed notification" )
382
+ }
383
+
384
+ // Verify both tools are available
385
+ assert .Len (t , session .GetSessionTools (), 2 )
386
+ assert .Contains (t , session .GetSessionTools (), "uninitialized-tool" )
387
+ assert .Contains (t , session .GetSessionTools (), "initialized-tool" )
388
+ }
389
+
390
+ func TestMCPServer_DeleteSessionToolsUninitialized (t * testing.T ) {
391
+ // This test verifies that deleting tools from an uninitialized session works correctly.
392
+ //
393
+ // This is a bit of a weird edge case but can happen if tools are added and
394
+ // deleted during the RegisterSession hook.
395
+ //
396
+ // In this case, we should:
397
+ // 1. Successfully delete the tools from the session
398
+ // 2. Not attempt to send a notification (since the session isn't ready)
399
+ // 3. Have the tools properly deleted once the session is initialized
400
+ // 4. Not trigger any error hooks when deleting tools from uninitialized sessions
401
+
402
+ // Set up error hook to track if it's called
403
+ errorChan := make (chan error )
404
+ hooks := & Hooks {}
405
+ hooks .AddOnError (
406
+ func (ctx context.Context , id any , method mcp.MCPMethod , message any , err error ) {
407
+ errorChan <- err
408
+ },
409
+ )
410
+
411
+ server := NewMCPServer ("test-server" , "1.0.0" ,
412
+ WithToolCapabilities (true ),
413
+ WithHooks (hooks ),
414
+ )
415
+ ctx := context .Background ()
416
+
417
+ // Create an uninitialized session with some tools
418
+ sessionChan := make (chan mcp.JSONRPCNotification , 1 )
419
+ session := & sessionTestClientWithTools {
420
+ sessionID : "uninitialized-session" ,
421
+ notificationChannel : sessionChan ,
422
+ initialized : false ,
423
+ sessionTools : map [string ]ServerTool {
424
+ "tool-to-delete" : {Tool : mcp .NewTool ("tool-to-delete" )},
425
+ "tool-to-keep" : {Tool : mcp .NewTool ("tool-to-keep" )},
426
+ },
427
+ }
428
+
429
+ // Register the session
430
+ err := server .RegisterSession (ctx , session )
431
+ require .NoError (t , err )
432
+
433
+ // Delete a tool from the uninitialized session
434
+ err = server .DeleteSessionTools (session .SessionID (), "tool-to-delete" )
435
+ require .NoError (t , err )
436
+
437
+ select {
438
+ case err := <- errorChan :
439
+ t .Errorf ("Expected error hooks not to be called, got error: %v" , err )
440
+ case <- time .After (25 * time .Millisecond ): // No errors
441
+ }
442
+
443
+ // Verify no notification was sent (channel should be empty)
444
+ select {
445
+ case <- sessionChan :
446
+ t .Error ("Expected no notification to be sent for uninitialized session" )
447
+ default :
448
+ // This is the expected case - no notification should be sent
449
+ }
450
+
451
+ // Verify tool was deleted from session
452
+ assert .Len (t , session .GetSessionTools (), 1 )
453
+ assert .NotContains (t , session .GetSessionTools (), "tool-to-delete" )
454
+ assert .Contains (t , session .GetSessionTools (), "tool-to-keep" )
455
+
456
+ // Initialize the session
457
+ session .Initialize ()
458
+
459
+ // Now verify that subsequent tool deletions will send notifications
460
+ err = server .DeleteSessionTools (session .SessionID (), "tool-to-keep" )
461
+ require .NoError (t , err )
462
+
463
+ select {
464
+ case err := <- errorChan :
465
+ t .Errorf ("Expected error hooks not to be called, got error: %v" , err )
466
+ case <- time .After (200 * time .Millisecond ): // No errors
467
+ }
468
+
469
+ // Verify notification was sent for the initialized session
470
+ select {
471
+ case notification := <- sessionChan :
472
+ assert .Equal (t , "notifications/tools/list_changed" , notification .Method )
473
+ case <- time .After (100 * time .Millisecond ):
474
+ t .Error ("Expected notification not received for initialized session" )
475
+ }
476
+
477
+ // Verify all tools are deleted
478
+ assert .Len (t , session .GetSessionTools (), 0 )
479
+ }
480
+
299
481
func TestMCPServer_CallSessionTool (t * testing.T ) {
300
482
server := NewMCPServer ("test-server" , "1.0.0" , WithToolCapabilities (true ))
301
483
0 commit comments