Skip to main content
Middleware lets you intercept MCP requests to add logging, authentication, rate limiting, validation, or any cross-cutting logic.

Quick Start

How It Works

Middleware executes in an onion model: each middleware wraps the next, with the handler at the center.
Each middleware can:
  • Inspect/modify the request before calling call_next
  • Inspect/modify the response after call_next returns
  • Short-circuit by returning early without calling call_next
  • Reject by raising an exception

Hooks

Override these methods to intercept specific request types:
Typed context: Each hook receives a fully-typed context.message. For example, on_initialize gets ServerMiddlewareContext[InitializeRequestParams], so your editor knows exactly what fields are available (like context.message.clientInfo.name). No guessing, full autocomplete.

Hook nesting

When you override both on_request and a specific hook, they nest: on_request wraps the specific hook.
Use on_request for logic that applies to all requests. Use specific hooks when you only care about certain operations.

Context

Every hook receives a ServerMiddlewareContext with:
Context is immutable. Use context.copy() to pass data downstream:

Examples

Reject requests without a valid API key:

Middleware Order

Order matters. Middleware runs in the order added, with earlier middleware wrapping later ones.
Recommended order: Logging → Authentication → Rate limiting → Validation. This ensures logging sees all requests (including rejected ones) and auth rejects early before expensive operations.

Best Practices

  • Single responsibility: Each middleware does one thing
  • Fail fast: Reject invalid requests early, before expensive operations
  • Always call call_next: Unless intentionally short-circuiting
  • Re-raise exceptions: If you catch errors to log them, always re-raise

Full Example

middleware_example.py

Complete working server with logging, auth, rate limiting, and validation middleware.