# `window.__TAURITAVERN__.api.mcp` — MCP Manager API 本文档描述已经落地的 MCP Manager Host ABI,以及 Agent 与 Legacy generation 的消费契约。MCP 是独立平台能力;当前提供 registration、persistent tool catalog、model-facing tool description override、显式 refresh、第一方 Manager test call,并由 Agent 与 Legacy generation 消费 cached tools。 状态:Manager、Agent 与 Legacy generation 集成已实现,Project Contract(实验性)。 第一方管理 UI 位于 Extensions 抽屉的 MCP 内置扩展中。该扩展只是 `api.mcp` 的 React/strict TSX presentation;TauriTavern Settings 不再提供平行入口。 ## 1. 入口 ```js await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__); const mcp = window.__TAURITAVERN__.api.mcp; ``` ## 2. API ```ts type TauriTavernMcpApi = { servers: { list(): Promise<{ servers: McpServer[]; storageIssues: McpStorageIssue[] }>; create(input: { displayName: string; endpoint: string; headers?: Record; protocolVersion?: McpProtocolVersion; }): Promise; update(input: { registrationId: string; displayName: string; endpoint: string; headers: Record; protocolVersion: McpProtocolVersion; }): Promise; setState(input: { registrationId: string; state: 'active' | 'paused' }): Promise; remove(input: string | { registrationId: string }): Promise; discover(input: string | { registrationId: string }): Promise; refresh(input: string | { registrationId: string }): Promise; }; tools: { setPermission(input: { registrationId: string; nativeName: string; permission: 'off' | 'ask' | 'allow'; }): Promise; setDescriptionOverride(input: { registrationId: string; nativeName: string; override: ToolDescriptionOverride | null; }): Promise; testCall(input: { registrationId: string; nativeName: string; argumentsJson: string; }, options?: { signal?: AbortSignal }): Promise; }; }; ``` 没有 `connect`、`disconnect` 或 `connected`。只有 cold discovery、显式 refresh 和 test call 才建立短生命周期 RMCP client;memory/disk catalog hit 不连接 server。`active` 表示允许读取该 registration 的 snapshot,并在需要时向其 endpoint 发起 discovery 或用户 test call。 ## 3. DTO ```ts type McpServer = { id: string; // canonical lowercase UUID displayName: string; endpoint: string; // normalized HTTP(S) URL headers: Record; protocolVersion: McpProtocolVersion; state: 'active' | 'paused'; toolPermissions: Record; toolDescriptionOverrides: Record; }; type ToolDescriptionOverride = { description?: string; properties?: Record; }; type McpProtocolVersion = | 'auto' | '2026-07-28' | '2025-11-25' | '2025-06-18' | '2025-03-26'; type McpDiscoveryResult = { registrationId: string; protocolVersion: string; serverName?: string; serverVersion?: string; tools: Array<{ id: string; // mcp/: nativeName: string; title?: string; description?: string; inputSchema: object; outputSchema?: object; annotations: object; // untrusted hints only permission: 'off' | 'ask' | 'allow'; }>; diagnostics: Array<{ code: string; nativeName?: string; message: string; }>; staleTools: Array<{ nativeName: string; permission: 'ask' | 'allow'; }>; }; type McpTestCallOutcome = | { outcome: 'known_response'; response: | { kind: 'tool_result'; isError: boolean; textBlocks: Array<{ index: number; text: string }>; structuredJson?: string; diagnostics: Array<{ code: string; message: string; contentIndex?: number }>; } | { kind: 'server_error'; code: number; message: string; dataJson?: string } | { kind: 'unsupported_response'; responseType: string; message: string }; } | { outcome: 'not_sent'; code: string; message: string } | { outcome: 'outcome_unknown'; code: string; message: string }; ``` `storageIssues` 显式报告已经定位到具体文件的读取失败、损坏、未知 schema/kind、非 canonical 文件名或文件内 ID 不匹配;健康 registration 仍正常返回。registration 目录本身无法枚举时,`list()` 仍 reject。 ## 4. Registration 契约 - `create()` 总是创建 `paused` registration;Manager 在切换为 Active 前展示并确认 exact endpoint。 - Manager 首次加载时会通过现有 `create()` 契约添加一次 Paused 的 Exa Search 推荐项;处理标记保存在扩展 store 中。删除该普通 registration 不会清除标记,因此同一 data root 中不会自动恢复。 - display name、endpoint、custom request headers 与 protocol version 由 `update()` 原子修改,不影响 UUID、ToolId、Active/Paused 或已保存权限。endpoint/headers/protocol 实际变化会删除该 registration 的 memory/disk catalog;仅改名称不清 catalog,也不会联网。 - headers 的名称和值原样保存;registration 不维护 reserved-header 列表或数量/总量上限,HTTP transport 无法接受的配置在发送前返回 `not_sent`。endpoint credentials 与 header values 明文保存在 registration 文件中、由 `servers.list()` 回传给同一 WebView 的编辑器,并随包含该 data root 的备份流转。 - protocol version 缺省为 `auto`。固定版本只允许该版本参与 lifecycle 协商;当前选项与 Streamable HTTP transport 实际支持集一致。 - `off` 是缺省值,不写入 `toolPermissions`;`setPermission(..., 'off')` 删除对应持久设置。 - `setDescriptionOverride()` 按 native name 保存 model-facing description/property-description 覆盖;字符串原样保存,不做 trim 或改写。`override: null` 是唯一删除语义,空对象显式拒绝。值不写入或改写 discovery catalog,也不改变 ToolId、schema 结构、permission 或执行目标。 - 模型描述优先级固定为 server catalog → registration override → Agent Profile `tools.toolDescriptions`。Legacy 消费前两层;Agent Profile 同一字段存在时覆盖 registration 值。 - discovery 消失的 Ask/Allow 设置作为 `staleTools` 返回,但不会成为可用工具。 - registration 保存为 `_tauritavern/mcp/registrations/.json` 的严格 v1 单文件记录;persistent catalog 保存为 `_tauritavern/mcp/catalogs/.json` 的独立严格 v1 记录。两者都没有旧 schema reader 或 revision graph。 - Manager 的 Add 弹窗提供手动与 JSON 两种输入;JSON 每次只接受一个 Streamable HTTP server,支持 `{ "name": { "url": "...", "headers": {...}, "protocolVersion": "auto" } }` 和标准 `mcpServers` 包装。已保存 server 的 Edit 弹窗复用同一手动表单编辑名称、endpoint、headers 与 protocol version。无效 JSON、多个 server、非字符串 header、未知协议版本或不支持的 transport 会整体拒绝,不做 partial import。 ## 5. Discovery 契约 - transport 仅支持 Streamable HTTP;不实现 OAuth 流程,认证可由 registration custom headers 提供。 - registration headers 会应用到 initialize、discovery、session 与 tool call 的全部 HTTP 请求;系统不在不同消费路径复制或重建认证状态。 - endpoint 接受任意带 host 的 HTTP(S) URL,包括公网 HTTP、userinfo 与 query;未加密 HTTP 的风险由 Manager 在激活前提示。fragment 不会进入 HTTP 请求,因此仍作为无效 endpoint 显式报错;redirect 仍由 transport 禁止。 - `protocolVersion: 'auto'` 使用 RMCP 3.1.2 `ClientLifecycleMode::Auto` 优先协商 `2026-07-28`;固定值使用同一 lifecycle,但将 discover candidates 与 legacy initialize version 同时收窄到该版本。标准 `-32022` 协商与 SDK 可见的 `-32601` legacy fallback 由 RMCP 处理。若启动返回 implementation-defined `-32000`,或有限 SSE error 响应在 SDK 中退化为 `ConnectionClosed`,则用新 Peer 单次尝试相同配置的 legacy initialize;该额外路径不匹配其他错误。 - `tools/list` 必须完整分页;cursor 循环、页数/工具数/catalog 总量超限或分页失败会使该 server 的本次 discovery 失败,不返回 partial catalog。 - duplicate native name 隔离整个同名组;无效 input schema、单工具超限或名称无效只隔离该工具并返回 diagnostic。无效的可选 output schema 只移除该字段并返回 diagnostic,不阻止工具使用。 - input/output schema 按 JSON Schema 2020-12 编译验证;不会读取远端 `$ref`。 - annotations 只原样展示,不授予权限。 - `servers.discover()` 按 application memory、persistent file、真实 discovery 的顺序读取。磁盘 snapshot 绑定 registration UUID 与规范化 endpoint,载入后重新执行 application canonical validation。 - `servers.refresh()` 始终使用新的 RMCP Peer,绕过 memory/disk snapshot。cold discovery/refresh 在完整分页和全部 validation 成功后发布;写盘失败返回可用的 memory-only catalog 与 `mcp.catalog_persistence_failed` diagnostic,并保留旧磁盘 snapshot。网络或 validation 失败仍 reject,旧 snapshot 保留但不作为该请求的返回值。 - permission 与 `staleTools` 每次按当前 registration 投影,不写入 catalog snapshot。Paused gate 先于 snapshot lookup;registration 删除不受派生 catalog 清理失败影响,清理失败只记录 warning。 - catalog 跨应用重启保留,由用户手动 refresh 决定何时更新;没有 TTL、后台 refresh、list-changed subscription、自动 retry、source/age 字段或 migration reader。损坏/未知 schema/UUID/endpoint mismatch 显式报错,refresh 可绕过并修复。 ## 6. User test call 契约 `tools.testCall()` 是第一方 Manager 的显式用户动作,不是面向任意扩展的通用 raw RPC: - frontend 只提交 registration ID、native tool name 与原始 `argumentsJson`;endpoint、header、RMCP session 均由 backend registration 与 transport 决定。 - server 必须为 Active。Off/Ask/Allow 不阻止用户 test call,调用也不会修改保存的 permission。 - `argumentsJson` 上限为 256 KiB,backend 权威解析为 JSON object;不按 input schema 补默认值、转换类型、删除字段或在 server 之前做业务校验。字符串 Host 边界与 Rust `serde_json` 解析共同保留 `i64/u64` 范围内的 JavaScript 不安全整数。 - 每次点击只发送一次 `tools/call`,没有自动 retry。RMCP 2026 协商会在同一 Peer 上分页 `tools/list` 直到找到目标工具,仅用于填充 SEP-2243 standard-header metadata;目标始终未出现时才遍历完整目录。它不成为 application catalog cache,也不改变调用参数。 - 使用一次响应的低层 request handle,不自动驱动 `input_required`、Tasks 或其他 MRTR 后续轮次;这些 server 响应以 `unsupported_response` 明确展示。 - `AbortSignal` 表示停止本地等待,不能撤销远端副作用。Host 在调用前使用私有 start acknowledgement 建立取消事实,避免 cancel-before-register 竞态;该命令不属于公共 API。 顶层 outcome 是远端事实,而非 UI loading 状态: - `known_response`:server 已明确响应。`isError: true` 仍是 tool result;JSON-RPC error 是 `server_error`,都不变成 command rejection。 - `not_sent`:backend 能证明目标 `tools/call` 尚未交给 transport,例如 registration 暂时无法读取、Paused、JSON 不合法、HTTP client/header 配置失败、metadata hydration 失败或发送前取消。 - `outcome_unknown`:request handle 已建立后发生 cancel、timeout、disconnect 或无法确认响应;调用可能已经执行,系统绝不自动重试。 已知 tool result 按原顺序投影 text、确定性 structured JSON、`isError` 与 diagnostic。当前不显示的 image/audio/resource block 与 metadata 会产生可见 diagnostic,不会抹掉“server 已响应”的事实。完整 response 已受 4 MiB wire 上限约束,text/structured 不再做第二次静默截断;raw response 超限或 malformed 时则是 `outcome_unknown`。取得已知响应后的 client close/join 失败只记录日志,不会改写 outcome。 该入口沿用当前 WebView trust model:同一 WebView 内的第一方/vendor extension script 被视为用户授权代码,backend 不声称能证明一次 command 源自物理点击或隔离 hostile extension。若 trust model 改变,应增加真实 command capability boundary,而不是在 DTO 中伪造 click flag。 ## 7. Agent 消费契约 Agent 没有新增公共 raw-call API。Profile v3 以 `mcp/:` 选择工具;每个 root、return-mode child 与 handoff invocation 都通过 `McpService` 读取 memory→disk persistent snapshot,绝不因启动 Agent 隐式 cold discovery。无 snapshot 或工具不在缓存时,工具从该 invocation 省略并留下 diagnostic;用户在 Manager 中 discovery/refresh 后,下一个 invocation 才观察新目录。 只有 Active 且 permission 为 Ask/Allow、input schema root 明确为 `type: "object"` 的工具可以进入 Agent binding。共享 resolver 先将 registration description override 应用到 catalog descriptor 的副本,Agent invocation 再应用 Profile `tools.toolDescriptions`,因此 Profile 优先。当前按用户要求不实现 Ask 审批 UI:Ask 与 Allow 都自动执行;Off 与 Paused 在广告前过滤,并在实际发送前重新读取 registration 复核。参数必须是 256 KiB 内 JSON object,Host 不按 schema 改写。alias 由 Agent snapshot 生成,不属于 Manager DTO 或 MCP identity。 已知结果投影为现有 `AgentToolResult`。内部 `structured`、`error_code` 与 `resource_refs` 不作为字段发送给模型;text 保持原文,structured content 去重后转为 `## Details` Markdown,可操作的非文本 content diagnostic 转为 `## Notes`,metadata diagnostic 不进入模型。模型可见 `content` 超过 Agent Profile 的 `tools.mcpResultInlineCharLimit`(默认 50,000)时,完整 JSON 保存在内部 audit,同时生成同一完整 Markdown 的行可读 `.txt` 视图;模型得到最多前 3,000 个 Unicode 字符的前缀预览、可读视图路径与分段读取指引,不接收 audit JSON 路径。`.txt` 视图换行超长物理行,JSON audit 保留精确原始结果。该值只属于 Agent invocation 投影,不进入共享 `McpService` 调用契约。`outcome_unknown` 不自动 retry,也不伪造 tool result;当前没有审批/未知结果交互状态机,因此终止当前 Agent run。 ## 8. Legacy 消费契约 Legacy 集成没有扩展上面的公共 `api.mcp`。Legacy 使用第一方内部 commands 从 `McpService` 获取 Active + Ask/Allow 的 cached descriptors,并通过 permission-aware call 用例执行;第三方仍没有公共 raw MCP executor。 - catalog preparation 只读 memory/disk snapshot,cache miss 不联网;单 registration 的 snapshot 读取/校验失败形成 diagnostic,不阻塞健康 MCP/local tools 或普通生成。 - registration description override 在共享 resolver 中应用,Legacy root 冻结覆盖后的 descriptor;后续 Manager 修改只影响下一个 root。 - 每个实际 Legacy root generation 冻结 descriptors;工具递归复用 descriptor snapshot,但每轮根据当前 local view 重新分配 alias 并重建 provider schema。执行只解析当前 round binding;MCP 不进入全局 ToolManager、slash commands 或 extension enumeration。 - local alias 优先;settings hook 后只接受仍唯一存在的初始 MCP alias。删除、改名、重复或失去 binding 的 alias 不按名称/schema猜 canonical ToolId。 - `custom_include_body` / `custom_exclude_body` 继续作为用户最终 upstream body 意图;Legacy MCP 不保护、拒绝或重注入 `tools` / `tool_choice`。 - 调用只提交 canonical ToolId、ToolManager 序列化的 `argumentsJson` 与独立 UUID `executionCallId`;不做 input-schema coercion。provider `tool_call_id` 只用于聊天消息关联。发送前重新检查 registration Active、Off 与 cancellation;Ask/Allow 当前都自动执行。 - Known error 和普通 NotSent 进入现有 error Tool message,让模型下一轮修正。`outcome_unknown` 不重试、不伪造结果、不部分提交 Tool turn,并终止当前 root/group generation。 - 保存、事件与递归继续使用 SillyTavern Legacy first-class tool-turn contract;旧 `extra.tool_invocations` 只读,不恢复新写。 ## 9. 明确未支持 当前没有以下 API 或行为: - Ask 审批、公共 raw-call API及 Legacy unknown-outcome 恢复交互; - OAuth、credential、stdio、2024 HTTP+SSE; - Resources、Prompts、Tasks、Apps、subscriptions/list-changed; - background discovery、discovery/list 通用 retry、catalog TTL/revision history; - registration/Profile 之外的 description scope hierarchy、Manager-defined/global model alias。 model alias 属于短命 Agent/Legacy invocation binding,不属于 registration/discovery/test call。Legacy 集成不把 MCP tool 注册进全局 SillyTavern `ToolManager`。