Middlewares
Intercept HTTP requests and MCP operations using src/middleware.ts.
For the complete documentation index, see llms.txt. Markdown variants of every page are available by appending .md to the URL.Define HTTP middleware with the default export of src/middleware.ts, and MCP middleware with its named mcp export. Both can live in the same file.
HTTP middleware
HTTP middleware intercepts requests and responses, enabling authentication, rate limiting, and other processing tasks.
Create a src/middleware.ts file to define your middleware:
xmcp provides built-in middlewares for common tasks like API key authentication and JSON web token authentication.
Chaining middlewares
Define multiple middlewares as an array to chain them in sequence:
MCP middleware
Export one McpMiddleware function or an array as mcp. xmcp discovers it through the same src/middleware.ts file and applies it to tool calls, prompts, resource reads, completions, and listings over HTTP, STDIO, and the Node adapters. A default export is optional.
The first function enters first and receives the result last. await next() runs the remaining chain and the operation handler. Return a protocol result without calling next() to skip the remaining chain and handler. Call next() at most once; calling it twice rejects rather than executing the handler again. Return or await its promise so work stays inside the request lifecycle.
ctx.method identifies the operation and narrows ctx.params:
| Method | Parameters |
|---|---|
tools/call | name and SDK-validated arguments |
prompts/get | name and optional arguments |
resources/read | uri, for static, templated, and generated UI resources |
completion/complete | ref, argument, and optional completion context |
tools/list, prompts/list, resources/list, resources/templates/list | Optional pagination cursor and request metadata |
Always check ctx.method before accessing operation-specific parameters. Existing middleware that assumes every request is a tool call should begin with if (ctx.method !== "tools/call") return next();.
ctx also includes authInfo, clientInfo, http, signal, set, get, progress, and log from request context. getRequestContext() works before and after next(), and inside prompt, resource, and completion callbacks. Local values last for one operation and are isolated from concurrent requests. HTTP metadata is absent on STDIO.
Middleware receives normalized MCP results from next(). Return the result format for the current method; string and number shorthand belongs in component handlers. Check the result shape before accessing method-specific fields. Exceptions retain SDK behavior: tools produce tool-error results, while reads, prompts, completion, and listings reject with protocol errors. Invalid mcp exports or a failed middleware import prevent server setup from completing.
Tool middleware runs after the SDK resolves the tool and validates its input. Other methods are wrapped at SDK request-handler registration, before component lookup or callbacks. Resource template reads, template listing callbacks, and completion callbacks share that same chain and request scope. Only methods installed by the registered components are available. Initialization, ping, notifications, and unknown methods are unaffected.
Keep output-schema requirements intact when changing a tool result, and preserve interim inputRequired results when passing through multi-round tools.
Registered tool metadata
After narrowing ctx.method to "tools/call", ctx.tool.metadata is always available. Its exported type is McpToolMetadata: resolved name and description, optional annotations, icons, tags, and _meta. Defaults are applied, and _meta matches tools/list, including "xmcp/tags" and flattened UI keys such as "ui/resourceUri". Resource-only UI settings are excluded. This is the registered view, not the tool module's original ToolMetadata export.
The metadata is a snapshot taken at registration. Plain objects and arrays are copied and frozen without freezing the original tool export or SDK configuration. Treat all metadata as read-only. tool is absent on other middleware methods and is not a field of getRequestContext().
Use the same metadata policy to filter discovery and authorize direct calls:
Declare _meta: { "demo/requires": ["greet"] } in the protected tool's metadata. Supply verified authentication through your HTTP integration; see request-context authentication. Tools with no policy remain available, while malformed policies deny access. The middleware array example includes demo credentials and requests for both permitted and restricted callers.
Filter discovery and deny direct access
Filtering a listing alone does not prevent clients from calling a known name or URI. Enforce the corresponding operation as well:
The same pattern applies to prompt and tool listings. Completion middleware can return { completion: { values: [] } } without calling next() to suppress suggestions for a restricted prompt or resource reference.
The default HTTP export retains its existing transport behavior. STDIO loads the module to read mcp but does not invoke the default HTTP middleware. Keep module-level code safe to import on both transports.
For the Next.js adapter, keep the xmcp project separate from the Next.js application's source directory and import its compiled adapter into the route. Next.js reserves src/middleware.ts for its own request hook; placing xmcp's middleware there can make Next.js compile it as a Next.js hook. For example, use sibling mcp/ and web/ projects, with xmcp's file at mcp/src/middleware.ts and the Next.js route in web/app/.
See the runnable middleware array example for both exports together and curl requests that exercise each chain.
Accessing headers
Use the xmcp/headers module to read request headers in your tools, prompts, or resources—useful for API keys, authentication tokens, and other custom headers.
