turul_http_mcp_server/middleware/
traits.rs

1//! Core middleware trait definitions
2
3use async_trait::async_trait;
4use super::{RequestContext, SessionInjection, DispatcherResult, MiddlewareError};
5use turul_mcp_session_storage::SessionView;
6
7/// Core middleware trait for intercepting MCP requests and responses
8///
9/// Middleware can inspect and modify requests before they reach the dispatcher,
10/// and inspect/modify responses before they're sent to the client.
11///
12/// # Lifecycle
13///
14/// 1. **Before Dispatch**: Called before the MCP method handler executes
15///    - Access to request method, parameters, and metadata
16///    - Can inject state into session via `SessionInjection`
17///    - Can short-circuit request by returning error
18///
19/// 2. **After Dispatch**: Called after the MCP method handler completes
20///    - Access to the result (success or error)
21///    - Can modify the response
22///    - Can log, audit, or transform results
23///
24/// # Transport Agnostic
25///
26/// Middleware works across all transports (HTTP, Lambda) via normalized `RequestContext`.
27///
28/// # Examples
29///
30/// ```rust,no_run
31/// use turul_http_mcp_server::middleware::{McpMiddleware, RequestContext, SessionInjection, MiddlewareError};
32/// use turul_mcp_session_storage::SessionView;
33/// use async_trait::async_trait;
34///
35/// struct AuthMiddleware {
36///     api_key: String,
37/// }
38///
39/// #[async_trait]
40/// impl McpMiddleware for AuthMiddleware {
41///     async fn before_dispatch(
42///         &self,
43///         ctx: &mut RequestContext<'_>,
44///         session: Option<&dyn SessionView>,
45///         injection: &mut SessionInjection,
46///     ) -> Result<(), MiddlewareError> {
47///         // Extract API key from metadata
48///         let provided_key = ctx.metadata()
49///             .get("api-key")
50///             .and_then(|v| v.as_str())
51///             .ok_or_else(|| MiddlewareError::Unauthorized("Missing API key".into()))?;
52///
53///         // Validate
54///         if provided_key != self.api_key {
55///             return Err(MiddlewareError::Unauthorized("Invalid API key".into()));
56///         }
57///
58///         // Inject auth metadata into session (if session exists)
59///         injection.set_metadata("authenticated", serde_json::json!(true));
60///
61///         // For initialize (session is None), injection will be applied when session is created
62///         // For other methods (session is Some), can also read existing state if needed
63///         if let Some(sess) = session {
64///             // Can check existing session state for rate limiting, etc.
65///         }
66///
67///         Ok(())
68///     }
69/// }
70/// ```
71#[async_trait]
72pub trait McpMiddleware: Send + Sync {
73    /// Called before the MCP method handler executes
74    ///
75    /// # Parameters
76    ///
77    /// - `ctx`: Mutable request context (method, params, metadata)
78    /// - `session`: Optional read-only access to session view
79    ///   - `None` for `initialize` (session doesn't exist yet)
80    ///   - `Some(session)` for all other methods (session already created)
81    /// - `injection`: Write-only mechanism to populate session state/metadata
82    ///
83    /// # Returns
84    ///
85    /// - `Ok(())`: Continue to next middleware or dispatcher
86    /// - `Err(MiddlewareError)`: Short-circuit and return error to client
87    ///
88    /// # Notes
89    ///
90    /// - Middleware executes in registration order
91    /// - First error stops the chain
92    /// - Session injection is applied after all middleware succeed
93    /// - For `initialize`, session is `None` but middleware can still validate headers/rate-limit
94    async fn before_dispatch(
95        &self,
96        ctx: &mut RequestContext<'_>,
97        session: Option<&dyn SessionView>,
98        injection: &mut SessionInjection,
99    ) -> Result<(), MiddlewareError>;
100
101    /// Called after the MCP method handler completes (optional)
102    ///
103    /// # Parameters
104    ///
105    /// - `ctx`: Read-only request context
106    /// - `result`: Mutable dispatcher result (can modify response/error)
107    ///
108    /// # Returns
109    ///
110    /// - `Ok(())`: Continue to next middleware
111    /// - `Err(MiddlewareError)`: Replace result with error
112    ///
113    /// # Notes
114    ///
115    /// - Middleware executes in reverse registration order
116    /// - Default implementation is a no-op
117    /// - Can transform successful responses or errors
118    #[allow(unused_variables)]
119    async fn after_dispatch(
120        &self,
121        ctx: &RequestContext<'_>,
122        result: &mut DispatcherResult,
123    ) -> Result<(), MiddlewareError> {
124        Ok(()) // Default: no-op
125    }
126}