1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Runtime-agnostic (wasm-friendly) cancellation token and shadow of
//! [`RequestHandlerExtra`].
//!
//! See the canonical [`crate::server::cancellation`] module for the full API,
//! extensions typemap semantics, semver posture, and session-id plumbing
//! limitation.
//!
//! This shared shadow carries `extensions: http::Extensions` in parity with
//! the canonical struct and is marked `#[non_exhaustive]`. The `peer` field
//! does **not** exist here because the peer back-channel is non-wasm only
//! and lives on the canonical struct.
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// A token that can be used to signal cancellation of an operation.
///
/// This is a simple, runtime-agnostic implementation suitable for use in
/// code that needs to be compatible with both native `tokio` environments
/// and WASI.
#[derive(Clone, Debug, Default)]
pub struct CancellationToken {
is_cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
/// Creates a new `CancellationToken`.
pub fn new() -> Self {
Self {
is_cancelled: Arc::new(AtomicBool::new(false)),
}
}
/// Cancels the token, signaling that the operation should be aborted.
pub fn cancel(&self) {
self.is_cancelled.store(true, Ordering::Relaxed);
}
/// Checks if the token has been cancelled.
pub fn is_cancelled(&self) -> bool {
self.is_cancelled.load(Ordering::Relaxed)
}
}
/// Extra context passed to request handlers.
///
/// This struct is runtime-agnostic and uses the shared `CancellationToken`.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct RequestHandlerExtra {
/// Cancellation token for the request
pub cancellation_token: CancellationToken,
/// Request ID
pub request_id: String,
/// Session ID
pub session_id: Option<String>,
/// Authentication info
pub auth_info: Option<crate::types::auth::AuthInfo>,
/// Validated authentication context (if auth is enabled)
#[cfg(not(target_arch = "wasm32"))]
pub auth_context: Option<crate::server::auth::AuthContext>,
/// The request's `_meta` object as raw JSON (MCP `_meta`).
///
/// Parity mirror of the canonical
/// [`crate::server::cancellation::RequestHandlerExtra::request_meta`] so
/// handlers running on the runtime-agnostic (wasm) path can read arbitrary
/// namespaced `_meta` keys transport-agnostically. `None` when the request
/// carried no `_meta`.
pub request_meta: Option<serde_json::Value>,
/// Typed request-scoped state for middleware→handler transfer.
///
/// Inserting values requires `T: Clone + Send + Sync + 'static`. Debug prints type names only,
/// not values, making this safe for logging. Cloning `RequestHandlerExtra` clones the entire
/// extensions map — prefer `Arc<T>` for large values.
///
/// # Semver note
/// The enclosing struct carries `#[non_exhaustive]`. This is a breaking change
/// only for downstream code that used POSITIONAL struct-literal construction of
/// `RequestHandlerExtra`. `::new(...)`, `::default()`, and the `.with_*(...)` builder chain are
/// fully source-compatible.
pub extensions: http::Extensions,
}
impl RequestHandlerExtra {
/// Create new handler extra context.
pub fn new(request_id: String, cancellation_token: CancellationToken) -> Self {
Self {
cancellation_token,
request_id,
session_id: None,
auth_info: None,
#[cfg(not(target_arch = "wasm32"))]
auth_context: None,
request_meta: None,
extensions: http::Extensions::new(),
}
}
/// Set the session ID.
pub fn with_session_id(mut self, session_id: Option<String>) -> Self {
self.session_id = session_id;
self
}
/// Set the auth info.
pub fn with_auth_info(mut self, auth_info: Option<crate::types::auth::AuthInfo>) -> Self {
self.auth_info = auth_info;
self
}
/// Attach the request's `_meta` object (raw JSON) for handler inspection.
///
/// Parity mirror of the canonical
/// [`crate::server::cancellation::RequestHandlerExtra::with_request_meta`].
#[must_use]
pub fn with_request_meta(mut self, meta: Option<serde_json::Value>) -> Self {
self.request_meta = meta;
self
}
/// Set the auth context.
#[cfg(not(target_arch = "wasm32"))]
pub fn with_auth_context(
mut self,
auth_context: Option<crate::server::auth::AuthContext>,
) -> Self {
self.auth_context = auth_context;
self
}
/// Get the auth context if available.
#[cfg(not(target_arch = "wasm32"))]
pub fn auth_context(&self) -> Option<&crate::server::auth::AuthContext> {
self.auth_context.as_ref()
}
/// Check if the request has been cancelled.
pub fn is_cancelled(&self) -> bool {
self.cancellation_token.is_cancelled()
}
/// Returns a reference to the typed extensions map.
pub fn extensions(&self) -> &http::Extensions {
&self.extensions
}
/// Returns a mutable reference to the typed extensions map.
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
&mut self.extensions
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shared_extensions_parity() {
let mut extra = RequestHandlerExtra::new("r1".to_string(), CancellationToken::new());
extra.extensions_mut().insert(42u64);
assert_eq!(extra.extensions().get::<u64>(), Some(&42u64));
}
}