外部MCP服务接入指南.md 14 KB

外部 MCP 服务接入指南

本文档说明 AX 文档编辑器如何接入外部 MCP(Model Context Protocol)服务,以及当前项目已经具备的能力和需要补充的实现。

1. 先明确当前能力边界

当前项目已经实现的是 WebMCP Provider 和 Bridge

其他网站
  -> POST /api/v1/webmcp/invoke
  -> AX 后端 WebMCP Bridge
  -> AX 前端页面 WebSocket Connector
  -> AX 前端注册的 WebMCP 工具

当前页面注册的工具包括:

  • open_document
  • list_documents
  • get_document
  • search_document
  • get_block
  • get_document_toc
  • get_document_stats
  • list_export_records
  • insert_block
  • update_block
  • delete_block
  • export_document
  • download_export_record

这些工具在前端 webMcpService.ts 中定义,由当前 AX 页面执行。

当前项目还不是通用 MCP Client,因此不能仅通过现有 WebMCP Bridge 自动发现或调用其他 MCP Server 的工具。要接入外部 MCP Server,需要在后端新增 MCP Client 层。

2. 推荐的目标架构

外部 MCP 服务应该由 FastAPI 后端连接,浏览器只负责展示结果和触发经过授权的操作。

用户聊天输入
  -> AX 前端 Chat Agent
  -> AX 后端统一工具目录
       ├── AX 本地 WebMCP 工具
       └── 外部 MCP 工具
  -> 工具路由器
       ├── 本地工具:通过 WebMCP Bridge 调用当前页面
       └── 外部工具:通过 MCP Client 调用对应 MCP Server
  -> 返回结构化结果
  -> 前端展示结果

外部 MCP Server 可能使用以下连接方式:

  • stdio:后端启动本地 MCP Server 子进程;
  • Streamable HTTP:后端通过 HTTP 连接远端 MCP Server;
  • SSE:连接支持 SSE 的 MCP Server;
  • 需要 API Key、OAuth 或其他服务端认证方式的远程服务。

推荐把外部连接放在后端,原因如下:

  • 浏览器不能安全地启动 stdio 子进程;
  • API Key、OAuth Refresh Token 等机密不能放入 VITE_* 环境变量;
  • 后端可以统一做工具白名单、用户权限、限流、审计和写操作确认;
  • 多个前端页面可以复用同一个 MCP Client 连接池。

3. 当前项目中已经可用的 WebMCP Bridge

如果目标只是让其他网站调用 AX 自己的工具,不需要接入外部 MCP Server,可以直接使用现有接口。

3.1 启动服务

启动后端:

cd "D:\work space\ax-shell\ax-backend-v1"
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

启动前端:

cd "D:\work space\ax-shell\ax-frontend-app"
npm run dev

然后保持 AX 页面打开:

http://localhost:5173/

页面启动时会连接 WebSocket Bridge:

ws://localhost:8000/api/v1/webmcp/bridge/{clientId}

3.2 调用 AX 工具

async function invokeAxTool(tool, argumentsValue = {}) {
  const response = await fetch('http://localhost:8000/api/v1/webmcp/invoke', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-WebMCP-Token': '从服务端安全配置中读取',
    },
    body: JSON.stringify({
      clientId: 'ax-editor-local',
      tool,
      arguments: argumentsValue,
    }),
  });

  const payload = await response.json();
  if (!response.ok || payload.code !== 0) {
    throw new Error(payload.detail || payload.message || 'WebMCP 调用失败');
  }

  const result = payload.data.result;
  if (!result.ok) {
    throw new Error(result.error || 'AX WebMCP 工具执行失败');
  }

  return result.data;
}

const documents = await invokeAxTool('list_documents', {
  page: 1,
  pageSize: 20,
});

当前 AX 页面必须保持打开,因为 open_documentupdate_block 等工具依赖浏览器中的编辑器状态。

4. 接入外部 MCP Server 的实施步骤

步骤 1:选择连接方式

先确认外部服务提供的协议和认证方式:

类型 适用场景 机密存放位置
stdio 本机工具、文件系统工具、内部脚本 后端主机环境变量或密钥管理服务
Streamable HTTP 远程部署的 MCP Server 后端服务端配置
SSE 旧版或仍提供 SSE 的 MCP Server 后端服务端配置
OAuth 第三方 SaaS 服务 后端安全 Token 存储

不要把外部 MCP Server 的命令、Token 或 API Key 写入前端代码。

步骤 2:在后端增加 MCP Client 依赖

当前 requirements.txt 尚未包含 MCP Client SDK。接入时应选择与目标 Server 协议匹配的官方或可信 SDK,并固定版本。

安装依赖前应确认:

  • SDK 支持当前 Python 版本;
  • SDK 支持目标 Server 的传输方式;
  • SDK 的许可证和维护状态符合项目要求;
  • 依赖不会把不受信任的命令执行能力暴露给公网请求。

不要直接把任意用户输入拼接到 stdio 命令或参数中。

步骤 3:增加外部 Server 配置

建议在后端配置中增加 Server 配置,而不是把配置硬编码到业务代码:

EXTERNAL_MCP_ENABLED=false
EXTERNAL_MCP_SERVERS_JSON={}
EXTERNAL_MCP_CONNECT_TIMEOUT=10
EXTERNAL_MCP_CALL_TIMEOUT=60
EXTERNAL_MCP_MAX_RESULT_BYTES=524288

生产环境更推荐使用单独的密钥管理系统。配置内容至少应包括:

{
  "filesystem": {
    "enabled": true,
    "transport": "stdio",
    "command": "npx",
    "args": ["-y", "@modelcontextprotocol/server-filesystem", "D:/documents"],
    "allowedTools": ["read_file", "list_directory"]
  }
}

注意:上面的配置是接入设计示例,不代表当前项目已经支持该配置格式。

步骤 4:增加 MCP Client 管理器

建议新增以下后端结构:

ax-backend-v1/app/mcp/
  __init__.py
  client.py       # 单个 MCP Server 的连接和调用
  manager.py      # 多个 Server 的生命周期管理
  registry.py     # 工具目录、命名空间和白名单
  schemas.py      # 工具和调用结果的数据结构

单个 Client 至少应提供:

class ExternalMcpClient:
    async def connect(self) -> None:
        """建立连接并完成 MCP initialize。"""

    async def list_tools(self) -> list[dict]:
        """调用 tools/list 获取工具定义。"""

    async def call_tool(self, name: str, arguments: dict) -> dict:
        """调用 tools/call 执行工具。"""

    async def close(self) -> None:
        """释放连接、子进程和后台任务。"""

管理器应负责:

  • 应用启动时连接已启用的 Server;
  • 应用关闭时释放连接;
  • 连接断开后的有限次重连;
  • tools/list 结果缓存和刷新;
  • 工具名称冲突处理;
  • 单 Server 和单用户的并发限制;
  • 调用超时和结果大小限制。

步骤 5:为外部工具添加命名空间

外部工具不能直接使用裸名称,否则容易与 AX 本地工具冲突。建议统一使用:

filesystem.read_file
github.search_repositories
notion.search

内部映射可以是:

TOOL_MAPPING = {
    "filesystem.read_file": {
        "server": "filesystem",
        "remote_name": "read_file",
    },
}

对外暴露的统一工具结构建议如下:

{
    "name": "filesystem.read_file",
    "title": "读取文件",
    "description": "读取允许目录中的文件内容",
    "inputSchema": {
        "type": "object",
        "properties": {
            "path": {"type": "string"}
        },
        "required": ["path"]
    },
    "source": "external",
    "server": "filesystem",
    "requiresConfirmation": false
}

步骤 6:增加后端代理接口

推荐新增独立的外部 MCP API,而不是把外部工具直接塞进现有 AX Bridge 白名单:

GET /api/v1/mcp/tools
POST /api/v1/mcp/tools/call

获取工具:

GET /api/v1/mcp/tools
Authorization: Bearer <user-token>

调用工具:

POST /api/v1/mcp/tools/call
Authorization: Bearer <user-token>
Content-Type: application/json

{
  "tool": "filesystem.read_file",
  "arguments": {
    "path": "D:/documents/example.txt"
  }
}

返回结果建议统一成:

{
  "ok": true,
  "tool": "filesystem.read_file",
  "server": "filesystem",
  "data": {
    "content": "文件内容"
  }
}

外部 MCP Server 的原始结果应在后端转换成受控结构,不能无条件把任意对象、二进制数据或超大文本直接返回给浏览器。

步骤 7:接入聊天工具目录

当前聊天转译上下文主要来自前端 webMcpAgentService.ts 的本地工具列表。接入外部工具后,应将本地和外部工具合并:

本地工具:getWebMcpTools()
外部工具:GET /api/v1/mcp/tools
统一工具目录:localTools + externalTools

工具调用时按来源路由:

open_document
  -> AX 前端 WebMCP Runtime

filesystem.read_file
  -> AX 后端 MCP Client

自然语言转译结果必须经过服务端二次校验,不能因为模型返回了某个工具名称就直接执行。

5. 安全要求

5.1 工具白名单

只允许配置明确启用的 Server 和工具:

ALLOWED_EXTERNAL_TOOLS = {
    "filesystem.read_file",
    "filesystem.list_directory",
}

不能允许请求方任意传入 Server 地址、命令、工具名称或远程 URL。

5.2 写操作确认

以下类型的外部工具默认需要用户确认:

  • 写入、删除或移动文件;
  • 创建、修改或删除第三方平台数据;
  • 发送邮件、消息或通知;
  • 创建费用、订单或权限变更;
  • 执行脚本、命令或数据库写操作。

确认凭证必须绑定:

  • 当前用户;
  • 当前会话;
  • 工具名称;
  • 完整参数哈希;
  • 短时间有效期;
  • 一次性使用标识。

5.3 认证和权限

生产环境不能使用长期共享的 VITE_WEBMCP_BRIDGE_TOKEN。当前项目的生产配置应使用:

WEBMCP_AUTH_REQUIRED=true
WEBMCP_JWT_SECRET=独立的高熵服务端密钥
WEBMCP_REDIS_URL=redis://...
WEBMCP_REQUIRE_SHARED_STORE=true

外部 MCP Client 还必须校验:

  • 当前用户是否有权访问目标 Server;
  • 当前用户是否有权访问目标资源;
  • 文档、文件或第三方对象是否属于当前租户;
  • 外部工具返回内容是否可展示给当前用户。

5.4 SSRF 和命令执行防护

如果支持 HTTP MCP Server:

  • 不允许用户直接提交任意 URL;
  • 只允许服务端配置的域名或地址;
  • 禁止访问云元数据地址、内网管理地址和本机敏感端口;
  • 对重定向进行重新校验;
  • 配置连接、读取和总调用超时。

如果支持 stdio MCP Server:

  • 命令和参数只能来自服务端配置;
  • 不要使用 shell 拼接执行;
  • 使用参数数组启动进程;
  • 限制工作目录和环境变量;
  • 使用专用低权限系统用户;
  • 限制子进程数量、内存和运行时间。

5.5 结果和日志

必须限制:

  • 请求参数大小;
  • 工具结果大小;
  • 单用户并发数;
  • 单 Server 并发数;
  • 调用频率;
  • 连接重试次数。

日志中不要记录:

  • API Key;
  • OAuth Token;
  • Cookie;
  • 完整文件内容;
  • 可能包含个人信息的工具参数。

建议记录:

  • 用户 ID 和会话 ID;
  • Server 名称;
  • 命名空间工具名;
  • 调用是否需要确认;
  • 成功、失败或超时;
  • 调用耗时和结果大小。

6. 推荐的分阶段落地顺序

阶段一:只读工具

先接入一个只读外部 MCP Server,例如:

  • 文件列表;
  • 文件读取;
  • 搜索;
  • 查询类工具。

只实现:

  • Server 配置;
  • Client 连接;
  • tools/list
  • tools/call
  • 工具白名单;
  • 超时和结果限制;
  • 只读调用 API。

阶段二:接入聊天

将外部工具目录加入聊天 Agent:

  • 展示工具名称和描述;
  • 将工具 Schema 提供给转译器;
  • 服务端校验工具名称和参数;
  • 将调用结果转换成适合聊天展示的文本或结构化卡片。

阶段三:写操作和确认

增加:

  • 用户确认弹窗;
  • 一次性确认凭证;
  • 参数哈希绑定;
  • 审计日志;
  • 失败重试和幂等策略。

阶段四:多租户和生产部署

增加:

  • 每个租户独立的 MCP Server 配置;
  • 用户级 OAuth Token;
  • Redis 或其他共享状态存储;
  • 多实例连接管理;
  • 监控、告警和调用成本统计。

7. 上线检查清单

  • 外部 MCP Client 只运行在后端。
  • 外部 MCP Server 地址来自服务端配置,不来自用户输入。
  • API Key 和 OAuth Token 没有进入前端构建产物。
  • 所有外部工具使用命名空间。
  • 工具和 Server 都有白名单。
  • 只读和写操作明确区分。
  • 写操作需要当前用户确认。
  • 确认凭证绑定用户、会话、工具和参数哈希。
  • 配置连接超时、调用超时和结果大小限制。
  • 配置单用户和单 Server 并发限制。
  • HTTP Server 已防护 SSRF。
  • stdio Server 使用低权限进程和固定参数。
  • 日志已脱敏。
  • 已测试断线、超时、错误结果和超大结果。
  • 已测试多用户和多租户权限隔离。
  • 生产环境未使用长期共享 Bridge Token。

8. 相关代码和文档

本指南中的外部 MCP Client 目录、配置项和代理接口属于推荐设计。当前仓库已经实现的是 AX 自有 WebMCP 工具及 Bridge;在外部 MCP Client 代码落地前,不应把外部 Server 工具当作当前系统已支持的功能对外承诺。