feat: prefer remote MCP OAuth installation

This commit is contained in:
2026-08-18 18:24:47 +00:00
parent 55e43e3451
commit 0d5e192e95
20 changed files with 449 additions and 392 deletions
@@ -0,0 +1,275 @@
# 钉钉审批兼容客户端安装任务
仅当当前 Agent 明确不支持远程 Streamable HTTP MCP 或标准 OAuth 时,才使用本兼容安装。支持远程 MCP 的宿主应返回主安装页并直接连接 `{{GATEWAY_URL}}`,不要安装本地 Bridge。
用户把本文件或本文件的链接交给你,即表示授权你为当前用户安装或更新“钉钉审批”兼容客户端、Skill 和名为 `dingtalk-approval` 的 MCP。该授权不包含同意、拒绝、退回、撤回或发起任何审批。
请从“步骤 1”开始逐项执行并验收,不要跳步,不要让用户手工填写企业凭证,也不要根据经验自行改写安装器返回的路径或 MCP 配置。Windows 是完整支持的平台,必须优先使用下面的 PowerShell 步骤。
## 固定发布入口
- 版本:`{{VERSION}}`
- 安装器:`{{INSTALLER_URL}}`
- 安装器 SHA-256:`{{INSTALLER_SHA256}}`
- 发布公钥:`{{PUBLIC_KEY_URL}}`
- 公钥 SHA-256:`{{PUBLIC_KEY_SHA256}}`
- 公钥编号:`{{RELEASE_KEY_ID}}`
- 签名清单:`{{MANIFEST_URL}}`
- 清单签名:`{{MANIFEST_SIGNATURE_URL}}`
- 备用发布源:
{{DOWNLOAD_MIRROR_BASE_URLS_MARKDOWN}}
- 统一网关:`{{GATEWAY_URL}}`
- Windows 安装引导:`{{BOOTSTRAP_WINDOWS_URL}}`
- Windows 安装引导备用地址:
{{BOOTSTRAP_WINDOWS_MIRROR_URLS_MARKDOWN}}
- Windows 安装引导 SHA-256:`{{BOOTSTRAP_WINDOWS_SHA256}}`
- Linux/macOS 安装引导:`{{BOOTSTRAP_POSIX_URL}}`
- Linux/macOS 安装引导备用地址:
{{BOOTSTRAP_POSIX_MIRROR_URLS_MARKDOWN}}
- Linux/macOS 安装引导 SHA-256:`{{BOOTSTRAP_POSIX_SHA256}}`
- 私有 Node.js:`{{NODE_VERSION}}`(最低兼容版本 `{{NODE_MINIMUM_VERSION}}`)
首次安装通过上述 HTTPS 地址和固定 SHA-256 建立信任;安装器随后必须使用固定公钥验证 Ed25519 清单签名和全部发布物哈希。URL、TLS、签名或哈希任一不匹配都必须停止。不得索取 AppKey、AppSecret、AuthoritySecret、CorpId、userId、员工号、Cookie 或 Token。
## 完成标准
只有以下条件全部成立,才能告诉用户“安装完成,可以使用”:
1. 对应平台安装引导的 SHA-256 正确,并已选用兼容的系统 Node 或经过官方哈希校验的私有 Node;
2. 安装器 JSON 返回 `ok: true`;
3. 已按当前 Agent 的官方方式安装返回的 `skillPath`;
4. 已原样使用返回的 `mcpConfig` 注册并信任本地 stdio MCP;
5. `approval_login` 确认已登录;
6. `approval_list_todos` 的双来源验证中,钉钉 OA 与薪福通都为 `ok`;
7. 未执行任何审批写操作。
## 步骤 1:确认操作系统与架构
支持以下当前用户环境:
- Windows 10/11:x64 或 ARM64;
- Linux:x64 或 ARM64;
- macOS:Intel x64 或 Apple Silicon ARM64。
系统无需预装 Node.js。安装引导会优先复用版本不低于 `{{NODE_MINIMUM_VERSION}}` 且 npm 可用的系统 Node;否则自动准备只属于本插件的 Node `{{NODE_VERSION}}`。不要让用户安装全局 Node,不要使用管理员权限、`sudo`、全局 npm 安装、DWS 或浏览器自动化。
## 步骤 2:下载、校验并执行平台安装引导
不要在浏览器中复制脚本内容,不要对 URL 解码、重新编码或手工拼接。必须先校验安装引导 SHA-256,再执行它。
为执行平台安装引导的 Agent 工具调用设置合理超时。引导的单个下载会在连接失败、长时间无数据或 90 秒总时限到达时明确失败并换用下一固定来源,不会静默等待 300 秒;安装器会把 `[dingtalk-approval]` 开头的阶段进度写入 stderr,把唯一的最终结果 JSON 写入 stdout。进度文本不是失败,也不能混入 JSON 解析。进程仍在运行时不得并发启动第二次安装。
安装器和后续更新优先使用系统 `curl` 下载签名清单与发布物;`curl` 会直接使用 `HTTPS_PROXY`、`HTTP_PROXY`、`ALL_PROXY` 和 `NO_PROXY`,Node `fetch` 仅在系统没有 `curl` 时兜底。登录和 Bridge 仍会按 Node 能力启用环境代理。不要设置全局 `NODE_OPTIONS=--use-env-proxy`,也不要手工预装 `node_modules`;这会让 npm 与 Node 的代理机制叠加,并破坏安装器的原子安装与故障恢复。
### Windows(PowerShell)
无论当前 Agent 默认使用 PowerShell、CMD 还是 Git Bash,Windows 都必须调用普通 PowerShell 执行下面的完整流程。不得从 Git Bash 直接运行内部的 `node dingtalk-approval-installer.mjs ...` 命令;内部安装器不是用户安装入口。
在普通 PowerShell 中逐行运行:
```powershell
$dingBootstrapDir = Join-Path ([System.IO.Path]::GetTempPath()) ("dingmcp-bootstrap-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $dingBootstrapDir -Force | Out-Null
$dingBootstrap = Join-Path $dingBootstrapDir "dingtalk-approval-bootstrap.ps1"
$dingBootstrapUrls = @({{BOOTSTRAP_WINDOWS_URLS_POWERSHELL}})
$dingBootstrapDownloaded = $false
$dingCurl = Get-Command curl.exe -ErrorAction SilentlyContinue
foreach ($dingBootstrapUrl in $dingBootstrapUrls) {
try {
if ($dingCurl) {
& $dingCurl.Source --fail --location --silent --show-error --connect-timeout 10 --max-time 90 --speed-time 20 --speed-limit 1024 --output $dingBootstrap $dingBootstrapUrl
if ($LASTEXITCODE -ne 0) { throw "curl.exe 退出码 $LASTEXITCODE" }
} else {
Invoke-WebRequest -UseBasicParsing -Uri $dingBootstrapUrl -OutFile $dingBootstrap -TimeoutSec 90
}
} catch {
continue
}
$dingBootstrapHash = (Get-FileHash -Algorithm SHA256 $dingBootstrap).Hash.ToLowerInvariant()
if ($dingBootstrapHash -ne "{{BOOTSTRAP_WINDOWS_SHA256}}") { throw "Windows 安装引导 SHA-256 不匹配,停止安装:$dingBootstrapUrl" }
$dingBootstrapDownloaded = $true
break
}
if (-not $dingBootstrapDownloaded) { throw "所有 Windows 安装引导下载源均不可用" }
$dingInstallJson = & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $dingBootstrap
if ($LASTEXITCODE -ne 0) { throw "钉钉审批安装引导执行失败:$dingInstallJson" }
$dingInstall = $dingInstallJson | ConvertFrom-Json
if ($dingInstall.ok -ne $true) { throw "钉钉审批安装失败:$($dingInstall.error)" }
$dingInstall | ConvertTo-Json -Depth 20
```
完成后可删除 `$dingBootstrapDir`。任一下载或哈希检查失败都不能忽略。
### Linux / macOS(POSIX shell)
```bash
ding_bootstrap_dir="$(mktemp -d)"
ding_bootstrap="$ding_bootstrap_dir/dingtalk-approval-bootstrap.sh"
ding_bootstrap_downloaded=0
for ding_bootstrap_url in {{BOOTSTRAP_POSIX_URLS_POSIX}}; do
rm -f "$ding_bootstrap"
if command -v curl >/dev/null 2>&1; then
curl --fail --location --connect-timeout 10 --max-time 90 --speed-time 20 --speed-limit 1024 --output "$ding_bootstrap" "$ding_bootstrap_url" || continue
elif command -v wget >/dev/null 2>&1; then
wget --https-only --timeout=90 --output-document="$ding_bootstrap" "$ding_bootstrap_url" || continue
else
echo '缺少 curl 或 wget,无法下载安装引导' >&2
exit 1
fi
if command -v sha256sum >/dev/null 2>&1; then
ding_bootstrap_hash="$(sha256sum "$ding_bootstrap" | awk '{print $1}')"
else
ding_bootstrap_hash="$(shasum -a 256 "$ding_bootstrap" | awk '{print $1}')"
fi
if [ "$ding_bootstrap_hash" != '{{BOOTSTRAP_POSIX_SHA256}}' ]; then
echo "Linux/macOS 安装引导 SHA-256 不匹配,停止安装:$ding_bootstrap_url" >&2
exit 1
fi
ding_bootstrap_downloaded=1
break
done
if [ "$ding_bootstrap_downloaded" -ne 1 ]; then
echo '所有 Linux/macOS 安装引导下载源均不可用' >&2
exit 1
fi
ding_install_json="$(sh "$ding_bootstrap")"
printf '%s\n' "$ding_install_json"
```
完成后可删除 `$ding_bootstrap_dir`。如果 `sha256sum` 与 `shasum` 都不存在,必须停止,不能跳过校验。
## 步骤 3:验收安装结果
步骤 2 的平台引导是唯一通用安装入口。它会:
1. 检查系统 Node;缺失或版本不兼容时,根据当前操作系统和 CPU 架构选择私有 Node;
2. 按“阿里云精确版本 → npmmirror 精确版本 → Node 官方精确版本”的顺序下载 Node;项目安装引导和发布物则优先使用国内 Gitea 静态发布源,再回退到自建 HTTPS 下载源和其他已配置镜像。网络或 HTTP 下载失败才切换来源,任一已下载文件的签名或 SHA-256 不匹配都会立即终止;
3. 把私有 Node 放在插件自己的当前用户数据目录,不修改系统 PATH,不要求管理员权限;
4. 下载并校验首次安装器和发布公钥,再由安装器验证 Ed25519 清单签名与全部客户端发布物;下载源不是信任根,备用源不能绕过签名或哈希;
5. 返回一份 JSON 安装结果。
安装器会在稳定的当前用户数据目录安装客户端与 Skill,不会全局安装 npm 包,也不会修改 Agent 私有配置。保留完整 JSON 结果,后续只使用其中的真实字段,不要猜路径。
安装结果至少必须包含:
- `ok: true`、`status: "installed"` 和 `version`;
- `cliPath`、`bridgePath`、`skillPath`;
- `loginCommand`;
- `mcpConfig.mcpServers.dingtalk-approval`;
- `postInstall`。
- `nodeRuntime`,其中 `managed: true` 表示正在使用插件私有 Node。
Windows 的 `bridgePath` 和 `mcpConfig...command` 正常应以 `dingtalk-approval-bridge.cmd` 结尾。路径中可能包含空格;保留完整字符串,不要拆分,不要去掉 `.cmd`,不要改为 `node`、`npx` 或远程 URL。
## 步骤 4:安装 Skill 并注册 MCP
1. 按当前 Agent 的官方方式安装 `skillPath` 指向的整个目录,而不是只复制 `SKILL.md`。确认目录中同时存在 `SKILL.md` 和 `references/create-approval.md`。
2. 使用 `mcpConfig` 只新增或替换名称精确为 `dingtalk-approval` 的连接器。安装器本身不会猜测或修改 Agent 的私有配置。
3. 如果 Agent 接受完整 MCP JSON,原样使用整个 `mcpConfig`;如果界面只接受单个服务器配置,原样使用 `mcpConfig.mcpServers.dingtalk-approval`。
4. 该连接器必须是本地 `stdio`,`command` 必须是返回的本地 `bridgePath`,`args` 必须为空数组。Windows 中必须保留 JSON 已正确转义的反斜杠。
5. 不要把 `{{GATEWAY_URL}}` 或 `https://approval.justconn.top/mcp` 作为 MCP URL、command 或参数;Bridge 会从已签名安装状态取得网关地址。
6. 不要添加 `--url`、企业凭证、用户 ID、额外环境变量或 shell 包装器。
正确配置的形态由安装器返回,语义如下;示例路径不能代替真实返回值:
```json
{
"mcpServers": {
"dingtalk-approval": {
"type": "stdio",
"command": "<安装器返回的本地 bridgePath;Windows 为 .cmd>",
"args": []
}
}
}
```
## 步骤 5:处理首次登录状态
安装器已经主动尝试准备登录。读取返回的 `postInstall` 及其 `status`,不得等到用户第一次查询审批:
- `authorization_required`:在完成本地 Skill/MCP 配置的同一轮回复中,把 `postInstall.authChallenge.directUrl` 作为完整、可点击的 URL 原样发给用户;不得只说“已生成”或“已推送”。同时说明链接有有效期,需由用户本人核对授权页面并确认。
- `authenticated`:已有登录可复用,不需要再次让用户授权,继续步骤 6 和步骤 7。
- `unavailable`:客户端安装仍然有效。先完成步骤 4;连接器加载后调用 `approval_login` 重试。不要删除会话、不要重装。
保存 `loginCommand.command` 和 `loginCommand.args`。如果首次链接过期且 MCP 尚未加载,按该对象原样执行命令;Windows PowerShell 的通用执行方式是:
```powershell
$dingLoginJson = & $dingInstall.loginCommand.command @($dingInstall.loginCommand.args)
$dingLogin = $dingLoginJson | ConvertFrom-Json
$dingLogin | ConvertTo-Json -Depth 20
```
把新结果中的 `authChallenge.directUrl` 原样展示。该命令只检查登录或生成新链接,不下载发布物、不修改 Agent 配置。MCP 已加载时则直接重新调用 `approval_login`。登录链接过期时绝不能重新安装。
## 步骤 6:信任并加载连接器
1. 如果当前 Agent 要求信任本地连接器,按其官方界面或命令确认名称为 `dingtalk-approval` 的连接器。
2. 如果不能热加载,完整退出并重新启动 Agent,或新建一个确定会重新读取 MCP 配置的会话;只刷新聊天内容通常不够。
3. 重启后确认能发现 `approval_login`、`approval_list_todos` 等钉钉审批工具。当前版本应发现 13 个工具。
4. 若连接器进程退出、显示 302 或无法启动,先执行步骤 9 的故障排查,不要把连接器改成远程 HTTP MCP。
## 步骤 7:确认登录并验证两个审批来源
用户完成网页授权后:
1. 调用 `approval_login`。工具不会通过其他消息通道自动推送链接;如果响应仍给出 URL,必须把 `content.text` 中的完整登录 URL 原样写入当前回复。
2. 登录成功后,按 `postInstall.verifyTool` 调用一次只读的 `approval_list_todos`,即 `source="all"`、空搜索、`limit=1`。
3. 检查结果中的 `sourceStatus`:`dingtalk.status` 和 `xft.status` 都必须是 `ok`。`disabled`、`auth_required`、`unavailable` 或缺少任一来源都不算安装验证完成。
4. 验证期间只读取最多一条待办,不调用任何写工具。
5. 成功响应包含 `onboarding.firstRun=true` 时,把其中的简短示例和安全提示向用户展示一次,不要自动执行示例。
## 步骤 8:向用户报告结果
最终只需清楚报告:已安装的版本、Skill 与 MCP 已加载、登录是否成功、钉钉 OA 与薪福通是否都通过只读验证。可以给出一次性使用示例。不要向用户倾倒安装 JSON、内部路径、Token、审批引用或诊断细节,除非用户正在排障。
安装阶段不得处理审批,也不得在登录和双来源验证完成前声称“已可使用”。
## 步骤 9:常见故障排查
按顺序排查,不要跳到重装:
1. **显示 302 或启动后立即退出**:检查连接器是否被错误配置成 `{{GATEWAY_URL}}` 或其他 HTTP URL。正确配置必须是 `type: "stdio"`,`command` 为本机 `dingtalk-approval-bridge.cmd`,`args: []`。重新复制安装结果中的 `mcpConfig`。
2. **路径不存在或 Node 升级后失效**:不要引用 Node/NVM 的版本目录。执行安装结果中的 `cliPath`,或运行 `dingtalk-approval print-config --format mcp-json` 重新取得稳定配置。
3. **Bridge 仍无法启动**:执行 `<cliPath> doctor --json`。只有所有 `checks` 都为 `ok: true` 才继续;把失败检查和错误原文报告给维护者,不要提供 Token 或会话文件。
4. **提示找不到 npm-cli.js 或 Node 运行时损坏**:重新执行步骤 2 中已经过哈希校验的平台引导,让它修复插件私有 Node;不要安装全局 npm。
5. **路径包含空格**:不要手写命令行字符串。把安装器返回的整个 `mcpConfig` 作为 JSON 对象交给 Agent;在 PowerShell 中执行 CLI 时用调用运算符 `&` 和独立参数。
6. **连接器一直未出现**:确认已信任本地连接器并完整重启 Agent。不要通过反复重装刷新连接器。
7. **授权链接过期**:MCP 已加载就再次调用 `approval_login`;否则执行保存的 `loginCommand`。不要重装。
8. **Linux/macOS 缺少下载或校验命令**:必须先具备 `curl` 或 `wget`,以及 `sha256sum` 或 `shasum`;不能绕过 SHA-256 校验。系统 `tar` 必须支持 gzip 归档。
9. **安装长时间无最终 JSON或被宿主终止**:查看 stderr 中最后一条 `[dingtalk-approval]` 阶段信息。单个下载超过 90 秒或连续 20 秒低速应自动失败,而不是继续等待;不要改用内部安装器,也不要并发重试。原进程确认已经退出后,可重新执行同一平台引导;它会识别已经死亡的安装进程、清理残留 staging 并安全续装。若明确提示另一个安装仍在运行,应等待该进程结束。
## 更新
更新不需要重新下载首次安装器。使用上次安装结果中的 `cliPath`:
Windows PowerShell:
```powershell
$dingUpdateJson = & $dingInstall.cliPath update --json
$dingUpdate = $dingUpdateJson | ConvertFrom-Json
$dingUpdate | ConvertTo-Json -Depth 20
```
Linux / macOS:
```text
<cliPath> update --json
```
更新会复用已固定的签名清单地址和公钥并保留 OAuth 会话。更新成功后,重新使用返回的 `mcpConfig` 核对同名连接器,并重新连接 MCP;必要时完整重启 Agent。然后重复步骤 7 的登录确认和双来源只读验证。
## 卸载
1. 按当前 Agent 的官方方式删除名称精确为 `dingtalk-approval` 的 MCP/连接器条目。
2. 执行安装结果中的 `cliPath uninstall --yes --json`;Windows PowerShell 可运行:
```powershell
& $dingInstall.cliPath uninstall --yes --json
```
3. 确认返回 `uninstalled` 或 `cleanup_scheduled`。后者表示当前 CLI 退出后完成自清理。
卸载默认清除客户端运行时、Skill、安装记录和 OAuth 会话。不要手工递归删除未知目录。
授权页面会展示请求客户端、回调域名、权限范围或设备授权码。必须由用户本人核对并确认,不得替用户自动提交 consent。网关短暂不可用时保留现有登录状态并稍后重试,不要删除会话文件或主动要求用户重新授权。
+23 -255
View File
@@ -1,267 +1,35 @@
# 钉钉审批客户端安装任务
# 钉钉审批 MCP 安装任务
用户把本文件或本文件的链接交给你,即表示授权你为当前用户安装或更新“钉钉审批”客户端、Skill 和名为 `dingtalk-approval` 的 MCP。该授权不包含同意、拒绝、退回、撤回或发起任何审批。
用户把本文件或链接交给你,即授权你为当前用户连接或更新名为 `dingtalk-approval` 的 MCP。该授权不包含任何审批写操作。
请从“步骤 1”开始逐项执行并验收,不要跳步,不要让用户手工填写企业凭证,也不要根据经验自行改写安装器返回的路径或 MCP 配置。Windows 是完整支持的平台,必须优先使用下面的 PowerShell 步骤。
版本:`{{VERSION}}`
## 固定发布入口
## 默认安装
- 版本:`{{VERSION}}`
- 安装器:`{{INSTALLER_URL}}`
- 安装器 SHA-256:`{{INSTALLER_SHA256}}`
- 发布公钥:`{{PUBLIC_KEY_URL}}`
- 公钥 SHA-256:`{{PUBLIC_KEY_SHA256}}`
- 公钥编号:`{{RELEASE_KEY_ID}}`
- 签名清单:`{{MANIFEST_URL}}`
- 清单签名:`{{MANIFEST_SIGNATURE_URL}}`
- 备用发布源:
{{DOWNLOAD_MIRROR_BASE_URLS_MARKDOWN}}
- 统一网关:`{{GATEWAY_URL}}`
- Windows 安装引导:`{{BOOTSTRAP_WINDOWS_URL}}`
- Windows 安装引导备用地址:
{{BOOTSTRAP_WINDOWS_MIRROR_URLS_MARKDOWN}}
- Windows 安装引导 SHA-256:`{{BOOTSTRAP_WINDOWS_SHA256}}`
- Linux/macOS 安装引导:`{{BOOTSTRAP_POSIX_URL}}`
- Linux/macOS 安装引导备用地址:
{{BOOTSTRAP_POSIX_MIRROR_URLS_MARKDOWN}}
- Linux/macOS 安装引导 SHA-256:`{{BOOTSTRAP_POSIX_SHA256}}`
- 私有 Node.js:`{{NODE_VERSION}}`(最低兼容版本 `{{NODE_MINIMUM_VERSION}}`)
1. 检查当前 Agent 是否支持远程 **Streamable HTTP MCP** 和 MCP 标准 **OAuth 2.0**。按能力判断,不要按 Agent 品牌判断。
2. 若支持,按宿主官方方式新增或更新连接器:
- 名称:`dingtalk-approval`
- URL:`{{GATEWAY_URL}}`
- 传输:Streamable HTTP
- 认证:OAuth(允许宿主使用服务端发现元数据和动态客户端注册)
3. 不要安装 Node.js、npm、本地 Bridge、DWS 或浏览器自动化;不要索取 AppKey、AppSecret、AuthoritySecret、CorpId、userId、员工号、Cookie、Token 或其他企业凭证。
4. 让宿主发起标准 OAuth 登录,把宿主返回的授权页面交给用户本人确认。不得代替用户点击授权,也不得声称链接已通过其他消息通道发送。
5. 连接成功后调用一次只读 `approval_list_todos`,参数为 `source="all"`、空搜索、`limit=1`。只有响应中的钉钉 OA 与薪福通来源都为 `ok`,才报告安装完成。不得在安装验证中执行任何审批写操作。
6. 若宿主需要重启或重新加载才能识别新连接器,按其官方机制操作;不要因为等待加载而改装本地 Bridge。
首次安装通过上述 HTTPS 地址和固定 SHA-256 建立信任;安装器随后必须使用固定公钥验证 Ed25519 清单签名和全部发布物哈希。URL、TLS、签名或哈希任一不匹配都必须停止。不得索取 AppKey、AppSecret、AuthoritySecret、CorpId、userId、员工号、Cookie 或 Token。
服务端会通过 MCP `instructions` 和工具描述提供审批工作流。登录完成后的首次响应可能包含简短示例;向用户展示一次即可,不要自动执行示例。
## 完成标准
## 兼容安装
只有以下条件全部成立,才能告诉用户“安装完成,可以使用”:
只有在宿主明确不支持远程 Streamable HTTP MCP,或不能完成 MCP 标准 OAuth 时,才使用本地 STDIO Bridge:
1. 对应平台安装引导的 SHA-256 正确,并已选用兼容的系统 Node 或经过官方哈希校验的私有 Node;
2. 安装器 JSON 返回 `ok: true`;
3. 已按当前 Agent 的官方方式安装返回的 `skillPath`;
4. 已原样使用返回的 `mcpConfig` 注册并信任本地 stdio MCP;
5. `approval_login` 确认已登录;
6. `approval_list_todos` 的双来源验证中,钉钉 OA 与薪福通都为 `ok`;
7. 未执行任何审批写操作。
`{{COMPAT_INSTALL_URL}}`
## 步骤 1:确认操作系统与架构
备用地址:
{{COMPAT_INSTALL_MIRROR_URLS_MARKDOWN}}
支持以下当前用户环境:
不要先尝试兼容安装来“提高成功率”。远程连接失败时应先报告宿主给出的具体错误;只有确认属于能力缺失,才进入兼容路径。
- Windows 10/11:x64 或 ARM64;
- Linux:x64 或 ARM64;
- macOS:Intel x64 或 Apple Silicon ARM64。
## 完成后
系统无需预装 Node.js。安装引导会优先复用版本不低于 `{{NODE_MINIMUM_VERSION}}` 且 npm 可用的系统 Node;否则自动准备只属于本插件的 Node `{{NODE_VERSION}}`。不要让用户安装全局 Node,不要使用管理员权限、`sudo`、全局 npm 安装、DWS 或浏览器自动化。
## 步骤 2:下载、校验并执行平台安装引导
不要在浏览器中复制脚本内容,不要对 URL 解码、重新编码或手工拼接。必须先校验安装引导 SHA-256,再执行它。
为执行平台安装引导的 Agent 工具调用设置至少 10 分钟超时。引导和安装器会把 `[dingtalk-approval]` 开头的阶段进度写入 stderr,把唯一的最终结果 JSON 写入 stdout;进度文本不是失败,也不能混入 JSON 解析。进程仍在运行时不得并发启动第二次安装。
安装器和后续更新优先使用系统 `curl` 下载签名清单与发布物;`curl` 会直接使用 `HTTPS_PROXY`、`HTTP_PROXY`、`ALL_PROXY` 和 `NO_PROXY`,Node `fetch` 仅在系统没有 `curl` 时兜底。登录和 Bridge 仍会按 Node 能力启用环境代理。不要设置全局 `NODE_OPTIONS=--use-env-proxy`,也不要手工预装 `node_modules`;这会让 npm 与 Node 的代理机制叠加,并破坏安装器的原子安装与故障恢复。
### Windows(PowerShell)
无论当前 Agent 默认使用 PowerShell、CMD 还是 Git Bash,Windows 都必须调用普通 PowerShell 执行下面的完整流程。不得从 Git Bash 直接运行内部的 `node dingtalk-approval-installer.mjs ...` 命令;内部安装器不是用户安装入口。
在普通 PowerShell 中逐行运行:
```powershell
$dingBootstrapDir = Join-Path ([System.IO.Path]::GetTempPath()) ("dingmcp-bootstrap-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $dingBootstrapDir -Force | Out-Null
$dingBootstrap = Join-Path $dingBootstrapDir "dingtalk-approval-bootstrap.ps1"
$dingBootstrapUrls = @({{BOOTSTRAP_WINDOWS_URLS_POWERSHELL}})
$dingBootstrapDownloaded = $false
foreach ($dingBootstrapUrl in $dingBootstrapUrls) {
try {
Invoke-WebRequest -UseBasicParsing -Uri $dingBootstrapUrl -OutFile $dingBootstrap -TimeoutSec 300
} catch {
continue
}
$dingBootstrapHash = (Get-FileHash -Algorithm SHA256 $dingBootstrap).Hash.ToLowerInvariant()
if ($dingBootstrapHash -ne "{{BOOTSTRAP_WINDOWS_SHA256}}") { throw "Windows 安装引导 SHA-256 不匹配,停止安装:$dingBootstrapUrl" }
$dingBootstrapDownloaded = $true
break
}
if (-not $dingBootstrapDownloaded) { throw "所有 Windows 安装引导下载源均不可用" }
$dingInstallJson = & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $dingBootstrap
if ($LASTEXITCODE -ne 0) { throw "钉钉审批安装引导执行失败:$dingInstallJson" }
$dingInstall = $dingInstallJson | ConvertFrom-Json
if ($dingInstall.ok -ne $true) { throw "钉钉审批安装失败:$($dingInstall.error)" }
$dingInstall | ConvertTo-Json -Depth 20
```
完成后可删除 `$dingBootstrapDir`。任一下载或哈希检查失败都不能忽略。
### Linux / macOS(POSIX shell)
```bash
ding_bootstrap_dir="$(mktemp -d)"
ding_bootstrap="$ding_bootstrap_dir/dingtalk-approval-bootstrap.sh"
ding_bootstrap_downloaded=0
for ding_bootstrap_url in {{BOOTSTRAP_POSIX_URLS_POSIX}}; do
rm -f "$ding_bootstrap"
if command -v curl >/dev/null 2>&1; then
curl --fail --location --connect-timeout 10 --max-time 300 --output "$ding_bootstrap" "$ding_bootstrap_url" || continue
elif command -v wget >/dev/null 2>&1; then
wget --https-only --timeout=300 --output-document="$ding_bootstrap" "$ding_bootstrap_url" || continue
else
echo '缺少 curl 或 wget,无法下载安装引导' >&2
exit 1
fi
if command -v sha256sum >/dev/null 2>&1; then
ding_bootstrap_hash="$(sha256sum "$ding_bootstrap" | awk '{print $1}')"
else
ding_bootstrap_hash="$(shasum -a 256 "$ding_bootstrap" | awk '{print $1}')"
fi
if [ "$ding_bootstrap_hash" != '{{BOOTSTRAP_POSIX_SHA256}}' ]; then
echo "Linux/macOS 安装引导 SHA-256 不匹配,停止安装:$ding_bootstrap_url" >&2
exit 1
fi
ding_bootstrap_downloaded=1
break
done
if [ "$ding_bootstrap_downloaded" -ne 1 ]; then
echo '所有 Linux/macOS 安装引导下载源均不可用' >&2
exit 1
fi
ding_install_json="$(sh "$ding_bootstrap")"
printf '%s\n' "$ding_install_json"
```
完成后可删除 `$ding_bootstrap_dir`。如果 `sha256sum` 与 `shasum` 都不存在,必须停止,不能跳过校验。
## 步骤 3:验收安装结果
步骤 2 的平台引导是唯一通用安装入口。它会:
1. 检查系统 Node;缺失或版本不兼容时,根据当前操作系统和 CPU 架构选择私有 Node;
2. 按“阿里云精确版本 → npmmirror 精确版本 → Node 官方精确版本”的顺序下载 Node;项目安装引导和发布物则优先使用国内 Gitea 静态发布源,再回退到自建 HTTPS 下载源和其他已配置镜像。网络或 HTTP 下载失败才切换来源,任一已下载文件的签名或 SHA-256 不匹配都会立即终止;
3. 把私有 Node 放在插件自己的当前用户数据目录,不修改系统 PATH,不要求管理员权限;
4. 下载并校验首次安装器和发布公钥,再由安装器验证 Ed25519 清单签名与全部客户端发布物;下载源不是信任根,备用源不能绕过签名或哈希;
5. 返回一份 JSON 安装结果。
安装器会在稳定的当前用户数据目录安装客户端与 Skill,不会全局安装 npm 包,也不会修改 Agent 私有配置。保留完整 JSON 结果,后续只使用其中的真实字段,不要猜路径。
安装结果至少必须包含:
- `ok: true`、`status: "installed"` 和 `version`;
- `cliPath`、`bridgePath`、`skillPath`;
- `loginCommand`;
- `mcpConfig.mcpServers.dingtalk-approval`;
- `postInstall`。
- `nodeRuntime`,其中 `managed: true` 表示正在使用插件私有 Node。
Windows 的 `bridgePath` 和 `mcpConfig...command` 正常应以 `dingtalk-approval-bridge.cmd` 结尾。路径中可能包含空格;保留完整字符串,不要拆分,不要去掉 `.cmd`,不要改为 `node`、`npx` 或远程 URL。
## 步骤 4:安装 Skill 并注册 MCP
1. 按当前 Agent 的官方方式安装 `skillPath` 指向的整个目录,而不是只复制 `SKILL.md`。确认目录中同时存在 `SKILL.md` 和 `references/create-approval.md`。
2. 使用 `mcpConfig` 只新增或替换名称精确为 `dingtalk-approval` 的连接器。安装器本身不会猜测或修改 Agent 的私有配置。
3. 如果 Agent 接受完整 MCP JSON,原样使用整个 `mcpConfig`;如果界面只接受单个服务器配置,原样使用 `mcpConfig.mcpServers.dingtalk-approval`。
4. 该连接器必须是本地 `stdio`,`command` 必须是返回的本地 `bridgePath`,`args` 必须为空数组。Windows 中必须保留 JSON 已正确转义的反斜杠。
5. 不要把 `{{GATEWAY_URL}}` 或 `https://approval.justconn.top/mcp` 作为 MCP URL、command 或参数;Bridge 会从已签名安装状态取得网关地址。
6. 不要添加 `--url`、企业凭证、用户 ID、额外环境变量或 shell 包装器。
正确配置的形态由安装器返回,语义如下;示例路径不能代替真实返回值:
```json
{
"mcpServers": {
"dingtalk-approval": {
"type": "stdio",
"command": "<安装器返回的本地 bridgePath;Windows 为 .cmd>",
"args": []
}
}
}
```
## 步骤 5:处理首次登录状态
安装器已经主动尝试准备登录。读取返回的 `postInstall` 及其 `status`,不得等到用户第一次查询审批:
- `authorization_required`:在完成本地 Skill/MCP 配置的同一轮回复中,把 `postInstall.authChallenge.directUrl` 作为完整、可点击的 URL 原样发给用户;不得只说“已生成”或“已推送”。同时说明链接有有效期,需由用户本人核对授权页面并确认。
- `authenticated`:已有登录可复用,不需要再次让用户授权,继续步骤 6 和步骤 7。
- `unavailable`:客户端安装仍然有效。先完成步骤 4;连接器加载后调用 `approval_login` 重试。不要删除会话、不要重装。
保存 `loginCommand.command` 和 `loginCommand.args`。如果首次链接过期且 MCP 尚未加载,按该对象原样执行命令;Windows PowerShell 的通用执行方式是:
```powershell
$dingLoginJson = & $dingInstall.loginCommand.command @($dingInstall.loginCommand.args)
$dingLogin = $dingLoginJson | ConvertFrom-Json
$dingLogin | ConvertTo-Json -Depth 20
```
把新结果中的 `authChallenge.directUrl` 原样展示。该命令只检查登录或生成新链接,不下载发布物、不修改 Agent 配置。MCP 已加载时则直接重新调用 `approval_login`。登录链接过期时绝不能重新安装。
## 步骤 6:信任并加载连接器
1. 如果当前 Agent 要求信任本地连接器,按其官方界面或命令确认名称为 `dingtalk-approval` 的连接器。
2. 如果不能热加载,完整退出并重新启动 Agent,或新建一个确定会重新读取 MCP 配置的会话;只刷新聊天内容通常不够。
3. 重启后确认能发现 `approval_login`、`approval_list_todos` 等钉钉审批工具。当前版本应发现 13 个工具。
4. 若连接器进程退出、显示 302 或无法启动,先执行步骤 9 的故障排查,不要把连接器改成远程 HTTP MCP。
## 步骤 7:确认登录并验证两个审批来源
用户完成网页授权后:
1. 调用 `approval_login`。工具不会通过其他消息通道自动推送链接;如果响应仍给出 URL,必须把 `content.text` 中的完整登录 URL 原样写入当前回复。
2. 登录成功后,按 `postInstall.verifyTool` 调用一次只读的 `approval_list_todos`,即 `source="all"`、空搜索、`limit=1`。
3. 检查结果中的 `sourceStatus`:`dingtalk.status` 和 `xft.status` 都必须是 `ok`。`disabled`、`auth_required`、`unavailable` 或缺少任一来源都不算安装验证完成。
4. 验证期间只读取最多一条待办,不调用任何写工具。
5. 成功响应包含 `onboarding.firstRun=true` 时,把其中的简短示例和安全提示向用户展示一次,不要自动执行示例。
## 步骤 8:向用户报告结果
最终只需清楚报告:已安装的版本、Skill 与 MCP 已加载、登录是否成功、钉钉 OA 与薪福通是否都通过只读验证。可以给出一次性使用示例。不要向用户倾倒安装 JSON、内部路径、Token、审批引用或诊断细节,除非用户正在排障。
安装阶段不得处理审批,也不得在登录和双来源验证完成前声称“已可使用”。
## 步骤 9:常见故障排查
按顺序排查,不要跳到重装:
1. **显示 302 或启动后立即退出**:检查连接器是否被错误配置成 `{{GATEWAY_URL}}` 或其他 HTTP URL。正确配置必须是 `type: "stdio"`,`command` 为本机 `dingtalk-approval-bridge.cmd`,`args: []`。重新复制安装结果中的 `mcpConfig`。
2. **路径不存在或 Node 升级后失效**:不要引用 Node/NVM 的版本目录。执行安装结果中的 `cliPath`,或运行 `dingtalk-approval print-config --format mcp-json` 重新取得稳定配置。
3. **Bridge 仍无法启动**:执行 `<cliPath> doctor --json`。只有所有 `checks` 都为 `ok: true` 才继续;把失败检查和错误原文报告给维护者,不要提供 Token 或会话文件。
4. **提示找不到 npm-cli.js 或 Node 运行时损坏**:重新执行步骤 2 中已经过哈希校验的平台引导,让它修复插件私有 Node;不要安装全局 npm。
5. **路径包含空格**:不要手写命令行字符串。把安装器返回的整个 `mcpConfig` 作为 JSON 对象交给 Agent;在 PowerShell 中执行 CLI 时用调用运算符 `&` 和独立参数。
6. **连接器一直未出现**:确认已信任本地连接器并完整重启 Agent。不要通过反复重装刷新连接器。
7. **授权链接过期**:MCP 已加载就再次调用 `approval_login`;否则执行保存的 `loginCommand`。不要重装。
8. **Linux/macOS 缺少下载或校验命令**:必须先具备 `curl` 或 `wget`,以及 `sha256sum` 或 `shasum`;不能绕过 SHA-256 校验。系统 `tar` 必须支持 gzip 归档。
9. **安装长时间无最终 JSON或被宿主终止**:先确认工具调用超时不少于 10 分钟,并查看 stderr 中最后一条 `[dingtalk-approval]` 阶段信息。不要改用内部安装器,也不要并发重试。原进程确认已经退出后,可重新执行同一平台引导;它会识别已经死亡的安装进程、清理残留 staging 并安全续装。若明确提示另一个安装仍在运行,应等待该进程结束。
## 更新
更新不需要重新下载首次安装器。使用上次安装结果中的 `cliPath`:
Windows PowerShell:
```powershell
$dingUpdateJson = & $dingInstall.cliPath update --json
$dingUpdate = $dingUpdateJson | ConvertFrom-Json
$dingUpdate | ConvertTo-Json -Depth 20
```
Linux / macOS:
```text
<cliPath> update --json
```
更新会复用已固定的签名清单地址和公钥并保留 OAuth 会话。更新成功后,重新使用返回的 `mcpConfig` 核对同名连接器,并重新连接 MCP;必要时完整重启 Agent。然后重复步骤 7 的登录确认和双来源只读验证。
## 卸载
1. 按当前 Agent 的官方方式删除名称精确为 `dingtalk-approval` 的 MCP/连接器条目。
2. 执行安装结果中的 `cliPath uninstall --yes --json`;Windows PowerShell 可运行:
```powershell
& $dingInstall.cliPath uninstall --yes --json
```
3. 确认返回 `uninstalled` 或 `cleanup_scheduled`。后者表示当前 CLI 退出后完成自清理。
卸载默认清除客户端运行时、Skill、安装记录和 OAuth 会话。不要手工递归删除未知目录。
授权页面会展示请求客户端、回调域名、权限范围或设备授权码。必须由用户本人核对并确认,不得替用户自动提交 consent。网关短暂不可用时保留现有登录状态并稍后重试,不要删除会话文件或主动要求用户重新授权。
只向用户说明连接器已就绪、登录状态,以及钉钉 OA/薪福通是否通过只读验证。不要展示内部 Token、用户 ID、审批引用或连接器诊断数据。
+2 -2
View File
@@ -4,9 +4,9 @@
- 测试包:`{{BUNDLE_URL}}`
- SHA-256:`{{BUNDLE_SHA256}}`
- 模式:轻量本地桥 + 统一审批网关;一次钉钉登录同时覆盖钉钉 OA 与薪福通,必须完整验证两个审批源
- 模式:远程 Streamable HTTP MCP + 宿主原生 OAuth;一次钉钉登录同时覆盖钉钉 OA 与薪福通,必须完整验证两个审批源
安装必须从包内的 `install.md` 进入,通过原生安装器完成 Ed25519 清单和发布物校验;不要改用旧版全局 npm 安装步骤。
安装必须从包内的 `install.md` 进入。默认不得安装 Node/npm 或本地 Bridge;只有宿主明确不支持远程 MCP 或标准 OAuth 时,才按其中链接的 `compat-install.md` 走签名兼容安装,并在报告中说明能力缺口。
只通过安装后的 `dingtalk-approval` MCP 读取和处理审批业务,不使用上游接口旁路。验证一次登录、钉钉 OA 与薪福通的待办、详情和动作能力;任何来源显示 `disabled` 都应视为发行配置错误并停止测试。真实写操作仅限用户明确确认的一次同意;可从任一来源选择,但不得因其中一方不可用而跳过。不要真实拒绝或退回。若结果为 `outcome_uncertain`,禁止自动重试。
+3 -2
View File
@@ -29,7 +29,8 @@ ding_hash_file() {
ding_fetch() {
if command -v curl >/dev/null 2>&1; then
if ding_fetch_http="$(curl --fail --location --silent --show-error --connect-timeout 10 --max-time 300 \
if ding_fetch_http="$(curl --fail --location --silent --show-error --connect-timeout 10 --max-time 90 \
--speed-time 20 --speed-limit 1024 \
--write-out '%{http_code}' --output "$2" "$1")"; then
return 0
else
@@ -47,7 +48,7 @@ ding_fetch() {
return 1
elif command -v wget >/dev/null 2>&1; then
ding_wget_log="${2}.wget.log"
if wget --https-only --timeout=300 --server-response --output-document="$2" "$1" 2>"$ding_wget_log"; then
if wget --https-only --timeout=90 --server-response --output-document="$2" "$1" 2>"$ding_wget_log"; then
rm -f "$ding_wget_log"
return 0
else
+16 -3
View File
@@ -23,17 +23,30 @@ function Get-DingSha256([string] $dingPath) {
function Save-DingDownload([string[]] $dingUrls, [string] $dingTarget, [string] $dingExpectedSha256) {
$dingLastError = $null
$dingCurl = Get-Command curl.exe -ErrorAction SilentlyContinue
foreach ($dingUrl in $dingUrls) {
Write-DingProgress "正在下载并校验:$dingUrl"
try {
if (Test-Path -LiteralPath $dingTarget) { Remove-Item -LiteralPath $dingTarget -Force }
Invoke-WebRequest -UseBasicParsing -Uri $dingUrl -OutFile $dingTarget -TimeoutSec 300
if ($dingCurl) {
$dingCurlHttp = & $dingCurl.Source --fail --location --silent --show-error --connect-timeout 10 --max-time 90 --speed-time 20 --speed-limit 1024 --write-out "%{http_code}" --output $dingTarget $dingUrl
$dingCurlExit = $LASTEXITCODE
if ($dingCurlExit -ne 0) {
if ($dingCurlHttp -match '^[1-5][0-9][0-9]$' -and $dingCurlHttp -ne '000') { throw "HTTP $dingCurlHttp" }
throw "curl.exe 退出码 $dingCurlExit"
}
} else {
Invoke-WebRequest -UseBasicParsing -Uri $dingUrl -OutFile $dingTarget -TimeoutSec 90
}
} catch {
$dingLastError = $_.Exception.Message
$dingStatusCode = $null
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
try { $dingStatusCode = [int]$_.Exception.Response.StatusCode } catch { $dingStatusCode = $null }
}
if ($null -eq $dingStatusCode -and $dingLastError -match 'HTTP ([0-9][0-9][0-9])') {
$dingStatusCode = [int]$Matches[1]
}
if ($null -eq $dingStatusCode -or $dingStatusCode -eq 404) { continue }
throw "下载失败且不允许换源(HTTP $dingStatusCode):$dingUrl;$dingLastError"
}
@@ -49,7 +62,7 @@ function Save-DingDownload([string[]] $dingUrls, [string] $dingTarget, [string]
function Test-DingNode([string] $dingNodePath, [bool] $dingRequireAdjacentNpm) {
if (-not $dingNodePath -or -not (Test-Path -LiteralPath $dingNodePath -PathType Leaf)) { return $false }
try {
& $dingNodePath -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>22||(a===22&&b>=13)?0:1)'
& $dingNodePath -e "const [a,b]=process.versions.node.split('.').map(Number);process.exit(a>22||(a===22&&b>=13)?0:1)"
if ($LASTEXITCODE -ne 0) { return $false }
if ($dingRequireAdjacentNpm) {
$dingNpmCli = Join-Path (Split-Path -Parent $dingNodePath) "node_modules\npm\bin\npm-cli.js"
@@ -126,7 +139,7 @@ try {
$dingProxyConfigured = $env:HTTPS_PROXY -or $env:HTTP_PROXY -or $env:ALL_PROXY
$dingProxyAlreadyEnabled = ($env:NODE_OPTIONS -match '(^|\s)--use-env-proxy($|\s)') -or ($env:NODE_USE_ENV_PROXY -match '^(1|true)$')
if ($dingProxyConfigured -and -not $dingProxyAlreadyEnabled) {
& $dingNodePath -e 'process.exit(process.allowedNodeEnvironmentFlags.has("--use-env-proxy") ? 0 : 1)'
& $dingNodePath -e "process.exit(process.allowedNodeEnvironmentFlags.has('--use-env-proxy') ? 0 : 1)"
if ($LASTEXITCODE -eq 0) { $dingNodeProxyArgs = @("--use-env-proxy") }
}
$dingInstallOutput = & $dingNodePath @dingNodeProxyArgs @dingInstallerArgs