☰
Yao Agent MCP 集成实战:为智能助手接入外部工具、资源与跨服务器并发调用
2026/9/26 2:35:35 网站建设 项目流程
  • Agent 框架
  • 后端
  • 低代码
  • RAG

【免费下载链接】yao

✨ All your agents and workspaces in one place, on every device you own. Track tasks on a board, accessible from desktop, mobile, browser, or API. Self-hosted.

项目地址:https://gitcode.com/gh_mirrors/ya/yao
点击查看免费下载

Model Context Protocol(MCP)是连接大语言模型与外部服务/工具的标准协议。本指南聚焦于 Yao 仓库中agent/模块的 MCP 集成能力,讲解如何在助手(Assistant)中定义 MCP 服务器、通过package.yao与 Hook 启用工具、调用工具与资源,以及利用All/Any/Race实现跨服务器并发编排。读完本文,你将掌握一套完整的、可直接落地的 MCP 接入方案,并理解其底层实现原理。

1. 目录结构与命名空间约定

在 Yao Agent 中,每个助手(Assistant)可以在自身的mcps/目录下定义独立的 MCP 服务器,并自动以agents.<assistant-id>.前缀加载。标准目录结构如下:

assistants/ └── my-assistant/ ├── package.yao └── mcps/ ├── tools.mcp.yao # → agents.my-assistant.tools ├── calculator.mcp.yao # → agents.my-assistant.calculator └── mapping/ └── tools/ └── schemes/ ├── search.in.yao └── search.out.yao
  • tools.mcp.yao文件会被注册为agents.my-assistant.tools这一 MCP 服务器 ID;
  • mapping/目录用于存放工具输入/输出 Schema 映射文件(详见第 6 节);
  • 一个助手可以同时拥有多个 MCP 服务器文件(tools、calculator等),相互之间命名空间隔离。

在源码层面,配置解析位于 agent/assistant/load.go(约第 763-769 行),package.yao中的mcp字段会被转换为store.MCPServers结构并挂载到 Assistant 上。

2. 定义 MCP 服务器与四种传输方式

在助手目录中创建mcps/tools.mcp.yao,即可定义一个 MCP 服务器。最基础的形式如下:

{ "label": "Tools", "description": "Custom tools for the assistant", "transport": "process", "tools": { "search": "scripts.tools.Search", "create": "models.data.Create" } }

其中tools是「工具名 → Yao Process」的映射。MCP 支持四种transport类型,分别面向不同的外部服务接入场景:

2.1 Process(Yao 内部进程)

将 Yao Process 直接映射为 MCP 工具,无需启动外部进程:

{ "transport": "process", "tools": { "search": "models.data.Paginate", "create": "models.data.Create" }, "resources": { "detail": "models.data.Find" } }

resources字段将 MCP 资源(resource)映射到 Process,供ReadResource调用。这是与 Yao 自身能力(模型、脚本)集成最紧密的传输方式。

2.2 STDIO(本地子进程)

启动本地命令与 MCP 服务器通信,适合 Python/Node 等语言编写的 MCP Server:

{ "transport": "stdio", "command": "python", "arguments": ["mcp_server.py"], "env": { "API_KEY": "$ENV.API_KEY" } }

env支持$ENV.XXX占位符语法,可从运行环境注入密钥,避免明文写入配置文件。

2.3 HTTP(REST API)

对接部署在远程的 MCP HTTP 端点:

{ "transport": "http", "url": "https://mcp.example.com/api", "authorization_token": "$ENV.TOKEN" }

2.4 SSE(Server-Sent Events)

对接基于 SSE 推送的 MCP 服务:

{ "transport": "sse", "url": "https://mcp.example.com/events", "authorization_token": "$ENV.TOKEN" }

说明:以上authorization_token同样支持$ENV.占位符注入,适合存放 API Key、Token 等敏感凭据。

3. 在 package.yao 中启用 MCP 服务器

