#[non_exhaustive]pub struct HttpConfig {Show 13 fields
pub bind: SocketAddr,
pub path: HttpPath,
pub max_request_body: RequestBodyLimit,
pub max_concurrent_sessions: SessionLimit,
pub session_idle_timeout: IdleTimeout,
pub header_read_timeout: HeaderReadTimeout,
pub max_concurrent_connections: ConnectionLimit,
pub stream_liveness: StreamLiveness,
pub listen_lease: Option<ListenLease>,
pub allowed_origins: Box<[AllowedOrigin]>,
pub allowed_hosts: Box<[AllowedHost]>,
pub response_stream_deadline: ResponseStreamDeadline,
pub write_stall_timeout: WriteStallTimeout,
}transport-http only.Expand description
Configuration for the HTTP transport.
Passed inside Transport::Http to control the TCP bind address and the
URL path the MCP service is mounted at.
§Note on DNS rebinding
rmcp’s StreamableHttpService validates the Host header against an
allow-list. mcpls builds it from the loopback names (localhost,
127.0.0.1, ::1, any port), the bound address when it is a specific
non-loopback IP literal (any port), and
HttpConfig::allowed_hosts. There is no wildcard: a bind to 0.0.0.0
or [::] allows only the loopback names and the configured hosts, so
clients must send a Host that matches one of them, or use a reverse
proxy that rewrites the Host header.
A request carrying an Origin header is accepted only when it names a
loopback origin (localhost, 127.0.0.1, [::1]) on the bound port or
one of HttpConfig::allowed_origins; anything else, including
Origin: null, is answered with 403. A request without Origin (every
non-browser client) is unaffected.
HttpConfig::allowed_origins serves browser pages whose requests reach
mcpls with an allowed Host, through a tunnel or a reverse proxy. It does
not make a Host acceptable: the Host check runs first, so a deployment
behind a non-loopback name needs HttpConfig::allowed_hosts as well.
§Examples
use std::net::SocketAddr;
use mcpls_core::{HttpConfig, Transport};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap());
let transport = Transport::Http(cfg);Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.bind: SocketAddrTCP address to bind (e.g. 127.0.0.1:3000).
path: HttpPathURL path prefix the MCP service is mounted at (e.g. "/mcp").
The same service also answers at the root path /, regardless of
this value, so a reverse proxy must not rely on path alone to
restrict which URLs reach mcpls.
max_request_body: RequestBodyLimitMaximum size of a single POST request body.
Enforced by rmcp’s StreamableHttpService while streaming the body,
independent of Content-Length or chunked transfer encoding. Requests
exceeding this limit receive 413 Payload Too Large. Defaults to
RequestBodyLimit::DEFAULT (4 MiB), which comfortably covers MCP
tool-call request bodies (large results, e.g. from workspace/symbol
or bulk edits, are returned in the response, which this limit does not
constrain). Larger values are clamped by RequestBodyLimit::new.
max_concurrent_sessions: SessionLimitMaximum number of concurrent HTTP sessions.
This is a hard bound, enforced atomically at session creation via a
semaphore — never more than this many sessions can be active at once,
regardless of request concurrency.
Requests that would start a new session beyond this limit receive
429 Too Many Requests. Defaults to SessionLimit::DEFAULT.
session_idle_timeout: IdleTimeoutHow long a session may go without client activity before it is closed.
header_read_timeout: HeaderReadTimeoutLongest a client may take to send a complete request header, and the longest it may pause between request-body chunks. It is also how long an idle keep-alive connection stays open.
Bounds slow-header and slow-body (“slowloris”) clients: a stalled
header closes the connection, a stalled body is answered with
408 Request Timeout. A whole request body must also arrive within
four times this timeout, and within at least two minutes, so a client
trickling one chunk per window is cut too. It never interrupts a
response, so long-lived SSE streams are unaffected, and it does not
bound a client that stops reading a response
(HttpConfig::write_stall_timeout does). Defaults to
HeaderReadTimeout::DEFAULT.
max_concurrent_connections: ConnectionLimitMaximum number of concurrently open TCP connections.
Further connections wait in the kernel accept queue until one
closes. Defaults to ConnectionLimit::DEFAULT. Values above what
the underlying semaphore supports are clamped by
ConnectionLimit::new.
stream_liveness: StreamLivenessLiveness probing of each session’s standalone GET (SSE) stream.
Defaults to StreamLiveness::DEFAULT.
listen_lease: Option<ListenLease>Lifetime of stateless subscriptions/listen streams.
None (the default) follows HttpConfig::stream_liveness: the
default lease while probing, none when it is
StreamLiveness::Disabled.
allowed_origins: Box<[AllowedOrigin]>Browser origins accepted in addition to the loopback origins on the bound port. Empty by default.
allowed_hosts: Box<[AllowedHost]>Host header values accepted in addition to the loopback names and
the bound IP literal. Empty by default.
response_stream_deadline: ResponseStreamDeadlineLongest a POST or request-wise resume response stream stays open, counted from when it opens.
A total lifetime bound, not an idle timeout: the stream is cut when it
elapses however much it is still sending, which releases its hold on
the session so the idle reaper can expire it. While the connection’s
write to a peer that stopped reading is stuck the body is not polled,
so the cut cannot fire; HttpConfig::write_stall_timeout closes the
connection and frees its HttpConfig::max_concurrent_connections
permit, and the session slot is freed after the idle timeout.
Defaults to ResponseStreamDeadline::DEFAULT.
write_stall_timeout: WriteStallTimeoutLongest a write to the peer may stay stalled before the connection is closed.
Frees the connection permit of a peer that stops reading a response or
reads it too slowly: a write that goes pending opens a window of this
length, which closes when a flush completes or the peer has taken at
least 2 KiB per second of the window (between 4 KiB and 1 MiB); a
window that expires first closes the connection. A streamed (SSE)
response is flushed after every frame, so each flush closes the window
and the floor there is one frame per window, the same a quiet
legitimate subscriber gives; the rate floor binds responses written in
large buffers. Defaults to WriteStallTimeout::DEFAULT.
Implementations§
Source§impl HttpConfig
impl HttpConfig
Sourcepub fn new(bind: SocketAddr) -> Self
pub fn new(bind: SocketAddr) -> Self
Create an HttpConfig mounted at HttpPath::default with default
body-size, session, timeout and connection caps.
§Examples
use mcpls_core::HttpConfig;
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap());
assert_eq!(cfg.path.as_str(), "/mcp");Sourcepub fn with_path(self, path: HttpPath) -> Self
pub fn with_path(self, path: HttpPath) -> Self
Override the URL path the MCP service is mounted at.
§Examples
use mcpls_core::{HttpConfig, HttpPath};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_path("/api/mcp".parse::<HttpPath>().unwrap());
assert_eq!(cfg.path.as_str(), "/api/mcp");Sourcepub const fn with_max_request_body(self, limit: RequestBodyLimit) -> Self
pub const fn with_max_request_body(self, limit: RequestBodyLimit) -> Self
Override the maximum POST request body size.
§Examples
use mcpls_core::{HttpConfig, RequestBodyLimit};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_max_request_body(RequestBodyLimit::new(1024).unwrap());
assert_eq!(cfg.max_request_body.get(), 1024);Sourcepub const fn with_max_concurrent_sessions(self, max: SessionLimit) -> Self
pub const fn with_max_concurrent_sessions(self, max: SessionLimit) -> Self
Override the maximum number of concurrent HTTP sessions.
Sourcepub const fn with_header_read_timeout(self, timeout: HeaderReadTimeout) -> Self
pub const fn with_header_read_timeout(self, timeout: HeaderReadTimeout) -> Self
Override the request read (header, body pause, idle keep-alive) timeout.
Sourcepub const fn with_max_concurrent_connections(self, max: ConnectionLimit) -> Self
pub const fn with_max_concurrent_connections(self, max: ConnectionLimit) -> Self
Override the maximum number of concurrently open connections.
Sourcepub fn with_allowed_origins(
self,
origins: impl IntoIterator<Item = AllowedOrigin>,
) -> Self
pub fn with_allowed_origins( self, origins: impl IntoIterator<Item = AllowedOrigin>, ) -> Self
Accept browser requests from origins in addition to the loopback
origins on the bound port.
§Examples
use mcpls_core::{AllowedOrigin, HttpConfig};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_allowed_origins(["https://app.example.com".parse::<AllowedOrigin>().unwrap()]);
assert_eq!(cfg.allowed_origins.len(), 1);Sourcepub fn with_allowed_hosts(
self,
hosts: impl IntoIterator<Item = AllowedHost>,
) -> Self
pub fn with_allowed_hosts( self, hosts: impl IntoIterator<Item = AllowedHost>, ) -> Self
Accept requests whose Host is one of hosts in addition to the
loopback names and the bound IP literal.
Clients and proxies omit the default port from Host, so a pin to
:80 or :443 is rejected when the AllowedHost is parsed: list the
host without a port, which matches any port.
§Examples
use mcpls_core::{AllowedHost, HttpConfig};
let cfg = HttpConfig::new("0.0.0.0:3000".parse().unwrap())
.with_allowed_hosts(["mcp.example.com".parse::<AllowedHost>().unwrap()]);
assert_eq!(cfg.allowed_hosts.len(), 1);Sourcepub const fn with_write_stall_timeout(self, timeout: WriteStallTimeout) -> Self
pub const fn with_write_stall_timeout(self, timeout: WriteStallTimeout) -> Self
Override how long a write may stall before the connection is closed.
§Examples
use std::time::Duration;
use mcpls_core::{HttpConfig, WriteStallTimeout};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_write_stall_timeout(WriteStallTimeout::new(Duration::from_secs(10)).unwrap());
assert_eq!(cfg.write_stall_timeout.get(), Duration::from_secs(10));Sourcepub const fn with_response_stream_deadline(
self,
deadline: ResponseStreamDeadline,
) -> Self
pub const fn with_response_stream_deadline( self, deadline: ResponseStreamDeadline, ) -> Self
Override how long a POST or resume response stream may stay open.
§Examples
use std::time::Duration;
use mcpls_core::{HttpConfig, ResponseStreamDeadline};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_response_stream_deadline(ResponseStreamDeadline::new(Duration::from_mins(30)).unwrap());
assert_eq!(cfg.response_stream_deadline.get(), Duration::from_mins(30));Sourcepub const fn with_session_idle_timeout(self, timeout: IdleTimeout) -> Self
pub const fn with_session_idle_timeout(self, timeout: IdleTimeout) -> Self
Override how long a session may go without client activity before it is closed.
§Examples
use std::time::Duration;
use mcpls_core::HttpConfig;
use mcpls_core::transport::IdleTimeout;
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_session_idle_timeout(IdleTimeout::new(Duration::from_mins(10)).unwrap());
assert_eq!(cfg.session_idle_timeout.get(), Duration::from_mins(10));Sourcepub const fn with_stream_liveness(self, liveness: StreamLiveness) -> Self
pub const fn with_stream_liveness(self, liveness: StreamLiveness) -> Self
Override how standalone GET streams are probed for liveness.
§Examples
use mcpls_core::{HttpConfig, StreamLiveness};
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_stream_liveness(StreamLiveness::Disabled);
assert_eq!(cfg.stream_liveness, StreamLiveness::Disabled);Sourcepub const fn with_listen_lease(self, lease: ListenLease) -> Self
pub const fn with_listen_lease(self, lease: ListenLease) -> Self
Override the lifetime of stateless subscriptions/listen streams
instead of deriving it from HttpConfig::stream_liveness.
§Examples
use mcpls_core::HttpConfig;
use mcpls_core::transport::ListenLease;
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap())
.with_listen_lease(ListenLease::Unbounded);
assert_eq!(cfg.effective_listen_lease(), ListenLease::Unbounded);Sourcepub const fn effective_listen_lease(&self) -> ListenLease
pub const fn effective_listen_lease(&self) -> ListenLease
The listen lease in force: the explicit override, else the default lease while stream liveness probing is on and none when it is off.
§Examples
use mcpls_core::{HttpConfig, StreamLiveness};
use mcpls_core::transport::ListenLease;
let cfg = HttpConfig::new("127.0.0.1:3000".parse().unwrap());
assert_eq!(cfg.effective_listen_lease(), ListenLease::default());
let off = cfg.with_stream_liveness(StreamLiveness::Disabled);
assert_eq!(off.effective_listen_lease(), ListenLease::Unbounded);