Mastra 开发(06):MCP 集成(TS 侧)

QuibblerAgentQuibblerAgent 2026-11-11 约 21 分钟 4 次阅读

前五篇把 Mastra 单 Agent 的模型接入、Zod 工具、结构化输出与记忆线程陆续跑通,Agent 已经能在自己进程里干活。但真实世界的 Agent 迟早要连外部系统:查维基百科、读本地文件、查生产数据库。MCP(Model Context Protocol)就是为这件事生的——一套开放协议把工具提供方与使用方解耦成 Server 与 Client 两端,任何一端换实现都不影响另一端。

MCP 的协议原理、Server 开发与 Python 侧接入,在《AI Agent 开发》系列第 19~21 篇已经讲透,本篇不重复,只回答一个问题:TypeScript 工程师怎么把 MCP Server 接进 Mastra Agent。全文按"全景对照 → MCPClient 上手 → stdio 与 HTTP 两种连接 → 工具命名与过滤 → 生命周期管理 → 双服务器实战"组织,结尾留一个跨栈练习。


1、TS 侧接入全景

先看分层,两层结构如下:

flowchart TD A["Agent(@mastra/core)"] --> B["MCPClient(@mastra/mcp)"] B --> C["StdioClientTransport"] B --> D["StreamableHTTPClientTransport"] C --> E["本地子进程 Server"] D --> F["远程 HTTP Server"]

解读:底层是 MCP 官方 TypeScript SDK,Client 类负责 JSON-RPC 协议语义,StdioClientTransport 管子进程通信,StreamableHTTPClientTransport 管远程 HTTP 通信;上层是 Mastra 的 MCPClient,包名 @mastra/mcp,负责自动选择 transport、把 MCP 工具转换成 Mastra 工具、管理连接生命周期。日常接入只写 MCPClient,写自定义客户端或自研 Server 时才需要下探裸 SDK——这个分工先记住。

版本事实先对齐:截至 2026 年 10 月,@mastra/mcp 在 1.x 线上活跃发版,随 @mastra/core 的 1.x 稳定线一同演进。有一个历史包袱要当心——v0 时代的类名叫 MastraMCPClient,v1 起已废弃换成 MCPClient,网上大量老教程还在用旧名,照抄会吃到废弃告警,官方有迁移指南可查。

《AI Agent 开发》第 21 篇里,Python 侧的接入层是 langchain-mcp-adapters 的 MultiServerMCPClient;本篇是同一件事的 TypeScript 版。关键差异一张表看清:

维度 Python 版 TypeScript 版
客户端类 MultiServerMCPClient MCPClient
传输指定 配置里显式写 transport 字段 有 command 走 stdio、有 url 走 HTTP
取工具 await client.get_tools() await client.listTools()
挂到 Agent create_agent(tools=...) 或 bind_tools 构造期 tools 或运行期 toolsets
HTTP 认证 连接配置里带 headers requestInit.headers,标准 fetch 参数

三点观察。其一,TS 版把"选哪种传输"从配置里拿掉了,凭 command 还是 url 自动判断,少写一个字段也少一个写错的字段。其二,两边的工具获取都是异步的,因为都要先连 Server 拉清单。其三,挂载方式 TS 版多出"运行期 toolsets"一条路,专门服务按请求换凭证的场景,第 6 节展开。

2、MCPClient 快速上手

三步:装包、建客户端、把工具挂给 Agent。先装包:

pnpm add @mastra/mcp@latest

迭代活跃的包建议带 @latest 显式拉新版,npm 与 yarn 同理。接着建客户端,servers 里每个键下注册一个 MCP Server:

import { MCPClient } from '@mastra/mcp'

export const mcpClient = new MCPClient({
  id: 'main-client',
  servers: {
    wikipedia: {
      command: 'npx',
      args: ['-y', 'wikipedia-mcp'],
    },
  },
})

三个细节。id 是客户端实例的标识,会出现在日志里;servers 的键名(这里的 wikipedia)不只是个名字,它就是工具的命名空间前缀,第 5 节细说;command 加 args 声明一个 stdio 子进程。最后一步,把工具挂给 Agent:

