Aller au contenu principal

Adaptateurs personnalisés

Créez un adaptateur personnalisé lorsque les adaptateurs de serveur prédéfinis (Hono, Express, Fastify, Koa) ne prennent pas en charge votre framework ou que vous avez des exigences particulières pour le traitement des requêtes et des réponses.

Un adaptateur personnalisé assure la traduction entre les définitions de routes de Mastra et le système de routage de votre framework. Vous implémenterez des méthodes qui enregistrent le middleware, traitent les requêtes et envoient les réponses au moyen des API de votre framework.

info

Utilisez l’un des adaptateurs de serveur prédéfinis suivants :

Classe abstraite
Lien direct vers Classe abstraite

La classe abstraite MastraServer du package @mastra/server/server-adapter constitue la base de tous les adaptateurs. Elle gère la logique d’enregistrement des routes, la validation des paramètres et d’autres fonctionnalités partagées. Votre adaptateur personnalisé étend cette classe et implémente les éléments propres au framework.

La classe accepte trois paramètres de type qui représentent les types de votre framework :

my-framework-adapter.ts
import { MastraServer } from '@mastra/server/server-adapter'

export class MyFrameworkServer extends MastraServer<
// Your framework's app type (e.g., FastifyInstance)
MyApp,
// Your framework's request type (e.g., FastifyRequest)
MyRequest,
// Your framework's response type (e.g., FastifyReply)
MyResponse
> {
// Implement abstract methods
}

Ces paramètres garantissent la sûreté des types dans toute l’implémentation de votre adaptateur et permettent un typage correct lors de l’accès aux API propres au framework.

Méthodes requises
Lien direct vers Méthodes requises

Vous devez implémenter ces six méthodes abstraites. Chacune gère une partie précise du cycle de vie de la requête, depuis l’association du contexte jusqu’à l’envoi des réponses.

registerContextMiddleware()
Lien direct vers registercontextmiddleware

Cette méthode s’exécute en premier et associe le contexte Mastra à chaque requête entrante. Pour fonctionner, les gestionnaires de routes doivent accéder à l’instance Mastra, aux Tools et à d’autres éléments de contexte. La manière d’associer ce contexte dépend de votre framework : Express utilise res.locals, Hono utilise c.set(), et les autres frameworks suivent leurs propres modèles.

registerContextMiddleware(): void {
this.app.use('*', (req, res, next) => {
// Attach context to your framework's request/response
res.locals.mastra = this.mastra;
res.locals.requestContext = new RequestContext();
res.locals.tools = this.tools;
res.locals.abortSignal = createAbortSignal(req);
next();
});
}

Contexte à associer :

CléTypeDescription
mastraMastraInstance Mastra
requestContextRequestContextMap de contexte limitée à la requête
toolsRecord<string, Tool>Tools disponibles
abortSignalAbortSignalSignal d’annulation de la requête
taskStoreInMemoryTaskStoreStockage des tâches A2A, s’il est configuré

registerAuthMiddleware()
Lien direct vers registerauthmiddleware

Enregistrez le middleware d’authentification et d’autorisation. Cette méthode doit vérifier si l’authentification est configurée dans l’instance Mastra et, dans le cas contraire, ignorer entièrement l’enregistrement. Lorsqu’elle est configurée, vous enregistrez généralement deux fonctions middleware : l’une pour l’authentification, qui valide les tokens et définit l’utilisateur, et l’autre pour l’autorisation, qui vérifie si l’utilisateur peut accéder à la ressource demandée.

registerAuthMiddleware(): void {
const authConfig = this.mastra.getServer()?.auth;
if (!authConfig) return;

// Register authentication (validate token, set user)
this.app.use('*', async (req, res, next) => {
const token = extractToken(req);
const user = await authConfig.authenticateToken?.(token, req);
if (!user) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.locals.user = user;
next();
});

// Register authorization (check permissions)
this.app.use('*', async (req, res, next) => {
const allowed = await authConfig.authorize?.(
req.path,
req.method,
res.locals.user,
res
);
if (!allowed) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
});
}

registerRoute()
Lien direct vers registerroute

