turul_http_mcp_server/middleware/context.rs
1//! Request context and session injection types
2
3use serde_json::{Value, Map};
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
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
39impl<'a> RequestContext<'a> {
40 /// Create a new request context
41 ///
42 /// # Parameters
43 ///
44 /// - `method`: MCP method name (e.g., "tools/call")
45 /// - `params`: Optional request parameters
46 pub fn new(method: &'a str, params: Option<Value>) -> Self {
47 Self {
48 method,
49 params,
50 metadata: Map::new(),
51 }
52 }
53
54 /// Get the MCP method name
55 pub fn method(&self) -> &str {
56 self.method
57 }
58
59 /// Get request parameters (if any)
60 pub fn params(&self) -> Option<&Value> {
61 self.params.as_ref()
62 }
63
64 /// Get mutable request parameters
65 pub fn params_mut(&mut self) -> Option<&mut Value> {
66 self.params.as_mut()
67 }
68
69 /// Get transport metadata (read-only)
70 pub fn metadata(&self) -> &Map<String, Value> {
71 &self.metadata
72 }
73
74 /// Add metadata entry
75 ///
76 /// # Examples
77 ///
78 /// ```rust
79 /// use turul_http_mcp_server::middleware::RequestContext;
80 /// use serde_json::json;
81 ///
82 /// let mut ctx = RequestContext::new("tools/call", None);
83 /// ctx.add_metadata("client-ip", json!("127.0.0.1"));
84 /// ```
85 pub fn add_metadata(&mut self, key: impl Into<String>, value: Value) {
86 self.metadata.insert(key.into(), value);
87 }
88}
89
90/// Write-only mechanism for middleware to populate session state
91///
92/// Prevents middleware from interfering with core session management while
93/// allowing controlled injection of custom state and metadata.
94///
95/// # Design
96///
97/// - **Write-only**: Middleware cannot read existing session state
98/// - **Deferred application**: Changes applied after all middleware succeed
99/// - **Isolation**: Each middleware's changes are independent
100///
101/// # Examples
102///
103/// ```rust
104/// use turul_http_mcp_server::middleware::SessionInjection;
105/// use serde_json::json;
106///
107/// let mut injection = SessionInjection::new();
108///
109/// // Set typed state
110/// injection.set_state("user_id", json!(12345));
111/// injection.set_state("role", json!("admin"));
112///
113/// // Set metadata
114/// injection.set_metadata("authenticated_at", json!("2025-10-04T12:00:00Z"));
115///
116/// // State and metadata are applied to session after middleware succeeds
117/// assert!(!injection.is_empty());
118/// ```
119#[derive(Debug, Default, Clone)]
120pub struct SessionInjection {
121 /// State entries to inject into session
122 state: HashMap<String, Value>,
123
124 /// Metadata entries to inject into session
125 metadata: HashMap<String, Value>,
126}
127
128impl SessionInjection {
129 /// Create a new empty session injection
130 pub fn new() -> Self {
131 Self::default()
132 }
133
134 /// Set a state entry
135 ///
136 /// # Parameters
137 ///
138 /// - `key`: State key (used with `SessionContext::get_typed_state()`)
139 /// - `value`: JSON value to store
140 pub fn set_state(&mut self, key: impl Into<String>, value: Value) {
141 self.state.insert(key.into(), value);
142 }
143
144 /// Set a metadata entry
145 ///
146 /// # Parameters
147 ///
148 /// - `key`: Metadata key
149 /// - `value`: JSON value to store
150 pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
151 self.metadata.insert(key.into(), value);
152 }
153
154 /// Get all state entries (for internal use)
155 pub(crate) fn state(&self) -> &HashMap<String, Value> {
156 &self.state
157 }
158
159 /// Get all metadata entries (for internal use)
160 pub(crate) fn metadata(&self) -> &HashMap<String, Value> {
161 &self.metadata
162 }
163
164 /// Check if injection is empty
165 pub fn is_empty(&self) -> bool {
166 self.state.is_empty() && self.metadata.is_empty()
167 }
168}
169
170/// Result from the MCP dispatcher (success or error)
171///
172/// Middleware can inspect and modify this result in `after_dispatch()`.
173///
174/// # Examples
175///
176/// ```rust
177/// use turul_http_mcp_server::middleware::DispatcherResult;
178/// use serde_json::json;
179///
180/// let mut result = DispatcherResult::Success(json!({"output": "Hello"}));
181///
182/// // Middleware can transform successful responses
183/// if let DispatcherResult::Success(ref mut value) = result {
184/// if let Some(obj) = value.as_object_mut() {
185/// obj.insert("timestamp".to_string(), json!("2025-10-04T12:00:00Z"));
186/// }
187/// }
188/// ```
189#[derive(Debug, Clone)]
190pub enum DispatcherResult {
191 /// Successful response (JSON-RPC result field)
192 Success(Value),
193
194 /// Error response (will be converted to JSON-RPC error)
195 Error(String),
196}
197
198impl DispatcherResult {
199 /// Check if result is successful
200 pub fn is_success(&self) -> bool {
201 matches!(self, Self::Success(_))
202 }
203
204 /// Check if result is an error
205 pub fn is_error(&self) -> bool {
206 matches!(self, Self::Error(_))
207 }
208
209 /// Get success value (if any)
210 pub fn success(&self) -> Option<&Value> {
211 match self {
212 Self::Success(v) => Some(v),
213 Self::Error(_) => None,
214 }
215 }
216
217 /// Get mutable success value (if any)
218 pub fn success_mut(&mut self) -> Option<&mut Value> {
219 match self {
220 Self::Success(v) => Some(v),
221 Self::Error(_) => None,
222 }
223 }
224
225 /// Get error message (if any)
226 pub fn error(&self) -> Option<&str> {
227 match self {
228 Self::Success(_) => None,
229 Self::Error(e) => Some(e),
230 }
231 }
232}