import { Agent } from '@mastra/core/agent'
import { mcpClient } from './mcp/index.ts'

export const wikiAgent = new Agent({
  id: 'wiki-agent',
  name: '维基助手',
  instructions: '回答事实类问题前,先用 MCP 工具查证。',
  model: 'anthropic/claude-sonnet-5-5',
  tools: await mcpClient.listTools(),
})

listTools() 是关键一环:它会真正连接所有注册的 Server,拉取工具清单,把 MCP 的 JSON Schema 转换成 Mastra 工具,返回一个"工具名到工具"的对象——形状与第 3 篇手写的 tools 完全一致。对 Agent 来说,MCP 工具和本地 Zod 工具没有任何区别,模型照常调用,Studio 的 trace 里照常可见。工具的 description 也一并从 Server 带过来——模型判断何时调用,靠的就是 Server 端写的工具说明,客户端不需要再补文档。顶层的 await 依赖 ESM 的 top-level await,模块加载时连接就建好了。

注意:MCPClient 没有显式的 connect() 方法,连接是惰性建立的——第一次需要工具清单或第一次调用工具时才握手。顶层 await listTools() 等于在模块加载期把连接"焐热",代价是冷启动变慢,Serverless 场景要权衡。

3、stdio 连接本地 Server

stdio 是本地 Server 的标准形态:Mastra 起一个子进程,JSON-RPC 消息走标准输入输出。以《AI Agent 开发》第 20 篇写的 SQLite 查询 Server(FastMCP 工程,uv 管理)为演示对象,配置如下:

sqlite: {
  command: 'uv',
  args: ['run', 'server.py'],
  env: { DB_PATH: './data/blog.db' },
  inheritDefaultEnv: false,
  timeout: 15000,
},

五个字段这样读:command 是可执行文件,args 是参数;env 传子进程专用的环境变量;inheritDefaultEnv 设为 false 后子进程只拿到 env 里列出的变量,默认 true 时继承的也是 SDK 维护的白名单(PATH、HOME 等),不是整个父进程环境——这个默认值比想象中安全;timeout 给这个 Server 单独设毫秒级超时,不设则回落到客户端级默认 60 秒。

底层发生的事情:command 字段一出现,MCPClient 就自动选用 SDK 的 StdioClientTransport,spawn 子进程、建立 stdio 通道、完成 MCP 握手,全部封装在内,不需要 import 任何 transport 类。子进程的生死完全跟随客户端:MCPClient 断开或宿主进程退出,stdio Server 一起终止,不存在孤儿服务占端口的问题——HTTP Server 则是独立部署独立运维,两边运维模型不同。Python 写的 Server 与 TS 写的客户端在 stdio 上完全互通——协议就是干这个的,第 8 节的练习会让你亲手验证。

4、Streamable HTTP 连接远程

远程 Server 走 Streamable HTTP:单个 url 端点承载工具调用与响应。带认证的配置如下:

weather: {
  url: new URL('https://weather.example.com/mcp'),
  requestInit: {
    headers: {
      Authorization: `Bearer ${process.env.WEATHER_TOKEN}`,
    },
  },
},

url 一出现,MCPClient 自动换用 StreamableHTTPClientTransport。认证走 requestInit——它就是标准 fetch 的 RequestInit,这里的 headers 会在每次请求上携带;Bearer token 从环境变量读,与第 2 篇的密钥纪律一致:凭证不进代码、不进 git。

两种连接的取舍很直观:stdio 免部署、延迟低(进程间管道),适合本地工具与受信环境;HTTP 天然可远程、可扩展、可挂负载均衡,适合团队共享的 Server。要点是两种形态可以混用——同一个 MCPClient 的 servers 里同时写 command 与 url 两类条目,第 7 节就这么干。另外,OAuth 保护的 Server 不用手拼 token,MCPClient 提供 authenticate() 方法走浏览器授权流程,细节见官方文档。

5、工具命名与过滤

多 Server 接入后第一个问题:两个 Server 都有叫 search 的工具怎么办?MCPClient 的答案是强制命名空间——工具名一律加 Server 键名前缀,且前缀不可自定义:

