[Bug Fix] MCP: allow rubyui.com host in DNS-rebinding protection

Merged
DJ
3mo ago
+82-1

Fixes #481

Root cause

The remote MCP endpoint at https://rubyui.com/mcp (and www.rubyui.com/mcp) rejected every JSON-RPC request with:

{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Forbidden: Invalid Host header"}}

RubyUI::MCP::RackApp#initialize (mcp/lib/ruby_ui/mcp/rack_app.rb) builds MCP::Server::Transports::StreamableHTTPTransport (from the third-party mcp gem) without an allowed_hosts: argument. That transport has built-in DNS-rebinding protection: it validates the incoming Host header against an allow list that defaults to loopback only (127.0.0.1, ::1, localhost). Since rubyui.com was never on that list, every real request got rejected by validate_host.

This is unrelated to Rails' own HostAuthorization middleware / config.hosts — that's commented out in docs/config/environments/production.rb and isn't the cause; the rejection happens entirely inside the mcp gem's transport.

Fix

RubyUI::MCP::RackApp now passes allowed_hosts: ["rubyui.com", "www.rubyui.com"] (plus the transport's own loopback defaults) when constructing the transport, extendable via a comma-separated RUBY_UI_MCP_ALLOWED_HOSTS env var — same pattern already used for RUBY_UI_MCP_REGISTRY in mcp/lib/ruby_ui/mcp/registry.rb.

allowed_origins: was not touched: the transport treats a request as same-origin (and thus allowed) whenever Origin matches Host, and non-browser MCP clients typically send no Origin header at all, so once Host is allow-listed the existing same-origin check already covers browser-based clients hitting rubyui.com/www.rubyui.com.

Second sub-problem (redirect)

The issue also reports https://rubyui.com/mcp 301-redirecting to https://www.rubyui.com/mcp, tripping up some MCP clients mid-handshake. I searched docs/config, docs/app/controllers, docs/config/routes.rb, and docs/Procfile and found no www-redirect logic, middleware, or infra config files (no _redirects, render.yaml, Cloudflare config, etc.) anywhere in the repo. config.force_ssl = true forces HTTPS only, not a www redirect. This redirect is happening at the Cloudflare/Render layer in front of the app, outside this repo — flagging as a known limitation/follow-up for whoever manages that infra config. This PR's fix does make it less harmful in the meantime: both rubyui.com and www.rubyui.com are now allow-listed, so a client that does follow the redirect will succeed on the second hop instead of failing on Host validation either way.

Test plan

  • Added mcp/test/rack_app_test.rb: asserts requests with Host: rubyui.com, Host: www.rubyui.com, and Host: localhost are not rejected with "Invalid Host header", asserts Host: evil.example.com is still rejected (DNS-rebinding protection intact), and covers the RUBY_UI_MCP_ALLOWED_HOSTS env override.
  • cd mcp && bundle exec rake test — 30 runs, 73 assertions, 0 failures, 0 errors, 0 skips.
  • cd mcp && bundle exec standardrb lib/ruby_ui/mcp/rack_app.rb test/rack_app_test.rb — clean, no offenses. (Note: bundle exec rake standard across the whole mcp/ tree currently reports pre-existing offenses in unrelated files — mcp/ has no committed Gemfile.lock, so bundle install resolved newer standard/rubocop versions than whatever last verified the tree; unrelated to this change.)
  • docs/ was not touched: /mcp routing itself doesn't change, so no SiteFiles/site_files:generate update is needed. docs depends on ruby_ui-mcp via path: "../mcp", so this fix ships with the next docs deploy without any version bump or gem republish.

Summary by cubic

Fixes “Invalid Host header” errors on the MCP endpoint by allow-listing production hosts while keeping DNS-rebinding protection intact. Requests to https://rubyui.com/mcp and https://www.rubyui.com/mcp now work.

  • Bug Fixes
    • Pass allowed_hosts to StreamableHTTPTransport with ["rubyui.com", "www.rubyui.com"], extendable via RUBY_UI_MCP_ALLOWED_HOSTS.
    • Preserve loopback defaults; non-allowed hosts still return 403.
    • Add tests covering production hosts, loopback, rejection of other hosts, and the env var override.