Middleware
mcp-use middleware sits between the transport and the tool handler. Two helpers ship in the toolkit; both are pass-through when disabled so callers can wire them unconditionally and let config decide.
Both read the caller through resolveCaller (packages/core/src/auth/caller.ts): mcp-use 2 hands middleware the SDK AuthInfo, with the provider-mapped user under ctx.auth.extra.user — there is no ctx.auth.user in middleware.
The factories return generic middlewares (OrgGateMiddlewareFn, RoleFilterMiddlewareFn) that server.use accepts without a cast. The non-generic OrgGateMiddleware / RoleFilterMiddleware aliases are for typing your own wrappers.
Org gate
createOrgGateMiddleware(orgId: string | undefined): OrgGateMiddlewareFn- Register as
server.use("mcp:*", createOrgGateMiddleware(orgId)). - Every RPC must come from a caller whose mapped user has
organizationId === orgId(the 1.x-eraorganization_idis read too). - Callers without an organization are rejected too (forces org-scoped login).
undefined→ pass-through. Useful for single-tenant deployments that don't setWORKOS_ORG_ID.
Source: packages/core/src/middleware/org-gate.ts.
Role filter
createRoleFilterMiddleware(roleToModules: Record<string, string[]>): {
toolsList: RoleFilterMiddlewareFn
toolsCall: RoleFilterMiddlewareFn
}- Register the pair:ts
const { toolsList, toolsCall } = createRoleFilterMiddleware(rules) server.use("mcp:tools/list", toolsList) server.use("mcp:tools/call", toolsCall) rules—{ roleName: [moduleName, ...] }. A role listed as a key restricts users with that role to the listed modules.- Users whose roles include no key in the mapping → unrestricted. This is deliberate opt-in so adding a new role doesn't silently revoke tools.
- Roles are the string entries of the mapped
user.roles. - Tool → module mapping uses the prefix convention
<module>_<tool>. Tools without an underscore (framework tools,render-view, etc.) are always allowed. toolsListfilterstools/listoutput;toolsCallblocks a mismatchedtools/callas defence-in-depth.
Source: packages/core/src/middleware/role-filter.ts.
Caveat — server-internal calls bypass middleware
When a pipeline step dispatches through the injected callTool closure, the closure resolves the call in-process and skips the MCP RPC surface. The role filter doesn't apply. Step code is part of the trusted server, so the default is acceptable — add explicit role checks inside the step if a step path must also be gated.
Wiring via createFrameworkApp
await createFrameworkApp({
...,
middleware: {
orgGate: process.env.WORKOS_ORG_ID,
roleFilter: JSON.parse(process.env.ROLE_MODULES ?? "{}"),
},
})The factory skips each helper when its option is missing or empty — see packages/core/src/tools/create-framework-app.ts:68-79.
See also
- middleware-and-auth — full walkthrough.