Skip to content

Commit 80e9e93

Browse files
committed
refactor: rename stateful_mode to legacy_session_mode
1 parent d27b509 commit 80e9e93

13 files changed

Lines changed: 43 additions & 34 deletions

conformance/src/bin/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ async fn main() -> anyhow::Result<()> {
12291229
let server = ConformanceServer::new();
12301230
let stateless = std::env::var_os("STATELESS").is_some();
12311231
let config = StreamableHttpServerConfig::default()
1232-
.with_stateful_mode(!stateless)
1232+
.with_legacy_session_mode(!stateless)
12331233
.with_json_response(stateless);
12341234
let service = StreamableHttpService::new(
12351235
move || Ok(server.clone()),

crates/rmcp/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- **BREAKING**: rename `StreamableHttpServerConfig::stateful_mode` to `legacy_session_mode` (and the builder `with_stateful_mode` to `with_legacy_session_mode`) to clarify that the option only affects legacy protocol versions (`< 2026-07-28`); per SEP-2567 the `2026-07-28` draft version is always served statelessly ([#999](https://github.com/modelcontextprotocol/rust-sdk/pull/999))
13+
1014
## [2.2.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v2.1.0...rmcp-v2.2.0) - 2026-07-08
1115

1216
### Added

crates/rmcp/src/transport/streamable_http_server/session.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
//!
1414
//! * [`local::LocalSessionManager`] — in-memory session store (default).
1515
//! * [`never::NeverSessionManager`] — rejects all session operations, used
16-
//! when stateful mode is disabled.
16+
//! when legacy session mode is disabled.
1717
//!
1818
//! # Custom session managers
1919
//!

crates/rmcp/src/transport/streamable_http_server/tower.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,13 @@ pub struct StreamableHttpServerConfig {
5959
pub sse_retry: Option<Duration>,
6060
/// If true, the server will create a session for each request and keep it alive.
6161
/// When enabled, SSE priming events are sent to enable client reconnection.
62-
pub stateful_mode: bool,
63-
/// When true and `stateful_mode` is false, the server prefers
62+
///
63+
/// Only applies to legacy protocol versions (`< 2026-07-28`). Per SEP-2567,
64+
/// sessions are removed from the `2026-07-28` draft version, so requests
65+
/// negotiating that version are always served statelessly regardless of
66+
/// this setting.
67+
pub legacy_session_mode: bool,
68+
/// When true and `legacy_session_mode` is false, the server prefers
6469
/// `Content-Type: application/json` for simple request-response tools.
6570
/// If the handler emits a notification or request before the final response,
6671
/// the server falls back to `text/event-stream` so no message is lost.
@@ -130,7 +135,7 @@ impl Default for StreamableHttpServerConfig {
130135
Self {
131136
sse_keep_alive: Some(Duration::from_secs(15)),
132137
sse_retry: Some(Duration::from_secs(3)),
133-
stateful_mode: true,
138+
legacy_session_mode: true,
134139
json_response: false,
135140
cancellation_token: CancellationToken::new(),
136141
allowed_hosts: vec!["localhost".into(), "127.0.0.1".into(), "::1".into()],
@@ -176,8 +181,8 @@ impl StreamableHttpServerConfig {
176181
self
177182
}
178183

179-
pub fn with_stateful_mode(mut self, stateful: bool) -> Self {
180-
self.stateful_mode = stateful;
184+
pub fn with_legacy_session_mode(mut self, legacy_session_mode: bool) -> Self {
185+
self.legacy_session_mode = legacy_session_mode;
181186
self
182187
}
183188

@@ -690,7 +695,7 @@ fn validate_origin_header(
690695
///
691696
/// ## Session management
692697
///
693-
/// When [`StreamableHttpServerConfig::stateful_mode`] is `true` (the default),
698+
/// When [`StreamableHttpServerConfig::legacy_session_mode`] is `true` (the default),
694699
/// the server creates a session for each client that sends an `initialize`
695700
/// request. The session ID is returned in the `Mcp-Session-Id` response header
696701
/// and the client must include it on all subsequent requests.
@@ -1186,13 +1191,13 @@ where
11861191
return response;
11871192
}
11881193
let method = request.method().clone();
1189-
let allowed_methods = match self.config.stateful_mode {
1194+
let allowed_methods = match self.config.legacy_session_mode {
11901195
true => "GET, POST, DELETE",
11911196
false => "POST",
11921197
};
1193-
let result = match (method, self.config.stateful_mode) {
1198+
let result = match (method, self.config.legacy_session_mode) {
11941199
(Method::POST, _) => self.handle_post(request).await,
1195-
// if we're not in stateful mode, we don't support GET or DELETE because there is no session
1200+
// if legacy session mode is disabled, we don't support GET or DELETE because there is no session
11961201
(Method::GET, true) => self.handle_get(request).await,
11971202
(Method::DELETE, true) => self.handle_delete(request).await,
11981203
_ => {
@@ -1372,7 +1377,7 @@ where
13721377
};
13731378

13741379
let use_session =
1375-
self.config.stateful_mode && is_legacy_request(Some(&message), &part.headers);
1380+
self.config.legacy_session_mode && is_legacy_request(Some(&message), &part.headers);
13761381

13771382
if use_session {
13781383
// do we have a session id?

crates/rmcp/tests/test_discover_http_client_startup.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async fn discover_http_client_bootstraps_headers_without_initialize() {
5454
|| Ok(DiscoverHttpServer),
5555
Default::default(),
5656
StreamableHttpServerConfig::default()
57-
.with_stateful_mode(false)
57+
.with_legacy_session_mode(false)
5858
.with_json_response(true)
5959
.with_cancellation_token(ct.child_token()),
6060
);

crates/rmcp/tests/test_server_discover_http.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ impl ServerHandler for DiscoveryServer {
3232
}
3333

3434
async fn spawn_server(json_response: bool) -> (reqwest::Client, String, CancellationToken) {
35-
spawn_server_with_stateful_mode(json_response, false).await
35+
spawn_server_with_legacy_session_mode(json_response, false).await
3636
}
3737

38-
async fn spawn_server_with_stateful_mode(
38+
async fn spawn_server_with_legacy_session_mode(
3939
json_response: bool,
40-
stateful_mode: bool,
40+
legacy_session_mode: bool,
4141
) -> (reqwest::Client, String, CancellationToken) {
4242
let cancellation_token = CancellationToken::new();
4343
let config = StreamableHttpServerConfig::default()
44-
.with_stateful_mode(stateful_mode)
44+
.with_legacy_session_mode(legacy_session_mode)
4545
.with_json_response(json_response)
4646
.with_sse_keep_alive(None)
4747
.with_cancellation_token(cancellation_token.clone());
@@ -136,8 +136,8 @@ async fn discover_returns_server_metadata_without_session() {
136136
}
137137

138138
#[tokio::test]
139-
async fn discover_does_not_require_initialization_in_stateful_mode() {
140-
let (client, url, cancellation_token) = spawn_server_with_stateful_mode(true, true).await;
139+
async fn discover_does_not_require_initialization_in_legacy_session_mode() {
140+
let (client, url, cancellation_token) = spawn_server_with_legacy_session_mode(true, true).await;
141141

142142
let response = post_discover(&client, &url, "2025-11-25", Some("2025-11-25")).await;
143143

crates/rmcp/tests/test_stateless_protocol_version.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use common::calculator::Calculator;
1616

1717
fn stateless_json_config() -> StreamableHttpServerConfig {
1818
StreamableHttpServerConfig::default()
19-
.with_stateful_mode(false)
19+
.with_legacy_session_mode(false)
2020
.with_json_response(true)
2121
.with_sse_keep_alive(None)
2222
.with_cancellation_token(CancellationToken::new())

crates/rmcp/tests/test_streamable_http_disconnect_cancel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ async fn spawn_stateless_server(json_response: bool) -> anyhow::Result<TestServe
8181

8282
let server_ct = CancellationToken::new();
8383
let config = StreamableHttpServerConfig::default()
84-
.with_stateful_mode(false)
84+
.with_legacy_session_mode(false)
8585
.with_json_response(json_response)
8686
// A short keep-alive lets the SSE server notice a dropped connection
8787
// quickly (hyper only observes the disconnect on its next write).

crates/rmcp/tests/test_streamable_http_json_response.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ async fn stateless_json_response_returns_application_json() -> anyhow::Result<()
103103
let ct = CancellationToken::new();
104104
let (client, url, ct) = spawn_server(
105105
StreamableHttpServerConfig::default()
106-
.with_stateful_mode(false)
106+
.with_legacy_session_mode(false)
107107
.with_json_response(true)
108108
.with_sse_keep_alive(None)
109109
.with_cancellation_token(ct.child_token()),
@@ -145,7 +145,7 @@ async fn stateless_json_response_falls_back_to_sse_for_progress() -> anyhow::Res
145145
let ct = CancellationToken::new();
146146
let (client, url, ct) = spawn_progress_server(
147147
StreamableHttpServerConfig::default()
148-
.with_stateful_mode(false)
148+
.with_legacy_session_mode(false)
149149
.with_json_response(true)
150150
.with_sse_keep_alive(None)
151151
.with_cancellation_token(ct.child_token()),
@@ -197,7 +197,7 @@ async fn stateless_sse_mode_default_unchanged() -> anyhow::Result<()> {
197197
let ct = CancellationToken::new();
198198
let (client, url, ct) = spawn_server(
199199
StreamableHttpServerConfig::default()
200-
.with_stateful_mode(false)
200+
.with_legacy_session_mode(false)
201201
.with_sse_keep_alive(None)
202202
.with_cancellation_token(ct.child_token()),
203203
)
@@ -234,9 +234,9 @@ async fn stateless_sse_mode_default_unchanged() -> anyhow::Result<()> {
234234
}
235235

236236
#[tokio::test]
237-
async fn json_response_ignored_in_stateful_mode() -> anyhow::Result<()> {
237+
async fn json_response_ignored_in_legacy_session_mode() -> anyhow::Result<()> {
238238
let ct = CancellationToken::new();
239-
// json_response: true has no effect when stateful_mode: true — server still uses SSE
239+
// json_response: true has no effect when legacy_session_mode: true — server still uses SSE
240240
let (client, url, ct) = spawn_server(
241241
StreamableHttpServerConfig::default()
242242
.with_json_response(true)

crates/rmcp/tests/test_streamable_http_priming.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use common::calculator::Calculator;
1414
async fn test_priming_on_stream_start() -> anyhow::Result<()> {
1515
let ct = CancellationToken::new();
1616

17-
// stateful_mode: true automatically enables priming with DEFAULT_RETRY_INTERVAL (3 seconds)
17+
// legacy_session_mode: true automatically enables priming with DEFAULT_RETRY_INTERVAL (3 seconds)
1818
let service: StreamableHttpService<Calculator, LocalSessionManager> =
1919
StreamableHttpService::new(
2020
|| Ok(Calculator::new()),
@@ -416,7 +416,7 @@ async fn test_priming_on_stream_close() -> anyhow::Result<()> {
416416
let ct = CancellationToken::new();
417417
let session_manager = Arc::new(LocalSessionManager::default());
418418

419-
// stateful_mode: true automatically enables priming with DEFAULT_RETRY_INTERVAL (3 seconds)
419+
// legacy_session_mode: true automatically enables priming with DEFAULT_RETRY_INTERVAL (3 seconds)
420420
let service = StreamableHttpService::new(
421421
|| Ok(Calculator::new()),
422422
session_manager.clone(),

0 commit comments

Comments
 (0)