Skip to main content

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}