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}