Skip to main content

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 example
Direct link to Usage example

src/server/run-tool.ts
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
}

Import
Direct link to Import

import { dispatchRoute } from '@mastra/server/server-adapter'

Signature
Direct link to Signature

function dispatchRoute(
route: ServerRoute,
input: DispatchRouteInput,
context: ServerContext,
): Promise<DispatchRouteResult>

Parameters
Direct link to Parameters

route:

ServerRoute
The route to run, such as one from SERVER_ROUTES, exported from @mastra/server/handlers/*, or created with `createRoute()`.

input.pathParams?:

Record<string, unknown>
Path parameters, validated against the route's pathParamSchema. Every :param in the route path needs a non-empty string that isn't . or ...

input.query?:

Record<string, unknown>
Query parameters, validated against the route's queryParamSchema.

input.body?:

unknown
Request body, validated against the route's bodySchema.

context.mastra:

Mastra
The Mastra instance the route runs against.

context.requestContext:

RequestContext
The caller's request context, already authenticated by Mastra server auth. Its user, permissions, and auth mode decide what the caller may do.

context.abortSignal:

AbortSignal
Cancels the route handler.

context.request?:

Request
The caller's HTTP request. Its headers are passed to authorizeUser, and it's handed to route handlers that read the request.

context.routePrefix?:

string
The prefix of the server adapter whose paths apply, such as /api. Required when no adapter is registered, or when several are and the request context comes from none of them.

Returns
Direct link to Returns

A DispatchRouteResult, which is one of two shapes:

ok:

boolean
true when the handler ran, false when a check failed or the handler threw.

result:

unknown
When ok is true: the handler's raw return value. Streaming routes return their stream unconverted.

status:

number
When ok is false: the HTTP status an adapter would have answered with, such as 400, 401, or 403.

body:

unknown
When ok is false: the JSON error body an adapter would have sent.

Behavior
Direct 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() returns 401, unless the route's path is public.
  • Authorization hooks see the HTTP path. authorize(), authorizeUser(), and rules receive 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 serves routePrefix, dispatchRoute() returns 401 for 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 returns 400. 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 protected patterns.
  • Adapter overrides apply. A checkRoutePermission() override on the serving MastraServer subclass 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 of context.request and no body.
  • Server context can't come from the input. request, routePrefix, registeredTools, and taskStore reach the handler only from context, never from body or query fields with those names.

Custom adapters
Direct 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 })