Skip to content

Commit 95200b5

Browse files
authored
Merge pull request #253 from koic/support_cors_and_accept_wildcard_for_browser_based_mcp_clients
Support CORS and Accept wildcard for browser-based MCP clients
2 parents a2514a0 + e5d3b2c commit 95200b5

File tree

6 files changed

+97
-0
lines changed

6 files changed

+97
-0
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ gem "rubocop-rake", require: false
1111
gem "rubocop-shopify", ">= 2.18", require: false if RUBY_VERSION >= "3.1"
1212

1313
gem "puma", ">= 5.0.0"
14+
gem "rack-cors"
1415
gem "rackup", ">= 2.1.0"
1516

1617
gem "activesupport"

examples/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ The client will:
121121
- Provide an interactive menu to trigger notifications
122122
- Display all received SSE events in real-time
123123

124+
### Testing with MCP Inspector
125+
126+
[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is a browser-based tool for testing and debugging MCP servers.
127+
128+
1. Start the server:
129+
130+
```console
131+
$ ruby examples/streamable_http_server.rb
132+
```
133+
134+
2. Start Inspector in another terminal:
135+
136+
```console
137+
$ npx @modelcontextprotocol/inspector
138+
```
139+
140+
3. Open `http://localhost:6274` in a browser:
141+
142+
- Set Transport Type to "Streamable HTTP"
143+
- Set URL to `http://localhost:9393`
144+
- Disable the Authorization header toggle (the example server does not require authentication)
145+
- Click "Connect"
146+
147+
Once connected, you can list tools, call them, and see SSE notifications in the Inspector UI.
148+
124149
### Testing SSE with cURL
125150

126151
You can also test SSE functionality manually using cURL:

examples/http_server.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
44
require "mcp"
5+
require "rack/cors"
56
require "rackup"
67
require "json"
78
require "logger"
@@ -142,6 +143,20 @@ def template(args, server_context:)
142143

143144
# Wrap the app with Rack middleware
144145
rack_app = Rack::Builder.new do
146+
# Enable CORS to allow browser-based MCP clients (e.g., MCP Inspector)
147+
# WARNING: origins("*") allows all origins. Restrict this in production.
148+
use(Rack::Cors) do
149+
allow do
150+
origins("*")
151+
resource(
152+
"*",
153+
headers: :any,
154+
methods: [:get, :post, :delete, :options],
155+
expose: ["Mcp-Session-Id"],
156+
)
157+
end
158+
end
159+
145160
# Use CommonLogger for standard HTTP request logging
146161
use(Rack::CommonLogger, Logger.new($stdout))
147162

examples/streamable_http_server.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
44
require "mcp"
5+
require "rack/cors"
56
require "rackup"
67
require "json"
78
require "logger"
@@ -129,6 +130,20 @@ def call(message:, delay: 0)
129130

130131
# Build the Rack application with middleware
131132
rack_app = Rack::Builder.new do
133+
# Enable CORS to allow browser-based MCP clients (e.g., MCP Inspector)
134+
# WARNING: origins("*") allows all origins. Restrict this in production.
135+
use(Rack::Cors) do
136+
allow do
137+
origins("*")
138+
resource(
139+
"*",
140+
headers: :any,
141+
methods: [:get, :post, :delete, :options],
142+
expose: ["Mcp-Session-Id"],
143+
)
144+
end
145+
end
146+
132147
use(Rack::CommonLogger, Logger.new($stdout))
133148
use(Rack::ShowExceptions)
134149
run(app)

lib/mcp/server/transports/streamable_http_transport.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ def validate_accept_header(request, required_types)
193193
return not_acceptable_response(required_types) unless accept_header
194194

195195
accepted_types = parse_accept_header(accept_header)
196+
return if accepted_types.include?("*/*")
197+
196198
missing_types = required_types - accepted_types
197199
return not_acceptable_response(required_types) unless missing_types.empty?
198200

test/mcp/server/transports/streamable_http_transport_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,21 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
855855
assert_equal 200, response[0]
856856
end
857857

858+
test "POST request with Accept: */* succeeds" do
859+
request = create_rack_request_without_accept(
860+
"POST",
861+
"/",
862+
{
863+
"CONTENT_TYPE" => "application/json",
864+
"HTTP_ACCEPT" => "*/*",
865+
},
866+
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
867+
)
868+
869+
response = @transport.handle_request(request)
870+
assert_equal 200, response[0]
871+
end
872+
858873
test "GET request without Accept header returns 406" do
859874
init_request = create_rack_request(
860875
"POST",
@@ -928,6 +943,30 @@ class StreamableHTTPTransportTest < ActiveSupport::TestCase
928943
assert_equal "text/event-stream", response[1]["Content-Type"]
929944
end
930945

946+
test "GET request with Accept: */* succeeds" do
947+
init_request = create_rack_request(
948+
"POST",
949+
"/",
950+
{ "CONTENT_TYPE" => "application/json" },
951+
{ jsonrpc: "2.0", method: "initialize", id: "123" }.to_json,
952+
)
953+
init_response = @transport.handle_request(init_request)
954+
session_id = init_response[1]["Mcp-Session-Id"]
955+
956+
request = create_rack_request_without_accept(
957+
"GET",
958+
"/",
959+
{
960+
"HTTP_MCP_SESSION_ID" => session_id,
961+
"HTTP_ACCEPT" => "*/*",
962+
},
963+
)
964+
965+
response = @transport.handle_request(request)
966+
assert_equal 200, response[0]
967+
assert_equal "text/event-stream", response[1]["Content-Type"]
968+
end
969+
931970
test "stateless mode allows requests without session IDs, responding with no session ID" do
932971
stateless_transport = StreamableHTTPTransport.new(@server, stateless: true)
933972

0 commit comments

Comments
 (0)