Mastra Koa 服务端适配器实战:从基础接入到流式传输与安全加固
2026/9/15 10:41:52 网站建设 项目流程

Mastra Koa 服务端适配器实战:从基础接入到流式传输与安全加固

【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra

Mastra 是一套面向 AI 应用与 Agent 的现代 TypeScript 框架,其内置的 HTTP 服务层通过「服务端适配器」接入不同的 Node.js Web 框架。@mastra/koa正是这套适配器家族(express / fastify / hono / koa / nestjs / next / tanstack-start)中的 Koa 实现,让你可以用熟悉的 Koa 中间件与洋葱模型承载 Agent、Workflow、工具、MCP 与流式响应。本文以 server-adapters/koa/CHANGELOG.md 记录的版本演进为主线,结合 server-adapters/koa/src/index.ts 的实现源码与测试用例,系统讲解@mastra/koa的接入方式、路由体系、流式传输、请求上下文、鉴权/RBAC 与异常处理等核心能力,读完即可在生产项目中把它跑起来并踩平已知的坑。

一、认识 @mastra/koa:定位、安装与最小接入

1.1 包定位与依赖关系

从 server-adapters/koa/package.json 可以看到该包的几个关键事实:

  • name@mastra/koadescription为 “Mastra Koa adapter for the server”,当前版本1.7.11-alpha.3
  • 运行时依赖仅两个:@mastra/server(Mastra 统一服务层)与@fastify/busboy(用于 multipart 表单解析);
  • peerDependencies要求@mastra/core >= 1.50.0-0 <2.0.0-0koa ^3.0.0,即只支持 Koa 3.x
  • 运行环境要求node >= 22.13.0
  • 导出同时支持 ESM 与 CJS(dist/index.jsdist/index.cjs)。

适配器本身很薄:它继承自@mastra/server/server-adapter导出的MastraServerBase,只负责把 Mastra 的路由、鉴权、流式协议翻译成 Koa 的中间件与ctx交互。这也解释了为什么大多数功能迭代都发生在@mastra/core@mastra/server,而@mastra/koa的 CHANGELOG 中大量条目是 “Updated dependencies”(依赖升级),仅有少数条目是适配器自身的改动。

1.2 安装与最小可运行示例

server-adapters/koa/README.md 给出了官方的最小接入代码,它也是理解整个适配器入口的钥匙:

import Koa from 'koa'; import bodyParser from 'koa-bodyparser'; import { MastraServer } from '@mastra/koa'; import { mastra } from './mastra'; const app = new Koa(); app.use(bodyParser()); const server = new MastraServer({ app, mastra }); await server.init(); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); });

安装命令:npm install @mastra/koa

四个要点值得展开:

  1. 必须挂载koa-bodyparser。适配器读取请求体依赖ctx.request.body(见 src/index.ts 的getParams),如果漏掉 bodyParser,POST/PUT 请求的 body 将是undefined
  2. new MastraServer({ app, mastra })是组合而非继承 Koaapp是你自己的 Koa 实例,适配器会向它注册中间件,因此你可以在适配器之外继续叠加自己的路由(如健康检查)。
  3. await server.init()是异步的init()会先注册全局错误边界中间件,再调用父类初始化,注册 Agent、Workflow、Tool、MCP 等全部内建路由。
  4. 支持openapiPath等选项。仓库自带的完整示例 server-adapters/koa/examples/index.ts 展示了new MastraServer({ mastra, app, openapiPath: '/openapi.json' })的用法,并额外挂了一个/health路由:
const app = new Koa(); app.use(bodyParser()); const koaServerAdapter = new MastraServer({ mastra, app, openapiPath: '/openapi.json' }); await koaServerAdapter.init(); // Add a simple health check route app.use(async ctx => { if (ctx.path === '/health' && ctx.method === 'GET') { ctx.body = { status: 'ok' }; } }); const PORT = 3001; app.listen(PORT, () => { console.info(`Server is running on http://localhost:${PORT}`); console.info(`OpenAPI spec: http://localhost:${PORT}/openapi.json`); });

该示例还演示了如何构建一个可被MastraServer托管的完整 Mastra 实例:注册三个 Agent(含一个带Memory+LibSQLStore的天气 Agent)、一个两步骤的天气 Workflow、一个天气工具以及 Observability,非常适合作为自己项目脚手架。

二、路由体系:Mastra 内建路由、createRoute 自定义路由与 Koa 原生路由的协同

2.1 内建路由(Agents / Workflows / Tools / MCP / Studio)

