dispatchRoute()
The dispatchRoute() function runs a server route in-process as the calling user. It expects a request context that Mastra server auth already authenticated, then applies what a server adapter applies to an HTTP request for that route: your auth provider's authorize, authorizeUser, and rules, schema validation, RBAC permission, and fine-grained authorization (FGA). It never allows a call the HTTP request would reject.
Use it to build another transport on top of the built-in routes, including streaming routes. For JSON routes called from a tool, invokeRoute() is the smaller API.
Usage exampleDirect link to Usage example
import type { Mastra } from '@mastra/core/mastra'
import type { RequestContext } from '@mastra/core/request-context'
import { SERVER_ROUTES, dispatchRoute } from '@mastra/server/server-adapter'
const executeTool = SERVER_ROUTES.find(
route => route.method === 'POST' && route.path === '/tools/:toolId/execute',
)!
export async function runWeatherTool(
mastra: Mastra,
requestContext: RequestContext,
request: Request,
) {
const outcome = await dispatchRoute(
executeTool,
{ pathParams: { toolId: 'weather' }, body: { data: { city: 'Paris' } } },
{ mastra, requestContext, abortSignal: request.signal, request },
)
if (!outcome.ok) throw new Error(`Tool route failed with status ${outcome.status}`)
return outcome.result
}
ImportDirect link to Import
import { dispatchRoute } from '@mastra/server/server-adapter'
SignatureDirect link to Signature
function dispatchRoute(
route: ServerRoute,
input: DispatchRouteInput,
context: ServerContext,
): Promise<DispatchRouteResult>
ParametersDirect link to Parameters
route:
SERVER_ROUTES, exported from @mastra/server/handlers/*, or created with `createRoute()`.input.pathParams?:
pathParamSchema. Every :param in the route path needs a non-empty string that isn't . or ...input.query?:
queryParamSchema.input.body?:
bodySchema.context.mastra:
context.requestContext:
context.abortSignal:
context.request?:
authorizeUser, and it's handed to route handlers that read the request.context.routePrefix?:
/api. Required when no adapter is registered, or when several are and the request context comes from none of them.ReturnsDirect link to Returns
A DispatchRouteResult, which is one of two shapes:
ok:
true when the handler ran, false when a check failed or the handler threw.result:
ok is true: the handler's raw return value. Streaming routes return their stream unconverted.status:
ok is false: the HTTP status an adapter would have answered with, such as 400, 401, or 403.body:
ok is false: the JSON error body an adapter would have sent.BehaviorDirect link to Behavior
- Authentication isn't re-run. When server or Studio auth is configured, the request context must already carry the user Mastra server auth resolved. Otherwise
dispatchRoute()returns401, unless the route's path is public. - Authorization hooks see the HTTP path.
authorize(),authorizeUser(), andrulesreceive the route's method and the path it's served under: the adapter's prefix plus the route path with path parameters filled in. - The serving adapter's paths and checks apply. Each adapter binds itself to the request contexts it creates, outside the context's entries so agent and tool code can't read it, so a route dispatched with that request context gets that adapter's prefix and overrides, even when another adapter serves the same Mastra instance under the same prefix. Outside a request, the only adapter serving the Mastra instance (or
context.routePrefix) is used. If the adapter can't be told, or no adapter servesroutePrefix,dispatchRoute()returns401for a caller without a user and throws otherwise. It never picks one of several adapters on its own. - Path parameters are checked like a URL. A missing, empty, non-string,
., or..value returns400. An optional:param?can be left out. - Path parameters win. A body or query field with the same name as a path parameter is overridden, so authorization and the handler act on the resource named in the path.
- Protected patterns don't open other paths. With auth configured, a route needs a user unless its path is declared public, even when it's outside your
protectedpatterns. - Adapter overrides apply. A
checkRoutePermission()override on the servingMastraServersubclass also runs for dispatched routes. authorizeUser()gets a stand-in request. It receives a request for the dispatched route's path and method that carries the headers ofcontext.requestand no body.- Server context can't come from the input.
request,routePrefix,registeredTools, andtaskStorereach the handler only fromcontext, never from body or query fields with those names.
Custom adaptersDirect link to Custom adapters
Adapters that extend MastraServer tell dispatchRoute() where they serve routes. An adapter that doesn't must register itself, and bind the returned handle to each request context it creates:
import { bindDispatchAdapter, registerDispatchAdapter } from '@mastra/server/server-adapter'
const handle = registerDispatchAdapter(mastra, { prefix: '/api' })
// For each request:
bindDispatchAdapter(requestContext, handle)
An adapter that doesn't extend MastraServer can run the route permission and FGA checks of the built-in adapters with authorizeRoute(), after authenticating the request and validating its parameters. It resolves to null when the call is allowed, or to a 403 error to send back. checkRouteRBAC() runs the permission check alone.
import { authorizeRoute } from '@mastra/server/server-adapter'
const denied = await authorizeRoute(mastra, route, params, requestContext)
if (denied)
return Response.json({ error: denied.error, message: denied.message }, { status: denied.status })