-
Notifications
You must be signed in to change notification settings - Fork 0
Middleware Cookbook
Jeffrie Budde edited this page Nov 25, 2025
·
1 revision
Cross-cutting concerns for FastRMCP servers. See Core Concepts for registration order.
-
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).
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_requestviacontext.set_metadata("user", json!("alice")).await;and read them later.
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" }
}-
Auth: In
before_request, validate tokens/headers fromrequest.params["metadata"]and returnMCPError::InvalidRequest("unauthorized")on failure. -
Rate limiting: store counters in middleware struct (e.g.,
Arc<DashMap>). Keep hot paths non-blocking. -
Response shaping: Use
after_responseto 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 for auth/rate-limit guidance • Observability for tracing fields.