turul_http_mcp_server/middleware/context.rs
1//! Request context and session injection types
2
3use serde_json::{Map, Value};
4use std::collections::HashMap;
5
6/// Normalized request context across all transports (HTTP, Lambda, etc.)
7///
8/// Provides uniform access to request data regardless of transport mechanism.
9///
10/// # Examples
11///
12/// ```rust,no_run
13/// use turul_http_mcp_server::middleware::RequestContext;
14/// use serde_json::json;
15///
16/// let mut ctx = RequestContext::new(
17/// "tools/call",
18/// Some(json!({"name": "calculator"})),
19/// );
20///
21/// ctx.add_metadata("user-agent", json!("Claude-Code/1.0"));
22///
23/// assert_eq!(ctx.method(), "tools/call");
24/// assert!(ctx.params().is_some());
25/// assert_eq!(ctx.metadata().get("user-agent").unwrap(), "Claude-Code/1.0");
26/// ```
27#[derive(Debug, Clone)]
28pub struct RequestContext<'a> {
29 /// MCP method name (e.g., "tools/call", "resources/read")
30 method: &'a str,
31
32 /// Request parameters (JSON-RPC params field)
33 params: Option<Value>,
34
35 /// Transport-specific metadata (HTTP headers, Lambda event fields, etc.)
36 metadata: Map<String, Value>,
37
38 /// Bearer token extracted from Authorization header (D5: isolated from metadata)
39 bearer_token: Option<String>,
40
41 /// Request-scoped extensions for passing data between middleware and tools (D3)
42 ///
43 /// Written by pre-session middleware (e.g., auth claims), threaded through
44 /// to `SessionContext.extensions` for tool access. Never persisted to storage.
45 extensions: HashMap<String, Value>,
46}
47
48impl<'a> RequestContext<'a> {
49 /// Create a new request context
50 ///
51 /// # Parameters
52 ///
53 /// - `method`: MCP method name (e.g., "tools/call")
54 /// - `params`: Optional request parameters
55 pub fn new(method: &'a str, params: Option<Value>) -> Self {
56 Self {
57 method,
58 params,
59 metadata: Map::new(),
60 bearer_token: None,
61 extensions: HashMap::new(),
62 }
63 }
64
65 /// Get the MCP method name
66 pub fn method(&self) -> &str {
67 self.method
68 }
69
70 /// Get request parameters (if any)
71 pub fn params(&self) -> Option<&Value> {
72 self.params.as_ref()
73 }
74
75 /// Get mutable request parameters
76 pub fn params_mut(&mut self) -> Option<&mut Value> {
77 self.params.as_mut()
78 }
79
80 /// Get transport metadata (read-only)
81 pub fn metadata(&self) -> &Map<String, Value> {
82 &self.metadata
83 }
84
85 /// Add metadata entry
86 ///
87 /// # Examples
88 ///
89 /// ```rust,no_run
90 /// use turul_http_mcp_server::middleware::RequestContext;
91 /// use serde_json::json;
92 ///
93 /// let mut ctx = RequestContext::new("tools/call", None);
94 /// ctx.add_metadata("client-ip", json!("127.0.0.1"));
95 /// ```
96 pub fn add_metadata(&mut self, key: impl Into<String>, value: Value) {
97 self.metadata.insert(key.into(), value);
98 }
99
100 /// Get the Bearer token (if extracted from Authorization header)
101 pub fn bearer_token(&self) -> Option<&str> {
102 self.bearer_token.as_deref()
103 }
104
105 /// Set the Bearer token (called by transport during extraction)
106 pub fn set_bearer_token(&mut self, token: String) {
107 self.bearer_token = Some(token);
108 }
109
110 /// Get request-scoped extensions (read-only)
111 pub fn extensions(&self) -> &HashMap<String, Value> {
112 &self.extensions
113 }
114
115 /// Set an extension value (used by middleware to pass data to tools)
116 pub fn set_extension(&mut self, key: impl Into<String>, value: Value) {
117 self.extensions.insert(key.into(), value);
118 }
119
120 /// Get an extension value by key
121 pub fn get_extension(&self, key: &str) -> Option<&Value> {
122 self.extensions.get(key)
123 }
124
125 /// Take ownership of extensions (used by transport to thread to SessionContext)
126 pub fn take_extensions(&mut self) -> HashMap<String, Value> {
127 std::mem::take(&mut self.extensions)
128 }
129}
130
131/// Write-only mechanism for middleware to populate session state
132///
133/// Prevents middleware from interfering with core session management while
134/// allowing controlled injection of custom state and metadata.
135///
136/// # Design
137///
138/// - **Write-only**: Middleware cannot read existing session state
139/// - **Deferred application**: Changes applied after all middleware succeed
140/// - **Isolation**: Each middleware's changes are independent
141///
142/// # Examples
143///
144/// ```rust,no_run
145/// use turul_http_mcp_server::middleware::SessionInjection;
146/// use serde_json::json;
147///
148/// let mut injection = SessionInjection::new();
149///
150/// // Set typed state
151/// injection.set_state("user_id", json!(12345));
152/// injection.set_state("role", json!("admin"));
153///
154/// // Set metadata
155/// injection.set_metadata("authenticated_at", json!("2025-10-04T12:00:00Z"));
156///
157/// // State and metadata are applied to session after middleware succeeds
158/// assert!(!injection.is_empty());
159/// ```
160#[derive(Debug, Default, Clone)]
161pub struct SessionInjection {
162 /// State entries to inject into session
163 state: HashMap<String, Value>,
164
165 /// Metadata entries to inject into session
166 metadata: HashMap<String, Value>,
167}
168
169impl SessionInjection {
170 /// Create a new empty session injection
171 pub fn new() -> Self {
172 Self::default()
173 }
174
175 /// Set a state entry
176 ///
177 /// # Parameters
178 ///
179 /// - `key`: State key (used with `SessionContext::get_typed_state()`)
180 /// - `value`: JSON value to store
181 pub fn set_state(&mut self, key: impl Into<String>, value: Value) {
182 self.state.insert(key.into(), value);
183 }
184
185 /// Set a metadata entry
186 ///
187 /// # Parameters
188 ///
189 /// - `key`: Metadata key
190 /// - `value`: JSON value to store
191 pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
192 self.metadata.insert(key.into(), value);
193 }
194
195 /// Get all state entries (for internal use)
196 pub(crate) fn state(&self) -> &HashMap<String, Value> {
197 &self.state
198 }
199
200 /// Get all metadata entries (for internal use)
201 pub(crate) fn metadata(&self) -> &HashMap<String, Value> {
202 &self.metadata
203 }
204
205 /// Check if injection is empty
206 pub fn is_empty(&self) -> bool {
207 self.state.is_empty() && self.metadata.is_empty()
208 }
209}
210
211/// Result from the MCP dispatcher (success or error)
212///
213/// Middleware can inspect and modify this result in `after_dispatch()`.
214///
215/// # Examples
216///
217/// ```rust,no_run
218/// use turul_http_mcp_server::middleware::DispatcherResult;
219/// use serde_json::json;
220///
221/// let mut result = DispatcherResult::Success(json!({"output": "Hello"}));
222///
223/// // Middleware can transform successful responses
224/// if let DispatcherResult::Success(ref mut value) = result {
225/// if let Some(obj) = value.as_object_mut() {
226/// obj.insert("timestamp".to_string(), json!("2025-10-04T12:00:00Z"));
227/// }
228/// }
229/// ```
230#[derive(Debug, Clone)]
231pub enum DispatcherResult {
232 /// Successful response (JSON-RPC result field)
233 Success(Value),
234
235 /// Error response (will be converted to JSON-RPC error)
236 Error(String),
237}
238
239impl DispatcherResult {
240 /// Check if result is successful
241 pub fn is_success(&self) -> bool {
242 matches!(self, Self::Success(_))
243 }
244
245 /// Check if result is an error
246 pub fn is_error(&self) -> bool {
247 matches!(self, Self::Error(_))
248 }
249
250 /// Get success value (if any)
251 pub fn success(&self) -> Option<&Value> {
252 match self {
253 Self::Success(v) => Some(v),
254 Self::Error(_) => None,
255 }
256 }
257
258 /// Get mutable success value (if any)
259 pub fn success_mut(&mut self) -> Option<&mut Value> {
260 match self {
261 Self::Success(v) => Some(v),
262 Self::Error(_) => None,
263 }
264 }
265
266 /// Get error message (if any)
267 pub fn error(&self) -> Option<&str> {
268 match self {
269 Self::Success(_) => None,
270 Self::Error(e) => Some(e),
271 }
272 }
273}