Skip to main content

HttpConfig

Struct HttpConfig 

Source
#[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,
}
Available on crate feature 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
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§bind: SocketAddr

TCP address to bind (e.g. 127.0.0.1:3000).

§path: HttpPath

URL 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: RequestBodyLimit

Maximum 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: SessionLimit

Maximum 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: IdleTimeout

How long a session may go without client activity before it is closed.

§header_read_timeout: HeaderReadTimeout

Longest 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: ConnectionLimit

Maximum 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: StreamLiveness

Liveness 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: ResponseStreamDeadline

Longest 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: WriteStallTimeout

Longest 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

Source

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");
Source

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");
Source

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);
Source

pub const fn with_max_concurrent_sessions(self, max: SessionLimit) -> Self

Override the maximum number of concurrent HTTP sessions.

Source

pub const fn with_header_read_timeout(self, timeout: HeaderReadTimeout) -> Self

Override the request read (header, body pause, idle keep-alive) timeout.

Source

pub const fn with_max_concurrent_connections(self, max: ConnectionLimit) -> Self

Override the maximum number of concurrently open connections.

Source

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);
Source

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);
Source

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));
Source

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));
Source

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));
Source

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);
Source

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);
Source

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);

Trait Implementations§

Source§

impl Clone for HttpConfig

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for HttpConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more