Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions crates/rust-mcp-sdk/src/hyper_servers/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub mod streamable_http_routes;
use super::HyperServerOptions;
use crate::mcp_http::McpAppState;
use crate::mcp_http::McpHttpHandler;
use axum::{Extension, Router};
use axum::{extract::DefaultBodyLimit, Extension, Router};
use std::sync::Arc;

/// Constructs the Axum router with all application routes
Expand Down Expand Up @@ -63,7 +63,12 @@ pub fn app_routes(
}

router = router.merge(fallback_routes::routes());
router.with_state(state).layer(Extension(http_handler))
router
.with_state(state)
.layer(Extension(http_handler))
// Reject oversized request bodies with `413 Payload Too Large`
// before they are buffered into memory.
.layer(DefaultBodyLimit::max(server_options.max_request_body_size()))
};

router
Expand Down
15 changes: 15 additions & 0 deletions crates/rust-mcp-sdk/src/hyper_servers/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ use tokio::signal;
// Default client ping interval (12 seconds)
const DEFAULT_CLIENT_PING_INTERVAL: Duration = Duration::from_secs(12);
const GRACEFUL_SHUTDOWN_TMEOUT_SECS: u64 = 5;
// Default maximum size (in bytes) of an incoming HTTP request body (4 MiB)
const DEFAULT_MAX_REQUEST_BODY_SIZE: usize = 4 * 1024 * 1024;

/// Configuration struct for the Hyper server
/// Used to configure the HyperServer instance.
Expand Down Expand Up @@ -97,6 +99,11 @@ pub struct HyperServerOptions {
/// Interval between automatic ping messages sent to clients to detect disconnects
pub ping_interval: Duration,

/// Maximum size in bytes of an incoming HTTP request body. Requests larger
/// than this are rejected with `413 Payload Too Large`.
/// Defaults to 4 MiB when `None`.
pub max_request_body_size: Option<usize>,

/// Enables SSL/TLS if set to `true`
pub enable_ssl: bool,

Expand Down Expand Up @@ -254,6 +261,13 @@ impl HyperServerOptions {
.unwrap_or(DEFAULT_STREAMABLE_HTTP_ENDPOINT)
}

/// Maximum incoming HTTP request body size in bytes, falling back to the
/// default (4 MiB) when not configured.
pub fn max_request_body_size(&self) -> usize {
self.max_request_body_size
.unwrap_or(DEFAULT_MAX_REQUEST_BODY_SIZE)
}

pub fn needs_dns_protection(&self) -> bool {
self.dns_rebinding_protection
&& (self.allowed_hosts.is_some() || self.allowed_origins.is_some())
Expand All @@ -273,6 +287,7 @@ impl Default for HyperServerOptions {
custom_streamable_http_endpoint: None,
custom_messages_endpoint: None,
ping_interval: DEFAULT_CLIENT_PING_INTERVAL,
max_request_body_size: None,
transport_options: Default::default(),
enable_ssl: false,
ssl_cert_path: None,
Expand Down
26 changes: 26 additions & 0 deletions crates/rust-mcp-sdk/tests/test_streamable_http_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1815,6 +1815,32 @@ async fn should_handle_elicitation() {
server.hyper_runtime.await_server().await.unwrap();
}

// should reject request bodies that exceed the configured size limit
#[tokio::test]
async fn should_reject_oversized_request_body() {
let server_options = HyperServerOptions {
port: random_port(),
session_id_generator: Some(Arc::new(TestIdGenerator::new(vec![
"AAA-BBB-CCC".to_string()
]))),
max_request_body_size: Some(1024),
..Default::default()
};

let server = create_start_server(server_options).await;
tokio::time::sleep(Duration::from_millis(250)).await;

let oversized_body = "x".repeat(4096);
let response = send_post_request(&server.streamable_url, &oversized_body, None, None)
.await
.expect("Request failed");

assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE);

server.hyper_runtime.graceful_shutdown(ONE_MILLISECOND);
server.hyper_runtime.await_server().await.unwrap()
}

// should return 400 error for invalid JSON-RPC messages
// should keep stream open after sending server notifications
// NA: should reject second initialization request
Expand Down
Loading