@@ -452,6 +452,78 @@ void test_default_timeout_allows_slow_tool()
452452 server.stop ();
453453}
454454
455+ void test_notification_handling ()
456+ {
457+ std::cout << " test_notification_handling... " << std::flush;
458+
459+ const int port = 18356 ;
460+ const std::string host = " 127.0.0.1" ;
461+
462+ // Create minimal handler
463+ tools::ToolManager tool_mgr;
464+ std::unordered_map<std::string, std::string> descriptions;
465+ auto handler = mcp::make_mcp_handler (" notification_test" , " 1.0.0" , tool_mgr, descriptions);
466+
467+ server::StreamableHttpServerWrapper server (handler, host, port, " /mcp" );
468+ bool started = server.start ();
469+ assert (started && " Server failed to start" );
470+
471+ std::this_thread::sleep_for (std::chrono::milliseconds (500 ));
472+
473+ try
474+ {
475+ httplib::Client cli (host, port);
476+ cli.set_connection_timeout (5 , 0 );
477+ cli.set_read_timeout (5 , 0 );
478+
479+ // First initialize to get a session
480+ Json init_request = {{" jsonrpc" , " 2.0" },
481+ {" id" , 1 },
482+ {" method" , " initialize" },
483+ {" params" ,
484+ {{" protocolVersion" , " 2024-11-05" },
485+ {" capabilities" , Json::object ()},
486+ {" clientInfo" , {{" name" , " test" }, {" version" , " 1.0" }}}}}};
487+
488+ auto init_res = cli.Post (" /mcp" , init_request.dump (), " application/json" );
489+ assert (init_res && init_res->status == 200 );
490+
491+ std::string session_id = init_res->get_header_value (" Mcp-Session-Id" );
492+ assert (!session_id.empty () && " Should have session ID" );
493+
494+ // Now send a notification (no "id" field = notification per JSON-RPC 2.0)
495+ // JSON-RPC 2.0 spec: server MUST NOT reply to notifications
496+ Json notification = {{" jsonrpc" , " 2.0" }, {" method" , " notifications/initialized" }};
497+
498+ httplib::Headers headers = {{" Mcp-Session-Id" , session_id}};
499+ auto notif_res = cli.Post (" /mcp" , headers, notification.dump (), " application/json" );
500+
501+ // Server should return 202 Accepted with no content body
502+ assert (notif_res && " Notification request should succeed" );
503+ assert (notif_res->status == 202 && " Notification should return 202 Accepted" );
504+ assert (notif_res->body .empty () && " Notification response should have no body" );
505+
506+ // Test another common notification: notifications/cancelled
507+ Json cancel_notification = {{" jsonrpc" , " 2.0" },
508+ {" method" , " notifications/cancelled" },
509+ {" params" , {{" requestId" , " 123" }, {" reason" , " timeout" }}}};
510+
511+ auto cancel_res = cli.Post (" /mcp" , headers, cancel_notification.dump (), " application/json" );
512+ assert (cancel_res && cancel_res->status == 202 && " Cancel notification should return 202" );
513+ assert (cancel_res->body .empty () && " Cancel notification response should have no body" );
514+
515+ std::cout << " PASSED\n " ;
516+ }
517+ catch (const std::exception& e)
518+ {
519+ std::cout << " FAILED: " << e.what () << " \n " ;
520+ server.stop ();
521+ throw ;
522+ }
523+
524+ server.stop ();
525+ }
526+
455527int main ()
456528{
457529 std::cout << " Streamable HTTP Integration Tests\n " ;
@@ -465,6 +537,7 @@ int main()
465537 test_server_info ();
466538 test_error_handling ();
467539 test_default_timeout_allows_slow_tool ();
540+ test_notification_handling ();
468541
469542 std::cout << " \n All tests passed!\n " ;
470543 return 0 ;
0 commit comments