# MOV 开发者文档 · 全文合集(llms-full) > 本文件由 tests/build-llms-full.mjs 从 6 件 md 真源合并生成(勿手改;改 md 后重跑生成)。索引版见 llms.txt。 --- # 快速开始(5 分钟)(docs/quick-start.md) # 快速开始:5 分钟跑通第一个工具(镜像版) > 本文件为 [quick-start.html](../quick-start.html) 的 markdown 镜像(页面与 md 同源·防漂移测试守护)。零依赖 Node 18+ 单文件模板,本地三条 curl 验证,然后部署公网提交上架。 ## 要点(30 秒) - 零依赖 Node 18+ 单文件模板(hello-server.mjs)——5 分钟跑通第一个可上架工具。 - 生态全旅程 7 步:入驻 → 认证 → 建服务 → 测试 → 审核 → 发布 → 看数(每步标真实入口与状态)。 - 本地三条 curl 全通 = 服务端就绪;再部署公网 https(工具必须公网可达)。 - 提交通道与审核标准见「上架流程」;商品线(扫码建草稿)与本页工具线分离。 ## 生态全旅程(7 步) 从入驻到看数,一条链走完。每步都标出**真实入口**与当前状态:已在线 / 上线在途。 1. **① 入驻**:设备注册即登录(`POST /register`);与协作者配对(`/pair/request`、`/pair/accept`、`GET /peers`)。入口:`/merchant/`(商家后台)。已在线 2. **② 认证**:提交申请(`POST /merchant/apply`)→ 登记材料(`POST /merchant/materials`)→ 按缺件补齐(`GET /merchant/missing`)→ 核验(`GET /merchant/verify/items`)→ 状态自查(`GET /merchant/status`)。状态机 `APPLIED → VERIFYING → APPROVED / REJECTED`;缺件是「进行中」不是打回。已在线 3. **③ 建服务**:工具线=按[开发规范](../spec.html)实现 MCP 服务(本文第 0–5 步);商品线=App 扫码生成商品草稿(`POST /merchant/product/draft`,契约已定)与草稿台。工具线在线 / 已上线 4. **④ 测试**:工具=三条 curl 自检(本文第 2 步);商品=草稿台预览。工具线在线 / 已上线 5. **⑤ 审核**:工具=服务端四道卡 + 人工审核(见[上架流程](../publish.html));商家=资质人工核验(核验项台账),驳回附理由、可修改重提。已在线 6. **⑥ 发布**:工具=并入 `registry.json` 即刻生效;商品=发布后买家可见。工具线在线 / 已上线 7. **⑦ 看数**:店铺健康分四维(信息完整度 / 响应时延 / 纠纷率 / 资质时效,`GET /merchant/health`)+ 买家信任信号(`GET /merchant/health/{device_id}`)。已在线 ## 认证/类目决定表 先看你的类目要备哪些材料——按类目直查,不用猜: | 类目(category) | 必备材料 | 法规锚 | |---|---|---| | `food` 食品 | 营业执照 + 食品经营许可证 | 《电子商务法》第 27 条 · 《食品安全法》第 35 条 | | `cosmetic` 化妆品 | 营业执照 + 产品备案凭证 | 《电子商务法》第 27 条 · 《化妆品监督管理条例》 | | `general` 一般 | 营业执照 | 《电子商务法》第 27 条 | 核验方式(MVP=人工,按审核要求执行):受控拍摄 / 实人核验 / 对公打款 / 要素核验;只留核验结果与凭证号,不存人脸原图。材料提交即进入核验中(`VERIFYING`);驳回(`REJECTED`)修改后可重新提交(回到 `APPLIED`)。 ## 第 0 步 · 准备 - 一台能跑 Node 18+ 的机器(本地即可); - 一个能部署公网 https 的位置(上线前再准备,本地调试用 127.0.0.1 就够)。 官方模板:`templates/hello-server.mjs`(本仓库 `official-web/dev/templates/`),全文只有一个 HTTP 服务器和一份工具声明。 ## 第 1 步 · 本地起服务 模板文件 `hello-server.mjs` 核心结构(完整文件见上方模板包): ```js import http from 'node:http'; const TOOLS = [ { // ① 工具声明四件套 name: 'hello_world', description: '收到问候语(hello / hi 等)时调用;返回问候语 "Hello world"。', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, level: 'readonly', // 当前版本只做只读工具 } ]; http.createServer((req, res) => { let body = ''; req.on('data', c => body += c); req.on('end', () => { let rpc = null; try { rpc = JSON.parse(body || '{}'); } catch {} if (!rpc || !rpc.method) { // ② 健康检查:空 body → 200 即存活 res.writeHead(200).end('{}'); return; } const reply = r => res.writeHead(200, {'Content-Type':'application/json'}) .end(JSON.stringify({jsonrpc:'2.0', id:rpc.id ?? null, result:r})); if (rpc.method === 'tools/list') return reply({ tools: TOOLS }); // ③ 发现 if (rpc.method === 'tools/call') return reply({ content:[{type:'text', text:`Hello, ${rpc.params?.arguments?.who ?? 'world'}!`}], isError:false }); // ④ 调用 res.writeHead(404).end('{}'); }); }).listen(8787); ``` 运行: ```bash node hello-server.mjs # → http://127.0.0.1:8787 ``` ## 第 2 步 · 三条 curl 自检(全通 = 服务端就绪) ```bash # ① 健康检查:应为 200(405 也会被 app 接受) curl -s -o /dev/null -w '%{http_code}' -X POST http://127.0.0.1:8787 \ -H 'Content-Type: application/json' -d '{}' # ② 工具清单:应返回 result.tools 数组 curl -s -X POST http://127.0.0.1:8787 \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' # ③ 真实调用一次 curl -s -X POST http://127.0.0.1:8787 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"hello_world","arguments":{"who":"MOV"}}}' ``` 三条的真实应答(本模板实测): ```text ① 200 ② {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"hello_world", "description":"收到问候语(hello / hi 等)时调用;返回问候语 \"Hello world\"。", "inputSchema":{"type":"object","properties":{},"additionalProperties":false}, "level":"readonly"}]}} ③ {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Hello, MOV!"}],"isError":false}} ``` > 想看线上真实案例?市场目录里已有一个 `hello-world` 包,服务 `/hello-mcp/`,上面第 ② ③ 条 curl 把地址换成 `<当前域名>/hello-mcp/` 即可直接体验(工具名 `hello_world`,应答 `"Hello world"`)。 ## 第 3 步 · 部署公网 https - app 端全局禁明文 http(仅放行回环地址),所以正式上架的服务必须 **https**; - 本地调试期间可以用任意内网穿透工具把 127.0.0.1:8787 临时映射成 https 地址; - 用户调用你的工具时,请求从**用户设备**直发你的服务器(我们不做转发),确认防火墙/网关允许公网直连; - 超时预算:连接 5s / 读取 30s——长任务请先返回再异步处理。 ## 第 4 步 · 提交上架 带齐目录条目字段(id / 名称 / 一句话简介 ≤20 字 / 分类 / 作者 / 联系方式 / 服务 URL),走[上架流程](../publish.html):网页提交或 app 内对话提交。服务端会现场做健康检查(空 POST,200/405 即通),通过后进入人工审核。 上架前请过一遍[自检清单](../publish.html#checklist)——三条 curl 全通 + 字段合规 = 可以提交。 ## 第 5 步 · 在用户设备上会发生什么 1. 用户在能力市场点「安装」,app 对你的服务做健康检查 + `tools/list` 发现; 2. 你的工具以 `ext.<服务器名>.<工具名>` 出现在用户的 AI 工具面(热挂载,无需重启 app); 3. AI 下一轮对话即可直接调用;用户可随时在市场里停用/卸载(热摘除)。 --- # 工具开发规范(spec.md) # MOV 工具开发规范 · 官方契约(镜像版) > 本文件为 [spec.html](https://mow.kim/dev/spec.html) 的 markdown 镜像(同源同步:改此文件 → WebMCP 工具 `mov_get_tool_contract` 输出同步变)。生态内任何工具 / MCP 的开发契约:声明、命名、参数、输出、错误、并行、权限——一页对照,**对齐 dsh(DeepSeek Harness)官方设计**。 ## 要点(30 秒) - 工具声明四件套 + MCP 暴露面四字段——与 tools/list 实态同源(铁律)。 - 命名规范 + 参数与输出契约。 - 错误契约统一 16 类。 - 并行与作用域(dsh 同款纪律)、权限分级(readonly/write 职责)。 - 含与 dsh 官方对比现状、「外部工具(市场安装)」特殊说明。 ## 一、工具声明(与 dsh 对齐的完整契约) 每个工具按以下字段声明(dsh `ToolDefinition` 同构): ```js { name: "mov.roomList", // ① 唯一名:领域.动作 description: "列出当前房间", // ② 描述:给模型看,禁止占位 parameters: { ... }, // ③ JSON Schema(类型/必填/描述) output: { schema: {...} }, // ④ 输出声明(可选,供 render) timeoutMs: 20000, // ⑤ 超时(策略层用,绝不发给模型) isConcurrencySafe: (args) => true, // ⑥ 并发:精确 true 才并行,未声明保守串行 scope: ["main"], // ⑦ 作用域:main / subagent execute: async (args, runCtx) => value } ``` 工厂接收 `defineTool()` 风格的参数声明(dsh 已落盘 `ToolDefinition`,见 `docs/MCP_TOOL_DEV_GUIDE.md`)。 ## 二、命名规范 - 格式:`领域.动作`(小写点分),如 `mov.roomList`、`torch.on`、`file.read`、`vault.get`、`market.install`; - 保留黑名单(禁止用作工具名):`sms.read / pay.query / identity.read / a2a.message / contact.read / call.log / location.read`; - 市场外部工具:`ext.<服务器>.<工具>`;不需要版本后缀(版本/分类存在于市场目录)。 ## 三、参数与输出契约 参数一律 **JSON Schema**(类型/必填/描述),模型侧校验:非法参数必须返回 `INVALID_ARGS`,禁止运行时再手写防错。 输出二选一: ```js // 成功 { ok: true, data: { ... } } // 失败(见错误契约) { ok: false, error: { code: "TOOL_TIMEOUT", message: "..." } } ``` MCP 侧投影为 MCP 标准 `content / isError`(McpServer 已实现)。 ## 四、错误契约(统一 16 类) 工具内部不要自造错误文本——用统一分类(`ERROR_CLASSES` 单源表): | code | 典型场景 | 能否重试 | |---|---|---| | RATE_LIMIT / TIMEOUT / TRANSPORT | 上游限流/超时/网络 | 可 | | CONTEXT_WINDOW_EXCEEDED / QUOTA / EMPTY_RESPONSE | 模型/配额类 | 可 | | TOOL_NOT_FOUND / TOOL_TIMEOUT / TOOL_ERROR / UNKNOWN | 工具执行类 | 看场景 | | PERMISSION_DENIED / APPROVAL_DENIED | 权限/审批类 | 不可 | | INVALID_CREDENTIAL / MISSING_CREDENTIAL / INVALID_ARGS / UI_ONLY_TOOL / SERVER | 配置/参数类 | 看场景 | 调度层错误码固定:`SCOPE_DENIED / BUSY / UI_ONLY_TOOL / TOOL_NOT_FOUND / APPROVAL_DENIED`;错误消息走 `formatToolError` 脱敏,禁止把密钥/路径明文返回。 ## 五、并行与作用域(dsh 同款纪律) dsh 明确:**只有 `isConcurrencySafe(args) === true` 才允许并行**,否则 fail-closed 为串行(exclusive 屏障)。并行工具 handler 内部必须线程安全(不得修改父状态;共享状态要么并发安全要么 fail-closed)。 作用域:`main`(默认)/ `subagent`——调用方 scope 与工具 scope 相交才放行。 ## 六、权限分级(工具属性的职责) 审批分级不是工具元数据,而是 `PermissionGuard` 外部名单(底线永远 DENY): - **敏感集**:读取凭据/支付/身份(sms.read 等)→ 永远 DENY; - **写类集**:写文件/发消息/支付 → 需要用户审批(ASK); - **无害级**:查询/状态类 → 自动放行(harmless auto-allow); - **高危**:shell 等 → 强制 ASK(plan 闸 + 更高审批级)。 ## 七、MCP 暴露面(四字段) 工具上架后出现在 `tools/list`:`name` / `description` / `inputSchema` / `level`(读/写)。 > **2026-08-29 更新**:MOV 内置面已收敛 `ToolDefinition` 登记层单源(声明/描述/Schema/输出声明一处声明多面消费),且 `tools/list` 已补传完整 `inputSchema`(此前仅部分工具携带)——外部服务器建议同样返回完整四字段,与内置面体验对齐。 > > **红线**:description 与 inputSchema **不允许占位**(如 "MOV 工具: xxx"、空 Schema 上架即打回)——新工具按本文档即视为合规。 ## 八、与 dsh 官方对比(现状) | 契约项 | dsh 官方 | MOV 现状 | |---|---|---| | 工具四件套(name/description/parameters/execute) | ToolDefinition | ✅ ToolDefinition 已落地(BuiltinMcpTools 全套践行) | | 参数为 JSON Schema + 模型侧校验 | defineTool 编译 + INVALID_ARGS | ✅ 有(toolParamSchemas + 校验) | | 输出声明(output/render/presentationMeta) | ToolOutputDefinition | ⚠️ 登记层单源已承载(BuiltinMcpTools 全套 + browser 14 实测返回键);存量 output 清偿批 3 在途 | | 并行(isConcurrencySafe 精确 true) | 并行池 + exclusive 屏障 | ✅ 同款(ToolRegistry + Semaphore 屏障) | | scope(agent 级掩码) | agent.ctx + restrict | ✅ 声明机制在(subagent 落地进行中) | | 错误分类(结构化 code + message) | HarnessError info{name, code} | ✅ ERROR_CLASSES 16 类单源 | | 权限门(allow/deny/ask,不改写参数) | PreToolDecision + approval service | ✅ PermissionGuard 同款(ASK/DENY/auto) | | 工具描述/空 Schema 占位 | 不允许 | ✅ 大幅收口(2026-08-29):登记层三通道收敛单源 + `tools/list` 补传 inputSchema + provider/scene 真描述;残留收口批 3/4 | 结论:契约主体(声明/错误/并行/权限/命名)已与 dsh 对齐;描述/Schema 已随登记层单源收敛(2026-08-29),收口项 = 存量 output 声明清偿(批 3 在途)。 ## 九、外部工具(市场安装)的特殊说明 从能力市场安装的外部 MCP 服务器,其工具以 `ext.<服务器名>.<工具名>` 热挂进工具面(服务器名取目录 `name`,非字母数字字符转下划线)。与内置工具同走调度层与权限管线: - 目录里声明了保留黑名单(sms.read 等)的条目会被剔除,黑名单工具外部服务器永远不可达; - `level=readonly` 默认放行;`level=write` 当前版本不挂出(只读先行); - token 只存用户本地,任何市场接口响应都不回显。 外部工具的协议端细节见 [api-reference.html](https://mow.kim/dev/api-reference.html),上架路径见 [publish.html](https://mow.kim/dev/publish.html)。 --- # 工具包结构(docs/package.md) # 工具包结构:目录条目字段级说明(镜像版) > 本文件为 [package.html](../package.html) 的 markdown 镜像(页面与 md 同源·防漂移测试守护)。每个字段都对照市场目录真实数据与 app 端解析代码(`McpMarket.kt`)编写。在线目录:`/market/registry.json`(本站域名下,链接可直接打开)。 ## 要点(30 秒) - 市场目录 = registry.json(https 托管);你的工具是 packages 数组中的一项。 - 两类形态:byo(自带服务器)与 declarative(声明式剧本);builtin 为内建保留。 - byo 条目字段级说明(服务地址 / 工具清单 / 类目等)。 - declarative 条目(workflow 剧本)字段与执行入口。 - 含保留名提醒——命名前先对照。 ## 一、目录整体结构 ```json { "version": 1, "updatedAt": "2026-08-25T06:21:03.000Z", "packages": [ { "id": "你的条目" } ] } ``` ## 二、工具包两类形态(kind) | kind | 形态 | 说明 | |---|---|---| | `byo`(缺省) | 自带服务器 | 你在自己的服务器上跑 MCP over HTTP 服务([快速开始](../quick-start.html)的 hello world 就是这种)。条目带 `server` 对象。网页提交只支持这一类。 | | `declarative` | 声明式剧本 | 零服务器:条目带顶层 `declarative` 对象(工作流剧本 + 字段映射),安装后由 app 内 `workflow.run` 工具族执行。剧本为**公开内容**,提交/审核链路会深度检查并拒绝含 token/apiKey/secret/password 类键的剧本。仅通过 app 内对话提交。 | | `builtin` | 官方内置 | MOV 官方内置能力,暂不对创作者开放提交。 | ## 三、byo(自带服务器)条目字段 ```json { "id": "weather-demo", "name": "天气示例", "desc": "查询城市天气(只读工具 × 2)", "cat": "life", "paid": false, "author": "onlywmw", "contact": "you@example.com", "homepage": "https://github.com/onlywmw/xxx", "kind": "byo", "server": { "url": "https://mcp.example.com/weather", "token": "" }, "toolPreview": ["weather.now", "weather.forecast"], "minAppVersion": "0.1.0", "ui": "https://weather-ui.example.com" } ``` | 字段 | 必填 | 约束 | |---|---|---| | `id` | ✅ | 全局唯一,上架后不可改(会成为用户本地服务器配置 id,`market.install` 撞 id 拒绝)。**网页提交口径:小写字母/数字/短横线,且以字母或数字开头**(提交表单按此校验;服务端更宽,但按表单口径准备最稳)。 | | `name` | ✅ | 展示名。安装后还决定工具前缀 `ext..<工具>`,非字母数字字符会转下划线。 | | `desc` | ✅ | **一句话简介 ≤ 20 字**(码点计):市场卡片等宽展示的前提,超限由提交校验直接拒绝。 | | `cat` | ✅ | 分类,自由字符串(市场前端按它分组),缺省归 `other`。 | | `author` | ✅ | 作者名/团队名,公开展示。 | | `contact` | ✅ | 联系方式(邮箱等),审核沟通用。 | | `paid` | — | 付费标记,仅展示;当前市场不做账号/支付。 | | `homepage` | — | 你的主页/仓库链接。 | | `server.url` | ✅ | 你的 MCP 端点,**必须 https**(提交时现场健康检查:空 POST,200/405 即通)。 | | `server.token` | — | **必须留空**。需要鉴权的服务由用户安装后在 app 里自填 token(每个请求带 `Authorization: Bearer`);目录中出现 token 结构直接拒收。 | | `toolPreview` | — | 展示用预览清单;真实工具清单以安装后 `tools/list` 发现为准。 | | `minAppVersion` | — | 最低 app 版本。 | | `ui` | — | 可选:工具自带 UI 页面入口 URL。用户把它钉到侧边栏后,点击直接打开这个页面;缺省则点击行为降级为回填聊天输入框。 | > **保留名提醒**:`tools/list` 返回的工具名命中保留黑名单(`sms.read / pay.query / identity.read / a2a.message / contact.read / call.log / location.read`)不会被挂出——声明了也白搭,命名时就避开。 ## 四、declarative(声明式剧本)条目 ```json { "id": "wx-merchant-onboard", "kind": "declarative", "name": "微信商户入驻助手", "desc": "对话式跑完入驻填表", "cat": "onboarding", "author": "MOV 官方", "declarative": { "workflowId": "demo-onboard", "version": "0.1.0", "workflow": { "id": "demo-onboard", "version": "0.1.0", "steps": [ { "id": "open_page", "action": "open", "url": "..." }, { "id": "fill_user", "action": "fillForm", "fields": [] }, { "id": "pause_captcha", "action": "waitUser", "reason": "..." } ] } }, "toolPreview": ["workflow.run", "workflow.status"] } ``` - 无 `server`、无健康检查/发现;`toolPreview` 展示 `workflow.*` 工具族; - 安装 = 把 `workflow` 落盘用户设备 `filesDir/workflows/.json`,卸载即删除; - 仅通过 app 内对话提交([见上架流程](../publish.html))。 --- # 上架流程(docs/publish.md) # 上架流程:注册 → 开发 → 提交 → 审核 → 上架(镜像版) > 本文件为 [publish.html](../publish.html) 的 markdown 镜像(页面与 md 同源·防漂移测试守护)。当前为**自助提交 + 人工审核制**:服务端四道校验自动过卡,通过后人工审核,通过即并入目录即刻生效。 ## 要点(30 秒) - 两条提交通道(按包形态选其一)。 - 服务端四道卡:提交即校验,不过关不进人工。 - 审核标准明文(什么会被拒)——上架被打回的高频原因前置。 - 上架前自检清单:本地先跑一遍再提交。 ## 一、全流程 | 阶段 | 你要做什么 | 系统做什么 | |---|---|---| | ① 注册 | 无需注册账号。作者信息随提交填写(`author` + `contact`)。 | — | | ② 开发 | 按[快速开始](../quick-start.html)与[开发规范](../spec.html)实现 MCP 服务(自带服务器、公网 https、只读工具)。 | — | | ③ 自检 | 跑一遍[自检清单](#五自检清单上架前自己先跑一遍)(三条 curl + 字段合规)。 | — | | ④ 提交 | 网页提交(市场站)或 app 内对话提交(见下)。 | **四道卡**现场校验,全过进入审核队列。 | | ⑤ 审核 | 等待;状态可查。 | 人工审核:服务可达、工具清单与描述相符、不碰敏感数据边界。 | | ⑥ 上架 | — | 并入 `registry.json` 即刻生效,用户可搜可装。 | | ⑦ 更新 | 改服务不用重新上架(工具清单以安装后发现为准);目录信息变化重新提交即可。 | 重新走 ④⑤。 | ## 二、两条提交通道 | 通道 | 入口 | 支持范围 | |---|---|---| | 网页提交 | 能力市场「提交工具」页(市场站表单) | **byo 自带服务器包**:id / 名称 / 简介≤20字 / 分类 / 作者 / 联系方式 / 服务 URL。id 校验口径:小写字母/数字/短横线。 | | app 内对话提交 | MOV app 对话里说「我要上架工具」,AI 逐项收集、**向你复述确认**后才提交(`market.submit` 需要 `confirm:true`,确认前不会提交) | byo 与 declarative 剧本包。 | 查询进度(app 内):`market.submitStatus` 带 `id` → 返回 `pending` / `rejected`(附理由)/ `approved`。网页提交同样可用市场站的审核状态查询。 > **限流**:公开提交端单 IP 每分钟 3 次、队列上限 100 条,超限返回 429;提交动作进审计(IP 记段)。请自检通过后再提交。 ## 三、服务端四道卡(提交即校验) | # | 校验 | 不过的典型表现 | |---|---|---| | 1 | 字段齐:id / name / desc / cat / author / contact | 提交被拒,缺哪项报哪项 | | 2 | id 不撞车(对既有 registry + 待审队列查重) | 409 冲突,换 id 重提 | | 3 | desc ≤ 20 字(码点计) | 直接拒绝,删到 20 字内 | | 4 | server.url 必须 https 且**现场健康检查**能通(空 POST,5s 超时,200/405 即通) | 「服务不可达」——检查证书/防火墙/公网直连 | 另有两条结构性拒收:`server.token` 非空直接拒绝(token 由用户安装后自填,永不进目录);提交 body 超 8KB 拒收(413)。 ## 四、审核标准(明文,什么会被拒) ### 直接打回(命中即拒) - 工具实际行为超出目录描述——装上后发现的工具与 `toolPreview`/描述明显不符; - 索取与功能无关的用户数据(尤其身份/支付/通讯类); - 工具名命中保留黑名单(`sms.read / pay.query / identity.read / a2a.message / contact.read / call.log / location.read`); - 只读声明下暗藏写操作(`level=readonly` 却有副作用); - description / inputSchema 占位(空 Schema、"MOV 工具: xxx" 式敷衍描述); - 目录条目携带 token / apiKey / secret / password 类字段(结构性拒收)。 ### 降低通过率的常见问题 - description 写给程序员而不是写给 AI——AI 靠它决定何时调用,要写清「什么时候该用、返回什么」; - 服务在国内访问不稳/证书链不全——用户设备直连你的服务器,稳定性直接影响体验; - 分类随意(`cat` 决定市场分组),建议看市场现有分类取近似值。 打回会附理由,修改后可重新提交。 ## 五、自检清单(上架前自己先跑一遍) ```bash # 1. 健康检查:应为 200(405 也接受) curl -s -o /dev/null -w '%{http_code}' -X POST https://你的服务器/mcp \ -H 'Content-Type: application/json' -d '{}' # 2. 工具清单:应返回 result.tools 数组 curl -s -X POST https://你的服务器/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' # 3. 真实调用一次 curl -s -X POST https://你的服务器/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"你的工具","arguments":{}}}' ``` - ☑ 三条 curl 全通; - ☑ id 符合口径、全库未撞车;desc ≤ 20 字; - ☑ description 真实完整、inputSchema 为有效 JSON Schema、全部 `level=readonly`; - ☑ 工具名避开保留黑名单; - ☑ 目录条目无 token/密钥类字段。 全过 = 可以提交。 --- # 接口参考(docs/api-reference.md) # 接口参考(字段级 · 镜像版) > 本文件为 [api-reference.html](../api-reference.html) 的 markdown 镜像(页面与 md 同源·防漂移测试守护)。两部分:**MCP 协议端**(你的服务器要实现的)与**市场动作面**(app 侧 market.* 工具,了解用户侧发生什么)。本文与 `McpClient.kt / McpExtDiscovery.kt / McpMarket.kt` 实现一致。 ## 要点(30 秒) - MCP 协议端(你的服务器要实现):tools/list、tools/call、鉴权、超时。 - 市场动作面(app 内 market.* 工具)。 - 错误码表(MARKET_*)。 - 安全约束(不可协商)。 ## 一、MCP 协议端(你的服务器要实现的) ### 传输 - 仅 HTTP POST;`Content-Type: application/json`;建议同时带 `Accept: application/json, text/event-stream`; - body 为 JSON-RPC 2.0;响应可以是纯 JSON,也可以是 SSE(`data: {...}` 行),两种都收; - 超时预算:连接 5s、读取 30s(`McpClient.kt` 常量);长任务请先应答再异步执行。 ### 健康检查(安装 / 启用时必调) ```text POST <你的URL> body: {} ``` 返回 **200 或 405** 即视为存活;其它状态码 / 超时 = 不可达,安装报 `MARKET_SERVER_UNREACHABLE`(配置保留,可重试)。 ### tools/list(发现) ```text → {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}} ← {"jsonrpc":"2.0","id":1,"result":{"tools":[ { } ]}} ``` | tools[] 字段 | 必填 | 说明 | |---|---|---| | `name` | ✅ | 工具名,建议 `领域.动作`。命中保留黑名单(sms.read 等 7 个)不会被挂出。 | | `description` | ✅ 必须 | 给 AI 的工具说明。禁止占位——空/占位描述上架审核即打回。 | | `inputSchema` | ✅ 必须 | 参数 JSON Schema(类型/必填/描述),给 AI 的。空 Schema 上架审核即打回;MOV 内置面自 2026-08-29 起全量携带。 | | `level` | — | `"readonly"`(缺省)或 `"write"`。**当前版本 write 不挂出**(只读先行)。 | ### tools/call(调用) ```text → {"jsonrpc":"2.0","id":2,"method":"tools/call", "params":{"name":"weather.now","arguments":{"city":"北京"}}} ← {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"..."}],"isError":false}} ``` 建议返回 `result.content`(MCP 标准内容数组);result 内结构化数据会原样转给 AI。失败可返回 `"isError": true` + 文本说明。 ### 鉴权(可选) 需要鉴权的服务:用户安装后在 app 内填 token,之后每个请求带 `Authorization: Bearer `。目录登记 `server.token` 留空,不要把真实 token 写进目录。 ## 二、市场动作面(app 内 market.* 工具) 全部经 app 工具面 `tools/call` 调用(调试:`adb forward tcp:8389 tcp:8389` + Bearer token)。返回统一 `{"ok": Boolean, ...}`,失败带 `{"ok": false, "error": ": "}`。 | 动作 | 入参 | 返回 / 行为 | |---|---|---| | `market.refresh` | `{"force": false}` | 拉取/刷新目录;距上次成功拉取 <1h 直接回缓存(force 绕过)。→ `{"ok":true,"packageCount":12,"fromCache":false,"updatedAt":"..."}` | | `market.list` | `{"cat": "可选筛选"}` | 目录列表(含安装状态 `installed/enabled` 与 `ui` 值)。 | | `market.install` | `{"id": "weather-demo"}` | 健康检查 + 发现 + 热挂 `ext.*` 工具(同步调用,发现可能数秒)。→ `{"ok":true,"serverId":"...","tools":["ext.xxx.a"]}`;不可达 → ok:false + `MARKET_SERVER_UNREACHABLE`(配置已写入,可重试)。 | | `market.uninstall` | `{"id": "..."}` | 仅 `origin="market"` 条目可卸(手动添加的拒绝)→ 删除 + 热摘除该服务器全部工具。→ `{"ok":true,"removed":3}` | | `market.enable` / `market.disable` | `{"id": "..."}` | 启停 = 重新发现热挂 / 热摘除。→ `{"ok":true,"enabled":true,"tools":[]}` | | `market.status` | `{"id": "可选"}` | 健康状态(可达性 / 工具数 / 最近错误 / 检查时间)。 | | `market.submit` | 条目字段 + `confirm:true` | 提交上架(确认前不会提交)。网页提交见[上架流程](../publish.html)。 | | `market.submitStatus` | `{"id": "..."}` | → `pending` / `rejected`(附理由)/ `approved`。 | 命名细节:外部工具前缀 `ext..` 的 tag = 目录 `name`(空则用 id),非 `[A-Za-z0-9_-]` 字符转下划线。 ## 三、错误码表(MARKET_*) | code | 含义 / 触发 | |---|---| | `MARKET_REGISTRY_UNAVAILABLE` | 目录拉取失败且无本地缓存 | | `MARKET_PACKAGE_NOT_FOUND` | install 的 id 不在目录里(declarative 缺剧本同码) | | `MARKET_ID_CONFLICT` | 与既有服务器 id 冲突(含手动添加的) | | `MARKET_SERVER_UNREACHABLE` | 健康检查失败或发现失败(配置保留,enable/重试可恢复) | | `MARKET_NOT_MARKET_ORIGIN` | uninstall 目标是手动添加的服务器 | | `MARKET_NOT_INSTALLED` | enable/disable/status 的 id 未安装 | 工具执行层通用错误(16 类单源表)见[开发规范 · 四](../spec.html)。 ## 四、安全约束(不可协商) - 目录与你的服务必须 https(app 全局禁明文,仅放行回环供调试); - 外部工具继承权限分级:readonly 默认放行;write 需用户显式开启(当前版本 write 不挂出); - 保留黑名单工具外部服务器永远不可达; - token 仅存用户本地,`market.list/status` 等任何响应不含 token。 --- # FAQ / 排错(docs/faq.md) # FAQ / 排错(镜像版) > 本文件为 [faq.html](../faq.html) 的 markdown 镜像(页面与 md 同源·防漂移测试守护)。常见拒审原因与安装失败排查。行为类答案均对照 app 实现编写。 ## 要点(30 秒) - 常见拒审原因(提交与审核口径一致)。 - 安装与调用排错:用户侧现象 → 逐步排查。 - 能力与边界(哪些现在能 / 不能做)。 - 报错 → 自查 → 找谁的路径表。 ## 提交与审核 ### 提交报「desc 超限」 简介按**码点**计 ≤ 20 字(中英文都算 1);这是市场卡片等宽展示的硬约束,删到 20 字内再提交。 ### 提交报 409(id 冲突) id 对既有目录与待审队列全库查重。换一个 id;**上架后 id 不可改**(它是用户本地服务器配置 id)。 ### id 能用大写字母吗 网页提交表单按 `^[a-z0-9][a-z0-9-]*$` 校验(小写字母/数字/短横线,字母或数字开头)。服务端虽更宽,但按表单口径准备最稳,避免后续通道不兼容。 ### 提交报「服务不可达」但我的服务明明开着 提交时服务端会从公网现场 POST 一次你的 URL(空 body,5 秒超时,返回 200/405 即通)。自查:① 是不是只有内网/本机能访问;② https 证书链是否完整(手机端校验更严);③ 是否把健康检查实现成了只接受 GET(要接受 POST);④ 响应是否 5 秒内返回。 ### 带 token 提交直接被拒 设计如此:目录永不存 token。需要鉴权的服务,用户安装后在 app 里自填 token(每个请求带 `Authorization: Bearer`),目录里 `server.token` 留空。 ### 限流 429 单 IP 每分钟 3 次提交、队列上限 100 条。自检通过后再提交,别用提交当调试。 ### 网页提交和 app 内提交有什么区别 网页表单只支持 `byo`(自带服务器)包;`declarative` 剧本包走 app 内对话提交(AI 收集 + 复述确认 + `confirm:true` 才提交)。两条通道的审核标准一致。 ## 安装与调用(用户侧现象 → 排查) ### 安装报 MARKET_SERVER_UNREACHABLE app 对你的 URL 做健康检查(空 POST,200/405 即存活)没通过。配置会保留,修好后用户重试即可。重点查:公网 https 直连(请求从用户设备发出,不是我们转发)、证书链、防火墙、健康检查响应速度。 ### 装上了但工具没出现 / 数量不对 真实工具清单以安装后 `tools/list` 发现为准(目录里的 `toolPreview` 只是展示)。另外两类工具会被静默剔除:**命中保留黑名单的**(sms.read / pay.query / identity.read / a2a.message / contact.read / call.log / location.read)与 **level=write 的**(只读先行,write 不挂出)。 ### 我刚改了服务器的工具清单,用户怎么没变 重新「启用」一次(或重装)会重新发现并热挂载;日常更新服务不需要重新上架。 ### 目录显示的还是旧信息 app 侧目录缓存 1 小时(`market.refresh` 距上次成功拉取 <1h 直接回缓存,force 可绕过)。 ### 工具响应 5 秒 / 30 秒就断 app 侧连接超时 5s、读取超时 30s。长任务不要同步等:先立即返回任务受理结果,提供查询进度的第二个工具。 ### 我的服务想返回 SSE 流 可以:响应为 `data: {...}` 行的 text/event-stream 也会被解析;纯 JSON 同样支持,二选一。 ### 返回错误怎么给 AI `tools/call` 返回 `result.isError: true` + 文本说明;错误文案走统一分类(16 类,见[开发规范 · 四](../spec.html)),不要自造格式。 ## 能力与边界 ### 能不能做写操作 / 支付 / 发消息类工具 当前版本只挂出只读工具(write 有独立授权闸,规划中)。涉及敏感数据的保留名能力(短信/支付/身份/通讯录/通话/位置)永远不对外部工具开放。 ### 能把工具的 UI 挂进 app 吗 可以:目录条目 `ui` 填入口 URL,用户把工具钉到侧边栏后点击直接打开该页面;不填则点击回填聊天输入框。 ### 你们帮我托管服务器吗 当前不托管(工具跑在你自己的服务器上)。托管在路线图远期;近期规划:声明式工具(零代码 HTTP 转发)、write 工具开放。 ## 报错 → 自查 → 找谁 先按表自查;自查过不去的,按「找谁」这列走,别在原地重试。 | 报错 / 现象 | 自查(先做这个) | 找谁 / 走哪条通道 | |---|---|---| | 提交报「xxx required」 | 必填字段是否齐(id / name / desc / cat / author / contact) | 补齐后原通道重提;表单入口问题→app 内对话提交(AI 会复述确认) | | 提交报 409(id 冲突) | id 是否全库唯一(含待审队列) | 换 id 重提;上架后 id 不可改 | | 提交报「服务不可达」 | 公网 https、证书链、健康检查是否接受 POST 且 5s 内返回 | 按[上架流程](../publish.html)自检清单逐条过;通过后再提 | | 带 token 提交被拒 | 目录条目是否携带 token / 密钥类字段 | 删掉;鉴权由用户安装后自填 | | 提交 429 | 是否在用提交当调试 | 自检后再提(单 IP 3 次/分、队列 100 条) | | 工具装上了没出现 / 数量不对 | 是否命中保留黑名单(sms.read 等)或 `level=write` | 改名 / 改只读;仍异常→市场里「启用」一次重新发现 | | 入驻线 401 | 请求是否带 `X-Device-Token` | 重新在商家后台(`/merchant/`)登录获取设备凭证 | | 入驻线 404「no merchant application」 | 是否已 `POST /merchant/apply` | 先提交申请,再看 `GET /merchant/status` | | 状态停在 APPLIED / VERIFYING | `GET /merchant/status` + `GET /merchant/missing` 看缺件 | 按缺件清单补齐材料(缺件=进行中,非打回);核验进度看 `GET /merchant/verify/items` | | 收到驳回(`reject_reason`) | 对照驳回理由逐条改材料 | 修改后重新提交(回到 APPLIED);对理由有疑问→提交时留的 contact 是审核沟通通道 | | 健康分低 / 有「去补」建议 | 面板里的缺件与建议清单(完整度/时延/纠纷/资质时效四维) | 按建议补材料 / 缩响应;健康分对买家可见,补完自动回升 |