Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Backend Connections And Transports

🚚 Transport boundary: downstream listener traffic, upstream backend traffic, and config-store traffic are separate concerns. Keep them separate even when they all use TCP underneath.

Backend connections and transports

The gateway has three transport classes on the hot path. They are built in different modules, configured from different fields, and serve different architecture roles.

Transport Classes

Transport classCurrent implementationMain ownerPurpose
Downstream listenerAxum/Hyper over TCP and optional Rustls TLS.transports/ and Gateway::run_gateway.Accept MCP streamable HTTP traffic from clients or the front door.
Upstream backendShared reqwest::Client plus RMCP StreamableHttpClientTransport.common.rs, gateway/mcp_service/initialization.rs, and gateway/backend_transports.rs.Open MCP client sessions to configured backend MCP servers.
Config storeRedis plain, TLS, or mTLS connection manager.common.rs and user_config_store/.Load UserConfig and plugin runtime config from control-plane authored storage.

The current MCP dataplane only uses streamable HTTP for backend MCP traffic. BackendMCPGateway.transport already has STREAMABLEHTTP, SSE, and STDIO, but upstream routing does not branch on that field yet.

Downstream Listeners

Gateway::run_gateway builds one Axum router and can expose it through TCP, TLS, or both.

ListenerConfig fieldsBehavior
TCPaddressBinds a Tokio TcpSocket, sets reuse options and keepalive, listens with backlog 1024, and serves Axum with graceful shutdown on ctrl_c.
TLStls_address, server_certificate, server_private_keyBuilds a Rustls server config, accepts TLS by hand, then serves the same Axum router through Hyper.

TLS listener setup has two important constraints:

ConstraintWhy
tls_address requires both certificate and private key.The listener cannot build a Rustls server config without both.
tls_address cannot equal address.TCP and TLS cannot bind the same socket in this process.

The downstream TLS listener currently uses with_no_client_auth(). Client identity is established by the gateway’s bearer JWT layer, not by downstream mTLS.

Upstream Backend Client

The upstream HTTP client is built once at gateway startup:

Config
  -> reqwest::Client::try_from(&config)
  -> clone per backend initialize task
  -> StreamableHttpClientTransport::with_client(...)

The process-level upstream mode controls whether backend URLs may use plain HTTP, HTTPS, or HTTPS with client identity:

Modereqwest behavior
unsethttps_only(true). Same as TlsOnly.
TlsOnlyHTTPS backends only.
PlainTextOrTlsHTTP or HTTPS backends.
PlainTextOrMTlsHTTP or HTTPS backends, with a client identity configured for TLS handshakes.
MtlsOnlyHTTPS backends only, with a client identity configured for TLS handshakes.

If upstream_trust_bundle is configured, the PEM bundle is merged into the client’s TLS trust roots. For mTLS modes, the upstream certificate and private key are read from disk and combined into a reqwest::Identity.

Backend MCP Transport

During initialize, the selected virtual host fans out to every configured backend:

VirtualHost.backends
  -> for each backend URL
  -> build StreamableHttpClientTransportConfig
  -> serve GatewayBackendClient over StreamableHttpClientTransport
  -> store running service in BackendTransports

For HTTPS backend URLs, the gateway also sets a custom Host header from the backend URL host and optional port. HTTP backend URLs do not get this custom header in the current code.

Backend connection failures are not fatal to the whole initialize call. The gateway stores the backend entry with no running service, so list calls can continue with available backends and routed calls to that backend can fail locally.

Config-Store Transport

Redis is the current config-store transport. It is used for user config and, when runtime plugins are enabled, plugin runtime config.

Redis modeConnection behavior
PlainTextConnects to host:port over TCP.
TlsConnects with rediss://host:port and a required trust bundle.
MtlsConnects with rediss://host:port, required trust bundle, required client certificate, and required client key.

The Redis user config adapter stores:

MessagePack(User::new(claims.sub)) -> MessagePack(UserConfig)

The Redis connection manager is configured with 1,000 retries. The adapter keeps an in-process LRU cache in front of Redis, but routing code should only depend on the UserConfigStore trait.

What Should Move To Runtime Config

Transport security is mostly process config today. That keeps startup simple, but it is not the final shape for every backend-specific decision.

SettingTodayBetter long-term owner
Downstream TLS certificateProcess config.Process config. It belongs to the gateway listener.
Upstream trust bundle and mTLS identityProcess config.Runtime config per backend or referenced secret material.
Backend auth headersNot applied from UserConfig yet.Runtime config per backend.
Backend transport typeModel field exists, not routed yet.Runtime config per backend.
Header pass-through policyModel field exists, not enforced yet.Runtime config per backend or route policy.

The boundary to preserve is simple: listener code should not know Redis schema, MCP routing code should not know Redis command details, and backend transport creation should stay behind a small, explicit upstream boundary.