MastraServer.init()完成后,框架会自动为注册的 Agent、Workflow、Tool、Memory、MCP server 等生成一套 REST 路由,例如:

  • POST /api/agents/:agentId/generatePOST /api/agents/:agentId/stream
  • POST /api/agents/:agentId/threads/:threadId/messages
  • GET /api/agentsPOST /api/workflows/:workflowId/execute

这些路由的 HTTP 方法、路径与参数校验(bodySchemaqueryParamSchemapathParamSchema)由@mastra/server统一管理,Koa 适配器只负责把它们翻译为 Koa 可执行的匹配逻辑。在 src/index.ts 中可以看到这一层翻译的实现细节:

  • pathToRegex:把 Express 风格的:param路径编译为正则。实现上先把:param替换为占位符,再转义全部正则元字符,最后替换为捕获组([^/]+),保证路径中的特殊字符不会被误解析;
  • extractParamNames:从路径中提取参数名(去掉前导:);
  • findRegisteredRoute:遍历注册路由,先比较 HTTP 方法(route.method.toUpperCase() !== 'ALL'时精确匹配),再对ctx.path执行正则匹配,把捕获到的值写入ctx.params
  • registerRoute:支持传入{ prefix }选项,最终完整路径为prefix + route.path

2.2 createRoute:通过 server.apiRoutes 声明自定义业务路由

