# Middleware Cookbook Cross-cutting concerns for FastRMCP servers. See [Core Concepts](Core-Concepts.md#registries) for registration order. ## Built-ins - **LoggingMiddleware**: `with_middleware(LoggingMiddleware::new())` (variants: `.requests_only()`, `.responses_only()`). - **AuthMiddleware**: `AuthMiddleware::bearer_token("secret")` (simplified pending metadata refactor). Use alongside custom auth for production. - **RateLimitMiddleware**: `RateLimitMiddleware::new(100, Duration::from_secs(60))` (simplified; per-client planned). ## Using Middleware ```rust use fastrmcp::middleware::{AuthMiddleware, LoggingMiddleware, RateLimitMiddleware}; use fastrmcp::prelude::*; use std::time::Duration; #[tokio::main] async fn main() -> Result<()> { let server = FastMCP::new("secured") .with_middleware(LoggingMiddleware::new()) // runs first before_request, last after_response .with_middleware(RateLimitMiddleware::new(100, Duration::from_secs(60))) .with_middleware(AuthMiddleware::bearer_token("secret")); server.run().await } ``` - Order matters: first added = outermost. Put logging first, auth/rate limiting next, business middleware last. - Metadata sharing: set values in `before_request` via `context.set_metadata("user", json!("alice")).await;` and read them later. ## Custom Middleware ```rust use async_trait::async_trait; use fastrmcp_core::{ context::Context, error::{MCPError, Result}, middleware::Middleware, protocol::{JsonRpcRequest, JsonRpcResponse}, }; use serde_json::json; pub struct Audit; #[async_trait] impl Middleware for Audit { async fn before_request(&self, request: &JsonRpcRequest, context: &mut Context) -> Result<()> { context.set_metadata("request_method", json!(request.method)).await; // Example auth: read bearer token from params.metadata or params._meta if let Some(token) = request .params .as_ref() .and_then(|p| p.get("metadata").or_else(|| p.get("_meta"))) .and_then(|m| m.get("auth").or_else(|| m.get("Authorization"))) .and_then(|v| v.as_str()) { context.set_metadata("auth_token", json!(token)).await; } Ok(()) } async fn after_response(&self, _req: &JsonRpcRequest, resp: &mut JsonRpcResponse, ctx: &Context) -> Result<()> { if let Some(method) = ctx.get_metadata("request_method").await { tracing::info!(method=?method, "completed"); } // mutate response if needed Ok(()) } async fn on_error(&self, req: &JsonRpcRequest, err: &MCPError, _ctx: &Context) -> Result<()> { tracing::warn!(method=%req.method, error=%err, "request failed"); Ok(()) } fn name(&self) -> &str { "Audit" } } ``` ## Patterns & Tips - **Auth**: In `before_request`, validate tokens/headers from `request.params["metadata"]` and return `MCPError::InvalidRequest("unauthorized")` on failure. - **Rate limiting**: store counters in middleware struct (e.g., `Arc`). Keep hot paths non-blocking. - **Response shaping**: Use `after_response` to attach metadata or strip sensitive fields; remember reverse execution order. - **Error handling**: Decide whether to block (`Err(...)`) or log and continue. Be explicit to avoid silent drops. - **Testing**: Middleware runs for every request; keep operations async and fast to avoid head-of-line blocking. Related: [Security](Security.md) for auth/rate-limit guidance • [Observability](Observability.md) for tracing fields. [Back to Home](Home.md)