Enregistrez une seule route dans votre framework. Cette méthode est appelée une fois pour chaque route Mastra lors de l’initialisation. Elle reçoit un objet ServerRoute contenant le chemin, la méthode HTTP, la fonction de gestion et les schémas Zod de validation. Votre implémentation doit relier ces éléments au système de routage de votre framework.

async registerRoute(
app: MyApp,
route: ServerRoute,
{ prefix }: { prefix?: string }
): Promise<void> {
const path = `${prefix || ''}${route.path}`;
const method = route.method.toLowerCase();

app[method](path, async (req, res) => {
try {
// 1. Extract parameters
const params = await this.getParams(route, req);

// 2. Validate with Zod schemas
const queryParams = await this.parseQueryParams(route, params.queryParams);
const body = await this.parseBody(route, params.body);

// 3. Build handler params
const handlerParams = {
...params.urlParams,
...queryParams,
...(typeof body === 'object' ? body : {}),
mastra: this.mastra,
requestContext: res.locals.requestContext,
tools: res.locals.tools,
abortSignal: res.locals.abortSignal,
taskStore: this.taskStore,
};

// 4. Call handler
const result = await route.handler(handlerParams);

// 5. Send response
return this.sendResponse(route, res, result);
} catch (error) {
const status = error.status ?? error.details?.status ?? 500;
return res.status(status).json({ error: error.message });
}
});
}

getParams()
Lien direct vers getparams

Extrayez de la requête entrante les paramètres d’URL, les paramètres de requête et le corps de la requête. Les frameworks exposent ces valeurs de différentes manières : Express utilise req.params, req.query et req.body, tandis que d’autres peuvent employer des noms de propriétés différents ou nécessiter des appels de méthodes. Cette méthode normalise l’extraction pour votre framework.

async getParams(
route: ServerRoute,
request: MyRequest
): Promise<{
urlParams: Record<string, string>;
queryParams: Record<string, string>;
body: unknown;
}> {
return {
// From route path (e.g., :agentId)
urlParams: request.params,
// From URL query string
queryParams: request.query,
// From request body
body: request.body,
};
}

sendResponse()
Lien direct vers sendresponse

Renvoyez la réponse au client selon le type de réponse de la route. Les routes Mastra peuvent renvoyer différents types de réponses : du JSON pour la plupart des réponses d’API, des streams pour la génération des Agents et des types particuliers pour les transports MCP. Votre implémentation doit traiter chaque type de manière adaptée à votre framework.

async sendResponse(
route: ServerRoute,
response: MyResponse,
result: unknown
): Promise<unknown> {
switch (route.responseType) {
case 'json':
return response.json(result);

case 'stream':
return this.stream(route, response, result);

case 'datastream-response':
// Return AI SDK Response directly
return result;

case 'mcp-http':
// Handle MCP HTTP transport
return this.handleMcpHttp(response, result);

case 'mcp-sse':
// Handle MCP SSE transport
return this.handleMcpSse(response, result);

default:
return response.json(result);
}
}

stream()
Lien direct vers stream

Gérez les réponses diffusées lors de la génération d’un Agent. Lorsqu’un Agent génère une réponse, il produit un stream de fragments qui doivent être envoyés au client dès qu’ils sont disponibles. Cette méthode lit le stream, applique éventuellement un masquage pour cacher les données sensibles, puis écrit les fragments dans la réponse au format approprié (SSE ou JSON délimité par des sauts de ligne).

async stream(
route: ServerRoute,
response: MyResponse,
result: unknown
): Promise<unknown> {
const isSSE = route.streamFormat === 'sse';

// Set streaming headers based on format
response.setHeader('Content-Type', isSSE ? 'text/event-stream' : 'text/plain');
response.setHeader('Transfer-Encoding', 'chunked');

const reader = result.fullStream.getReader();

try {
while (true) {
const { done, value } = await reader.read();
if (done) break;

// Apply redaction if enabled
const chunk = this.streamOptions.redact
? redactChunk(value)
: value;

// Format based on stream format
if (isSSE) {
response.write(`data: ${JSON.stringify(chunk)}\n\n`);
} else {
response.write(JSON.stringify(chunk) + '\x1E');
}
}

// Send completion marker (SSE uses data: [DONE], other formats use record separator)
if (isSSE) {
response.write('data: [DONE]\n\n');
}
response.end();
} catch (error) {
reader.cancel();
throw error;
}
}

