pub trait McpMiddleware: Send + Sync {
// Required method
fn before_dispatch<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
ctx: &'life1 mut RequestContext<'life2>,
session: Option<&'life3 dyn SessionView>,
injection: &'life4 mut SessionInjection,
) -> Pin<Box<dyn Future<Output = Result<(), MiddlewareError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait;
// Provided method
fn after_dispatch<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 RequestContext<'life2>,
result: &'life3 mut DispatcherResult,
) -> Pin<Box<dyn Future<Output = Result<(), MiddlewareError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait { ... }
}Expand description
Core middleware trait for intercepting MCP requests and responses
Middleware can inspect and modify requests before they reach the dispatcher, and inspect/modify responses before they’re sent to the client.
§Lifecycle
-
Before Dispatch: Called before the MCP method handler executes
- Access to request method, parameters, and metadata
- Can inject state into session via
SessionInjection - Can short-circuit request by returning error
-
After Dispatch: Called after the MCP method handler completes
- Access to the result (success or error)
- Can modify the response
- Can log, audit, or transform results
§Transport Agnostic
Middleware works across all transports (HTTP, Lambda) via normalized RequestContext.
§Examples
use turul_http_mcp_server::middleware::{McpMiddleware, RequestContext, SessionInjection, MiddlewareError};
use turul_mcp_session_storage::SessionView;
use async_trait::async_trait;
struct AuthMiddleware {
api_key: String,
}
#[async_trait]
impl McpMiddleware for AuthMiddleware {
async fn before_dispatch(
&self,
ctx: &mut RequestContext<'_>,
session: Option<&dyn SessionView>,
injection: &mut SessionInjection,
) -> Result<(), MiddlewareError> {
// Extract API key from metadata
let provided_key = ctx.metadata()
.get("api-key")
.and_then(|v| v.as_str())
.ok_or_else(|| MiddlewareError::Unauthorized("Missing API key".into()))?;
// Validate
if provided_key != self.api_key {
return Err(MiddlewareError::Unauthorized("Invalid API key".into()));
}
// Inject auth metadata into session (if session exists)
injection.set_metadata("authenticated", serde_json::json!(true));
// For initialize (session is None), injection will be applied when session is created
// For other methods (session is Some), can also read existing state if needed
if let Some(sess) = session {
// Can check existing session state for rate limiting, etc.
}
Ok(())
}
}Required Methods§
Sourcefn before_dispatch<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
ctx: &'life1 mut RequestContext<'life2>,
session: Option<&'life3 dyn SessionView>,
injection: &'life4 mut SessionInjection,
) -> Pin<Box<dyn Future<Output = Result<(), MiddlewareError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
fn before_dispatch<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
ctx: &'life1 mut RequestContext<'life2>,
session: Option<&'life3 dyn SessionView>,
injection: &'life4 mut SessionInjection,
) -> Pin<Box<dyn Future<Output = Result<(), MiddlewareError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Called before the MCP method handler executes
§Parameters
ctx: Mutable request context (method, params, metadata)session: Optional read-only access to session viewNoneforinitialize(session doesn’t exist yet)Some(session)for all other methods (session already created)
injection: Write-only mechanism to populate session state/metadata
§Returns
Ok(()): Continue to next middleware or dispatcherErr(MiddlewareError): Short-circuit and return error to client
§Notes
- Middleware executes in registration order
- First error stops the chain
- Session injection is applied after all middleware succeed
- For
initialize, session isNonebut middleware can still validate headers/rate-limit
Provided Methods§
Sourcefn after_dispatch<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 RequestContext<'life2>,
result: &'life3 mut DispatcherResult,
) -> Pin<Box<dyn Future<Output = Result<(), MiddlewareError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn after_dispatch<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 RequestContext<'life2>,
result: &'life3 mut DispatcherResult,
) -> Pin<Box<dyn Future<Output = Result<(), MiddlewareError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Called after the MCP method handler completes (optional)
§Parameters
ctx: Read-only request contextresult: Mutable dispatcher result (can modify response/error)
§Returns
Ok(()): Continue to next middlewareErr(MiddlewareError): Replace result with error
§Notes
- Middleware executes in reverse registration order
- Default implementation is a no-op
- Can transform successful responses or errors