Skip to content

[Bug]: StreamableHTTP transport sends spec-incorrect Origin header, causing 403 from GitHub's remote MCP server #140

@ebeigarts

Description

@ebeigarts

Summary

RubyLLM::MCP::Native::Transports::StreamableHTTP#build_common_headers sets:

headers["Origin"] = @url.to_s

https://github.com/patvice/ruby_llm-mcp/blob/v1.0.0/lib/ruby_llm/mcp/native/transports/streamable_http.rb#L277

This is incorrect on two counts, and it causes GitHub's remote MCP server (api.githubcopilot.com/mcp) to reject every request with HTTP 403.

Why the current behavior is wrong

  1. RFC 6454 §7 defines Origin as a user-agent-only header — "The user agent MAY include an Origin header field in any HTTP request." Server-to-server HTTP clients should not send it. Its value must also be scheme + host (+ port), not a full URL with a path.
  2. The MCP spec (Streamable HTTP transport, revisions 2025-03-26, 2025-06-18, and draft) only mandates server-side Origin validation for DNS-rebinding protection. It does not require or recommend clients send Origin. The draft explicitly says: "If the Origin header is present and invalid, servers MUST respond with 403 Forbidden."
  3. The MCP authorization spec never references the HTTP Origin header.
  4. The reference MCP SDKs (TypeScript and Python) do not set Origin on server-to-server fetches.

Assigning the full endpoint URL (including the path /mcp) as the Origin value is invalid per RFC 6454.

Reproduction

Any client using ruby_llm-mcp 1.0.0 connecting to GitHub's remote MCP server at https://api.githubcopilot.com/mcp with a valid OAuth token fails:

Outgoing request:

POST https://api.githubcopilot.com/mcp
user-agent: httpx.rb/1.7.2
accept: application/json, text/event-stream
x-client-id: 67267522-1918-4cc3-90e2-bbdbe416fef8
origin: https://api.githubcopilot.com/mcp      ← invalid: includes a path
authorization: Bearer ghu_...
content-type: application/json

Response:

HTTP/2 403
server: github-mcp-server-remote
content-type: text/plain; charset=utf-8
content-length: 120

cross-origin request detected, and/or browser is out of date: Sec-Fetch-Site is missing, and Origin does not match Host

Root cause (server side)

GitHub's MCP server uses Go's net/http.CrossOriginProtection.Check() (or equivalent), which performs (simplified):

  1. If Sec-Fetch-Site is same-origin or none → allow.
  2. Else if Origin is absent → allow (non-browser assumed).
  3. Else if url.Parse(origin).Host == req.Host → allow.
  4. Else → reject with the 403 above.

Even with the path stripped (so Origin: https://api.githubcopilot.com), url.Parse(origin).Host is api.githubcopilot.com but req.Host in Go may include the default port (api.githubcopilot.com:443), so the string comparison still fails. The robust fix is to simply not send the header.

Workaround (monkey-patch)

We ship the following initializer in our Rails app until this is fixed upstream:

module RubyLLMMCPOriginHeaderFix
  def build_common_headers
    super.except("Origin")
  end
end
RubyLLM::MCP::Native::Transports::StreamableHTTP.prepend(RubyLLMMCPOriginHeaderFix)

This resolves the 403 against GitHub's MCP server and is spec-compliant (clients are not required to send Origin).

Suggested fix

Remove line 277 of lib/ruby_llm/mcp/native/transports/streamable_http.rb. Origin should not be sent on server-to-server requests. If you want to keep it for completeness (e.g. for MCP servers that do happen to allow-list client origins), at minimum strip it to scheme+host(+non-default port), never include a path — but omitting it entirely matches the reference SDKs and RFC 6454.

Environment

  • ruby_llm-mcp 1.0.0
  • Ruby 3.4.9
  • Target: https://api.githubcopilot.com/mcp (GitHub remote MCP server)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions