Skip to main content
The middleware module provides three layers of interception for an MCPServer:
  1. Operation-level MCP middleware, a Hono-style chain registered with server.use('mcp:...') that wraps individual MCP operations (tool calls, resource reads, prompt gets, and list operations). Implemented by composeMiddleware, matchesPattern, and the MiddlewareContext types.
  2. HTTP middleware adapters, adaptMiddleware, adaptConnectMiddleware, and isExpressMiddleware, which let you register either Express/Connect middleware (such as morgan or express-rate-limit) or native Hono middleware on the underlying HTTP app.
  3. A CORS proxy, mountMcpProxy, that forwards browser-originated MCP requests to remote servers that do not support CORS.
All symbols below are exported from mcp-use/server.
import {
  composeMiddleware,
  matchesPattern,
  adaptMiddleware,
  adaptConnectMiddleware,
  isExpressMiddleware,
  mountMcpProxy,
  type McpMiddlewareEntry,
  type McpMiddlewareFn,
  type McpMiddlewareFnFor,
  type McpMiddlewarePatternMap,
  type MiddlewareContext,
  type ToolsCallMiddlewareContext,
  type ResourcesReadMiddlewareContext,
  type PromptsGetMiddlewareContext,
  type McpProxyOptions,
} from "mcp-use/server";

Operation-level MCP middleware

Hono-style middleware for intercepting MCP operations. Register handlers with server.use('mcp:...', handler). Each handler receives a MiddlewareContext and a next function, executes around next(), and may inspect or mutate ctx.params, short-circuit by returning a value, or reject by throwing.
import { composeMiddleware, matchesPattern } from "mcp-use/server";
The example below registers operation-level middleware directly on a server. Middleware runs in FIFO registration order (the first registered handler is the outermost).
import { MCPServer, text } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "middleware-example",
  version: "1.0.0",
});

// Logging middleware, fires for every MCP operation ('mcp:*')
server.use("mcp:*", async (ctx, next) => {
  const start = Date.now();
  console.log(`→ [${ctx.method}]`, JSON.stringify(ctx.params));
  const result = await next();
  console.log(`← [${ctx.method}] ${Date.now() - start}ms`);
  return result;
});

// Auth guard, checks OAuth scopes before any tool call ('mcp:tools/call')
server.use("mcp:tools/call", async (ctx, next) => {
  if (ctx.auth) {
    const toolName = (ctx.params as { name?: string }).name ?? "";
    const required = `tools:call:${toolName}`;
    if (
      !ctx.auth.scopes.includes(required) &&
      !ctx.auth.scopes.includes("tools:*")
    ) {
      throw new Error(`Insufficient scope. Required: ${required}`);
    }
  }
  return next();
});

server.tool(
  {
    name: "greet",
    description: "Return a greeting for a given name",
    schema: z.object({ name: z.string() }),
  },
  async ({ name }) => text(`Hello, ${name}!`)
);

await server.listen();

composeMiddleware

Compose a middleware chain for a single MCP method invocation. It filters entries down to those whose pattern matches method, then builds a next() chain with innerFn at the center and returns the outermost callable. If no entries match method, it returns a function that ignores its context and simply calls innerFn directly (zero overhead). Otherwise the returned function dispatches through each matching handler in FIFO registration order, with the first registered handler running as the outermost wrapper. Each handler’s next advances to the following handler (or to innerFn when the last handler is reached). Calling next() more than once within the same handler rejects with Error("next() called multiple times"). Parameters
entries
McpMiddlewareEntry[]
required
Registered middleware entries (pattern plus handler). Only entries matching method are included in the chain.
method
string
required
The MCP method being invoked, e.g. "tools/call" or "resources/read".
innerFn
() => Promise<unknown>
required
The terminal function (the actual handler) called once the chain reaches its center.
Returns
returns
(ctx: MiddlewareContext) => Promise<unknown>
The outermost callable. Invoke it with a MiddlewareContext to run the chain.
Signature
function composeMiddleware(entries: McpMiddlewareEntry[], method: string, innerFn: () => Promise<unknown>): (ctx: MiddlewareContext) => Promise<unknown>

matchesPattern

Test whether a registered middleware pattern matches a given MCP method. The pattern here is the value after the mcp: prefix has been stripped. Matching rules:
  • "*" matches any method.
  • A pattern ending in "/*" (such as "tools/*") prefix-matches any method that starts with the part before the * (such as "tools/"). The match is computed by slicing off the trailing * and calling method.startsWith(prefix).
  • Any other pattern (such as "tools/call") is an exact match only.
Parameters
pattern
string
required
The pattern to test, with the mcp: prefix already removed, e.g. "*", "tools/*", or "tools/call".
method
string
required
The MCP method name to match against, e.g. "tools/call".
Returns
returns
boolean
true if the pattern matches the method.
Signature
function matchesPattern(pattern: string, method: string): boolean

MiddlewareContext

The context passed to every MCP middleware handler. params is mutable: a middleware can modify it before calling next(), and those changes are visible to downstream middleware and to the handler. state is a shared Map for passing arbitrary data across middleware in the same request chain. Properties
method
string
required
MCP method name, e.g. "tools/call", "tools/list", "resources/read".
params
Record<string, unknown>
required
JSON-RPC request params. Mutable: mutations are passed downstream.
session
{ sessionId: string }
Session info if available (HTTP transports only).
auth
AuthInfo
OAuth info extracted from the JWT. Present only when OAuth is configured; otherwise undefined.
state
Map<string, unknown>
required
Shared state map for passing data across middleware in the same request.
Signature
interface MiddlewareContext {
  method: string;
  params: Record<string, unknown>;
  session?: { sessionId: string };
  auth?: AuthInfo;
  state: Map<string, unknown>;
}

McpMiddlewareFn

A single MCP middleware function. Call next() to pass control to the next middleware (or the handler). Return its result, or return a different value to override the response, or throw an error to reject the request. Parameters
ctx
MiddlewareContext
required
The middleware context for the current operation.
next
() => Promise<unknown>
required
Advances the chain. Resolves with the result of the next middleware or the handler.
Returns
returns
Promise<unknown>
The (possibly overridden) result of the operation.
Signature
type McpMiddlewareFn = (ctx: MiddlewareContext, next: () => Promise<unknown>) => Promise<unknown>

McpMiddlewareFnFor

A typed MCP middleware function whose ctx parameter is narrowed based on the pattern string P. When P is a key of McpMiddlewarePatternMap ("tools/call", "resources/read", or "prompts/get"), ctx is narrowed to the matching context type. For wildcard or unrecognized patterns it falls back to the base McpMiddlewareFn whose ctx is the base MiddlewareContext. Type parameters
P
extends string
required
The middleware pattern string used to select the narrowed context type.
Signature
type McpMiddlewareFnFor<P extends string> = P extends keyof McpMiddlewarePatternMap
  ? (ctx: McpMiddlewarePatternMap[P], next: () => Promise<unknown>) => Promise<unknown>
  : McpMiddlewareFn

McpMiddlewarePatternMap

Maps MCP middleware pattern strings to their narrowed context type. Used by McpMiddlewareFnFor to infer the correct ctx parameter for a given pattern. Properties
tools/call
ToolsCallMiddlewareContext
required
Narrowed context for the tools/call method.
resources/read
ResourcesReadMiddlewareContext
required
Narrowed context for the resources/read method.
prompts/get
PromptsGetMiddlewareContext
required
Narrowed context for the prompts/get method.
Signature
interface McpMiddlewarePatternMap {
  "tools/call": ToolsCallMiddlewareContext;
  "resources/read": ResourcesReadMiddlewareContext;
  "prompts/get": PromptsGetMiddlewareContext;
}

ToolsCallMiddlewareContext

A MiddlewareContext with method fixed to "tools/call" and params narrowed to the tools/call shape: name (the tool name), an optional arguments record, and an optional _meta record. The params type is intersected with Record<string, unknown> so arbitrary extra keys remain accessible. Properties
method
"tools/call"
required
Always the literal "tools/call".
params
{ name: string; arguments?: Record<string, unknown>; _meta?: Record<string, unknown> } & Record<string, unknown>
required
The narrowed tool-call params.
Signature
interface ToolsCallMiddlewareContext extends MiddlewareContext {
  method: "tools/call";
  params: { name: string; arguments?: Record<string, unknown>; _meta?: Record<string, unknown> } & Record<string, unknown>;
}

ResourcesReadMiddlewareContext

A MiddlewareContext with method fixed to "resources/read" and params narrowed to the resources/read shape: uri (the resource URI) and an optional _meta record. The params type is intersected with Record<string, unknown> so extra keys remain accessible. Properties
method
"resources/read"
required
Always the literal "resources/read".
params
{ uri: string; _meta?: Record<string, unknown> } & Record<string, unknown>
required
The narrowed resource-read params.
Signature
interface ResourcesReadMiddlewareContext extends MiddlewareContext {
  method: "resources/read";
  params: { uri: string; _meta?: Record<string, unknown> } & Record<string, unknown>;
}

PromptsGetMiddlewareContext

A MiddlewareContext with method fixed to "prompts/get" and params narrowed to the prompts/get shape: name (the prompt name), an optional arguments record of strings, and an optional _meta record. The params type is intersected with Record<string, unknown> so extra keys remain accessible. Properties
method
"prompts/get"
required
Always the literal "prompts/get".
params
{ name: string; arguments?: Record<string, string>; _meta?: Record<string, unknown> } & Record<string, unknown>
required
The narrowed prompt-get params.
Signature
interface PromptsGetMiddlewareContext extends MiddlewareContext {
  method: "prompts/get";
  params: { name: string; arguments?: Record<string, string>; _meta?: Record<string, unknown> } & Record<string, unknown>;
}

McpMiddlewareEntry

Internal storage entry for a registered MCP middleware. The pattern is stored with the mcp: prefix already stripped (e.g. "tools/call", "tools/*", or "*"). This is the element type consumed by composeMiddleware.
This interface is marked @internal. It is exported for advanced use and for typing custom middleware composition, but most servers register middleware through server.use('mcp:...') and never construct entries directly.
Properties
pattern
string
required
Pattern after stripping "mcp:", e.g. "tools/call", "tools/*", or "*".
handler
McpMiddlewareFn
required
The middleware function to run for matching methods.
Signature
interface McpMiddlewareEntry {
  pattern: string;
  handler: McpMiddlewareFn;
}

HTTP middleware adapters

Adapters that let you register either Express/Connect middleware or native Hono middleware on the server’s underlying HTTP app. server.use(...) uses these internally to detect the middleware style and adapt accordingly, so the same server.use(...) call accepts both.
import {
  adaptMiddleware,
  adaptConnectMiddleware,
  isExpressMiddleware,
} from "mcp-use/server";
The example below registers both Express middleware from npm and native Hono middleware on the same server. server.use(...) detects each style and adapts it for you.
import { MCPServer, text } from "mcp-use/server";
import morgan from "morgan";
import rateLimit from "express-rate-limit";

const server = new MCPServer({
  name: "express-middleware-example",
  version: "1.0.0",
});

// Express middleware from npm
server.use(morgan("combined"));
server.use("/api", rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));

// Hono middleware (c, next)
server.use(async (c, next) => {
  const start = Date.now();
  await next();
  console.log(`${c.req.method} ${c.req.path} - ${Date.now() - start}ms`);
});

await server.listen();

isExpressMiddleware

Detect whether a middleware function is Express/Connect style or Hono style. Express/Connect middleware has the signature (req, res, next) (or (err, req, res, next)); Hono middleware has the signature (c, next). Detection combines function arity with body pattern matching against the stringified function:
  • A non-function (or falsy) value returns false.
  • Functions with 3 or 4 parameters are treated as Express/Connect and return true (the body is checked for Express patterns such as res.send, res.json, req.body, but it returns true for 3-4 parameter functions regardless).
  • Functions with exactly 2 parameters are treated as Hono and return false, unless the body contains Express-specific patterns (then true). Hono-specific patterns such as c.req or c.json confirm the Hono case (false).
  • Functions with any other parameter count (0, 1, or 5+) default to Hono and return false.
Parameters
middleware
any
required
The middleware function to inspect.
Returns
returns
boolean
true if it is Express/Connect middleware, false if it is Hono middleware.
Signature
function isExpressMiddleware(middleware: any): boolean

adaptMiddleware

Adapt a middleware function so it works with Hono. It calls isExpressMiddleware: if the input is Express/Connect style, it delegates to adaptConnectMiddleware with the supplied path; otherwise it returns the middleware unchanged as a Hono MiddlewareHandler. Parameters
middleware
any
required
The middleware function (Express/Connect or Hono).
middlewarePath
string
default:"\"*\""
The path pattern the middleware is mounted at. Only used when the middleware is Express/Connect. Defaults to "*".
Returns
returns
Promise<MiddlewareHandler>
A Hono middleware handler.
Signature
function adaptMiddleware(middleware: any, middlewarePath?: string): Promise<MiddlewareHandler>