CHANGELOG 在1.7.0(Minor Changes)中记录了一个值得关注的能力:支持通过server.apiRoutes配置createRoute()创建的路由(PR #21184),原文给出了完整的示例代码:

const route = createRoute({ method: 'POST', path: '/items', responseType: 'json', bodySchema: z.object({ name: z.string() }), handler: async ({ name }) => ({ name }), }); const mastra = new Mastra({ server: { apiRoutes: [route] }, });

这意味着你不需要离开 Mastra 体系就能声明“纯业务”端点:createRoute提供类型安全的bodySchema校验、responseType声明(json/stream/datastream-response/mcp-http/mcp-sse)和handler回调,回调参数由框架自动注入(body、query、urlParams、requestContext、mastra、tools、taskStore、abortSignal、routePrefix、request)。在 Koa 适配器中,这类路由由registerCustomApiRoutes()处理,对应源码中的mastraCustomRouteDispatcher中间件(src/index.ts)。

2.3 适配 Koa 生态:子类化与 koa-router 的兼容

Koa 开发者常常用koa-router或“挂载的子应用”组织代码。CHANGELOG1.5.2记录了一次重要的兼容性修复(PR #16484):当子类把koa-router实例、挂载的子应用或自定义包装对象传给super.registerRoute()时,旧版本会因为读取不存在的app.middleware.length抛出TypeError: Cannot read properties of undefined (reading 'length')。修复后:

  • 适配器先检测目标对象是否暴露middleware数组(Array.isArray(middlewareStack));
  • 暴露则启用“dispatcher 复用”优化(同一组路由共用一个分发中间件,靠stackLengthAfterRegistration判断中间件是否被插入新路由,从而保证执行顺序);
  • 不暴露则回退到“每条路由注册独立 dispatcher”的旧行为,与 1.5.0 之前一致。

CHANGELOG 还给出了一个此前会崩溃、现在可正常工作的子类示例:

import { MastraServer } from '@mastra/koa'; import Router from 'koa-router'; class CustomKoaMastraServer extends MastraServer { private router = new Router(); async registerCustomApiRoutes() { const routes = this.mastra.getServer()?.apiRoutes ?? []; for (const route of routes) { // The router has no `middleware` array — this used to throw at init. await super.registerRoute(this.router as any, route, { prefix: this.prefix }); } this.app.use(this.router.routes()); } }

对应实现见 src/index.ts 的getRouteDispatcherGroup

三、流式传输:SSE、Data Stream 与客户端断连防护

Agent 与 Workflow 的流式响应是 Mastra 的核心体验,@mastra/koa为三种流式格式提供了完整支持,源码中sendResponsestream两个方法是关键(src/index.ts 与 #L714-L841)。

3.1 三种流式响应类型

  • stream(纯文本流)Content-Type: text/plain,分块以\x1E(Record Separator)分隔,适合自定义协议;
  • datastream-response:透传 AI SDK 的Response对象,把fetchResponse.body的 reader 逐块写入ctx.res,用于 Mastra Client 的processDataStream等场景;
  • mcp-http/mcp-sse:MCP 的 Streamable HTTP 与 SSE 传输,直接调用 MCP server 的startHTTP/startSSE,并把解析后的 body 挂到原始req上供 MCP 读取。

对于stream,适配器支持route.streamFormat'stream' | 'sse'):

  • SSE 格式会设置text/event-streamCache-Control: no-cacheConnection: keep-aliveX-Accel-Buffering: no,并以Transfer-Encoding: chunked写响应头,数据块以data: {json}\n\n发送;
  • route.sseFlushOnConnecttrue时,连接建立即先写入: connected\n\n注释行。CHANGELOG1.5.9记录:这个“连接注释”选项被进一步收窄为仅对 subscribe 端点生效,避免非订阅 SSE 端点产生多余输出。

3.2 流式响应中的敏感数据脱敏

stream()的写块循环里,适配器默认对每个 chunk 调用redactStreamChunk(通过this.streamOptions?.redact ?? true控制开关),用于在发送给客户端之前脱敏系统提示词、工具定义、API Key 等敏感信息——这在把 Agent 流暴露给浏览器端时非常实用。

3.3 不可序列化 chunk 不再杀死整个流

CHANGELOG1.6.1记录了一个极具实战价值的修复(PR #17843 / issue #17821):当流式 chunk 中包含无法 JSON 序列化的值(例如structuredOutputschema 中由 zod 强转产生的BigInt)时,旧实现会让整个 HTTP 流“静默死亡”,在 Studio 中表现为 Workflow 步骤节点一直停在 “running” 状态。修复后:

  • 可安全转换的值被转换(BigInt→ string、循环引用 →"[Circular]");
  • 仍无法序列化的 chunk 被跳过并记录包含路由路径与原因的错误日志,而不是终止流、丢弃后续所有 chunk。

对应实现即stream()中的serializeStreamChunk+continue分支(src/index.ts)。仓库测试 datastream-error-handling.test.ts 覆盖了这类错误处理路径。

3.4 客户端断连不再拖垮整个进程

CHANGELOG1.7.0(Patch Changes,PR #20756)描述了一个隐蔽而危险的 bug:客户端在流式传输中断连时,适配器的 abort/error 处理器会以无保护的void reader.cancel(reason)销毁 reader;如果底层流的 teardown 过程 reject(例如取消流时恰好有正在写入的存储操作失败),该 rejection 无人处理。在 Node >= 15 上,未处理的 Promise rejection 会直接终止进程,一次时机不当的断连就可能让整个服务宕机,丢掉所有其他在途请求。

修复思路是“取消是尽力而为的清理”,把 rejection 用.catch(() => {})吞掉——这一惯用法在client-sdks/client-jsintegrations/livekit中早已存在。hono适配器此前已有该保护,此次把expressfastifykoa对齐。在源码中可以看到两处体现:

ctx.res.on('close', () => { void reader.cancel('request aborted').catch(() => {}); });

以及 datastream 写错时的兜底:

const onResError = (err: unknown) => { this.mastra.getLogger()?.error('Error writing datastream response', { ... }); void reader.cancel('response write error').catch(() => {}); };

3.5 自定义路由的 AbortSignal

CHANGELOG1.5.7(PR #16335)为 Node 系适配器引入了“客户端断连时可取消长耗时自定义路由”的能力:适配器把AbortSignal传入自定义路由 handler,客户端断开时信号触发,Agent 流随之停止,同时正确清理响应流并向上游暴露响应体错误。CHANGELOG 给出的用法:

registerApiRoute('/stream', { method: 'GET', handler: async c => { const stream = await agent.stream(prompt, { abortSignal: c.req.raw.signal, }); return stream.toTextStreamResponse(); }, });

在 Koa 适配器中,请求上下文中间件createContextMiddleware会为每个请求创建AbortController,监听ctx.reqclose事件,且仅在响应尚未完成(!ctx.res.writableEnded)时触发controller.abort(),随后把ctx.state.abortSignal交给路由 handler 使用(src/index.ts)。

四、请求上下文(RequestContext)与 Koa 状态注入

@mastra/koa遵循 Koa 惯例,把每次请求的上下文数据写入ctx.state,供后续中间件与路由 handler 消费(见createContextMiddleware,src/index.ts):

ctx.state字段含义
requestContext合并后的请求上下文(含用户身份、租户信息、权限等)
mastra当前 Mastra 实例
tools已注册工具集合
taskStore任务存储(如InMemoryTaskStore
abortSignal与请求生命周期绑定的取消信号
customRouteAuthConfig自定义路由的鉴权开关映射

同时该中间件负责解析requestContext的两种来源:

  • POST/PUT:从Content-Type: application/json的请求体requestContext字段读取;
  • GET:从查询参数requestContext读取,优先尝试 JSON 解析,失败后回退到base64(JSON)解码。

解析完成后调用mergeRequestContext合并,再通过applyRequestMetadataToContext把元数据写入上下文。CHANGELOG1.5.6提到一个细节:适配器的权限校验改为从新的命名空间 keymastra__userPermissions读取用户权限(原来是userPermissions),以避免与调用方传入的上下文字段冲突。

另外,@mastra/koa通过declare module 'koa'扩展了DefaultState类型,所以在你的中间件里可以直接获得类型提示。

五、鉴权、RBAC 与错误处理

5.1 鉴权与透明会话刷新

每个匹配到的路由在执行业务 handler 之前都会经过checkRouteAuth,适配器为它构造了鉴权所需的访问器(读取 header、query、requestContext,并把 Koactx转换为 Web APIRequest供基于 Cookie 的鉴权 Provider 使用)。鉴权失败时写入错误状态与响应体;若鉴权流程携带了Set-Cookie等刷新头(透明会话刷新),也会一并写回响应(authError.headers的循环写入)。sendResponse中同样处理了 handler 返回值里__refreshHeaders的透传。

5.2 RBAC 与 FGA(EE 功能)

  • 若配置了鉴权(mastra.getStudio()?.auth || mastra.getServer()?.auth),适配器会按路由的权限要求做 RBAC 检查。权限名按约定从路由路径/方法推导,读取mastra__userPermissions后交给checkRoutePermission
  • 随后执行 FGA(Fine-Grained Authorization)检查checkRouteFGA
  • hasPermission函数通过动态import('@mastra/core/auth/ee')懒加载——CHANGELOG1.6.1记录了一个相关修复(PR #18319):fastify、hono、koa 三个适配器在 RBAC 预检中曾以非可选方式调用this.mastra.getStudio(),当部署的@mastra/core低于 1.42.0 时,Mastra类上不存在该方法,导致即使项目未配置任何鉴权,每个请求也会抛TypeError: this.mastra.getStudio is not a function并返回 500。修复改为getStudio?.()可选调用,并优雅回退到仅 server 鉴权。当前源码中正是this.mastra.getStudio?.()?.auth || this.mastra.getServer()?.auth的写法。

CHANGELOG1.5.13还提到适配器支持“双鉴权系统”:同时检查studio.authserver.auth,并根据x-mastra-client-type请求头把请求路由到正确的鉴权 Provider。

5.3 全局错误边界中间件与 onError 钩子

MastraServer.init()的第一步就是registerErrorMiddleware():把mastraErrorBoundary中间件注册到中间件链最顶端,作为兜底错误边界(src/index.ts)。其行为:

  1. 优先尝试server.onError(通过handleOnError构造一个兼容 Hono 风格onError签名的 context shim,并把返回的Response写回 Koactx;用_mastraOnErrorAttempted标记防止路由层与边界层双重调用);
  2. 未配置onError时返回默认 JSON 错误体{ error: message },并从错误的statusdetails.status中提取 HTTP 状态码(默认 500);
  3. 通过ctx.app.emit('error', err, ctx)发出事件(遵循 Koa 惯例),但不重新抛出——因为该中间件是最终错误边界。

仓库测试 on-error-hook.test.ts 与 http-logging.test.ts 覆盖了相关行为。

5.4 显式附加的 HTTP 异常响应

CHANGELOG1.7.9(PR #22728)记录:handler 抛出的“显式附加的 HTTP 异常响应”(通过getCustomHTTPExceptionResponse识别)现在会保留其状态码、响应头与响应体内容原样返回客户端(ctx.status、逐头写入、Buffer.from(await customResponse.arrayBuffer())),而不再被泛化为统一的 500。

5.5 参数校验错误的结构化响应

  • bodySchema/queryParamSchema/pathParamSchema的校验失败,适配器会区分类型返回 400,结构为{ error, issues: [{ field, message }] }
  • CHANGELOG1.5.9(PR #17172)修复了 zod v3 下的字段路径丢失问题:当消费者锁定zod@^3时,校验错误响应会丢失字段路径信息,issues[].field从实际的"agent_id"变成"unknown"。修复后按 schema 类别(body/query/path)解析 Zod 错误并还原真实字段名。仓库测试 validation-error-zod-v3.test.ts 与 malformed-json.test.ts 覆盖此类场景。

六、请求体解析:JSON、Query 与 multipart 文件上传

getParams(src/index.ts)统一处理三类入参:

  • URL 参数:来自ctx.params(路由正则捕获);
  • Query 参数:对ctx.queryParsedUrlQuery,值为string | string[])调用normalizeQueryParams归一化,再按queryParamSchema解析;
  • Body:对POST/PUT/PATCH/DELETE请求,若Content-Typemultipart/form-data,走parseMultipartFormData(基于@fastify/busboy),否则直接使用ctx.request.body(即 bodyParser 的结果)。

parseMultipartFormData的实现要点:

  • 支持route.maxBodySize ?? this.bodyLimitOptions?.maxSize作为文件大小上限,超限时触发 busboy 的limit事件并 reject(错误消息含上限字节数);尺寸相关错误会被重新抛出(上游按 400 处理),其他解析错误则记入bodyParseError,路由层据此返回400 { error: 'Invalid request body', issues: [{ field: 'body', message }] }
  • 文件字段累积为Buffer存入结果;普通字段尝试JSON.parse,解析失败则保留原始字符串(例如 JSON 字符串形态的options字段会被结构化)。

七、从 CHANGELOG 读版本演进:值得注意的工程实践

@mastra/koa的 CHANGELOG 本身也是一份不错的「适配器工程实践」教材,除前述功能外还有几条值得开发者了解的运维级变更:

  • 包瘦身(1.7.8,PR #22737):从 npm 分发文件中移除CHANGELOG.md,减小安装体积;同版本还更新了 README 使其信息准确(PR #22858)。
  • 供应链安全(1.5.16,PR #18056):针对 2026-06-17 “easy-day-js” 供应链事件做安全清理,发布干净版本并把latestdist-tag 前移,替代声明了恶意easy-day-js依赖的受损版本。这也提醒使用者关注依赖树审计。
  • Peer 依赖边界(1.5.6、1.6.5、1.6.2):多次提升@mastra/core的 peer 依赖下限(如>=1.34.0-0,以及为统一 schedules API 与agent-controller命名的调整),确保使用 Mastra server 的包与 Mastra core 兼容。其中 1.6.2 将Harness系列重命名为AgentController@mastra/core/agent-controller),旧@mastra/core/harness子路径保留为 deprecated 别名,@mastra/server的会话 API 也随之迁移到/agent-controller/...——如果你的项目还引用harness,应关注这一迁移。

八、测试与进一步探索

@mastra/koa附带了相当完整的 vitest 测试套件(server-adapters/koa/src/tests/),可以作为行为契约的权威参考,也是排查问题的第一现场:

  • koa-adapter.test.ts —— 适配器核心路由行为;
  • datastream-error-handling.test.ts —— 流式错误与断连处理;
  • on-error-hook.test.ts ——server.onError钩子;
  • auth-middleware.test.ts —— 鉴权中间件(配合 auth-middleware.ts 导出的createAuthMiddlewareKoaAuthMiddlewareOptions使用);
  • rbac-permissions.test.ts —— RBAC 权限校验;
  • mcp-routes.test.ts 与 mcp-transport.test.ts —— MCP HTTP/SSE 传输;
  • validation-error-zod-v3.test.ts、malformed-json.test.ts —— 参数校验与畸形请求;
  • http-logging.test.ts、server-app-access.test.ts —— 日志与应用访问。

如需在仓库内直接运行,可执行该包package.json中的test脚本(vitest run),或参考 examples/index.ts 启动一个真实服务并用 curl 验证。

结语

@mastra/koa用一层轻薄的中间件把 Koa 3 应用接入 Mastra 的完整服务能力:类型安全的自定义路由(createRoute)、Agent/Workflow/Tool/MCP 内建端点、SSE 与 Data Stream 流式响应、请求上下文注入、双鉴权体系与 RBAC/FGA、以及层层加固的异常与断连处理。结合 CHANGELOG 的演进记录可以清楚地看到,这个适配器在大流量与异常场景下的健壮性是被刻意打磨过的:流式 chunk 序列化失败被降级为跳过而非断流、客户端断连被降级为尽力清理而非进程崩溃、旧版核心兼容被显式守护。对希望用 Koa 技术栈承载 Mastra 应用的团队来说,把上述路由、流式与鉴权三个层面的行为吃透,足以覆盖绝大多数生产场景。

【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询