定义好mcps/*.mcp.yao后,还需要在package.yao的mcp.servers中声明启用哪些服务器,有三种形态:

3.1 启用全部工具

{ "mcp": { "servers": ["tools"] } }

3.2 只启用指定工具

{ "mcp": { "servers": [{ "server_id": "tools", "tools": ["search", "calculate"] }] } }

3.3 同时启用工具与资源

{ "mcp": { "servers": [ { "server_id": "data", "tools": ["query"], "resources": ["data://users/*"] } ] } }

resources使用 MCP URI 通配符(如data://users/*)指定允许读取的资源范围,未列出的资源在 Hook 中调用ReadResource时会被拦截。

3.4 多格式解析原理

从源码看,MCPServerConfig的反序列化支持四种输入格式(见 agent/store/types/types.go 第 298-357 行的UnmarshalJSON):

  1. 纯字符串:"server_id"
  2. 标准对象:{"server_id": "server1", "resources": [...], "tools": [...]}
  3. 工具数组对象:{"server_id": ["tool1", "tool2"]}
  4. 完整配置对象:{"server_id": {"resources": [...], "tools": [...]}}

反序列化时依次尝试「字符串 → 标准对象 → 单键对象」,因此第 3.1 节的简写["tools"]与 3.2/3.3 节的对象写法在语义上等价,可混用。对应的解析器store.ToMCPServers由 agent/assistant/load.go 在加载package.yao时调用。

4. 在 Hook 中动态配置 MCP 服务器

除了静态配置,还可以在 Hook 的Create阶段按消息内容动态决定启用哪些 MCP 服务器。返回字段mcp_servers会在本次对话周期内生效:

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create { return { messages, mcp_servers: [ { server_id: "tools", tools: ["search"] }, { server_id: "data", resources: ["data://reports"] }, ], }; }

动态配置与静态配置的优先级关系,在 agent/assistant/mcp.go 的buildMCPTools方法(第 75-96 行)中有明确实现:

  • Hook 返回的mcp_servers优先:只要Create响应携带了 MCP 服务器配置,就完全覆盖(override)静态配置;
  • 否则回退到package.yao中配置的ast.MCP.Servers;
  • 两者都没有时,跳过 MCP 工具构建。

这为「根据用户问题智能启用工具」提供了标准入口,例如仅在检测到数学表达式时才加载计算器工具。

5. 在 Hook 中调用 MCP:工具、资源与提示词

Hook 通过ctx.mcp访问 MCP 客户端能力。所有方法在 agent/context/jsapi_mcp.go 中注册,底层实现在 agent/context/mcp.go。

5.1 列出可用工具

const tools = ctx.mcp.ListTools("server-id"); // { tools: [{ name: "search", description: "...", inputSchema: {...} }] }

ListTools支持可选的游标参数(cursor)用于分页遍历大型工具集(见mcpListToolsMethod,位于 agent/context/jsapi_mcp.go 第 78-105 行)。

5.2 调用单个工具

// Returns parsed result directly - no wrapper object const result = ctx.mcp.CallTool("server-id", "search", { query: "example", limit: 10, }); console.log(result.items); // Direct access to parsed data

关键行为:CallTool返回的是解析后的结果本身,而非 MCP 响应包装对象。底层parseToolResponseContent(agent/context/mcp.go 第 788-833 行)按内容类型处理:

  • text类型:先尝试按 JSON 解析,解析失败则原样返回字符串;
  • image类型:返回{ type: "image", data, mimeType };
  • resource类型:直接返回 Resource 对象;
  • 若内容仅一项则直接返回该项,多项则返回数组。

5.3 批量调用工具

顺序批量(按顺序逐个执行):

// Sequential - returns array of parsed results const results = ctx.mcp.CallTools("server-id", [ { name: "step1", arguments: { input: "a" } }, { name: "step2", arguments: { input: "b" } }, ]); results.forEach(r => console.log(r));

并行批量(同一服务器内的工具并发执行):

// Parallel - returns array of parsed results const results = ctx.mcp.CallToolsParallel("server-id", [ { name: "api1", arguments: {} }, { name: "api2", arguments: {} }, ]); results.forEach(r => console.log(r));

5.4 读取资源

const resources = ctx.mcp.ListResources("server-id"); const data = ctx.mcp.ReadResource("server-id", "data://users/123");

5.5 获取提示词

const prompts = ctx.mcp.ListPrompts("server-id"); const prompt = ctx.mcp.GetPrompt("server-id", "system", { role: "helper" });

5.6 跨服务器并发调用(All / Any / Race)

当需要同时编排多个 MCP 服务器上的工具时,ctx.mcp提供了三个 Promise 语义的并发原语(实现见 agent/context/mcp.go 第 625-760 行):

// Wait for all (like Promise.all) const results = ctx.mcp.All([ { mcp: "server1", tool: "search", arguments: { q: "query" } }, { mcp: "server2", tool: "analyze", arguments: { data: "input" } } ]); // First success (like Promise.any) - good for fallback const results = ctx.mcp.Any([ { mcp: "primary", tool: "fetch", arguments: { id: 1 } }, { mcp: "backup", tool: "fetch", arguments: { id: 1 } } ]); // First complete (like Promise.race) - good for latency const results = ctx.mcp.Race([ { mcp: "region-us", tool: "ping", arguments: {} }, { mcp: "region-eu", tool: "ping", arguments: {} } ]); // Access results results.forEach(r => { if (r.error) { console.log(`${r.mcp}/${r.tool} failed: ${r.error}`); } else { console.log(`${r.mcp}/${r.tool} result:`, r.result); } });

各方法的语义差异值得注意:

  • All:等待所有请求完成,按请求顺序返回结果(CallToolAll用 channel 收集后按索引归位);
  • Any:一旦出现任一成功即返回(适合主备切换、容灾回退);底层用缓冲 channel 收集,找到首个成功即停止等待,其余结果在后台排空;
  • Race:返回第一个完成(无论成功失败)的结果,适合多地域择优、追求低延迟。

每个请求对象包含mcp(服务器 ID)、tool(工具名)、arguments(可选参数),其中mcp与tool为必填(校验见 agent/context/jsapi_mcp.go 的parseMCPToolRequests)。结果对象统一为{ mcp, tool, result?, error? },成功时result为解析后的数据,失败时error携带错误信息。

6. 工具 Schema 映射:x-process-args

对于process传输方式,MCP 参数需要映射到 Yao Process 的参数。在mcps/mapping/<server-id>/schemes/下按工具名放置*.in.yao(输入 Schema)与可选的*.out.yao(输出 Schema):

mcps/ └── mapping/ └── <server-id>/ └── schemes/ ├── search.in.yao # Input schema └── search.out.yao # Output schema (optional)

mapping/tools/schemes/search.in.yao:

{ "type": "object", "description": "Search data", "properties": { "keyword": { "type": "string" }, "page": { "type": "integer" } }, "x-process-args": [":arguments"] }

x-process-args声明 MCP 参数到 Yao Process 参数的映射方式,支持两种取值:

  • ":arguments":将整个参数对象原样传给 Process;
  • "$args.field":从参数对象中提取指定字段(如"$args.keyword")再传给 Process。

6.1 嵌套对象 Schema

当工具需要结构化输入时,可以使用完整的 JSON Schema 定义:

{ "type": "object", "description": "Extract structured data from input", "properties": { "intent": { "type": "string", "enum": ["query", "create", "update"], "description": "Operation intent" }, "items": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "value": { "type": "number" } }, "required": ["name", "value"] } } }, "required": ["intent"], "x-process-args": [":arguments"] }

该 Schema 会在工具调用前被用作参数校验依据:从源码看,执行路径会先用gouJson.Parse解析 LLM 生成的参数,再用gouJson.Validate与工具的InputSchema比对(见 agent/assistant/mcp.go 第 344-357 行)。校验失败会被标记为可重试错误,交由 LLM 修正参数后重试。

7. 使用助手自有模型作为 MCP 工具

MCP 工具可以引用助手自身的模型,把领域模型操作暴露为标准化工具。例如mcps/data.mcp.yao:

{ "label": "Data Tools", "transport": "process", "tools": { "list_orders": "models.agents.my-assistant.order.Paginate", "get_order": "models.agents.my-assistant.order.Find", "create_order": "models.agents.my-assistant.order.Create", "custom_query": "agents.my-assistant.orders.Query" } }

注意这里 Process 的完整路径为models.agents.my-assistant.order.*——即助手命名空间下的模型,与第 1 节的agents.<assistant-id>.前缀约定一致。自定义脚本(如agents.my-assistant.orders.Query)同样可以暴露为 MCP 工具。助手模型的定义方式参见 Models 文档。

8. 错误处理与可重试机制

在NextHook 中处理工具执行结果时,需要区分工具级错误并决定是否交给 LLM 修复:

function Next(ctx: agent.Context, payload: agent.Payload): agent.Next { const { tools } = payload; if (tools) { for (const tool of tools) { if (tool.error) { ctx.trace.Error(`Tool ${tool.tool} failed: ${tool.error}`); // Handle error } else { // Process result console.log(tool.result); } } } return null; }

源码层的容错设计更细致。在 agent/assistant/mcp.go 中:

  • executeToolCalls(第 223-237 行)采用智能执行策略:单工具走CallTool,多工具先尝试并行CallToolsParallel,遇到参数类错误再降级为顺序执行(shouldRetrySequential,第 538-548 行);
  • isRetryableToolError(第 484-535 行)通过错误文本模式区分两类错误:
    • 不可重试(MCP 内部问题,LLM 无法修复):network、timeout、connection、unauthorized、forbidden、unavailable、context canceled、server error等;
    • 可重试(参数/校验问题,LLM 可修正):invalid、required、missing、validation、schema、parse、argument等;
    • 未匹配任何模式时默认按可重试处理,给 LLM 修复机会。
  • ToolCallResult携带IsRetryableError标记(见 agent/assistant/types.go 第 50-58 行),供上层决定是否让 LLM 重新尝试。

此外,每条工具调用都会写入 trace:成功调用记录在trace.Add节点上标记为mcp_tool类型并Complete,失败则Fail(见 agent/assistant/mcp.go 第 294-311 行),便于排查与复盘。

9. 完整示例:一个带计算器工具的数学助手

下面是一个端到端的完整示例,展示了「定义服务器 → 静态/动态启用 → Hook 调用与结果处理」的完整链路。

mcps/calculator.mcp.yao

{ "label": "Calculator", "description": "Math operations", "transport": "process", "tools": { "add": "scripts.math.Add", "multiply": "scripts.math.Multiply" } }

package.yao

{ "name": "Math Assistant", "connector": "gpt-4o", "mcp": { "servers": [{ "server_id": "calculator", "tools": ["add", "multiply"] }] } }

src/index.ts

function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create { // Check if calculation is needed const query = messages[messages.length - 1]?.content || ""; if (/\d+\s*[\+\-\*\/]\s*\d+/.test(query)) { // Enable calculator return { messages, mcp_servers: [{ server_id: "calculator" }], }; } return { messages }; } function Next(ctx: agent.Context, payload: agent.Payload): agent.Next { const { tools } = payload; if (tools?.length > 0) { const calcResult = tools.find((t) => t.server === "calculator"); if (calcResult?.result) { return { data: { answer: calcResult.result, expression: calcResult.arguments, }, }; } } return null; }

这个示例演示了两个关键设计模式:

  1. 按需启用:CreateHook 用正则检测用户输入中是否含四则运算表达式,只有命中时才在mcp_servers中加载计算器,避免无关请求浪费上下文;
  2. 结果提取:NextHook 从tools数组中按server === "calculator"定位结果,将其包装进返回的data。

10. 工具命名规范与数量上限(源码细节)

使用 MCP 时有两个容易踩坑的实现细节值得了解(见 agent/assistant/mcp.go 第 17-71 行):

  • 命名规范:暴露给 LLM 的工具名格式为server_id__tool_name(双下划线分隔),server_id中的点号会被替换为单下划线。例如github.enterprise服务器上的search工具,最终名为github_enterprise__search。反向解析ParseMCPToolName在工具调用时负责还原服务器 ID。由于该格式约定,server_id不能包含下划线(仅允许点、字母、数字、连字符);
  • 数量上限:单次对话中加载的 MCP 工具总数上限为MaxMCPTools = 20(agent/assistant/mcp.go 第 19 行),超过时按服务器配置顺序截断并记录 warning,避免工具定义过多导致 LLM 上下文超限。

如果服务器声明了tools过滤列表(如["search"]),buildMCPTools会先通过ListTools拉取全部工具,再按白名单过滤,最终只把命中的工具转换为MCPTool(Name/Description/Parameters,见 agent/assistant/types.go 第 42-46 行)注入请求。另外,如果 MCP 服务器提供了工具使用样例(Samples),构建工具时会自动把每个工具最多 3 条样例组织为「MCP Tool Usage Examples」段落拼入系统提示词,帮助 LLM 更准确地调用工具(见 agent/assistant/mcp.go 第 161-203 行)。相关行为在 agent/assistant/mcp_integration_test.go 中有集成测试覆盖。

11. 总结

Yao Agent 的 MCP 集成提供了从「声明式服务器定义」到「Hook 内程序化调用」的完整能力矩阵:

  • 接入面:process、stdio、http、sse四种传输方式覆盖内部 Process、本地子进程与远程服务三类场景;
  • 配置面:package.yao静态配置支持字符串/对象多形态写法,HookCreate返回mcp_servers可实现按需动态启用;
  • 调用面:ctx.mcp提供工具(单个/顺序批量/并行批量)、资源、提示词三类操作,All/Any/Race将跨服务器并发编排与主备容灾变成几行代码;
  • 健壮性:参数 Schema 校验、可重试错误分类、并行失败降级顺序执行与全链路 trace 共同保障了生产可用性。

接入新工具时,推荐遵循「先建mcps/*.mcp.yao定义服务器,再在package.yao或 Hook 中声明启用,最后在 Hook 中调用与处理结果」的三步流程,并可对照第 9 节完整示例快速起步。

  • Agent 框架
  • 后端
  • 低代码
  • RAG

【免费下载链接】yao

✨ All your agents and workspaces in one place, on every device you own. Track tasks on a board, accessible from desktop, mobile, browser, or API. Self-hosted.

项目地址:https://gitcode.com/gh_mirrors/ya/yao
点击查看免费下载

相关推荐

上一篇:如何开发Day.js插件:从零开始构建自定义日期功能扩展
下一篇:Forge中的自动化测试:生成和执行测试用例的LLM工作流

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

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

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

立即咨询