adaptConnectMiddleware

Adapt a Connect/Express middleware handler to a Hono MiddlewareHandler. It dynamically imports node-mocks-http to build a mock IncomingMessage/ServerResponse pair, runs the Connect middleware against them, and translates the result back into a Hono response. Behavior to note:
  • The middlewarePath is normalized by stripping a trailing * and a trailing /, then that prefix is removed from the request pathname so the Connect middleware sees the path without its mount prefix.
  • If the Connect middleware ends the response (calls res.end), the captured status, headers, and body become the Hono response. Status codes 204 and 304 are sent without a body.
  • If the Connect middleware calls next() without ending the response, control passes to Hono’s next() so the request continues down the chain.
Parameters
connectMiddleware
any
required
The Connect/Express middleware handler to adapt.
middlewarePath
string
required
The path pattern the middleware is mounted at, e.g. "/mcp-use/widgets/*".
Returns
returns
Promise<MiddlewareHandler>
A Hono middleware handler wrapping the Connect middleware.
Signature
function adaptConnectMiddleware(connectMiddleware: any, middlewarePath: string): Promise<MiddlewareHandler>

CORS proxy

A CORS proxy for browser-based MCP clients that need to connect to remote MCP servers which do not support CORS or require server-side forwarding. Mount it onto a Hono app with mountMcpProxy.
import { mountMcpProxy } from "mcp-use/server";

mountMcpProxy

Mount the MCP proxy middleware on a Hono app. The proxy reads a target URL from the X-Target-URL header (or from an __mcp_target query parameter used for OAuth discovery, which takes precedence), forwards the request to that target with sanitized headers, and streams the response back to the client. It enables CORS (exposing all response headers, with Authorization allowed explicitly), handles SSE streaming, manages redirects manually to avoid ArrayBuffer detachment, and rewrites the resource field on OAuth discovery responses to match the proxy URL. Returns void. The proxy registers routes under options.path (default "/mcp/proxy"): a CORS handler and an optional logger on the path/* glob, plus a catch-all handler for every HTTP method on that same glob. Error and status behavior:
  • Missing target URL responds 400 with an X-Target-URL header is required error.
  • An invalid target URL format responds 400.
  • When authenticate returns false, responds 401 (Unauthorized).
  • When validateRequest returns false, responds 403 (Invalid target URL).
  • Upstream failures respond 500; connection-refused errors (ECONNREFUSED or fetch failed) are logged as a warning rather than an error.
This proxy does not implement authentication by default. For production use, provide an authenticate function or restrict access to localhost only.
Parameters
app
Hono
required
The Hono application instance to mount the proxy on.
options
McpProxyOptions
default:"{}"
Proxy configuration. Defaults to an empty object.
Returns
returns
void
Signature
function mountMcpProxy(app: Hono, options?: McpProxyOptions): void
import { Hono } from "hono";
import { mountMcpProxy } from "mcp-use/server";

const app = new Hono();

// With authentication and target validation
mountMcpProxy(app, {
  path: "/api/proxy",
  authenticate: async (c) => {
    const token = c.req.header("Authorization");
    return token === `Bearer ${process.env.SECRET_TOKEN}`;
  },
  validateRequest: (targetUrl) => targetUrl.startsWith("https://mcp.example.com"),
});

McpProxyOptions

Configuration for mountMcpProxy. Every field is optional. Properties
path
string
default:"\"/mcp/proxy\""
Route path for the proxy endpoint, e.g. "/inspector/api/proxy".
authenticate
(c: Context) => Promise<boolean> | boolean
Optional authentication function. Return true to allow the request, false to reject with 401.
validateRequest
(targetUrl: string, c: Context) => Promise<boolean> | boolean
Optional validator checking whether the target URL is allowed. Return true to allow, false to reject with 403.
enableLogging
boolean
default:"true"
Enable request logging. Disabled only when explicitly set to false.
Signature
interface McpProxyOptions {
  path?: string;
  authenticate?: (c: Context) => Promise<boolean> | boolean;
  validateRequest?: (targetUrl: string, c: Context) => Promise<boolean> | boolean;
  enableLogging?: boolean;
}

See also

MCPServer

The server class whose use(...) method registers the middleware documented here.

Source on GitHub

Full middleware and proxy implementation.