From 0797ad58e564427b4edaddf140c309788f8f02e9 Mon Sep 17 00:00:00 2001 From: polaon <> Date: Tue, 21 Apr 2026 17:52:38 +0200 Subject: [PATCH 1/7] Streamable HTTP server wrapper fixes. --- src/server/streamable_http_server.cpp | 38 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index 52cd2cd..3b375f3 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -117,7 +117,8 @@ bool StreamableHttpServerWrapper::start() svr_->Options(mcp_path_, [this](const httplib::Request&, httplib::Response& res) { - res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS"); + res.set_header("Access-Control-Allow-Methods", + "GET, POST, DELETE, OPTIONS"); res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization, Mcp-Session-Id"); apply_additional_response_headers(res); @@ -129,6 +130,11 @@ bool StreamableHttpServerWrapper::start() mcp_path_, [this](const httplib::Request& req, httplib::Response& res) { + // Apply CORS / additional headers up-front so they are present on every + // response, including early returns (401, 503, 400, 404) and any exception + // propagated to the catch handlers below. + apply_additional_response_headers(res); + try { // Security: Check authentication if configured @@ -143,8 +149,6 @@ bool StreamableHttpServerWrapper::start() } } - apply_additional_response_headers(res); - // Parse JSON-RPC message auto message = fastmcpp::util::json::parse(req.body); @@ -336,8 +340,13 @@ bool StreamableHttpServerWrapper::start() // Handle GET request to return 405 Method Not Allowed svr_->Get(mcp_path_, - [](const httplib::Request&, httplib::Response& res) + [this](const httplib::Request&, httplib::Response& res) { + // CORS / additional headers must be applied on every response, including + // this 405. Without this, browsers reject the response with a misleading + // "No 'Access-Control-Allow-Origin' header is present" error. + apply_additional_response_headers(res); + res.status = 405; res.set_header("Allow", "POST"); res.set_header("Content-Type", "application/json"); @@ -349,6 +358,27 @@ bool StreamableHttpServerWrapper::start() res.set_content(error_response.dump(), "application/json"); }); + // Handle DELETE request for session termination (MCP Streamable HTTP spec). + // Without this handler, httplib would fall back to its default 404 response, + // which does not carry the configured CORS headers - causing browsers to report + // a "No 'Access-Control-Allow-Origin' header is present" error. + svr_->Delete(mcp_path_, + [this](const httplib::Request& req, httplib::Response& res) + { + apply_additional_response_headers(res); + + // If an Mcp-Session-Id header is provided, terminate that session. + auto session_it = req.headers.find("Mcp-Session-Id"); + if (session_it != req.headers.end()) + { + const std::string& session_id = session_it->second; + std::lock_guard lock(sessions_mutex_); + sessions_.erase(session_id); + } + + res.status = 204; // No Content + }); + running_ = true; thread_ = std::thread([this]() { run_server(); }); From 0a278d279dca13a0bc36c67164e17f3ceb41dde1 Mon Sep 17 00:00:00 2001 From: polaon <> Date: Tue, 21 Apr 2026 17:57:36 +0200 Subject: [PATCH 2/7] Mcp-Session-Id header fix. --- src/server/streamable_http_server.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index 3b375f3..d4c24c6 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -135,6 +135,12 @@ bool StreamableHttpServerWrapper::start() // propagated to the catch handlers below. apply_additional_response_headers(res); + // Expose response headers that cross-origin JS clients legitimately need to + // read. Without this, browsers hide Mcp-Session-Id from response.headers.get() + // even though it is sent on the wire, because browsers only expose a small + // whitelist of "safe" response headers by default. + res.set_header("Access-Control-Expose-Headers", "Mcp-Session-Id"); + try { // Security: Check authentication if configured From 22e3be26339b1a3f90882023abfd6b9849a4473a Mon Sep 17 00:00:00 2001 From: polaon <> Date: Tue, 21 Apr 2026 18:21:54 +0200 Subject: [PATCH 3/7] Formatting. --- src/server/streamable_http_server.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index d4c24c6..3c4b148 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -117,8 +117,7 @@ bool StreamableHttpServerWrapper::start() svr_->Options(mcp_path_, [this](const httplib::Request&, httplib::Response& res) { - res.set_header("Access-Control-Allow-Methods", - "GET, POST, DELETE, OPTIONS"); + res.set_header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization, Mcp-Session-Id"); apply_additional_response_headers(res); From 784cd9c5fab504b4575f97286954cc61ca82cc38 Mon Sep 17 00:00:00 2001 From: polaon <> Date: Wed, 22 Apr 2026 17:40:35 +0200 Subject: [PATCH 4/7] Better error handling in the DELETE method. --- src/server/streamable_http_server.cpp | 31 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index 3c4b148..13fd7f7 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -353,12 +352,12 @@ bool StreamableHttpServerWrapper::start() apply_additional_response_headers(res); res.status = 405; - res.set_header("Allow", "POST"); + res.set_header("Allow", "POST, DELETE, OPTIONS"); res.set_header("Content-Type", "application/json"); fastmcpp::Json error_response = { {"error", "Method Not Allowed"}, - {"message", "The MCP endpoint only supports POST requests."}}; + {"message", "The MCP endpoint only supports POST and DELETE requests."}}; res.set_content(error_response.dump(), "application/json"); }); @@ -372,16 +371,32 @@ bool StreamableHttpServerWrapper::start() { apply_additional_response_headers(res); - // If an Mcp-Session-Id header is provided, terminate that session. auto session_it = req.headers.find("Mcp-Session-Id"); - if (session_it != req.headers.end()) + if (session_it == req.headers.end() || session_it->second.empty()) + { + res.status = 400; + res.set_content("{\"error\":\"Mcp-Session-Id header required\"}", + "application/json"); + return; + } + + const std::string& session_id = session_it->second; + bool did_remove = false; { - const std::string& session_id = session_it->second; std::lock_guard lock(sessions_mutex_); - sessions_.erase(session_id); + did_remove = sessions_.erase(session_id) > 0; } - res.status = 204; // No Content + if (did_remove) + { + res.status = 204; // No Content + } + else + { + res.status = 404; + res.set_content("{\"error\":\"Invalid or expired session\"}", + "application/json"); + } }); running_ = true; From a0567801389d8c5616bc2d3b29209a8b4c23c9d0 Mon Sep 17 00:00:00 2001 From: polaon <> Date: Wed, 22 Apr 2026 17:46:31 +0200 Subject: [PATCH 5/7] Fix error response. --- src/server/streamable_http_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index 13fd7f7..51d7fbf 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -357,7 +357,7 @@ bool StreamableHttpServerWrapper::start() fastmcpp::Json error_response = { {"error", "Method Not Allowed"}, - {"message", "The MCP endpoint only supports POST and DELETE requests."}}; + {"message", "The MCP endpoint only supports POST, DELETE, and OPTIONS requests."}}; res.set_content(error_response.dump(), "application/json"); }); From cffff649acd33a4fe373d9d8789e3a17aed3f96d Mon Sep 17 00:00:00 2001 From: polaon <> Date: Wed, 22 Apr 2026 17:47:38 +0200 Subject: [PATCH 6/7] Formatting. --- src/server/streamable_http_server.cpp | 37 ++++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index 51d7fbf..e7252cc 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -343,24 +343,25 @@ bool StreamableHttpServerWrapper::start() }); // Handle GET request to return 405 Method Not Allowed - svr_->Get(mcp_path_, - [this](const httplib::Request&, httplib::Response& res) - { - // CORS / additional headers must be applied on every response, including - // this 405. Without this, browsers reject the response with a misleading - // "No 'Access-Control-Allow-Origin' header is present" error. - apply_additional_response_headers(res); - - res.status = 405; - res.set_header("Allow", "POST, DELETE, OPTIONS"); - res.set_header("Content-Type", "application/json"); - - fastmcpp::Json error_response = { - {"error", "Method Not Allowed"}, - {"message", "The MCP endpoint only supports POST, DELETE, and OPTIONS requests."}}; - - res.set_content(error_response.dump(), "application/json"); - }); + svr_->Get( + mcp_path_, + [this](const httplib::Request&, httplib::Response& res) + { + // CORS / additional headers must be applied on every response, including + // this 405. Without this, browsers reject the response with a misleading + // "No 'Access-Control-Allow-Origin' header is present" error. + apply_additional_response_headers(res); + + res.status = 405; + res.set_header("Allow", "POST, DELETE, OPTIONS"); + res.set_header("Content-Type", "application/json"); + + fastmcpp::Json error_response = { + {"error", "Method Not Allowed"}, + {"message", "The MCP endpoint only supports POST, DELETE, and OPTIONS requests."}}; + + res.set_content(error_response.dump(), "application/json"); + }); // Handle DELETE request for session termination (MCP Streamable HTTP spec). // Without this handler, httplib would fall back to its default 404 response, From 339034d4b8cfe3c9d1f1cb5011015033b930de37 Mon Sep 17 00:00:00 2001 From: polaon <> Date: Wed, 22 Apr 2026 17:57:47 +0200 Subject: [PATCH 7/7] Authentication check for the DELETE method. --- src/server/streamable_http_server.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/server/streamable_http_server.cpp b/src/server/streamable_http_server.cpp index e7252cc..0646e85 100644 --- a/src/server/streamable_http_server.cpp +++ b/src/server/streamable_http_server.cpp @@ -372,6 +372,18 @@ bool StreamableHttpServerWrapper::start() { apply_additional_response_headers(res); + // Security: Check authentication if configured + if (!auth_token_.empty()) + { + auto auth_it = req.headers.find("Authorization"); + if (auth_it == req.headers.end() || !check_auth(auth_it->second)) + { + res.status = 401; + res.set_content("{\"error\":\"Unauthorized\"}", "application/json"); + return; + } + } + auto session_it = req.headers.find("Mcp-Session-Id"); if (session_it == req.headers.end() || session_it->second.empty()) {