plugin-mcp-client — MCP 协议客户端桥
包名: @aalis/plugin-mcp-client源码: packages/plugin-mcp-client/src/index.ts
概述
作为 MCP (Model Context Protocol) client,通过 stdio 连接外部 MCP server(如 @modelcontextprotocol/server-github、server-filesystem、server-playwright 等),把这些 server 暴露的 tools 注册进 Aalis 的 ToolService,供 agent 直接调用外部工具。
总体架构与定位见 docs/plugins/mcp.md。
插件声明
ts
export default definePlugin({
name: '@aalis/plugin-mcp-client',
uses: {
tools,
logger,
lifecycle,
config,
hostConfig: optional(hostConfig),
webui: optional(webuiServer),
},
apply(caps) { /* 见源码 */ },
});配置
yaml
plugins:
"@aalis/plugin-mcp-client":
servers:
- id: github
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_xxx"
enabled: true # 可省略,默认 true
visibility: auto # auto | public | sensitive | restricted(默认 auto,按工具注解分档)
- id: fs
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
visibility: restricted # 文件系统访问视为受限,须被 owner/委托授予| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
servers | array | [] | MCP 服务器列表:通过 stdio 连接的 MCP 服务器。每条目至少需要 id 与 command;安全级别按需调整。 |
条目里的 args 是字符串数组(schema 类型 list,每项一个参数,可含空格或为空串),env 是 KEY: VALUE 映射(schema 类型 map),两者中不加引号的有限数字按字符串处理。
env 声明了 secret: true,每个 server 的整份 env 算一个密钥:Node 宿主把它移进数据目录的 secrets.yaml,配置文件里只留引用(第一个键是 @aalis/plugin-mcp-client.servers.env,其后的依次加 -2、-3;空的 env 不算密钥,原样留着),WebUI 不回显变量名与值,要改就整份重填。token 等凭据放进 env,不要写进 args:连接成功时的日志会写出 command 与全部 args。存放规则见 运行态与配置文档。
条目按 schema 逐项解析,问题都记录警告:
- 缺少
id或command(含空串),或其中之一只有空白的条目跳过,其它 server 不受影响。id与command去掉首尾空白后使用。 command写命令名(按 PATH 查找)或绝对路径(schema 声明format: 'executable')。写法判定忽略首尾空白:带前导空格的绝对路径照常通过,去掉空白后使用;写成相对路径的条目跳过并告警,其它 server 照常工作。args原样传给命令,参数里的文件路径请写绝对路径:宿主进程的 cwd 不带语义。- 缺省的
args/env分别补[]/{};显式给出错误形态,或数组、映射中有不能转换为字符串的成员时,整条 server 跳过,绝不截短参数后启动。 visibility不是四个取值之一、enabled不是布尔值时,整条 server 跳过。
行为
- 每个 server 是一个独立子进程,stdio 传输。
- 工具名映射为
mcp_<server-id>_<tool-name>:[a-zA-Z0-9_-]以外的字符替换为_,连续下划线合并为一个,超过 64 字符截断(OpenAI 工具名限制)。 - 工具分组:
mcp:<server-id>—— 可在 platform 配置中按需启用/禁用。 inputSchema顶层非type: 'object'时自动包装为{ input: schema }。- 某个 server 连不上(握手或列工具失败)时记一行 error 日志(
连接 MCP server "<id>" 失败: …),并经webuiServer发一条level: 'error'的提示:id 为connect:<server-id>,标题「MCP 服务器「<id>」连不上」,detail先写怎么修(检查这台服务器的command、args与env,改好后保存配置即重连),再接失败原因,跳转「去配置」。提示出现在 WebUI 的「需要处理」与本插件的详情里;它归属这次激活,改好配置后插件重载,旧提示随之撤回,新的激活连上了就不再发。连接只在激活时做一次,没有自动重连。WebUI 服务端不在时只记日志。 - 启动时的握手与列工具在停用或停机时中止,中止不记连接失败,也不发提示。每个 server 连接成功后,经
lifecycle.use.onDispose注册client.close();插件关闭时断开所有已连接的 server,关闭时抛出的错误被忽略,仅记 debug 日志。 - 桥进来的工具声明可转后台(
background,让出时限用执行器的默认 30 秒):远端工具多久结束不可知,在前台等满让出时限还没结束,plugin-tools 的执行器把这次调用登记成后台任务、先回给模型,正常结束时通知起它的会话。调用带上执行器给这次调用的中止信号,中止时 SDK 向服务端发notifications/cancelled;请求超时取setTimeout能表示的最大值 2147483647 毫秒(SDK 默认的 60 秒会把长任务截断,传Infinity又会被 Node 当成 1 毫秒),等多久由执行器的让出时限管。远端工具的参数表里已有runInBackground或waitSeconds的(按 api-tools 的takenBackgroundParams判断),执行器不会让它让出,本插件就不声明可转后台、记一条警告,请求超时照 SDK 默认的 60 秒,工具照常登记。 - 远端工具的返回文本经
wrapUntrustedContent套上不可信内容边界。返回isError时不套边界,结果为{ error: "MCP 工具返回错误:\n<正文>" };调用失败(含被中止)时结果为{ error: "MCP 调用 <server-id>/<工具名> 失败: <原因>" }。两种都按工具报失败的约定放进error,工具卡与后台任务据此记为失败。 - 插件另外注册
mcp:_meta分组下的两个自服务工具:mcp_list_servers(public,只读列出有效 server 的 id / command / enabled / visibility)和mcp_set_server_enabled(restricted,切换列表中已有条目的enabled并持久化,插件经 bounce 后生效)。后者以插件在配置文档里的原配置为底,只改目标条目的enabled(env的引用原样交回,密钥不变),经host-config的applyPluginConfig一步改运行态、写文档并落盘;host-config缺席时返回失败,不改运行态;插件停机中或正在卸载、运行态不受理时返回失败,配置不变。落盘失败(如配置文件有尚未生效的外部修改而拒写)时运行态已经切换、插件照常 bounce,返回的文本注明这一点与写入失败的原因:配置文件里仍是原值,重启或配置重新载入后可能回退(拒写时配置重新载入以文件为准;其它写入错误时改动仍留在配置文档里,之后别处保存成功会一并写进文件)。不提供新增 server 的工具;未配置任何有效 server 时只注册这两个。
安全注意事项
- 外部 server 是不受信任的第三方进程。默认
auto按注解分档、未知即 restricted(失败关闭); 只有确认全部工具都只读的 server,才应显式放宽为public(最低等级 0)。 - Aalis 自身的能力闸仍然生效:
restricted工具默认要求触发者等级 >= 2,sensitive工具默认要求等级 >= 1,owner 不受等级限制;等级不足即拒。
依赖
@modelcontextprotocol/sdk^1.29.0uses.tools:required,描述符来自@aalis/api-toolsuses.webui:optional,描述符来自@aalis/api-webui,用来发连不上的提示
已知限制
- 仅支持 stdio transport,SSE / HTTP client 暂未实现。
- 单个 server 连接失败不会阻止插件或 Aalis 启动,也不影响其它 server:错误写进日志并发提示(见「行为」),该 server 的工具不会注册。已连上的 server 中途断开时不发提示,它的工具调用回失败原因;重载插件后重新连接。