MetaMCP Middleware System for Request/Response Processing: Architecture and Implementation Guide
MetaMCP uses a layered middleware pipeline that combines Next.js edge functions for frontend routing with an Express-based backend system enhanced by a functional middleware core, enabling composable request/response transformations for MCP JSON-RPC handlers.
MetaMCP implements a sophisticated middleware architecture to normalize, authenticate, rate-limit, and enrich every request before it reaches core handlers like List Tools and Call Tool. The system separates cross-cutting concerns from business logic through two distinct layers: a frontend edge middleware stack for internationalization and session validation, and a backend pipeline combining Express middleware with a functional composition framework. This design enables type-safe, reusable request/response transformers that maintain clean separation between transport concerns and MCP protocol handling.
Next.js Edge Middleware: Frontend Request Processing
The frontend layer runs on Vercel Edge Runtime to handle locale routing and session validation before requests reach the backend API.
Locale Detection and Routing
In apps/frontend/middleware.ts, the system examines the pathname, preferred-language cookies, and the Accept-Language header to determine the user's locale via the getLocale() function. Static assets and API routes bypass processing via NextResponse.next(), while missing locale prefixes trigger redirects to /<locale>/<original-path> using NextResponse.redirect().
Session Validation
After locale resolution, the middleware validates user sessions by calling the internal /api/auth/get-session endpoint with original request cookies using betterFetch. Failed authentication redirects users to /<locale>/login, ensuring only authenticated traffic reaches the MetaMCP proxy endpoints.
Backend Express Middleware Pipeline
The backend handles the MetaMCP API and MCP proxy endpoints (/mcp-proxy/*) through a sequential Express middleware chain defined in the router configuration.
Endpoint Resolution and Authentication
The pipeline begins with three critical middlewares that attach security context to requests:
-
lookup-endpoint.middleware.ts– Resolves the:endpoint_nameURL parameter to a database record, attachingnamespaceUuid,endpointName, and the fullendpointobject to the request object for downstream handlers. -
api-key-oauth.middleware.ts– Extracts API keys or OAuth bearer tokens from request headers, validates credentials against the database, enforces per-endpoint access rules, and tracks failed authentication attempts for rate-limiting purposes. -
better-auth-mcp.middleware.ts– For internal proxy routes, forwards request cookies to the Better-Auth session endpoint, aborting with HTTP 401 or 500 status codes on validation failures.
Rate Limiting and Access Control
The rate-limit.middleware.ts implements dual limiting strategies based on endpoint configuration:
- Sliding-Window Limiter – Returns HTTP 429 when
enable_client_max_rateis exceeded - Token-Bucket Limiter – Returns HTTP 503 when
enable_max_ratethresholds are breached
These middlewares execute in strict order before reaching the RPC handler:
// Execution order in router configuration
lookupEndpoint → apiKeyOAuth → betterAuthMcp → rateLimitMiddleware → RPC handler
Functional Middleware Core for Business Logic
Beyond Express middleware, MetaMCP implements a functional middleware framework in functional-middleware.ts that wraps business-logic handlers with composable request/response transformers.
Composable Handler Architecture
The core defines generic types (ListToolsHandler, CallToolHandler, ListToolsMiddleware, CallToolMiddleware) and two essential utilities:
createFunctionalMiddleware()– Builds wrappers that can transform requests before the handler and/or responses after executioncompose()– Chains multiple middleware wrappers into a single executable function
This approach keeps MCP protocol handlers as pure async functions accepting (request, MetaMCPHandlerContext) while allowing orthogonal concerns like caching, filtering, and transformations to wrap them cleanly.
Creating Custom Middleware
Developers implement functional middleware by defining transformation functions:
import { createFunctionalMiddleware } from "@/lib/metamcp/metamcp-middleware/functional-middleware";
export function createAuditLogMiddleware() {
return createFunctionalMiddleware({
transformRequest: async (req, ctx) => {
console.log(`MetaMCP ${ctx.namespaceUuid} calling ${req.method} ${req.params.name}`);
return req; // Pass through unchanged
},
});
}
Compose multiple middlewares using the compose() utility:
import { compose } from "@/lib/metamcp/metamcp-middleware/functional-middleware";
import { createFilterListToolsMiddleware } from "@/lib/metamcp/metamcp-middleware/filter-tools.functional";
import { createToolOverridesListToolsMiddleware } from "@/lib/metamcp/metamcp-middleware/tool-overrides.functional";
// Pipeline: filter inactive tools, then apply namespace overrides
const listToolsPipeline = compose(
createFilterListToolsMiddleware({ cacheEnabled: true }),
createToolOverridesListToolsMiddleware({ cacheEnabled: true }),
);
export const listToolsHandler = listToolsPipeline(coreListToolsHandler);
Domain-Specific Middleware Implementations
MetaMCP ships with specialized functional middlewares for tool management and access control.
Tool Filtering and Access Control
The filter-tools.functional.ts middleware removes tools marked INACTIVE in a namespace and blocks execution attempts against disabled tools. It utilizes a short-lived cache for tool-status lookups to minimize database queries during high-volume List Tools operations.
Tool Overrides and Transformations
The tool-overrides.functional.ts middleware enables namespace-level customization of tool metadata:
- Renaming tools via string mapping
- Modifying titles and descriptions
- Injecting custom annotations
- Reverse-mapping overridden names back to original identifiers for Call Tool requests
This middleware optionally persists cache entries when List Tools requests serve as the source of truth, ensuring consistent behavior across distributed MCP clients.
End-to-End Request Flow
A complete request traverses both middleware layers sequentially:
- Next.js Edge – Locale detection and session validation
- Express Router – Endpoint lookup (
lookupEndpoint) - Authentication – API key/OAuth validation and Better-Auth session checks
- Rate Limiting – Sliding-window and token-bucket enforcement
- Functional Pipeline –
compose(filterTools, toolOverrides)applied to core handler - Response – JSON-RPC response returned to client
This architecture ensures that cross-cutting concerns like authentication and rate limiting execute before business logic, while functional middleware provides granular control over MCP protocol data transformations.
Extending the Middleware System
To add new Express middleware for custom routes, register handlers in the router configuration following the established pattern:
// apps/backend/src/routers/custom-router.ts
router.use(
"/mcp-proxy/:endpoint_name",
lookupEndpoint,
apiKeyOAuth,
betterAuthMcp,
rateLimitMiddleware,
async (req, res) => {
const { method, params } = req.body;
const ctx = {
namespaceUuid: (req as any).namespaceUuid,
sessionId: req.session.id
};
const result = await dispatchMethod(method, params, ctx);
res.json(result);
},
);
Summary
- MetaMCP's middleware system combines Next.js edge functions for frontend concerns with Express middleware for backend API processing.
- Express pipeline executes in fixed order: endpoint lookup → API key/OAuth auth → Better-Auth session validation → rate limiting (sliding-window and token-bucket).
- Functional middleware core provides
createFunctionalMiddleware()andcompose()utilities for building reusable request/response transformers around pure MCP handlers. - Domain middlewares like
filter-tools.functional.tsandtool-overrides.functional.tsenable namespace-specific tool filtering and metadata customization without modifying core handler logic. - Type-safe composition allows developers to chain multiple concerns (logging, caching, filtering) while maintaining clean separation between transport and business logic.
Frequently Asked Questions
How does MetaMCP handle authentication in the middleware pipeline?
MetaMCP implements dual authentication strategies in the backend pipeline. The api-key-oauth.middleware.ts validates API keys and OAuth tokens for external clients, while better-auth-mcp.middleware.ts handles session cookies for internal proxy routes. Both middlewares attach authentication context to the request object before rate limiting and endpoint resolution occur.
What is the difference between Express middleware and functional middleware in MetaMCP?
Express middleware handles transport-layer concerns like HTTP headers, cookies, rate limiting, and endpoint resolution in the apps/backend/src/middleware/ directory. Functional middleware operates on MCP protocol data after Express processing, using functional-middleware.ts utilities to transform JSON-RPC requests and responses around core business logic handlers like List Tools and Call Tool.
How can I add custom request logging to MetaMCP handlers?
Create a functional middleware using createFunctionalMiddleware() with a transformRequest function that logs the namespaceUuid and request parameters, then compose it with existing middlewares using the compose() utility from functional-middleware.ts. This approach logs requests without modifying the core handler implementation or Express middleware stack.
Where does rate limiting occur in the MetaMCP request lifecycle?
Rate limiting executes in rate-limit.middleware.ts after authentication but before functional middleware processing. The system applies both sliding-window limits (HTTP 429) and token-bucket limits (HTTP 503) based on endpoint configuration flags enable_client_max_rate and enable_max_rate, protecting downstream MCP handlers from overload.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →