Skip to content

[Bug]: MCP Gateway Doesn't Detect HTTPS/TLS Context or respect X-Forwarded-Proto when using Federation #424

@crivetimihai

Description

@crivetimihai

Bug Report: MCP Gateway Doesn't Detect HTTPS/TLS Context and Hardcodes HTTP URLs

Summary

MCP Gateway hardcodes http:// protocol in generated URLs and doesn't detect when it's running behind HTTPS (via Gunicorn with TLS or a reverse proxy). While the gateway can be deployed with TLS using Gunicorn's SSL support, internal URL generation still uses http:// causing protocol mismatches and federation issues.

This ticket suggests adding a GATEWAY_PROTOCOL=http|https|auto(default)

When Does This Bug Affect You?

1. Federation Scenarios (federation/discovery.py issue)

Affected when:

  • You have multiple MCP Gateways that need to communicate
  • You enable FEDERATION_ENABLED=true
  • You want gateways to share tools/resources

Example scenario:

Gateway A (HTTPS) ──federation──> Gateway B (HTTPS)
         ↓
   Generates http:// URL for registration ❌

NOT affected when:

  • Running a single standalone gateway
  • Adding MCP servers to the gateway
  • Federation is disabled (default)

What is Federation in MCP Gateway?

Federation is about connecting multiple MCP Gateways together to share resources. It's NOT about:

  • Adding MCP servers to the gateway (that's just normal server registration)
  • Clients accessing the gateway via HTTP/SSE/WebSocket

Federation allows:

  • Gateway A to discover Gateway B's tools/resources
  • Clients connected to Gateway A to use tools from Gateway B
  • Automatic peer discovery and synchronization

2. SSE Client Connections (sse_transport.py issue)

Affected when:

  • Clients connect using SSE transport
  • The gateway runs with HTTPS
  • The SSE endpoint URL sent to clients has wrong protocol

Example:

# Client connects to https://gateway.com/sse
# Gateway responds with endpoint URL: http://gateway.com/message?session_id=xxx ❌
# Should be: https://gateway.com/message?session_id=xxx ✓

NOT affected when:

  • Using HTTP transport (not SSE)
  • Using WebSocket transport
  • Running everything as HTTP (no TLS)

Do You Need to Worry?

Probably NOT if you:

  • Run a single gateway (no federation)
  • Use HTTP or WebSocket transport (not SSE)
  • Have FEDERATION_ENABLED=false (default)
  • Run everything as HTTP

You SHOULD care if you:

  • Want to federate multiple gateways over HTTPS
  • Use SSE transport with HTTPS
  • Need proper HTTPS URLs in responses

Quick Check

Run this to see if you're affected:

# Check the current settings
grep -E "FEDERATION_ENABLED|TRANSPORT_TYPE" .env

# If you see:
# FEDERATION_ENABLED=false  → Not affected by federation bug
# TRANSPORT_TYPE=http       → Not affected by SSE bug
# TRANSPORT_TYPE=ws         → Not affected by SSE bug

Most users running a single gateway to aggregate MCP servers won't hit this bug at all!


Current Behavior

When running the gateway with TLS enabled through Gunicorn:

SSL=true CERT_FILE=certs/cert.pem KEY_FILE=certs/key.pem ./run-gunicorn.sh

The gateway:

  1. Successfully serves HTTPS requests
  2. But generates HTTP URLs in:
    • Federation discovery: url = f"http://{addresses[0]}:{port}"
    • SSE transport base URL: f"http://{settings.host}:{settings.port}"
    • Documentation examples
    • Default allowed origins

Expected Behavior

The gateway should:

  1. Detect when it's running with TLS (via Gunicorn SSL or reverse proxy)
  2. Generate URLs with the correct protocol (https:// when using TLS)
  3. Support both HTTP and HTTPS deployments without code changes
  4. Respect X-Forwarded-Proto headers from reverse proxies

Impact

  • Severity: Medium
  • Components Affected: Federation, SSE Transport, URL generation
  • User Impact:
    • Federation fails when HTTPS gateway tries to register with http:// URLs
    • SSE clients receive incorrect endpoint URLs
    • Mixed protocol issues in federated deployments

Root Cause Analysis

1. No Protocol Detection

The gateway doesn't detect:

  • When Gunicorn is running with SSL certificates
  • X-Forwarded-Proto headers from reverse proxies
  • The actual protocol being used for incoming requests

2. Hardcoded Protocol in URL Generation

Multiple modules hardcode http://:

# federation/discovery.py - Line ~211
url = f"http://{addresses[0]}:{port}"

# transports/sse_transport.py - Line ~30
self._base_url = base_url or f"http://{settings.host}:{settings.port}"

# config.py - Default allowed origins
allowed_origins: Set[str] = {
    "http://localhost",
    "http://localhost:4444",
}

3. Missing Request Context

URL generation happens without access to the current request context, so it can't determine the actual protocol being used.

Proposed Solutions

Solution 1: Protocol Detection via Request Context (Recommended)

Detect protocol from the current request and use it for URL generation.

Implementation Steps:

  1. Pass request context to URL generation methods

  2. Check for HTTPS indicators:

    def get_protocol_from_request(request: Request) -> str:
        # Check X-Forwarded-Proto header
        forwarded_proto = request.headers.get("X-Forwarded-Proto")
        if forwarded_proto:
            return forwarded_proto
        
        # Check if request is secure
        if request.url.scheme == "https":
            return "https"
        
        # Check for other proxy headers
        if request.headers.get("X-Forwarded-SSL") == "on":
            return "https"
        
        return "http"
  3. Update URL generation to use detected protocol:

    # In SSE transport
    def __init__(self, request: Request, base_url: str = None):
        protocol = get_protocol_from_request(request)
        self._base_url = base_url or f"{protocol}://{settings.host}:{settings.port}"

Pros:

  • Automatic detection works with any deployment
  • No configuration needed
  • Works with reverse proxies

Cons:

  • Requires request context in more places
  • May need refactoring

Solution 2: Configuration-Based Protocol

Add optional protocol configuration that defaults to auto-detection.

Implementation Steps:

  1. Add configuration:

    # Protocol for generated URLs (http, https, or auto)
    gateway_protocol: str = "auto"
    
    @property
    def base_url(self) -> str:
        protocol = self.gateway_protocol
        if protocol == "auto":
            # Default to http, but can be overridden by request context
            protocol = "http"
        return f"{protocol}://{self.host}:{self.port}"
  2. Allow environment override:

    GATEWAY_PROTOCOL=https  # Force HTTPS URLs

Pros:

  • Simple to implement
  • Explicit control when needed
  • Backward compatible

Cons:

  • Requires manual configuration
  • Doesn't handle mixed deployments well

Solution 3: External URL Configuration

Allow explicit configuration of the external URL.

Implementation Steps:

  1. Add configuration:
    # External URL for the gateway (auto-detected if not set)
    external_url: Optional[str] = None
    
    @property
    def base_url(self) -> str:
        return self.external_url or f"http://{self.host}:{self.port}"

Pros:

  • Most flexible
  • Handles complex deployments

Cons:

  • Requires manual configuration
  • Another setting to manage

Recommended Fix

Implement Solution 1 (Protocol Detection) with Solution 2 as fallback:

  1. Update SSE Transport to accept request context:

    class SSETransport(Transport):
        def __init__(self, request: Request = None, base_url: str = None):
            if base_url:
                self._base_url = base_url
            elif request:
                protocol = get_protocol_from_request(request)
                self._base_url = f"{protocol}://{settings.host}:{settings.port}"
            else:
                # Fallback to configuration
                self._base_url = settings.base_url
  2. Add Protocol Detection Helper:

    # In utils or transport base
    def get_protocol_from_request(request: Request) -> str:
        """Detect protocol from request headers and context."""
        # Implementation as shown above
  3. Update Federation Discovery:

    # For DNS-SD, add protocol hint to service properties
    properties={
        "name": settings.app_name,
        "version": "1.0.0",
        "protocol": PROTOCOL_VERSION,
        "scheme": "https" if running_with_tls() else "http",
    }
  4. Add Optional Configuration:

    # For cases where auto-detection isn't sufficient
    gateway_protocol: str = Field(
        default="auto",
        pattern="^(http|https|auto)$",
        description="Protocol for generated URLs (http, https, or auto)"
    )

Workaround

Until fixed, when running with TLS:

  1. Ensure federated peers use full HTTPS URLs in FEDERATION_PEERS
  2. Configure reverse proxy to handle protocol translation
  3. Use explicit base_url parameter where possible

Test Cases

  1. Gateway with Gunicorn SSL generates HTTPS URLs
  2. Gateway behind HTTPS reverse proxy detects protocol correctly
  3. Direct HTTP access still generates HTTP URLs
  4. Federation between HTTP and HTTPS gateways works correctly
  5. SSE endpoint URL matches the protocol used to access the gateway
  6. X-Forwarded-Proto header is respected

References

  • Gunicorn SSL support: run-gunicorn.sh
  • URL generation: mcpgateway/federation/discovery.py, mcpgateway/transports/sse_transport.py
  • Configuration: mcpgateway/config.py

Metadata

Metadata

Labels

bugSomething isn't workingtriageIssues / Features awaiting triage

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions