Skip to content

Commit 4dbf0b4

Browse files
committed
feat: harden SEP-2322 MRTR support
1 parent 1c97de6 commit 4dbf0b4

8 files changed

Lines changed: 1321 additions & 59 deletions

File tree

crates/rmcp/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ features = [
2222
"client-side-sse",
2323
"elicitation",
2424
"macros",
25+
"request-state",
2526
"reqwest",
2627
"reqwest-native-tls",
2728
"reqwest-tls-no-provider",
@@ -64,6 +65,10 @@ schemars = { version = "1.0", optional = true, features = ["chrono04"] }
6465
# for image encoding
6566
base64 = { version = "0.22", optional = true }
6667

68+
# for SEP-2322 requestState integrity sealing (opt-in via the `request-state` feature)
69+
hmac = { version = "0.12", optional = true }
70+
sha2 = { version = "0.10", optional = true }
71+
6772
# for HTTP client
6873
reqwest = { version = "0.13.2", default-features = false, features = [
6974
"json",
@@ -120,6 +125,9 @@ server = ["transport-async-rw", "dep:schemars", "dep:pastey"]
120125
macros = ["dep:rmcp-macros", "dep:pastey"]
121126
elicitation = ["dep:url"]
122127

128+
# SEP-2322 requestState integrity helper (HMAC-SHA256 seal/open codec)
129+
request-state = ["dep:hmac", "dep:sha2", "base64"]
130+
123131
# reqwest http client
124132
__reqwest = ["dep:reqwest"]
125133

@@ -309,6 +317,11 @@ name = "test_trace_context"
309317
required-features = ["server", "client"]
310318
path = "tests/test_trace_context.rs"
311319

320+
[[test]]
321+
name = "test_mrtr_behavior"
322+
required-features = ["server", "client"]
323+
path = "tests/test_mrtr_behavior.rs"
324+
312325
[[test]]
313326
name = "test_prompt_macros"
314327
required-features = ["server", "client"]

crates/rmcp/src/model.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ mod extension;
1414
mod meta;
1515
mod mrtr;
1616
mod prompt;
17+
#[cfg(feature = "request-state")]
18+
mod request_state;
1719
mod resource;
1820
mod serde_impl;
1921
mod task;
@@ -26,6 +28,8 @@ pub use extension::*;
2628
pub use meta::*;
2729
pub use mrtr::*;
2830
pub use prompt::*;
31+
#[cfg(feature = "request-state")]
32+
pub use request_state::*;
2933
pub use resource::*;
3034
use serde::{Deserialize, Serialize, de::DeserializeOwned};
3135
use serde_json::Value;

crates/rmcp/src/model/mrtr.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@
1010
//! [`InputRequiredResult`] instead of the normal result. The client fulfills the
1111
//! [`InputRequests`], then retries the original request with [`InputResponses`] and
1212
//! the echoed `requestState`.
13+
//!
14+
//! # Using MRTR
15+
//!
16+
//! **Server:** return an [`InputRequiredResult`] from a tool/prompt/resource
17+
//! handler via the matching outcome enum ([`CallToolResponse`],
18+
//! [`GetPromptResponse`], [`ReadResourceResponse`]). The SDK only lets an
19+
//! `InputRequiredResult` reach a peer that negotiated protocol version
20+
//! `2026-07-28` or newer; older peers get a protocol error instead.
21+
//!
22+
//! **Client:** the high-level `RunningService` helpers — `call_tool`,
23+
//! `get_prompt`, and `read_resource` — automatically fulfil each
24+
//! [`InputRequest`] through the local `ClientHandler` and retry, up to
25+
//! [`DEFAULT_MRTR_MAX_ROUNDS`]. Use the `*_once` variants (e.g.
26+
//! `call_tool_once`) to receive an [`InputRequiredResult`] directly and drive
27+
//! the rounds yourself.
28+
//!
29+
//! # `requestState` is untrusted
30+
//!
31+
//! The client echoes `requestState` back verbatim, so a stateless server that
32+
//! stores meaningful data in it MUST verify integrity before trusting the echoed
33+
//! value. Enable the `request-state` feature and use `RequestStateCodec` to seal
34+
//! and open it, or keep the state server-side and use `requestState` only as an
35+
//! opaque handle.
36+
//!
37+
//! A complete runnable walkthrough lives in the `servers_mrtr` example.
1338
1439
use std::collections::BTreeMap;
1540

0 commit comments

Comments
 (0)