Méthodes utilitaires
Lien direct vers Méthodes utilitaires

La classe de base fournit des méthodes utilitaires que vous pouvez utiliser dans votre implémentation. Elles prennent en charge des tâches courantes, comme la validation des paramètres et l’enregistrement des routes, afin que vous n’ayez pas à les réimplémenter :

MéthodeDescription
parsePathParams(route, params)Valide les paramètres de chemin avec un schéma Zod
parseQueryParams(route, params)Valide les paramètres de requête avec un schéma Zod
parseBody(route, body)Valide le corps avec un schéma Zod
mergeRequestContext({ paramsRequestContext, bodyRequestContext })Fusionne les contextes de requête provenant de plusieurs sources
registerRoutes()Enregistre toutes les routes Mastra en appelant registerRoute pour chacune
registerOpenAPIRoute(app, config, { prefix })Enregistre le point de terminaison de la spécification OpenAPI

Les méthodes parse* utilisent les schémas Zod définis dans chaque route pour valider l’entrée et renvoyer des résultats typés. Si la validation échoue, elles lèvent une erreur qui détaille le problème rencontré.

Constructor
Lien direct vers Constructor

Le constructeur de votre adaptateur doit accepter les mêmes options que la classe de base et les transmettre à super(). Vous pouvez ajouter des options propres au framework si nécessaire :

constructor(options: {
app: MyApp;
mastra: Mastra;
prefix?: string;
openapiPath?: string;
bodyLimitOptions?: BodyLimitOptions;
streamOptions?: StreamOptions;
customRouteAuthConfig?: Map<string, boolean>;
}) {
super(options);
}

Consultez la page Adaptateurs de serveur pour obtenir la documentation complète de chaque option.

Exemple complet
Lien direct vers Exemple complet

Voici une implémentation minimale qui présente toutes les méthodes requises. Elle utilise du pseudocode pour les parties propres au framework ; remplacez-le par les API réelles de votre framework :

my-framework-adapter.ts
import { MastraServer, ServerRoute } from '@mastra/server/server-adapter'
import type { Mastra } from '@mastra/core'

export class MyFrameworkServer extends MastraServer<MyApp, MyRequest, MyResponse> {
constructor(options: { app: MyApp; mastra: Mastra; prefix?: string }) {
super(options)
}

registerContextMiddleware(): void {
this.app.use('*', (req, res, next) => {
res.locals.mastra = this.mastra
res.locals.requestContext = this.mergeRequestContext({
paramsRequestContext: req.query.requestContext,
bodyRequestContext: req.body?.requestContext,
})
res.locals.tools = this.tools ?? {}
res.locals.abortSignal = createAbortSignal(req)
next()
})
}

registerAuthMiddleware(): void {
const authConfig = this.mastra.getServer()?.auth
if (!authConfig) return
// ... implement auth middleware
}

async registerRoute(
app: MyApp,
route: ServerRoute,
{ prefix }: { prefix?: string },
): Promise<void> {
// ... implement route registration
}

async getParams(route: ServerRoute, request: MyRequest) {
return {
urlParams: request.params,
queryParams: request.query,
body: request.body,
}
}

async sendResponse(route: ServerRoute, response: MyResponse, result: unknown) {
if (route.responseType === 'stream') {
return this.stream(route, response, result)
}
return response.json(result)
}

async stream(route: ServerRoute, response: MyResponse, result: unknown) {
// ... implement streaming
}
}

Utilisation
Lien direct vers Utilisation

Une fois votre adaptateur implémenté, utilisez-le de la même manière que les adaptateurs fournis :

server.ts
import { MyFrameworkServer } from './my-framework-adapter'
import { mastra } from './mastra'

const app = createMyFrameworkApp()
const server = new MyFrameworkServer({ app, mastra })

await server.init()

app.listen(4111)
astuce

Les implémentations existantes de @mastra/hono et @mastra/express constituent de bonnes références pour créer votre adaptateur personnalisé. Elles montrent comment gérer les modèles propres au framework pour le stockage du contexte et l’enregistrement du middleware, ainsi que le traitement des réponses.

Si vous souhaitez utiliser Studio avec votre adaptateur de serveur, utilisez mastra studio afin de lancer uniquement l’interface de Studio.