方法 工具名格式 挂载时机
listTools() serverName_toolName 构造期,写进 tools
listToolsets() serverName.toolName 运行期,写进 toolsets

于是 wiki_search 与 db_search 天然消歧,重名冲突在 TS 侧不是问题;代价是起 servers 键名时要有点品味——键名叫 a,就会收获 a_search 这种没有信息量的工具名。

过滤是另一回事。MCPClient 的构造参数里没有工具过滤选项(MCPClient 参考文档里不存在 tools 白名单这类字段),正确姿势是在拿到工具对象后自己筛:

const all = await mcpClient.listTools()
const picked = Object.fromEntries(
  Object.entries(all).filter(([name]) => name.startsWith('sqlite_')),
)

listTools() 返回普通对象,Object.entries 一过滤、fromEntries 一组装,白名单就生效了。被滤掉的工具不会出现在 Agent 的 tools 里,模型完全看不到——过滤发生在"转换成 Mastra 工具之后、交给模型之前",不需要断开 Server。排查连接问题另有专用工具:listToolsWithErrors() 会把每个 Server 的连接错误与详情一并返回,比看一坨异常栈好读。

追求类型安全的还可以开 typegen:构造参数里写 typegen: { outFile: 'src/mcp/mcp-types.generated.ts' },再执行 npx @mastra/mcp generate ./src/mcp/client.ts,框架把所有 Server 的工具清单生成为带类型的定义文件,工具名按 serverName_toolName 静态推导——Server 端改了工具,重新生成一次,调用处的类型检查器立刻告诉你哪里断了。

6、连接生命周期管理

第 2 节的静态挂载(模块级 MCPClient 加顶层 await)适合工具集固定的场景:连接建一次、进程内复用,长驻的 Hono 或 Express 服务直接用。真正要小心的是按请求换凭证的场景——每个用户的 token 不同,模块级客户端装不下,必须每请求建一个。Hono 里的正确姿势如下:

import { Hono } from 'hono'
import { MCPClient } from '@mastra/mcp'

const app = new Hono()

app.post('/ask', async (c) => {
  const client = new MCPClient({
    id: 'per-request',
    servers: {
      docs: {
        url: new URL('https://docs.example.com/mcp'),
        requestInit: {
          headers: { Authorization: c.req.header('Authorization')! },
        },
      },
    },
  })
  try {
    const res = await agent.generate(await c.req.text(), {
      toolsets: await client.listToolsets(),
    })
    return c.text(res.text)
  } finally {
    await client.disconnect()
  }
})

三个要点。其一,用户自己的 Authorization 头透传进 requestInit,连的是同一个远程 Server,权限各归各。其二,工具不走构造期 tools,而是运行期传 toolsets: await client.listToolsets()——工具集跟着这次请求走。其三,finally 里的 disconnect() 不能省:它会关闭所有连接并清理资源,stdio 场景下意味着杀掉子进程。Next.js 的 Route Handler 里是同一个模式:模块顶层放静态 MCPClient 处理固定工具集,handler 函数内走 toolsets 处理用户凭证,代码几乎不用改。再提醒一次惰性连接:Serverless 上模块级顶层 await 会把建连算进冷启动,Server 多、握手慢时明显,必要时改用运行期 toolsets,把建连推迟到首个请求。

这个坑与《AI Agent 开发》第 21 篇 Python 版是同一个:每请求建客户端、用完不断开,stdio 子进程就会一个个堆积,直到耗尽资源。TS 侧的表现更隐蔽——Node 不会替你收尸 spawn 出来的子进程,disconnect() 必须显式调用。反过来,静态客户端无需手动断开,进程退出自然回收。

7、双服务器实战

把前面全部拼起来:一个 Agent 同时挂本地 stdio 文件系统 Server 与远程 HTTP 文档 Server。客户端定义如下:

import { MCPClient } from '@mastra/mcp'

export const mcp = new MCPClient({
  id: 'mixed-client',
  timeout: 20000,
  servers: {
    fs: {
      command: 'npx',
      args: ['-y', '@modelcontextprotocol/server-filesystem', './data'],
    },
    docs: {
      url: new URL('https://mcp.context7.com/mcp'),
    },
  },
})

