# 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 原始消息。 当前链路: ```text 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](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)、[Transports 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) 和 [Tools 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)。 ## 2. 代码位置 ```text 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 的后端配置示例: ```yaml 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` 使用 `.`; - 安装阶段只读 Manifest,不启动第三方进程; - 完成用户授权后,`enable` 才启动 Host。 ## 4. 生命周期 ### 4.1 启动 启用 MCP Plugin 时依次执行: 1. 检查 Plugin 声明权限是否全部获得授权; 2. 检查 Manifest 声明的 Tool ID 是否与现有 Registry 冲突; 3. 启动独立 stdio Server; 4. 发送 `initialize`; 5. 校验协商版本和 `tools` capability; 6. 发送 `notifications/initialized`; 7. 分页读取 `tools/list`; 8. 校验全部 Tool; 9. 确认发现集合与 Manifest 完全一致; 10. 将完整集合注册到 Tool Registry; 11. 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: ```json { "name": "search", "description": "Search notes", "inputSchema": { "type": "object", "properties": {} }, "_meta": { "notesagent/permission": "notes.read" } } ``` 进入系统后转换为: ```json { "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 ```http GET /api/plugins/{plugin_id}/host POST /api/plugins/{plugin_id}/host/restart ``` 状态响应包含: ```text plugin_id backend_type / transport status tools_count started_at / last_seen_at protocol_version server_name / server_version error ``` 状态值: ```text stopped starting ready unhealthy error ``` Restart 返回 `202 OperationResponse`。接口返回前已完成本地 Host 重启和 Tool 重新发现;`message` 中给出最终 Host 状态。 ## 8. 离线 Fixture Fixture 位于: ```text 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. 验证 ```powershell 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 的原则。