1. What's the Issue
Browser-based MCP clients (such as MCP Inspector) fail to connect to gopher-mcp servers due to two related problems:
Problem 1: OPTIONS Preflight Requests Not Handled
When a browser-based client attempts to make a cross-origin request to the MCP server, the browser first sends an OPTIONS preflight request to check if the server allows the request. The gopher-mcp server did not handle OPTIONS requests, causing the preflight to timeout or fail.
Error observed:
Access to fetch at 'http://localhost:3001/mcp' from origin 'http://localhost:6274'
has been blocked by CORS policy
Problem 2: JSON-RPC Notifications Hang Forever
After successful initialization, the client sends a notifications/initialized JSON-RPC notification. In JSON-RPC 2.0, notifications don't receive a response. However, over HTTP transport, the server must still send an HTTP response (even if the body is empty). The server was not sending any HTTP response for notifications, causing the connection to hang.
Error observed:
The notifications/initialized request would hang indefinitely waiting for a response that never came.
2. How to Reproduce
Prerequisites
- Build gopher-mcp with HTTP/SSE transport enabled
- Run an MCP server using HTTP/SSE transport
Steps to Reproduce Problem 1 (OPTIONS)
-
Start the MCP server:
./mcp_example_server # or any gopher-mcp server with HTTP/SSE transport
-
Send an OPTIONS preflight request:
curl -v -X OPTIONS http://localhost:3001/mcp \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type"
-
Expected: HTTP 204 No Content with CORS headers
-
Actual (before fix): Request times out or returns error
Steps to Reproduce Problem 2 (Notification Hang)
-
Start the MCP server:
./mcp_example_server # or any gopher-mcp server with HTTP/SSE transport
-
Send an initialize request (this works):
curl -s -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
-
Send a notification (this hangs):
curl -s --max-time 5 -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-d '{"method":"notifications/initialized","jsonrpc":"2.0"}'
-
Expected: HTTP 202 Accepted with empty body
-
Actual (before fix): Request hangs until timeout
Using MCP Inspector
- Open MCP Inspector at http://localhost:6274
- Select "Streamable HTTP" transport
- Enter URL: http://localhost:3001/mcp
- Click Connect
- Before fix: Connection fails with timeout error
3. How to Fix It
The fix involves two changes in src/filter/http_sse_filter_chain_factory.cc:
Fix 1: Add OPTIONS Preflight Handlers
Register OPTIONS handlers for all MCP-related paths:
void setupRoutingHandlers() {
// Register CORS preflight handler for all paths
auto corsHandler = [](const HttpRoutingFilter::RequestContext& req) {
HttpRoutingFilter::Response resp;
resp.status_code = 204; // No Content
resp.headers["Access-Control-Allow-Origin"] = "*";
resp.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
resp.headers["Access-Control-Allow-Headers"] =
"Content-Type, Authorization, Accept, Mcp-Session-Id, Mcp-Protocol-Version";
resp.headers["Access-Control-Max-Age"] = "86400"; // Cache for 24 hours
resp.headers["Content-Length"] = "0";
return resp;
};
// Register OPTIONS for common MCP paths
routing_filter_->registerHandler("OPTIONS", "/mcp", corsHandler);
routing_filter_->registerHandler("OPTIONS", "/mcp/events", corsHandler);
routing_filter_->registerHandler("OPTIONS", "/rpc", corsHandler);
routing_filter_->registerHandler("OPTIONS", "/health", corsHandler);
routing_filter_->registerHandler("OPTIONS", "/info", corsHandler);
// Also handle OPTIONS in default handler for any unregistered path
routing_filter_->registerDefaultHandler(
[](const HttpRoutingFilter::RequestContext& req) {
if (req.method == "OPTIONS") {
// Return 204 with CORS headers
...
}
// Pass through other methods
...
});
}
Fix 2: Send HTTP 202 for Notifications
In the onNotification handler, send an HTTP response:
void onNotification(const jsonrpc::Notification& notification) override {
mcp_callbacks_.onNotification(notification);
// For HTTP transport, send HTTP 202 Accepted response
// JSON-RPC notifications don't have responses, but HTTP requires one
if (is_server_ && write_callbacks_) {
std::string http_response =
"HTTP/1.1 202 Accepted\r\n"
"Content-Length: 0\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"
"Access-Control-Allow-Headers: Content-Type, Authorization, Accept, "
"Mcp-Session-Id, Mcp-Protocol-Version\r\n"
"Connection: keep-alive\r\n"
"\r\n";
OwnedBuffer response_buffer;
response_buffer.add(http_response);
write_callbacks_->connection().write(response_buffer, false);
}
}
Verification
After the fix:
# OPTIONS now returns 204 with CORS headers
$ curl -s -i -X OPTIONS http://localhost:3001/mcp
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, Mcp-Session-Id, Mcp-Protocol-Version
Access-Control-Max-Age: 86400
Content-Length: 0
# Notifications now return 202 immediately
$ curl -s -i -X POST http://localhost:3001/mcp \
-H "Content-Type: application/json" \
-d '{"method":"notifications/initialized","jsonrpc":"2.0"}'
HTTP/1.1 202 Accepted
Content-Length: 0
Access-Control-Allow-Origin: *
...
MCP Inspector can now successfully connect and interact with the server.
1. What's the Issue
Browser-based MCP clients (such as MCP Inspector) fail to connect to gopher-mcp servers due to two related problems:
Problem 1: OPTIONS Preflight Requests Not Handled
When a browser-based client attempts to make a cross-origin request to the MCP server, the browser first sends an OPTIONS preflight request to check if the server allows the request. The gopher-mcp server did not handle OPTIONS requests, causing the preflight to timeout or fail.
Error observed:
Problem 2: JSON-RPC Notifications Hang Forever
After successful initialization, the client sends a
notifications/initializedJSON-RPC notification. In JSON-RPC 2.0, notifications don't receive a response. However, over HTTP transport, the server must still send an HTTP response (even if the body is empty). The server was not sending any HTTP response for notifications, causing the connection to hang.Error observed:
The
notifications/initializedrequest would hang indefinitely waiting for a response that never came.2. How to Reproduce
Prerequisites
Steps to Reproduce Problem 1 (OPTIONS)
Start the MCP server:
./mcp_example_server # or any gopher-mcp server with HTTP/SSE transportSend an OPTIONS preflight request:
Expected: HTTP 204 No Content with CORS headers
Actual (before fix): Request times out or returns error
Steps to Reproduce Problem 2 (Notification Hang)
Start the MCP server:
./mcp_example_server # or any gopher-mcp server with HTTP/SSE transportSend an initialize request (this works):
Send a notification (this hangs):
Expected: HTTP 202 Accepted with empty body
Actual (before fix): Request hangs until timeout
Using MCP Inspector
3. How to Fix It
The fix involves two changes in
src/filter/http_sse_filter_chain_factory.cc:Fix 1: Add OPTIONS Preflight Handlers
Register OPTIONS handlers for all MCP-related paths:
Fix 2: Send HTTP 202 for Notifications
In the
onNotificationhandler, send an HTTP response:Verification
After the fix:
MCP Inspector can now successfully connect and interact with the server.