fs 走 stdio 挂官方文件系统 Server(限定在 ./data 目录内),docs 走 HTTP 挂 Context7 文档 Server,客户端级 timeout 20 秒统一兜底。Agent 与运行脚本如下:

import { Agent } from '@mastra/core/agent'
import { mcp } from './mcp/index.ts'

const helperAgent = new Agent({
  id: 'helper-agent',
  name: '文档助手',
  instructions: '先查本地 data 目录,文档类知识再查远程库。',
  model: 'anthropic/claude-sonnet-5-5',
  tools: await mcp.listTools(),
})

const result = await helperAgent.generate(
  'data 目录下有哪些文件?再查一下 Hono 框架怎么注册路由。',
)
console.log(result.text)
await mcp.disconnect()

跑起来后,模型会先用 fs_ 前缀的目录工具看本地文件,再用 docs_ 前缀的文档工具查 Hono 路由——两种 transport、两个命名空间、一轮对话里交叉调用,Agent 层完全无感。收尾的 disconnect() 是脚本的好习惯;把 Agent 注册进 Mastra 实例后,Studio 里能看到每个 MCP 工具的调用轨迹,与本地 Zod 工具长得一模一样。

8、总结

回顾全文:TS 侧接 MCP 是两层结构——官方 TypeScript SDK 提供协议与 transport,@mastra/mcp 的 MCPClient 在其上封装出"配置即工具";command 与 url 两个字段决定 stdio 还是 Streamable HTTP,传输选择全自动;工具名强制 serverName_toolName 前缀,重名免焦虑,过滤在拿到工具对象后自行完成;生命周期上,静态挂载用模块级客户端一劳永逸,按请求换凭证用 toolsets 加 try/finally 里的 disconnect()——坑与 Python 版同源,解法也同源。

留一个跨栈练习:把《AI Agent 开发》第 20 篇的 SQLite 查询 Server 接进本文的 Agent——command 用 uv run 启动它,env 传数据库路径,挂载后用 listToolsWithErrors() 确认连接成功,再到 Studio 里问一个要查库的问题、看 trace 里的工具调用。跑通这一步,你就有了一条 Python Server 供 TS Agent 调用的完整跨栈链路,MCP"一次实现、处处接入"的价值也就摸到了。对于正在做 TS Agent 的工程师,建议今天就挑一个现成的 MCP Server——文件系统、搜索、数据库任选——挂进自己的 Agent,比读十篇协议文档都有效。

相关推荐

获取系统SDK版本、判断手机ROM
精选Code

获取系统SDK版本、判断手机ROM

Build获取系统SDK版本Android中部分API的使用,需要在特定的SDK版本之后才能使用,因此在兼容老版本SDK的时候,经常需要判断API的版本。各种Android版本的对应关系参考《Android各版本对应的SDK版本》判断手机ROM有时候需要判断手机系统的ROM,检测ROM是MIUI、EMUI还是Flyme,可以使用getprop命令,去系统build.prop文件(关于build.p

4.2k
AndroidStudio中各种中文乱码问题
精选Code

AndroidStudio中各种中文乱码问题

AndroidStudio中各种中文乱码问题1、编译Java错误信息乱码在出现这个Annotation processors must be explicitly declared now...问题的时候,正好也发现AndroidStudio Build Output错误信息都乱码。经常遇到各种问题,习惯了,现在遇到的问题,都是以后的答案。 1.1、修改项目build.gradle(无效)在整个p

2.8k
Multi-Agent 详解:多智能体协作架构与实践
精选Agent

Multi-Agent 详解:多智能体协作架构与实践

一个智能体配上一套工具,已经能完成相当多任务。但让它同时“当产品经理、写后端、跑测试、做代码评审”,很快就会露馅:上下文里塞满了互相干扰的指令,十几件工具的清单来回切换,任务做到一半忘了开头。解法看起来很自然——像人类团队一样分工:让多个智能体各司其职,由一个协调者调度,这就是 Multi-Agent(多智能体)系统。它也是 2026 年智能体工程里讨论度最高、误用率也最高的架构。本文按“架构模式…

122