Skip to main content
The mcp-use middleware system provides a powerful, flexible way to intercept and process all MCP (Model Context Protocol) requests and responses. It uses a class-based hook pattern (with an Express-like next flow) so you can log, collect metrics, filter/transform requests, cache, rate limit, and more.

Middleware Process

mcp-use runs middleware in a chain. When a request like initialize, tools/call, resources/read, etc. is made, it flows through your middlewares (in order). In each middleware you can:
  1. Analyze the incoming request through the context
  2. Modify the request (and its metadata) before execution
  3. Execute the next handler via await call_next(context)
  4. Inspect/transform the result or handle errors
Transport support:
  • Fully supported: stdio, http (streamable HTTP and SSE), sandbox
  • Not yet supported: websocket

Overview

You implement middleware by subclassing Middleware and overriding one or more hooks. Each hook receives:
  • context: MiddlewareContext[T] - typed request context
  • call_next: NextFunctionT[T, R] - call to the next middleware or the actual MCP handler
Hooks are typed to the specific MCP method so you get strong IDE assistance when overriding specific operations like on_call_tool.

Quick Start

Core Types

MiddlewareContext[T]

NextFunctionT[T, R]

Middleware base class

You override hooks on the base class. If you only need a single entry point for all requests, override on_request.

Hook Reference

Available hooks (override any subset). Types below come from mcp.types.

Writing Middleware

Timing Example

Built-in Middleware

Default logging

  • A default logging middleware is automatically prepended by MCPClient.
  • It logs each request/response at debug level with timing.
  • You don’t need to add it manually; just pass your custom middlewares.

Metrics

Instantiate and pass to the client. Each middleware exposes a getter on the instance.

Middleware Chain Execution

Middleware executes in the order provided (outermost first):
Flow:
  1. mw1 starts → calls await call_next(context)
  2. mw2 starts → calls await call_next(context)
  3. mw3 starts → calls await call_next(context)
  4. Actual MCP call executes
  5. mw3 resumes with result
  6. mw2 resumes with result
  7. mw1 resumes with result

Error Handling

Always re-raise unless you’re intentionally transforming errors.

Best Practices

  1. Re-raise exceptions unless you have a clear alternative behavior.
  2. Use type hints on hooks for better IDE support.
  3. Keep each middleware focused on a single concern.
  4. Use context.metadata for cross-middleware communication.
Note on Modifying context.params and Headers Middleware receives a typed context.params object and may modify it before the request is executed. The runtime guarantees and recommended patterns are:
  • Preferred - Mutate fields on context.params:
    • Example: context.params.arguments["user_id"] = "alice"
    • These mutations are observed by the final MCP client call and are the most compatible approach.
  • Replacement - Reassigning context.params:
    • Example: context.params = NewParams(...)
    • This pattern is supported: the middleware system reads context.params at call-time so replacements are respected. However, prefer mutation for clarity and to avoid surprises for readers of middleware code.
  • Per-request HTTP headers:
    • Adding context.metadata["headers"] is useful for carrying header-like information through middleware, but it will only be applied to the actual HTTP transport if the connector/transport code explicitly reads and merges those values into the request headers.
    • There is no global automatic mechanism that takes context.metadata["headers"] and injects them into every transport unless the connector implements that behavior.
Example - add a trace id that downstream middleware or server can observe (does not automatically modify HTTP headers):
If you need middleware to inject actual HTTP headers per request, there are two safe approaches:
  1. Connector support (recommended): update the connector/transport to read context.metadata["headers"] and merge them into the outgoing HTTPX request headers for that call. This is robust and concurrency-safe when implemented correctly.
  2. Mutate request params the server understands: include header-like fields inside context.params (for example inside a tool’s arguments) and let the server interpret them.
If you’d like, we can add a small connector example showing how to merge context.metadata["headers"] into the HTTP request - say if you want automatic per-request header injection. Let me know and I will add that example as a follow-up.

Integration with MCP Clients

MCPClient

Per-session stacks

See the examples directory for a complete working example: