8.4 KiB
MCP Bridge 与 Plugin Host 开发说明
更新日期:2026-09-01。本文记录第二阶段阶段 C 已实现的本地 stdio MCP Bridge、隔离 Plugin Host、Tool Contract 转换和离线测试方式。Plugin Command 与 Settings 属于阶段 D,不在本文实现范围内。
1. 目标与实现状态
阶段 C 的目标是让外部 MCP Server 进入既有 Plugin、Tool、Permission、Agent 和 Trace 链路,同时避免 Agent Runtime、前端或 Benchmark 直接依赖 MCP 原始消息。
当前链路:
Plugin Manifest
→ Plugin Runtime
→ 独立 stdio MCP Server 进程
→ initialize / capability negotiation
→ tools/list 分页发现与校验
→ NotesAgent ToolDefinition
→ Tool Registry / Permission Manager
→ Agent Runtime / Agent Trace
已经实现:
- 本地 stdio 子进程启动、关闭和异常退出检测;
- UTF-8、换行分隔的 JSON-RPC 2.0 消息;
- initialize、协议版本与 tools capability 协商;
notifications/initialized;- 分页
tools/list; tools/call、业务错误与 JSON-RPC 错误转换;- 超时和
notifications/cancelled; - Tool 命名空间、JSON Schema、权限和 Manifest 集合校验;
- Host 状态查询、重启和异常后的 Tool 自动注销;
- stderr 隔离、环境变量裁剪、消息及结果大小限制;
- 无网络、无密钥的确定性 MCP Fixture。
实现依据为 MCP 官方 Lifecycle 2025-11-25、Transports 2025-11-25 和 Tools 2025-11-25。
2. 代码位置
backend/app/extensions/mcp.py
stdio 进程、JSON-RPC、MCP 生命周期、发现、调用和 Host 状态
backend/app/extensions/runtime.py
Plugin Manifest、权限、MCP Tool 批量注册/回滚和生命周期集成
backend/app/agent/tools.py
内部 Tool 参数校验、结构化执行错误和线程安全 Registry
backend/extensions/fixtures/mcp-echo/
确定性 stdio MCP Server 与 Plugin Manifest
3. Plugin Manifest
MCP Plugin 的后端配置示例:
id: example-mcp
name: Example MCP
version: 1.0.0
permissions:
- notes.read
contributes:
tools:
- example-mcp.search
backend:
type: mcp
transport: stdio
command: python
args: [server.py]
startup_timeout_seconds: 5
tool_timeout_seconds: 30
约束:
- 阶段 C 只接受
type: mcp与transport: stdio; - 命令和参数通过数组直接传给
subprocess.Popen,不经过 Shell; - PATH 中的 executable 使用名称,例如
python、node; - manifest 中带目录的 executable 必须解析到 Plugin 包内部;
contributes.tools使用<plugin_id>.<remote_name>;- 安装阶段只读 Manifest,不启动第三方进程;
- 完成用户授权后,
enable才启动 Host。
4. 生命周期
4.1 启动
启用 MCP Plugin 时依次执行:
- 检查 Plugin 声明权限是否全部获得授权;
- 检查 Manifest 声明的 Tool ID 是否与现有 Registry 冲突;
- 启动独立 stdio Server;
- 发送
initialize; - 校验协商版本和
toolscapability; - 发送
notifications/initialized; - 分页读取
tools/list; - 校验全部 Tool;
- 确认发现集合与 Manifest 完全一致;
- 将完整集合注册到 Tool Registry;
- Plugin 和 Host 进入
ready。
任何步骤失败都会注销本轮已注册 Tool、关闭子进程并把 Plugin 标记为 error,不会留下半启用状态。
4.2 停止与异常退出
停用、卸载或应用关闭时,先注销 Tool,再关闭 stdin,等待 Server 正常退出。超时后依次 terminate 和 kill。
Server 异常退出、stdout 出现非 JSON-RPC 内容或发送超大协议消息时:
- 未完成请求返回
PLUGIN_HOST_UNAVAILABLE; - Host 进入
unhealthy; - Plugin 进入
error; - 对应 Tool 从 Registry 中立即注销;
- 用户可以调用 Host Restart 接口重新协商和发现。
Server 发送 notifications/tools/list_changed 时不会直接信任新集合。当前实现先把 Host 标记为不健康并注销旧 Tool,要求通过 Restart 重新执行完整发现与校验。
5. Tool Contract 转换
MCP Tool:
{
"name": "search",
"description": "Search notes",
"inputSchema": { "type": "object", "properties": {} },
"_meta": { "notesagent/permission": "notes.read" }
}
进入系统后转换为:
{
"name": "example-mcp.search",
"description": "Search notes",
"parameters": { "type": "object", "properties": {} },
"permission": "notes.read",
"source": "plugin"
}
转换规则:
- 远端名称必须能转换为合法且稳定的项目 Tool ID;
inputSchema必须是有效的 object JSON Schema;_meta.notesagent/permission必须属于项目已知权限;- Tool 权限必须同时出现在 Plugin Manifest 中;
- Agent 仍通过 Tool Registry 执行参数校验、Permission、超时和 Trace;
- MCP
structuredContent存在时映射为内部 output;否则保留为受控content数组; - MCP
isError: true映射为MCP_TOOL_CALL_FAILED; - 结果超过 256 KiB 映射为
MCP_TOOL_RESULT_TOO_LARGE。
6. 隔离与安全边界
当前隔离是“独立进程 + 协议边界”,不是完整的操作系统沙箱。
已经执行的保护:
- 第三方模块不 import 到 AI Core;
- 子进程
cwd固定为 Plugin 包目录; - 不使用 Shell 拼接命令;
- 不把 Provider API Key、
APP_DB_PATH、Vault 路径和其他宿主环境变量传入子进程; - stderr 与 JSON-RPC stdout 分离,stderr 不进入 API 和 Agent Trace;
- stdout 只能发送合法 MCP JSON-RPC;
- 单条协议消息上限 2 MiB;
- 单次 Tool Result 上限 256 KiB;
- MCP Tool 不绕过 Permission Manager 和 Agent Tool Timeout。
当前尚未提供容器、受限系统账户、seccomp、Windows AppContainer 或 macOS Sandbox,因此 Plugin 进程仍具有当前操作系统用户授予的一般文件访问能力。正式社区插件分发前必须继续增加包签名、来源验证和平台级沙箱;不得把当前进程隔离描述为完全安全执行任意不可信代码。
7. Host API
GET /api/plugins/{plugin_id}/host
POST /api/plugins/{plugin_id}/host/restart
状态响应包含:
plugin_id
backend_type / transport
status
tools_count
started_at / last_seen_at
protocol_version
server_name / server_version
error
状态值:
stopped
starting
ready
unhealthy
error
Restart 返回 202 OperationResponse。接口返回前已完成本地 Host 重启和 Tool 重新发现;message 中给出最终 Host 状态。
8. 离线 Fixture
Fixture 位于:
backend/extensions/fixtures/mcp-echo
它提供:
mcp-fixture.echo:返回 structuredContent;mcp-fixture.fail:返回isError: true;mcp-fixture.sleep:验证超时和取消;mcp-fixture.large:验证结果大小上限;mcp-fixture.environment:验证宿主 Secret/路径没有进入子进程;mcp-fixture.exit:验证异常退出、Tool 注销和 Restart。
Fixture 的 tools/list 使用两页响应,用于覆盖分页发现。测试还会启动缺少 tools capability 和返回无效 Schema 的变体。
9. 验证
cd backend
uv run python -m compileall -q app
uv run pytest
cd ../frontend
pnpm test
pnpm type-check
pnpm build
阶段 C 新增测试覆盖:
- initialize、版本和 capability negotiation;
- 分页
tools/list与命名空间映射; - Permission、JSON Schema 与 Contribution 集合;
- Tool 成功、业务错误、结果过大和超时;
- Agent Runtime 调用 MCP Tool 并写入正式 Trace;
- Secret/Vault 环境隔离;
- Server 异常退出、Tool 注销和 Host Restart;
- 缺少 capability 与无效 MCP Schema;
- OpenAPI 发布 Host 状态和重启路径。
10. 当前边界与后续阶段
阶段 C 不包含:
- Streamable HTTP MCP transport;
- Resources、Prompts、Sampling、Elicitation 和 MCP Tasks;
- Plugin Command 与 Settings Contribution;
- Secret Reference 注入;
- Plugin Registry 持久化、签名与社区来源校验;
- 操作系统级沙箱;
- Tool 列表热更新的无中断替换。
阶段 D 将在当前 Plugin Runtime 上继续增加 Command、Settings、Secret Contract 和命名空间 Storage,不修改 Agent 使用内部 Tool Contract 的原则。