Skip to main content

flymcprust_core/transport/
http.rs

1//! Streamable HTTP transport, stateless (MCP 2026-07-28).
2//!
3//! [`StreamableHttpService`] is a `tower::Service<http::Request<B>>`, so it
4//! can be mounted in any tower-based framework, e.g. with axum:
5//! `Router::new().nest_service("/mcp", StreamableHttpService::new(server))`.
6//! [`Server::serve_http`] and [`serve`] run it on `hyper` directly.
7//!
8//! Request handling, in order:
9//! 1. `Origin` must be allowed (403); `Host` must be allowed (403).
10//! 2. Only `POST` (405); `OPTIONS` answers CORS preflights from allowed origins.
11//! 3. `Content-Type: application/json` (415); `Accept` must allow JSON (406).
12//! 4. At most [`HttpConfig::max_in_flight`] requests are processed at once,
13//!    and their bodies may total at most
14//!    [`HttpConfig::max_body_bytes_in_flight`] (503 beyond either); the body
15//!    is read up to the message limit (413) within
16//!    [`HttpConfig::body_timeout`] (408).
17//! 5. `MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name` and recognized
18//!    `Mcp-Param-*` headers must match the body (400, `-32020`).
19//! 6. The message is dispatched; notifications get `202`, unknown methods
20//!    `404`, malformed requests `400`, everything else `200`.
21//!
22//! `Last-Event-ID` is ignored: streams are not resumable. Dropping the
23//! response future (client disconnect) cancels a 2026-07-28 request.
24//!
25//! Legacy clients (2025-11-25, 2025-06-18) open with `initialize` and get a
26//! signed `MCP-Session-Id` that carries what they declared, so any instance
27//! sharing the `request_state_key` serves them without server-side session
28//! state. Their requests skip the 2026-07-28 header checks. When a handler
29//! asks for input, the response becomes an SSE stream carrying the
30//! server-to-client request; the client answers with a POST that is routed
31//! back to the waiting handler. `DELETE` gets `405`: sessions end when their
32//! token expires.
33
34use std::borrow::Cow;
35use std::convert::Infallible;
36use std::future::Future;
37use std::io;
38use std::pin::Pin;
39use std::sync::Arc;
40use std::task::{Context, Poll};
41use std::time::Duration;
42
43use base64::Engine as _;
44use bytes::Bytes;
45use http::header::{self, HeaderMap, HeaderValue};
46use http::{Method, Request, Response, StatusCode};
47use http_body::Body;
48use http_body_util::{BodyExt as _, Full};
49use serde::Deserialize;
50use serde_json::Value;
51use serde_json::value::RawValue;
52use tokio::net::TcpListener;
53use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc, oneshot};
54use tokio_util::sync::{CancellationToken, DropGuard};
55
56use super::oauth::Challenge;
57pub use super::oauth::{OAuth, OAuthBuilder, OAuthError, ProtectedResourceMetadata};
58use crate::auth::AuthContext;
59use crate::context::TransportContext;
60use crate::error::{ErrorKind, McpError};
61use crate::hints::{self, Once};
62use crate::jsonrpc::{self, Message, Rejection, Response as RpcResponse};
63use crate::legacy::{self, ChannelPeer, LegacySession, PendingRequests};
64use crate::mirror::ParamKind;
65use crate::notify::NotifyChannel;
66use crate::server::{McpRequest, Server};
67use crate::spec::{RequestId, methods};
68
69/// The HTTP response body: a complete JSON body, or an SSE stream (legacy
70/// clients asked for input).
71pub struct HttpBody(BodyKind);
72
73enum BodyKind {
74    Full(Full<Bytes>),
75    Stream(Pin<Box<dyn Body<Data = Bytes, Error = Infallible> + Send>>),
76}
77
78impl HttpBody {
79    pub(crate) fn full(bytes: impl Into<Bytes>) -> Self {
80        Self(BodyKind::Full(Full::new(bytes.into())))
81    }
82}
83
84impl std::fmt::Debug for HttpBody {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match &self.0 {
87            BodyKind::Full(_) => f.write_str("HttpBody::Full"),
88            BodyKind::Stream(_) => f.write_str("HttpBody::Stream"),
89        }
90    }
91}
92
93impl Body for HttpBody {
94    type Data = Bytes;
95    type Error = Infallible;
96
97    fn poll_frame(
98        self: Pin<&mut Self>,
99        cx: &mut Context<'_>,
100    ) -> Poll<Option<Result<http_body::Frame<Bytes>, Infallible>>> {
101        match &mut self.get_mut().0 {
102            BodyKind::Full(body) => Pin::new(body).poll_frame(cx),
103            BodyKind::Stream(body) => body.as_mut().poll_frame(cx),
104        }
105    }
106
107    fn is_end_stream(&self) -> bool {
108        match &self.0 {
109            BodyKind::Full(body) => body.is_end_stream(),
110            BodyKind::Stream(body) => body.is_end_stream(),
111        }
112    }
113
114    fn size_hint(&self) -> http_body::SizeHint {
115        match &self.0 {
116            BodyKind::Full(body) => body.size_hint(),
117            BodyKind::Stream(body) => body.size_hint(),
118        }
119    }
120}
121
122const MCP_PROTOCOL_VERSION: &str = "mcp-protocol-version";
123const MCP_METHOD: &str = "mcp-method";
124const MCP_NAME: &str = "mcp-name";
125const MCP_PARAM_PREFIX: &str = "mcp-param-";
126const MCP_SESSION_ID: &str = "mcp-session-id";
127const BASE64_PREFIX: &str = "=?base64?";
128const BASE64_SUFFIX: &str = "?=";
129const ALLOWED_REQUEST_HEADERS: &str =
130    "content-type, accept, authorization, mcp-protocol-version, mcp-method, mcp-name";
131
132/// `Origin` values accepted by the transport.
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[non_exhaustive]
135pub enum AllowedOrigins {
136    /// Origins whose host is `localhost`, `127.0.0.1` or `[::1]` (any
137    /// scheme and port). The default.
138    Localhost,
139    /// Exactly these origins (e.g. `https://app.example.com`).
140    List(Vec<String>),
141    /// Any origin. Only safe behind other protections such as
142    /// authentication; it disables DNS-rebinding protection.
143    Any,
144}
145
146/// `Host` values accepted by the transport.
147#[derive(Debug, Clone, PartialEq, Eq)]
148#[non_exhaustive]
149pub enum AllowedHosts {
150    /// `localhost`, `127.0.0.1` or `[::1]`, on any port. The default.
151    Localhost,
152    /// Exactly these host names (ports are ignored).
153    List(Vec<String>),
154    /// Any host.
155    Any,
156}
157
158/// Security settings of the Streamable HTTP transport.
159#[derive(Debug, Clone)]
160pub struct HttpConfig {
161    oauth: Option<OAuth>,
162    origins: AllowedOrigins,
163    hosts: AllowedHosts,
164    header_timeout: Duration,
165    body_timeout: Duration,
166    max_in_flight: usize,
167    max_body_bytes_in_flight: usize,
168    max_connections: usize,
169}
170
171impl Default for HttpConfig {
172    fn default() -> Self {
173        Self {
174            oauth: None,
175            origins: AllowedOrigins::Localhost,
176            hosts: AllowedHosts::Localhost,
177            header_timeout: Duration::from_secs(10),
178            body_timeout: Duration::from_secs(30),
179            max_in_flight: 4096,
180            max_body_bytes_in_flight: 64 * 1024 * 1024,
181            max_connections: 4096,
182        }
183    }
184}
185
186impl HttpConfig {
187    /// Secure defaults: localhost origins and hosts only.
188    #[must_use]
189    pub fn new() -> Self {
190        Self::default()
191    }
192
193    /// Requires OAuth 2.1 access tokens on every request (the server acts
194    /// as a resource server). See [`OAuth`].
195    #[must_use]
196    pub fn oauth(mut self, oauth: OAuth) -> Self {
197        self.oauth = Some(oauth);
198        self
199    }
200
201    /// Sets the accepted `Origin` values.
202    #[must_use]
203    pub fn allowed_origins(mut self, origins: AllowedOrigins) -> Self {
204        self.origins = origins;
205        self
206    }
207
208    /// Sets the accepted `Host` values. Public deployments must list their
209    /// own host names (or use [`AllowedHosts::Any`]).
210    #[must_use]
211    pub fn allowed_hosts(mut self, hosts: AllowedHosts) -> Self {
212        self.hosts = hosts;
213        self
214    }
215
216    /// Time allowed, in [`serve`], to receive a request's headers, counted
217    /// from when the connection opens or becomes idle. Slower or idle
218    /// connections are closed (slow-loris protection). Default: 10 s.
219    #[must_use]
220    pub fn header_timeout(mut self, timeout: Duration) -> Self {
221        self.header_timeout = timeout;
222        self
223    }
224
225    /// Time allowed to receive a request body; slower clients get `408`.
226    /// Default: 30 s.
227    #[must_use]
228    pub fn body_timeout(mut self, timeout: Duration) -> Self {
229        self.body_timeout = timeout;
230        self
231    }
232
233    /// Maximum number of requests being read or processed at once. Further
234    /// requests get `503`. Default: 4096. Memory is bounded separately by
235    /// [`max_body_bytes_in_flight`](Self::max_body_bytes_in_flight), so
236    /// requests that wait on other services can be numerous.
237    #[must_use]
238    pub fn max_in_flight(mut self, requests: usize) -> Self {
239        self.max_in_flight = requests.max(1);
240        self
241    }
242
243    /// Maximum total size of the request bodies held at once (being read,
244    /// parsed or handled). A request reserves its `Content-Length`, or the
245    /// whole message limit when the length is unknown, and gets `503` when
246    /// the budget is spent. Values below the message limit are raised to it.
247    /// Default: 64 MiB.
248    #[must_use]
249    pub fn max_body_bytes_in_flight(mut self, bytes: usize) -> Self {
250        self.max_body_bytes_in_flight = bytes;
251        self
252    }
253
254    /// Maximum number of open connections in [`serve`]; further connections
255    /// wait in the operating system's accept queue. Default: 4096.
256    #[must_use]
257    pub fn max_connections(mut self, connections: usize) -> Self {
258        self.max_connections = connections.max(1);
259        self
260    }
261}
262
263/// Streamable HTTP as a `tower::Service`.
264#[derive(Clone, Debug)]
265pub struct StreamableHttpService {
266    server: Server,
267    config: Arc<HttpConfig>,
268    in_flight: Arc<Semaphore>,
269    /// One permit per byte of request body held (see
270    /// [`HttpConfig::max_body_bytes_in_flight`]).
271    body_bytes: Arc<Semaphore>,
272    hints: Arc<Hints>,
273    /// Server-to-client requests of legacy sessions awaiting an answer.
274    pending: Arc<PendingRequests>,
275}
276
277/// Which developer hints were already logged.
278#[derive(Debug, Default)]
279struct Hints {
280    origin: Once,
281    host: Once,
282    busy: Once,
283    slow_body: Once,
284    too_large: Once,
285    session: Once,
286}
287
288impl StreamableHttpService {
289    /// Wraps a server with the default (localhost-only) security settings.
290    #[must_use]
291    pub fn new(server: Server) -> Self {
292        Self::with_config(server, HttpConfig::default())
293    }
294
295    /// Wraps a server with explicit security settings.
296    #[must_use]
297    pub fn with_config(server: Server, config: HttpConfig) -> Self {
298        match &config.oauth {
299            None if server.has_scopes() => tracing::warn!(
300                "handlers declare OAuth scopes, but this HTTP service does not require OAuth: \
301                 the scopes are not enforced\n  = help: add HttpConfig::new().oauth(OAuth::\
302                 builder(..)..build()?), or remove the scopes"
303            ),
304            Some(oauth) => {
305                let host = oauth
306                    .resource()
307                    .split_once("://")
308                    .map(|(_, rest)| rest.split(['/', '?']).next().unwrap_or_default())
309                    .unwrap_or_default();
310                if !host_name_allowed(&config.hosts, strip_port(host)) {
311                    tracing::warn!(
312                        "the OAuth resource {} is not on an allowed host, so its requests \
313                         will get HTTP 403\n  = help: list the host with \
314                         HttpConfig::allowed_hosts(AllowedHosts::List(..))",
315                        oauth.resource()
316                    );
317                }
318            }
319            None => {}
320        }
321        // A budget smaller than one message would refuse legitimate
322        // requests forever.
323        let body_budget = config
324            .max_body_bytes_in_flight
325            .max(server.limits().max_message_bytes)
326            .min(Semaphore::MAX_PERMITS);
327        Self {
328            body_bytes: Arc::new(Semaphore::new(body_budget)),
329            server,
330            in_flight: Arc::new(Semaphore::new(config.max_in_flight)),
331            config: Arc::new(config),
332            hints: Arc::default(),
333            pending: Arc::default(),
334        }
335    }
336
337    async fn handle<B>(self, request: Request<B>) -> Response<HttpBody>
338    where
339        B: Body,
340        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
341    {
342        let (mut parts, body) = request.into_parts();
343
344        let cors = match parts.headers.get(header::ORIGIN) {
345            None => None,
346            Some(origin) if origin_allowed(&self.config.origins, origin) => Some(origin.clone()),
347            Some(origin) => {
348                hints::warn_once(
349                    &self.hints.origin,
350                    format_args!(
351                        "rejected a request from Origin {} (HTTP 403)",
352                        hints::client_text(origin.to_str().unwrap_or("<not ASCII>"))
353                    ),
354                    match self.config.origins {
355                        AllowedOrigins::Localhost => {
356                            "only localhost origins are accepted by default (DNS-rebinding \
357                             protection); to allow a web app, list it: \
358                             HttpConfig::new().allowed_origins(AllowedOrigins::List(vec![\
359                             \"https://app.example.com\".into()]))"
360                        }
361                        _ => "add the origin to HttpConfig::allowed_origins if it is legitimate",
362                    },
363                );
364                return rpc_error(
365                    StatusCode::FORBIDDEN,
366                    None,
367                    &McpError::invalid_request("Origin not allowed"),
368                    None,
369                );
370            }
371        };
372        if !host_allowed(&self.config.hosts, &parts) {
373            let host = parts
374                .headers
375                .get(header::HOST)
376                .and_then(|h| h.to_str().ok())
377                .or_else(|| parts.uri.authority().map(http::uri::Authority::as_str))
378                .unwrap_or("<none>");
379            hints::warn_once(
380                &self.hints.host,
381                format_args!(
382                    "rejected a request for Host {} (HTTP 403)",
383                    hints::client_text(host)
384                ),
385                match self.config.hosts {
386                    AllowedHosts::Localhost => {
387                        "only localhost is accepted by default (DNS-rebinding protection); to \
388                         serve other machines, list your host names: \
389                         HttpConfig::new().allowed_hosts(AllowedHosts::List(vec![\
390                         \"mcp.example.com\".into()]))"
391                    }
392                    _ => "add the host name to HttpConfig::allowed_hosts if it is legitimate",
393                },
394            );
395            return rpc_error(
396                StatusCode::FORBIDDEN,
397                None,
398                &McpError::invalid_request("Host not allowed"),
399                None,
400            );
401        }
402
403        match parts.method {
404            Method::POST => {}
405            Method::OPTIONS if cors.is_some() => return preflight(cors, &parts.headers),
406            _ => {
407                let mut response = plain(StatusCode::METHOD_NOT_ALLOWED, cors.as_ref());
408                response
409                    .headers_mut()
410                    .insert(header::ALLOW, HeaderValue::from_static("POST"));
411                return response;
412            }
413        }
414
415        if !is_json_content_type(parts.headers.get(header::CONTENT_TYPE)) {
416            return rpc_error(
417                StatusCode::UNSUPPORTED_MEDIA_TYPE,
418                None,
419                &McpError::invalid_request("Content-Type must be application/json"),
420                cors.as_ref(),
421            );
422        }
423        if !accepts_json(&parts.headers) {
424            return rpc_error(
425                StatusCode::NOT_ACCEPTABLE,
426                None,
427                &McpError::invalid_request("Accept must allow application/json"),
428                cors.as_ref(),
429            );
430        }
431
432        // Bounds the memory held by bodies being read or processed.
433        let Ok(permit) = Arc::clone(&self.in_flight).try_acquire_owned() else {
434            hints::warn_once(
435                &self.hints.busy,
436                format_args!(
437                    "rejected a request: {} requests already in flight (HTTP 503)",
438                    self.config.max_in_flight
439                ),
440                "raise HttpConfig::max_in_flight(..) if the machine has room, or run more \
441                 instances",
442            );
443            return busy(cors.as_ref());
444        };
445
446        // Authorization comes before the body is read: unauthenticated
447        // clients cannot make the server buffer their messages.
448        let auth = match &self.config.oauth {
449            None => None,
450            Some(oauth) => match oauth.authenticate(&parts.headers).await {
451                Ok(auth) => {
452                    // Handlers see the principal, never the token: they
453                    // cannot pass it through to other services by mistake.
454                    parts.headers.remove(header::AUTHORIZATION);
455                    Some(auth)
456                }
457                Err(challenge) => return challenge_response(oauth, &challenge, cors.as_ref()),
458            },
459        };
460
461        let limits = self.server.limits();
462        let declared = parts
463            .headers
464            .get(header::CONTENT_LENGTH)
465            .and_then(|v| v.to_str().ok())
466            .and_then(|v| v.parse::<u64>().ok());
467        let too_large = || {
468            hints::warn_once(
469                &self.hints.too_large,
470                format_args!(
471                    "rejected a request body over the {}-byte message limit (HTTP 413)",
472                    limits.max_message_bytes
473                ),
474                "raise ServerBuilder::max_message_bytes(..) if clients legitimately                  send larger arguments",
475            );
476            rpc_error(
477                StatusCode::PAYLOAD_TOO_LARGE,
478                None,
479                &McpError::invalid_request("Message too large"),
480                cors.as_ref(),
481            )
482        };
483        if declared.is_some_and(|len| len > limits.max_message_bytes as u64) {
484            return too_large();
485        }
486        // Each frame reserves its size in the memory budget as it arrives,
487        // so a client that sends slowly holds only what it sent.
488        let read = read_body(body, limits.max_message_bytes, Arc::clone(&self.body_bytes));
489        let Ok(read) = tokio::time::timeout(self.config.body_timeout, read).await else {
490            hints::warn_once(
491                &self.hints.slow_body,
492                format_args!(
493                    "a client did not send its request body within {:?} (HTTP 408)",
494                    self.config.body_timeout
495                ),
496                "raise HttpConfig::body_timeout(..) if clients on slow networks are expected",
497            );
498            return rpc_error(
499                StatusCode::REQUEST_TIMEOUT,
500                None,
501                &McpError::invalid_request("Request body not received in time"),
502                cors.as_ref(),
503            );
504        };
505        // The reservation lasts as long as this request is handled.
506        let (bytes, _body_permit) = match read {
507            Ok(read) => read,
508            Err(BodyError::TooLarge) => return too_large(),
509            Err(BodyError::Busy) => {
510                hints::warn_once(
511                    &self.hints.busy,
512                    format_args!(
513                        "rejected a request: request bodies in flight reached {} bytes                          (HTTP 503)",
514                        self.config.max_body_bytes_in_flight
515                    ),
516                    "raise HttpConfig::max_body_bytes_in_flight(..) if the machine has memory                      to spare, or run more instances",
517                );
518                return busy(cors.as_ref());
519            }
520            Err(BodyError::Read) => {
521                return rpc_error(
522                    StatusCode::BAD_REQUEST,
523                    None,
524                    &McpError::invalid_request("Could not read the request body"),
525                    cors.as_ref(),
526                );
527            }
528        };
529
530        // Legacy clients: a session token, or a legacy version header.
531        let legacy = match self.legacy_session(&parts.headers, auth.as_ref()) {
532            Ok(legacy) => legacy,
533            Err(response) => return *response,
534        };
535
536        let message = match jsonrpc::parse(&bytes, limits) {
537            Ok(message) => message,
538            Err(Rejection::Error { id, error }) => {
539                return rpc_error(status_for(&error), id, &error, cors.as_ref());
540            }
541            Err(Rejection::UnexpectedResponse) if legacy.is_some() => {
542                // A legacy client answering one of our requests.
543                let owner = session_owner(&parts.headers);
544                let routed = jsonrpc::parse_client_response(&bytes)
545                    .is_some_and(|response| self.pending.complete(response, owner));
546                return if routed {
547                    plain(StatusCode::ACCEPTED, cors.as_ref())
548                } else {
549                    rpc_error(
550                        StatusCode::BAD_REQUEST,
551                        None,
552                        &McpError::invalid_request("The response answers no pending request"),
553                        cors.as_ref(),
554                    )
555                };
556            }
557            Err(Rejection::UnexpectedResponse) => {
558                return rpc_error(
559                    StatusCode::BAD_REQUEST,
560                    None,
561                    &McpError::invalid_request("Clients must not send JSON-RPC responses"),
562                    cors.as_ref(),
563                );
564            }
565        };
566
567        // A body that declares 2026-07-28 in `_meta` follows 2026-07-28 rules
568        // (including header/body agreement), whatever its version header.
569        let legacy = legacy.filter(|_| {
570            parts.headers.contains_key(MCP_SESSION_ID) || !declares_modern_meta(&message)
571        });
572
573        // `initialize` opens a legacy session, unless the request declares
574        // 2026-07-28 (legacy clients never send the version header with
575        // `initialize`): for that protocol the method does not exist.
576        let modern = parts
577            .headers
578            .get(MCP_PROTOCOL_VERSION)
579            .and_then(|v| v.to_str().ok())
580            .is_some_and(|v| self.server.supported_versions().iter().any(|s| s == v));
581        if let Message::Request(request) = &message
582            && request.method == "initialize"
583        {
584            if self.server.legacy_clients_enabled() && !modern {
585                return self.legacy_initialize(request, cors.as_ref(), auth.as_ref());
586            }
587            if modern {
588                return rpc_error(
589                    StatusCode::NOT_FOUND,
590                    Some(request.id.clone()),
591                    &McpError::method_not_found_explained(
592                        "Method not found: `initialize` is not part of MCP 2026-07-28",
593                    ),
594                    cors.as_ref(),
595                );
596            }
597        }
598        if let Some(session) = legacy {
599            let mut transport = TransportContext::new();
600            let owner = session_owner(&parts.headers);
601            transport.insert(parts);
602            if let Some(auth) = auth {
603                transport.insert(auth);
604            }
605            return self
606                .handle_legacy(message, session, owner, transport, permit, cors)
607                .await;
608        }
609
610        // A legacy client's `initialize` carries none of the modern headers;
611        // let the server answer it with an error naming the supported
612        // versions (the spec recommends that diagnostic for legacy clients).
613        let mut wants_notifications = false;
614        if let Message::Request(request) = &message
615            && request.method != "initialize"
616        {
617            match validate_headers(&self.server, &parts.headers, request) {
618                Ok(wants) => wants_notifications = wants,
619                Err(error) => {
620                    return rpc_error(
621                        StatusCode::BAD_REQUEST,
622                        Some(request.id.clone()),
623                        &error,
624                        cors.as_ref(),
625                    );
626                }
627            }
628        }
629
630        let mut transport = TransportContext::new();
631        transport.insert(parts);
632        if let Some(auth) = auth {
633            transport.insert(auth);
634        }
635        if wants_notifications {
636            // The client asked for progress or log messages: they can only
637            // travel on an SSE response, which closing cancels the request.
638            let cancellation = CancellationToken::new();
639            let guard = cancellation.clone().drop_guard();
640            let server = self.server.clone();
641            return self
642                .respond_streaming(permit, cors, Some(guard), move |out| {
643                    let mut transport = transport;
644                    transport.insert(NotifyChannel(out));
645                    Box::pin(async move {
646                        server
647                            .handle(McpRequest {
648                                message,
649                                transport,
650                                cancellation,
651                            })
652                            .await
653                    })
654                })
655                .await;
656        }
657        let response = self
658            .server
659            .handle(McpRequest {
660                message,
661                transport,
662                cancellation: CancellationToken::new(),
663            })
664            .await;
665        self.reply(response, cors.as_ref())
666    }
667
668    /// The HTTP response for a JSON-RPC response (`None`: a notification).
669    /// Scope errors carry an `insufficient_scope` challenge.
670    fn reply(
671        &self,
672        response: Option<RpcResponse>,
673        cors: Option<&HeaderValue>,
674    ) -> Response<HttpBody> {
675        let Some(response) = response else {
676            return plain(StatusCode::ACCEPTED, cors);
677        };
678        let status = response.as_error().map_or(StatusCode::OK, status_for);
679        let challenge = match (&self.config.oauth, response.as_error()) {
680            (Some(oauth), Some(error)) if error.kind() == ErrorKind::Forbidden => oauth
681                .www_authenticate(&Challenge::InsufficientScope(
682                    error.required_scopes().join(" "),
683                )),
684            _ => None,
685        };
686        let mut http = json(status, response.to_vec(), cors);
687        if let Some(challenge) = challenge {
688            http.headers_mut()
689                .insert(header::WWW_AUTHENTICATE, challenge);
690        }
691        http
692    }
693}
694
695impl StreamableHttpService {
696    /// The legacy session of a request, if it is a legacy request. A token
697    /// that fails verification (or expired) gets `404`, which tells the
698    /// client to initialize again. A 2026-07-28 version header always means
699    /// a 2026-07-28 request.
700    #[allow(clippy::result_large_err)]
701    fn legacy_session(
702        &self,
703        headers: &HeaderMap,
704        auth: Option<&AuthContext>,
705    ) -> Result<Option<LegacySession>, Box<Response<HttpBody>>> {
706        if !self.server.legacy_clients_enabled() {
707            return Ok(None);
708        }
709        let version = headers
710            .get(MCP_PROTOCOL_VERSION)
711            .and_then(|v| v.to_str().ok());
712        if version.is_some_and(|v| self.server.supported_versions().iter().any(|s| s == v)) {
713            return Ok(None);
714        }
715        match headers.get(MCP_SESSION_ID).and_then(|v| v.to_str().ok()) {
716            Some(token) => match self
717                .server
718                .open_session(token)
719                // A session opened with one user's token is not another's.
720                .filter(|s| s.principal.as_deref() == auth.map(|a| a.digest.as_str()))
721            {
722                Some(session) => Ok(Some(session)),
723                None => {
724                    hints::warn_once(
725                        &self.hints.session,
726                        format_args!("rejected an unknown or expired MCP-Session-Id (HTTP 404)"),
727                        "the client will initialize again; if this server runs as several \
728                         instances, give all of them the same ServerBuilder::request_state_key(..)",
729                    );
730                    Err(Box::new(rpc_error(
731                        StatusCode::NOT_FOUND,
732                        None,
733                        &McpError::invalid_request("Session not found or expired"),
734                        None,
735                    )))
736                }
737            },
738            None => Ok(version
739                .filter(|v| legacy::is_legacy_version(v))
740                .map(LegacySession::sessionless)),
741        }
742    }
743
744    fn legacy_initialize(
745        &self,
746        request: &jsonrpc::Request,
747        cors: Option<&HeaderValue>,
748        auth: Option<&AuthContext>,
749    ) -> Response<HttpBody> {
750        match self.server.legacy_initialize(request.params.as_deref()) {
751            Ok((result, mut session)) => {
752                session.principal = auth.map(|a| a.digest.clone());
753                let body = RpcResponse::Result {
754                    id: request.id.clone(),
755                    result,
756                }
757                .to_vec();
758                let mut response = json(StatusCode::OK, body, cors);
759                if let Some(token) = self.server.seal_session(&session)
760                    && let Ok(value) = HeaderValue::from_str(&token)
761                {
762                    response.headers_mut().insert(MCP_SESSION_ID, value);
763                }
764                response
765            }
766            Err(error) => rpc_error(status_for(&error), Some(request.id.clone()), &error, cors),
767        }
768    }
769
770    /// Handles a legacy message. A request answers with JSON, or with an SSE
771    /// stream as soon as the handler asks the client for input.
772    async fn handle_legacy(
773        &self,
774        message: Message,
775        session: LegacySession,
776        owner: u64,
777        transport: TransportContext,
778        permit: OwnedSemaphorePermit,
779        cors: Option<HeaderValue>,
780    ) -> Response<HttpBody> {
781        let Message::Request(request) = message else {
782            // Notifications (`initialized`, `cancelled`) need no answer.
783            return plain(StatusCode::ACCEPTED, cors.as_ref());
784        };
785        let pending = Arc::clone(&self.pending);
786        let timeout = self.server.limits().tool_timeout;
787        let server = self.server.clone();
788        // 2025-11-25: a disconnection is not a cancellation.
789        self.respond_streaming(permit, cors, None, move |out| {
790            let mut transport = transport;
791            transport.insert(NotifyChannel(out.clone()));
792            let peer = ChannelPeer {
793                pending,
794                owner,
795                out,
796                timeout,
797            };
798            Box::pin(async move {
799                server
800                    .handle_legacy(
801                        request,
802                        &session,
803                        transport,
804                        CancellationToken::new(),
805                        &peer,
806                    )
807                    .await
808            })
809        })
810        .await
811    }
812
813    /// Runs a request whose handler may emit messages before its response
814    /// (notifications, or requests to a legacy client). The response is
815    /// plain JSON if the handler finishes first, and an SSE stream from the
816    /// first message on. `cancel` is cancelled when the response is dropped
817    /// (the client closed the stream).
818    async fn respond_streaming<F>(
819        &self,
820        permit: OwnedSemaphorePermit,
821        cors: Option<HeaderValue>,
822        cancel: Option<DropGuard>,
823        run: F,
824    ) -> Response<HttpBody>
825    where
826        F: FnOnce(
827            mpsc::Sender<Vec<u8>>,
828        ) -> Pin<Box<dyn Future<Output = Option<RpcResponse>> + Send>>,
829    {
830        let (out, mut outgoing) = mpsc::channel::<Vec<u8>>(STREAM_BUFFER);
831        let (done, mut finished) = oneshot::channel::<Option<RpcResponse>>();
832        let task = run(out);
833        // The request counts against the in-flight limit until both the
834        // handler and the response stream are done: a legacy handler keeps
835        // running after its client disconnects.
836        let permit = Arc::new(permit);
837        let task_permit = Arc::clone(&permit);
838        tokio::spawn(async move {
839            let _ = done.send(task.await);
840            drop(task_permit);
841        });
842
843        let reply = |response: Option<RpcResponse>| self.reply(response, cors.as_ref());
844        let first = tokio::select! {
845            biased;
846            first = outgoing.recv() => first,
847            response = &mut finished => return reply(response.ok().flatten()),
848        };
849        let Some(first) = first else {
850            return reply(finished.await.ok().flatten());
851        };
852        let state = SseState {
853            queue: std::collections::VecDeque::from([first]),
854            outgoing,
855            finished: Some(finished),
856            _permit: permit,
857            _cancel: cancel,
858        };
859        let stream = futures_util::stream::unfold(state, |mut state| async move {
860            loop {
861                if let Some(message) = state.queue.pop_front() {
862                    return Some((Ok(sse_event(&message)), state));
863                }
864                let finished = state.finished.as_mut()?;
865                tokio::select! {
866                    biased;
867                    message = state.outgoing.recv() => match message {
868                        Some(message) => state.queue.push_back(message),
869                        None => {
870                            // Every sender is gone: the response follows.
871                            let response = state.finished.take()?.await.ok().flatten()?;
872                            state.queue.push_back(response.to_vec());
873                        }
874                    },
875                    response = finished => {
876                        // Messages sent before the response come first.
877                        state.finished = None;
878                        while let Ok(message) = state.outgoing.try_recv() {
879                            state.queue.push_back(message);
880                        }
881                        if let Ok(Some(response)) = response {
882                            state.queue.push_back(response.to_vec());
883                        }
884                    }
885                }
886            }
887        });
888        let body = http_body_util::StreamBody::new(stream);
889        let mut response = Response::new(HttpBody(BodyKind::Stream(Box::pin(body))));
890        let headers = response.headers_mut();
891        headers.insert(
892            header::CONTENT_TYPE,
893            HeaderValue::from_static("text/event-stream"),
894        );
895        headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
896        // Reverse proxies must not buffer the stream (spec recommendation).
897        headers.insert("x-accel-buffering", HeaderValue::from_static("no"));
898        headers.insert(
899            header::X_CONTENT_TYPE_OPTIONS,
900            HeaderValue::from_static("nosniff"),
901        );
902        with_cors(&mut response, cors.as_ref());
903        response
904    }
905}
906
907/// Whether a request's `_meta` carries the 2026-07-28 protocol version.
908fn declares_modern_meta(message: &Message) -> bool {
909    let Message::Request(request) = message else {
910        return false;
911    };
912    request.params.as_deref().is_some_and(|params| {
913        serde_json::from_str::<PeekParams<'_>>(params.get())
914            .ok()
915            .and_then(|p| p.meta)
916            .is_some_and(|meta| meta.protocol_version.is_some())
917    })
918}
919
920/// Messages buffered between a handler and its SSE response.
921const STREAM_BUFFER: usize = 32;
922
923/// State of an SSE response.
924struct SseState {
925    /// Messages ready to send, in order.
926    queue: std::collections::VecDeque<Vec<u8>>,
927    outgoing: mpsc::Receiver<Vec<u8>>,
928    /// The handler's response; `None` once received.
929    finished: Option<oneshot::Receiver<Option<RpcResponse>>>,
930    /// Counts against the in-flight limit until the stream ends.
931    _permit: Arc<OwnedSemaphorePermit>,
932    /// Cancels the request if the stream is dropped (client disconnect).
933    _cancel: Option<DropGuard>,
934}
935
936fn sse_event(message: &[u8]) -> http_body::Frame<Bytes> {
937    let mut event = Vec::with_capacity(message.len() + 24);
938    event.extend_from_slice(b"event: message\ndata: ");
939    event.extend_from_slice(message);
940    event.extend_from_slice(b"\n\n");
941    http_body::Frame::data(Bytes::from(event))
942}
943
944/// Binds server-to-client requests to the session that caused them, so one
945/// client cannot answer another's.
946fn session_owner(headers: &HeaderMap) -> u64 {
947    use std::hash::{BuildHasher as _, BuildHasherDefault, DefaultHasher};
948    headers.get(MCP_SESSION_ID).map_or(0, |token| {
949        BuildHasherDefault::<DefaultHasher>::default().hash_one(token.as_bytes())
950    })
951}
952
953impl<B> tower_service::Service<Request<B>> for StreamableHttpService
954where
955    B: Body + Send + 'static,
956    B::Data: Send,
957    B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
958{
959    type Response = Response<HttpBody>;
960    type Error = Infallible;
961    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Infallible>> + Send>>;
962
963    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
964        Poll::Ready(Ok(()))
965    }
966
967    fn call(&mut self, request: Request<B>) -> Self::Future {
968        let service = self.clone();
969        Box::pin(async move { Ok(service.handle(request).await) })
970    }
971}
972
973impl Server {
974    /// Serves Streamable HTTP at path `/mcp` with the default security
975    /// settings (localhost origins and hosts only) until the process stops.
976    ///
977    /// Bind to a loopback address (e.g. `127.0.0.1:8000`) unless the server
978    /// is meant to be reachable from other machines.
979    ///
980    /// # Errors
981    ///
982    /// Returns an error when the address cannot be bound.
983    pub async fn serve_http(
984        self,
985        addr: impl tokio::net::ToSocketAddrs + Clone + std::fmt::Debug,
986    ) -> io::Result<()> {
987        let listener = TcpListener::bind(addr.clone()).await.map_err(|error| {
988            let help = match error.kind() {
989                io::ErrorKind::AddrInUse => {
990                    "another program (or another copy of this server) is \
991                     already using this port; stop it or choose another port"
992                }
993                io::ErrorKind::PermissionDenied => {
994                    "ports below 1024 usually need elevated \
995                     privileges; choose a port such as 8000"
996                }
997                _ => "check the address, for example `127.0.0.1:8000`",
998            };
999            io::Error::new(
1000                error.kind(),
1001                format!("could not listen on {addr:?}: {error}\n  = help: {help}"),
1002            )
1003        })?;
1004        serve(
1005            listener,
1006            StreamableHttpService::new(self),
1007            "/mcp",
1008            std::future::pending(),
1009        )
1010        .await
1011    }
1012}
1013
1014/// Serves `service` at `path` on an already-bound listener until `shutdown`
1015/// completes, then waits up to the tool timeout for open connections.
1016///
1017/// # Errors
1018///
1019/// Returns an error when accepting connections fails.
1020pub async fn serve(
1021    listener: TcpListener,
1022    service: StreamableHttpService,
1023    path: &str,
1024    shutdown: impl Future<Output = ()>,
1025) -> io::Result<()> {
1026    use hyper::server::conn::http1;
1027    use hyper_util::rt::{TokioIo, TokioTimer};
1028    use hyper_util::server::graceful::GracefulShutdown;
1029
1030    if let Ok(addr) = listener.local_addr()
1031        && !addr.ip().is_loopback()
1032        && service.config.hosts == AllowedHosts::Localhost
1033    {
1034        tracing::warn!(
1035            "listening on {addr}, but only requests addressed to localhost are accepted\n  \
1036             = help: other machines will get HTTP 403; list the host names they use with \
1037             HttpConfig::allowed_hosts and serve with StreamableHttpService::with_config, or \
1038             bind 127.0.0.1"
1039        );
1040    }
1041
1042    let path: Arc<str> = Arc::from(path);
1043    let metadata = service.config.oauth.as_ref().map(|oauth| {
1044        (
1045            Arc::<[String]>::from(oauth.metadata_paths()),
1046            oauth.metadata_service(),
1047        )
1048    });
1049    let grace = service.server.limits().tool_timeout;
1050    let connections = Arc::new(Semaphore::new(service.config.max_connections));
1051    // HTTP/1.1 only: its header timeout runs from the moment a connection
1052    // opens, so idle connections cannot hold slots. (Automatic HTTP/2
1053    // detection waits for the first bytes without any deadline.) Mount
1054    // `StreamableHttpService` in another server for HTTP/2.
1055    let mut builder = http1::Builder::new();
1056    builder
1057        .timer(TokioTimer::new())
1058        .header_read_timeout(service.config.header_timeout);
1059    let graceful = GracefulShutdown::new();
1060    tokio::pin!(shutdown);
1061
1062    loop {
1063        // At capacity, stop accepting; new connections wait in the backlog.
1064        let permit = tokio::select! {
1065            permit = Arc::clone(&connections).acquire_owned() => match permit {
1066                Ok(permit) => permit,
1067                Err(_closed) => break,
1068            },
1069            () = &mut shutdown => break,
1070        };
1071        let (stream, peer) = tokio::select! {
1072            accepted = listener.accept() => match accepted {
1073                Ok(accepted) => accepted,
1074                Err(error) => {
1075                    // Transient errors (e.g. too many open files) must not
1076                    // stop the server.
1077                    tracing::warn!(%error, "failed to accept a connection");
1078                    tokio::time::sleep(Duration::from_millis(50)).await;
1079                    continue;
1080                }
1081            },
1082            () = &mut shutdown => break,
1083        };
1084        let service = service.clone();
1085        let path = Arc::clone(&path);
1086        let metadata = metadata.clone();
1087        let hyper_service =
1088            hyper::service::service_fn(move |request: Request<hyper::body::Incoming>| {
1089                let mut service = service.clone();
1090                let path = Arc::clone(&path);
1091                let metadata = metadata.clone();
1092                async move {
1093                    let requested = request.uri().path();
1094                    if requested == &*path {
1095                        return tower_service::Service::call(&mut service, request).await;
1096                    }
1097                    match &metadata {
1098                        Some((paths, metadata)) if paths.iter().any(|p| p == requested) => {
1099                            Ok(metadata.respond(request.method()))
1100                        }
1101                        _ => Ok(plain(StatusCode::NOT_FOUND, None)),
1102                    }
1103                }
1104            });
1105        let connection = builder.serve_connection(TokioIo::new(stream), hyper_service);
1106        let connection = graceful.watch(connection);
1107        tokio::spawn(async move {
1108            if let Err(error) = connection.await {
1109                tracing::debug!(%peer, %error, "connection closed with an error");
1110            }
1111            drop(permit);
1112        });
1113    }
1114
1115    if tokio::time::timeout(grace, graceful.shutdown())
1116        .await
1117        .is_err()
1118    {
1119        tracing::warn!("open connections did not close before the shutdown deadline");
1120    }
1121    Ok(())
1122}
1123
1124#[derive(Deserialize)]
1125struct PeekParams<'a> {
1126    #[serde(rename = "_meta", borrow, default)]
1127    meta: Option<PeekMeta<'a>>,
1128    #[serde(borrow, default)]
1129    name: Option<Cow<'a, str>>,
1130    #[serde(borrow, default)]
1131    uri: Option<Cow<'a, str>>,
1132    #[serde(borrow, default)]
1133    arguments: Option<&'a RawValue>,
1134}
1135
1136#[derive(Deserialize)]
1137struct PeekMeta<'a> {
1138    #[serde(rename = "io.modelcontextprotocol/protocolVersion", borrow, default)]
1139    protocol_version: Option<Cow<'a, str>>,
1140    #[serde(rename = "progressToken", borrow, default)]
1141    progress_token: Option<&'a RawValue>,
1142    #[serde(rename = "io.modelcontextprotocol/logLevel", borrow, default)]
1143    log_level: Option<&'a RawValue>,
1144}
1145
1146/// Checks the mirrored request headers against the body (SEP-2243). Returns
1147/// whether the request asks for notifications (a progress token or a log
1148/// level), which need an SSE response.
1149fn validate_headers(
1150    server: &Server,
1151    headers: &HeaderMap,
1152    request: &jsonrpc::Request,
1153) -> Result<bool, McpError> {
1154    let version = single_header(headers, MCP_PROTOCOL_VERSION)?
1155        .ok_or_else(|| McpError::header_mismatch("Missing MCP-Protocol-Version header"))?;
1156    let method = single_header(headers, MCP_METHOD)?
1157        .ok_or_else(|| McpError::header_mismatch("Missing Mcp-Method header"))?;
1158    if method != request.method {
1159        return Err(McpError::header_mismatch(
1160            "Mcp-Method header does not match the request method",
1161        ));
1162    }
1163
1164    // When the params cannot be read, the server reports that itself.
1165    let Ok(params) = serde_json::from_str::<PeekParams<'_>>(
1166        request.params.as_deref().map_or("{}", RawValue::get),
1167    ) else {
1168        return Ok(false);
1169    };
1170    let wants_notifications = params
1171        .meta
1172        .as_ref()
1173        .is_some_and(|m| m.progress_token.is_some() || m.log_level.is_some());
1174
1175    if let Some(body_version) = params
1176        .meta
1177        .as_ref()
1178        .and_then(|m| m.protocol_version.as_deref())
1179        && body_version != version
1180    {
1181        return Err(McpError::header_mismatch(
1182            "MCP-Protocol-Version header does not match `_meta`",
1183        ));
1184    }
1185
1186    let name_source = match request.method.as_str() {
1187        methods::TOOLS_CALL | methods::PROMPTS_GET => Some(params.name.as_deref()),
1188        methods::RESOURCES_READ => Some(params.uri.as_deref()),
1189        _ => None,
1190    };
1191    if let Some(body_name) = name_source {
1192        let header_name = single_header(headers, MCP_NAME)?
1193            .ok_or_else(|| McpError::header_mismatch("Missing Mcp-Name header"))?;
1194        if Some(decode_header_value(header_name)?.as_ref()) != body_name {
1195            return Err(McpError::header_mismatch(
1196                "Mcp-Name header does not match the request body",
1197            ));
1198        }
1199    }
1200
1201    if request.method == methods::TOOLS_CALL
1202        && let Some(name) = params.name.as_deref()
1203    {
1204        validate_param_headers(server, headers, name, params.arguments)?;
1205    }
1206    Ok(wants_notifications)
1207}
1208
1209fn validate_param_headers(
1210    server: &Server,
1211    headers: &HeaderMap,
1212    tool: &str,
1213    arguments: Option<&RawValue>,
1214) -> Result<(), McpError> {
1215    let Some(mirrored) = server.mirrored_params(tool).filter(|m| !m.is_empty()) else {
1216        return Ok(());
1217    };
1218    let arguments: Value = match arguments {
1219        Some(raw) => serde_json::from_str(raw.get()).unwrap_or(Value::Null),
1220        None => Value::Null,
1221    };
1222    for param in mirrored {
1223        let header_name = format!("{MCP_PARAM_PREFIX}{}", param.header().to_ascii_lowercase());
1224        let header = single_header(headers, &header_name)?;
1225        let value = param.value_in(&arguments).filter(|v| !v.is_null());
1226        let matches = match (value, header) {
1227            (None, None) => true,
1228            (None, Some(_)) | (Some(_), None) => false,
1229            (Some(value), Some(header)) => {
1230                let header = decode_header_value(header)?;
1231                match param.kind() {
1232                    ParamKind::String => value.as_str() == Some(header.as_ref()),
1233                    ParamKind::Boolean => {
1234                        value.as_bool().map(|b| if b { "true" } else { "false" })
1235                            == Some(header.as_ref())
1236                    }
1237                    ParamKind::Integer => integers_equal(value, &header),
1238                }
1239            }
1240        };
1241        if !matches {
1242            return Err(McpError::header_mismatch(format!(
1243                "Mcp-Param-{} header does not match the request body",
1244                param.header()
1245            )));
1246        }
1247    }
1248    Ok(())
1249}
1250
1251/// Compares a JSON integer with a header value exactly (no floating-point
1252/// rounding). `42.0` is accepted for `42` within the exactly representable
1253/// range of `f64`.
1254fn integers_equal(value: &Value, header: &str) -> bool {
1255    const EXACT_F64: f64 = 9_007_199_254_740_992.0; // 2^53
1256    let as_integer = |text: &str, float: Option<f64>| -> Option<i128> {
1257        text.parse::<i128>().ok().or_else(|| {
1258            float
1259                .filter(|f| f.fract() == 0.0 && f.abs() <= EXACT_F64)
1260                // The guard above keeps the value within i128 exactly.
1261                .map(|f| f as i128)
1262        })
1263    };
1264    let body = match value {
1265        Value::Number(n) => as_integer(&n.to_string(), n.as_f64()),
1266        _ => None,
1267    };
1268    let header = as_integer(header, header.parse::<f64>().ok());
1269    matches!((body, header), (Some(a), Some(b)) if a == b)
1270}
1271
1272/// Returns the single value of a header; duplicates and non-visible-ASCII
1273/// values are rejected.
1274fn single_header<'h>(headers: &'h HeaderMap, name: &str) -> Result<Option<&'h str>, McpError> {
1275    let mut values = headers.get_all(name).iter();
1276    let Some(first) = values.next() else {
1277        return Ok(None);
1278    };
1279    if values.next().is_some() {
1280        return Err(McpError::header_mismatch("Duplicate MCP header"));
1281    }
1282    first
1283        .to_str()
1284        .map(Some)
1285        .map_err(|_| McpError::header_mismatch("MCP header contains invalid characters"))
1286}
1287
1288/// Decodes the `=?base64?…?=` sentinel encoding.
1289fn decode_header_value(value: &str) -> Result<Cow<'_, str>, McpError> {
1290    match value
1291        .strip_prefix(BASE64_PREFIX)
1292        .and_then(|v| v.strip_suffix(BASE64_SUFFIX))
1293    {
1294        None => Ok(Cow::Borrowed(value)),
1295        Some(encoded) => base64::engine::general_purpose::STANDARD
1296            .decode(encoded)
1297            .ok()
1298            .and_then(|bytes| String::from_utf8(bytes).ok())
1299            .map(Cow::Owned)
1300            .ok_or_else(|| McpError::header_mismatch("Invalid base64-encoded MCP header")),
1301    }
1302}
1303
1304fn status_for(error: &McpError) -> StatusCode {
1305    match error.kind() {
1306        ErrorKind::BadRequest => StatusCode::BAD_REQUEST,
1307        ErrorKind::MethodNotFound => StatusCode::NOT_FOUND,
1308        ErrorKind::Internal => StatusCode::INTERNAL_SERVER_ERROR,
1309        ErrorKind::Forbidden => StatusCode::FORBIDDEN,
1310        _ => StatusCode::OK,
1311    }
1312}
1313
1314fn origin_allowed(allowed: &AllowedOrigins, origin: &HeaderValue) -> bool {
1315    let Ok(origin) = origin.to_str() else {
1316        return false;
1317    };
1318    match allowed {
1319        AllowedOrigins::Any => true,
1320        AllowedOrigins::List(list) => list.iter().any(|o| o.eq_ignore_ascii_case(origin)),
1321        AllowedOrigins::Localhost => {
1322            let Some((scheme, rest)) = origin.split_once("://") else {
1323                return false;
1324            };
1325            matches!(scheme, "http" | "https") && is_localhost(strip_port(rest))
1326        }
1327    }
1328}
1329
1330fn host_allowed(allowed: &AllowedHosts, parts: &http::request::Parts) -> bool {
1331    // RFC 9112 §3.2: a request with several `Host` headers is invalid. Only
1332    // the first would be checked, while an intermediary might use another.
1333    if parts.headers.get_all(header::HOST).iter().nth(1).is_some() {
1334        return false;
1335    }
1336    if *allowed == AllowedHosts::Any {
1337        return true;
1338    }
1339    // HTTP/1 sends `Host`; HTTP/2 carries the host in the URI authority.
1340    let host = parts
1341        .headers
1342        .get(header::HOST)
1343        .and_then(|h| h.to_str().ok())
1344        .or_else(|| parts.uri.authority().map(http::uri::Authority::as_str));
1345    let Some(host) = host else {
1346        return false;
1347    };
1348    host_name_allowed(allowed, strip_port(host))
1349}
1350
1351/// Whether a host name (without port) is allowed.
1352fn host_name_allowed(allowed: &AllowedHosts, host: &str) -> bool {
1353    match allowed {
1354        AllowedHosts::Any => true,
1355        AllowedHosts::List(list) => list.iter().any(|h| h.eq_ignore_ascii_case(host)),
1356        AllowedHosts::Localhost => is_localhost(host),
1357    }
1358}
1359
1360pub(crate) fn strip_port(authority: &str) -> &str {
1361    if authority.starts_with('[') {
1362        // `[::1]:8080` → `[::1]`
1363        return authority
1364            .find(']')
1365            .and_then(|end| authority.get(..=end))
1366            .unwrap_or(authority);
1367    }
1368    authority.split(':').next().unwrap_or(authority)
1369}
1370
1371pub(crate) fn is_localhost(host: &str) -> bool {
1372    host.eq_ignore_ascii_case("localhost") || host == "127.0.0.1" || host == "[::1]"
1373}
1374
1375fn is_json_content_type(value: Option<&HeaderValue>) -> bool {
1376    value
1377        .and_then(|v| v.to_str().ok())
1378        .and_then(|v| v.split(';').next())
1379        .is_some_and(|mime| mime.trim().eq_ignore_ascii_case("application/json"))
1380}
1381
1382/// The response is JSON, so `Accept` (when present) must allow it.
1383fn accepts_json(headers: &HeaderMap) -> bool {
1384    let mut values = headers.get_all(header::ACCEPT).iter().peekable();
1385    if values.peek().is_none() {
1386        return true;
1387    }
1388    values
1389        .filter_map(|v| v.to_str().ok())
1390        .flat_map(|v| v.split(','))
1391        .filter_map(|item| item.split(';').next())
1392        .map(str::trim)
1393        .any(|mime| {
1394            mime.eq_ignore_ascii_case("application/json")
1395                || mime.eq_ignore_ascii_case("application/*")
1396                || mime == "*/*"
1397        })
1398}
1399
1400fn preflight(origin: Option<HeaderValue>, request_headers: &HeaderMap) -> Response<HttpBody> {
1401    let mut response = plain(StatusCode::NO_CONTENT, origin.as_ref());
1402    let headers = response.headers_mut();
1403    headers.insert(
1404        header::ACCESS_CONTROL_ALLOW_METHODS,
1405        HeaderValue::from_static("POST"),
1406    );
1407    let allowed = request_headers
1408        .get(header::ACCESS_CONTROL_REQUEST_HEADERS)
1409        .cloned()
1410        .unwrap_or_else(|| HeaderValue::from_static(ALLOWED_REQUEST_HEADERS));
1411    headers.insert(header::ACCESS_CONTROL_ALLOW_HEADERS, allowed);
1412    headers.insert(
1413        header::ACCESS_CONTROL_MAX_AGE,
1414        HeaderValue::from_static("600"),
1415    );
1416    response
1417}
1418
1419fn with_cors(response: &mut Response<HttpBody>, origin: Option<&HeaderValue>) {
1420    if let Some(origin) = origin {
1421        let headers = response.headers_mut();
1422        headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
1423        headers.insert(header::VARY, HeaderValue::from_static("Origin"));
1424        // Browser clients must read OAuth challenges and legacy sessions.
1425        headers.insert(
1426            header::ACCESS_CONTROL_EXPOSE_HEADERS,
1427            HeaderValue::from_static("www-authenticate, mcp-session-id"),
1428        );
1429    }
1430}
1431
1432/// Why a request body could not be read.
1433enum BodyError {
1434    /// Over the message limit (`413`).
1435    TooLarge,
1436    /// The memory budget for bodies is spent (`503`).
1437    Busy,
1438    /// The connection failed while reading (`400`).
1439    Read,
1440}
1441
1442/// Reads a body up to `limit` bytes, reserving each frame's size in
1443/// `budget` as it arrives. Returns the body and its reservation.
1444async fn read_body<B>(
1445    body: B,
1446    limit: usize,
1447    budget: Arc<Semaphore>,
1448) -> Result<(Bytes, Option<OwnedSemaphorePermit>), BodyError>
1449where
1450    B: Body,
1451{
1452    let mut body = std::pin::pin!(body);
1453    let mut buffer = bytes::BytesMut::new();
1454    let mut reserved: Option<OwnedSemaphorePermit> = None;
1455    while let Some(frame) = body.frame().await {
1456        let frame = frame.map_err(|_| BodyError::Read)?;
1457        let Ok(data) = frame.into_data() else {
1458            continue; // trailers
1459        };
1460        let len = bytes::Buf::remaining(&data);
1461        if len == 0 {
1462            continue;
1463        }
1464        if buffer.len().saturating_add(len) > limit {
1465            return Err(BodyError::TooLarge);
1466        }
1467        let size = u32::try_from(len).map_err(|_| BodyError::TooLarge)?;
1468        let permit = Arc::clone(&budget)
1469            .try_acquire_many_owned(size)
1470            .map_err(|_| BodyError::Busy)?;
1471        match &mut reserved {
1472            Some(held) => held.merge(permit),
1473            None => reserved = Some(permit),
1474        }
1475        bytes::BufMut::put(&mut buffer, data);
1476    }
1477    Ok((buffer.freeze(), reserved))
1478}
1479
1480/// `503` with `Retry-After`: the server is at capacity.
1481fn busy(cors: Option<&HeaderValue>) -> Response<HttpBody> {
1482    let mut response = rpc_error(
1483        StatusCode::SERVICE_UNAVAILABLE,
1484        None,
1485        &McpError::internal_with_message("The server is busy; retry later."),
1486        cors,
1487    );
1488    response
1489        .headers_mut()
1490        .insert(header::RETRY_AFTER, HeaderValue::from_static("1"));
1491    response
1492}
1493
1494/// A `401`/`403`/`400`/`503` answer to a request that failed authorization.
1495fn challenge_response(
1496    oauth: &OAuth,
1497    challenge: &Challenge,
1498    cors: Option<&HeaderValue>,
1499) -> Response<HttpBody> {
1500    let error = match challenge {
1501        Challenge::Unavailable => McpError::internal_with_message(challenge.message()),
1502        _ => McpError::invalid_request(challenge.message()),
1503    };
1504    let mut response = rpc_error(challenge.status(), None, &error, cors);
1505    if let Some(value) = oauth.www_authenticate(challenge) {
1506        response
1507            .headers_mut()
1508            .insert(header::WWW_AUTHENTICATE, value);
1509    }
1510    if *challenge == Challenge::Unavailable {
1511        response
1512            .headers_mut()
1513            .insert(header::RETRY_AFTER, HeaderValue::from_static("5"));
1514    }
1515    response
1516}
1517
1518fn plain(status: StatusCode, cors: Option<&HeaderValue>) -> Response<HttpBody> {
1519    let mut response = Response::new(HttpBody::full(Bytes::new()));
1520    *response.status_mut() = status;
1521    with_cors(&mut response, cors);
1522    response
1523}
1524
1525fn json(status: StatusCode, body: Vec<u8>, cors: Option<&HeaderValue>) -> Response<HttpBody> {
1526    let mut response = Response::new(HttpBody::full(body));
1527    *response.status_mut() = status;
1528    response.headers_mut().insert(
1529        header::CONTENT_TYPE,
1530        HeaderValue::from_static("application/json"),
1531    );
1532    response.headers_mut().insert(
1533        header::X_CONTENT_TYPE_OPTIONS,
1534        HeaderValue::from_static("nosniff"),
1535    );
1536    with_cors(&mut response, cors);
1537    response
1538}
1539
1540fn rpc_error(
1541    status: StatusCode,
1542    id: Option<RequestId>,
1543    error: &McpError,
1544    cors: Option<&HeaderValue>,
1545) -> Response<HttpBody> {
1546    json(status, RpcResponse::error(id, error.clone()).to_vec(), cors)
1547}
1548
1549#[cfg(test)]
1550mod tests {
1551    use super::*;
1552
1553    #[test]
1554    fn decodes_base64_sentinel() {
1555        assert_eq!(decode_header_value("us-west1").unwrap(), "us-west1");
1556        assert_eq!(
1557            decode_header_value("=?base64?SGVsbG8sIOS4lueVjA==?=").unwrap(),
1558            "Hello, 世界"
1559        );
1560        assert_eq!(
1561            decode_header_value("=?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=").unwrap(),
1562            "=?base64?literal?="
1563        );
1564        assert!(decode_header_value("=?base64?***?=").is_err());
1565    }
1566
1567    #[test]
1568    fn origin_rules() {
1569        let localhost = AllowedOrigins::Localhost;
1570        let ok = |o: &'static str| origin_allowed(&localhost, &HeaderValue::from_static(o));
1571        assert!(ok("http://localhost:6274"));
1572        assert!(ok("http://127.0.0.1"));
1573        assert!(ok("https://[::1]:8443"));
1574        assert!(!ok("http://localhost.evil.com"));
1575        assert!(!ok("http://evil.com"));
1576        assert!(!ok("null"));
1577    }
1578
1579    fn parts(host: Option<&'static str>, uri: &str) -> http::request::Parts {
1580        let mut builder = Request::builder().uri(uri);
1581        if let Some(host) = host {
1582            builder = builder.header(header::HOST, host);
1583        }
1584        builder.body(()).unwrap().into_parts().0
1585    }
1586
1587    #[test]
1588    fn host_rules() {
1589        let local = parts(Some("127.0.0.1:8000"), "/mcp");
1590        assert!(host_allowed(&AllowedHosts::Localhost, &local));
1591        let evil = parts(Some("attacker.example"), "/mcp");
1592        assert!(!host_allowed(&AllowedHosts::Localhost, &evil));
1593        assert!(host_allowed(
1594            &AllowedHosts::List(vec!["attacker.example".into()]),
1595            &evil
1596        ));
1597        // HTTP/2: no Host header, authority in the URI.
1598        let h2 = parts(None, "http://localhost:8000/mcp");
1599        assert!(host_allowed(&AllowedHosts::Localhost, &h2));
1600        assert!(!host_allowed(
1601            &AllowedHosts::Localhost,
1602            &parts(None, "/mcp")
1603        ));
1604    }
1605
1606    #[test]
1607    fn accept_rules() {
1608        let mut headers = HeaderMap::new();
1609        assert!(accepts_json(&headers));
1610        headers.insert(
1611            header::ACCEPT,
1612            HeaderValue::from_static("application/json, text/event-stream"),
1613        );
1614        assert!(accepts_json(&headers));
1615        headers.insert(header::ACCEPT, HeaderValue::from_static("text/html"));
1616        assert!(!accepts_json(&headers));
1617    }
1618}