McpMiddleware

Trait McpMiddleware 

Source
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

  1. 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
  2. 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§

Source

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 view
    • None for initialize (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 dispatcher
  • Err(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 is None but middleware can still validate headers/rate-limit

Provided Methods§

Source

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 context
  • result: Mutable dispatcher result (can modify response/error)
§Returns
  • Ok(()): Continue to next middleware
  • Err(MiddlewareError): Replace result with error
§Notes
  • Middleware executes in reverse registration order
  • Default implementation is a no-op
  • Can transform successful responses or errors

Implementors§