TauriTavern/docs/API/MCP.md
Darkatse 974c1e0028 fix(agent): unify tool image storage and result projection
Store incoming images and rejection notes through one entry point, then project MCP results without duplicating structured text or describing image-only results as empty. Centralize stored image formats and file paths, reuse workspace text reads, and remove superseded identity checks.

Preserve Claude tool images and original text with or without current tool definitions. Route OpenAI-compatible tool media once and reject unsupported Gemini tool audio/video. Document capability, normalization and retention at their owning topics.

Validation: payload, gateway, runtime, MCP, image and Shell focused tests; Rust crate boundaries; pnpm run check.
2026-10-22 00:25:40 -04:00

18 KiB
Raw Permalink Blame History

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. 入口

await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);
const mcp = window.__TAURITAVERN__.api.mcp;

2. API

type TauriTavernMcpApi = {
  servers: {
    list(): Promise<{ servers: McpServer[]; storageIssues: McpStorageIssue[] }>;
    create(input: {
      displayName: string;
      endpoint: string;
      headers?: Record<string, string>;
      protocolVersion?: McpProtocolVersion;
    }): Promise<McpServer>;
    update(input: {
      registrationId: string;
      displayName: string;
      endpoint: string;
      headers: Record<string, string>;
      protocolVersion: McpProtocolVersion;
    }): Promise<McpServer>;
    setState(input: { registrationId: string; state: 'active' | 'paused' }): Promise<McpServer>;
    remove(input: string | { registrationId: string }): Promise<void>;
    discover(input: string | { registrationId: string }): Promise<McpDiscoveryResult>;
    refresh(input: string | { registrationId: string }): Promise<McpDiscoveryResult>;
  };
  tools: {
    setPermission(input: {
      registrationId: string;
      nativeName: string;
      permission: 'off' | 'ask' | 'allow';
    }): Promise<McpServer>;
    setDescriptionOverride(input: {
      registrationId: string;
      nativeName: string;
      override: ToolDescriptionOverride | null;
    }): Promise<McpServer>;
    testCall(input: {
      registrationId: string;
      nativeName: string;
      argumentsJson: string;
    }, options?: { signal?: AbortSignal }): Promise<McpTestCallOutcome>;
  };
};

没有 connect、disconnect 或 connected。只有 cold discovery、显式 refresh 和 test call 才建立短生命周期 RMCP client;memory/disk catalog hit 不连接 server。active 表示允许读取该 registration 的 snapshot,并在需要时向其 endpoint 发起 discovery 或用户 test call。

3. DTO

type McpServer = {
  id: string;                    // canonical lowercase UUID
  displayName: string;
  endpoint: string;              // normalized HTTP(S) URL
  headers: Record<string, string>;
  protocolVersion: McpProtocolVersion;
  state: 'active' | 'paused';
  toolPermissions: Record<string, 'ask' | 'allow'>;
  toolDescriptionOverrides: Record<string, ToolDescriptionOverride>;
};

type ToolDescriptionOverride = {
  description?: string;
  properties?: Record<string, string>;
};

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/<registration-uuid>:<native-name>
    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 无法接受的配置(非法 header,或 RMCP 保留的 Accept、Mcp-Session-Id、Last-Event-Id)在发送前返回 not_sent。endpoint credentials 与 header values 明文保存在 registration 文件中、由 servers.list() 回传给同一 WebView 的编辑器,并随包含该 data root 的备份流转。
  • protocol version 缺省为 auto。固定 2026-07-28 只走 server/discover lifecycle,不回退 legacy;固定 2025 版本直接以该版本 initialize,不发 discover 探测。server 最终协商出的版本不在该偏好的接受范围内时,连接作为启动失败处理。当前选项与 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/<uuid>.json 的严格 v1 单文件记录;persistent catalog 保存为 _tauritavern/mcp/catalogs/<uuid>.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 ClientLifecycleMode::Auto,以 2026-07-28 发起 server/discover。-32022 版本协商由 RMCP 处理;server 以其他非 modern JSON-RPC error 或无 session 的 4xx(401/403 除外)拒绝时,RMCP 在同一 transport 上回退到 2025-11-25 initialize。RMCP 目前把启动阶段 SSE 响应中的 JSON-RPC error 当作连接关闭,因此 Auto 启动以 ConnectionClosed 失败时,adapter 用新 Peer 单次尝试 2025-11-25 initialize,两次失败原因都保留在错误中;该额外路径不匹配其他错误。固定值见第 4 节。启动后协商版本不在接受范围内时,本次 discovery 失败。
  • 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。协商到 2026-07-28 时会在同一 Peer 上分页 tools/list 直到找到目标工具,仅用于让 RMCP 生成 SEP-2243 Mcp-Param-* header(RMCP 只从同一 transport 的 tools/list 响应取得 tool schema,缺失时会静默省略这些 header);目标始终未出现时才遍历完整目录。它不成为 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。Manager 测试调用与 Legacy 聊天不显示 image,image/audio/resource block 与 metadata 会产生可见 diagnostic,不会抹掉“server 已响应”的事实。text 与 structured 原样投影、不截断;server 显式返回 structuredContent: null 时 structuredJson 为 "null"(2026-07-28 允许任意 JSON 值),字段缺失时省略。adapter 不设自有响应体上限;transport 无法读取或解析已发送 call 的响应(包括超过 RMCP SSE 单事件上限)时为 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/<registration-uuid>:<native-name> 选择工具;每个 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:text 保持原文,structured content 去重后呈现,image 块进入 Agent 的共同 结果处理。audio、resource 产生可见诊断,metadata 留在内部记录。长结果也走共同结果处理,不改变共享 McpService 调用契约。outcome_unknown 终止当前 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。