1313#include < ostream>
1414#include < sstream>
1515#include < stdexcept>
16+ #include < stop_token>
1617#include < string>
1718#include < string_view>
1819#include < thread>
20+ #include < unordered_map>
1921#include < utility>
2022#include < vector>
2123
@@ -33,7 +35,7 @@ struct ServerInfo {
3335 bool tools_list_changed{false };
3436};
3537
36- template <bool EnableThreadSafety = false >
38+ template <bool EnableThreadSafety = true >
3739class BasicMcpStdioServer {
3840public:
3941 using RuntimeType = runtime::BasicRuntimeFacade<EnableThreadSafety>;
@@ -49,28 +51,8 @@ class BasicMcpStdioServer {
4951 {
5052 }
5153
52- std::optional<json> handleMessage (const json& message)
53- {
54- validateBaseMessage (message);
55-
56- const std::string method = message.at (" method" ).get <std::string>();
57- const auto params_it = message.find (" params" );
58- const json& params = params_it == message.end () ? empty_object () : *params_it;
59-
60- const auto id_it = message.find (" id" );
61- if (id_it == message.end ())
62- {
63- handleNotification (method, params);
64- return std::nullopt ;
65- }
66-
67- return handleRequest (*id_it, method, params);
68- }
69-
7054 void serveConcurrent (std::istream& input, std::ostream& output)
7155 {
72- static_assert (EnableThreadSafety, " serveConcurrent requires EnableThreadSafety=true" );
73-
7456 ResponseQueue response_queue;
7557 auto enqueue_response = [&response_queue](json response) {
7658 response_queue.push (std::move (response));
@@ -231,6 +213,16 @@ class BasicMcpStdioServer {
231213 return ;
232214 }
233215
216+ if (method == " notifications/cancelled" )
217+ {
218+ const auto id_it = params.find (" requestId" );
219+ if (id_it != params.end () && (id_it->is_string () || id_it->is_number ()))
220+ {
221+ cancelInFlightRequest (id_it->dump ());
222+ }
223+ return ;
224+ }
225+
234226 if (!params.is_object ())
235227 {
236228 throw std::runtime_error (" notification params must be a JSON object" );
@@ -352,12 +344,12 @@ class BasicMcpStdioServer {
352344 return error_response;
353345 }
354346
355- const auto invoke_result = runtime_.invoke (runtime::InvokeRequest{
347+ auto handle = runtime_.submitInvoke (runtime::InvokeRequest{
356348 std::move (request->name ),
357349 std::move (request->arguments ),
358350 });
359351
360- return makeToolsCallResponse (id, invoke_result );
352+ return makeToolsCallResponse (id, handle. future . get () );
361353 }
362354
363355 struct ToolCallRequest {
@@ -467,18 +459,30 @@ class BasicMcpStdioServer {
467459 }
468460
469461 const json id = *id_it;
470- auto invoke_future = runtime_.submitInvoke (runtime::InvokeRequest{
462+ const std::string id_key = id.dump ();
463+ auto handle = runtime_.submitInvoke (runtime::InvokeRequest{
471464 std::move (request->name ),
472465 std::move (request->arguments ),
473466 });
474467
468+ registerInFlightHandle (id_key, handle.stop_source );
469+
475470 try
476471 {
477472 completion_tasks.push_back (std::async (std::launch::async,
478- [this , id, invoke_future = std::move (invoke_future), enqueue_response]() mutable {
473+ [this , id, id_key, handle = std::move (handle), enqueue_response]() mutable {
474+ auto result = handle.future .get ();
475+ unregisterInFlightHandle (id_key);
476+
477+ if (result.error .has_value () &&
478+ (result.error ->code == " cancelled" || result.error ->code == " result_discarded" ))
479+ {
480+ return ;
481+ }
482+
479483 try
480484 {
481- enqueue_response (makeToolsCallResponse (id, invoke_future. get () ));
485+ enqueue_response (makeToolsCallResponse (id, result ));
482486 }
483487 catch (const std::exception& e)
484488 {
@@ -492,6 +496,7 @@ class BasicMcpStdioServer {
492496 }
493497 catch (const std::exception& e)
494498 {
499+ unregisterInFlightHandle (id_key);
495500 enqueue_response (makeErrorResponse (id, -32603 , e.what ()));
496501 }
497502 }
@@ -626,14 +631,38 @@ class BasicMcpStdioServer {
626631 value = std::string (begin, end);
627632 }
628633
634+ void registerInFlightHandle (const std::string& id_key, std::stop_source stop_source)
635+ {
636+ std::lock_guard<std::mutex> lock (in_flight_mutex_);
637+ in_flight_handles_.emplace (id_key, std::move (stop_source));
638+ }
639+
640+ void unregisterInFlightHandle (const std::string& id_key)
641+ {
642+ std::lock_guard<std::mutex> lock (in_flight_mutex_);
643+ in_flight_handles_.erase (id_key);
644+ }
645+
646+ void cancelInFlightRequest (const std::string& id_key)
647+ {
648+ std::lock_guard<std::mutex> lock (in_flight_mutex_);
649+ const auto it = in_flight_handles_.find (id_key);
650+ if (it != in_flight_handles_.end ())
651+ {
652+ it->second .request_stop ();
653+ }
654+ }
655+
629656 RuntimeType& runtime_;
630657 ServerInfo server_info_{};
631658 mutable std::mutex state_mutex_;
632659 bool initialize_seen_{false };
633660 bool initialized_notification_seen_{false };
661+ mutable std::mutex in_flight_mutex_;
662+ std::unordered_map<std::string, std::stop_source> in_flight_handles_;
634663};
635664
665+ using McpStdioServer = BasicMcpStdioServer<true >;
636666using McpStdioServerThreadSafe = BasicMcpStdioServer<true >;
637- using McpStdioServerUnsafe = BasicMcpStdioServer<false >;
638667
639668} // namespace mcp
0 commit comments