11 KiB
部署与分发
服务器形态
统一网关是一个小型 Node.js 服务,监听 127.0.0.1:3010,由 Nginx 提供 HTTPS。它只调用钉钉与薪福通 API,不运行浏览器,通常 1 核 CPU、512 MB–1 GB 内存即可。
需要:
- Node.js 22.13+;
- Nginx、Certbot 和固定域名;
- 一个专用钉钉企业内部应用;
- 薪福通正式 OpenAPI 应用。
钉钉应用 OAuth 回调:
https://approval.justconn.top/oauth/dingtalk/callback
钉钉应用需要用户登录/身份、通讯录个人信息读取(用于实时取得当前用户所属部门)、OA 审批待办与已处理工作记录、本人发起实例 ID、实例详情、模板 Schema、审批处理和发起实例所需权限。
薪福通除已有的用户映射、本人待办、单据详情、审批历史和 pass/reject/back 权限外,发起审批还需要一次性开通:
xft-member/openapi/xft-member/member/get/by-id(查询企业成员详情,用于由当前登录身份安全取得本人花名册员工号);itrip-gateway/v1/form/query-event-list;itrip-gateway/v1/form/customFields/eventSeqId/{eventSeqId};itrip-gateway/v1/approve/recall(单据撤回);itrip-gateway/v1/bills/apply/travel/create;itrip-gateway/v1/bills/apply/expense/create;itrip-gateway/v1/bills/apply/entertainment/create;itrip-gateway/v1/bills/apply/common/create;itrip-gateway/v1/bills/apply/use-car/create;itrip-gateway/v1/reimburse/bill/create;itrip-gateway/v1/reimburse/bill/travel/create。
统一历史查询还会使用已开通的 itrip-gateway/v1/bills/queryBillByCondition(本人发起单据)和 itrip-gateway/v1/bills/bill-list(精简列表信息)。两者必须保留权限校验;empId 只能由网关从当前登录用户的成员档案派生。
创建接口必须保持权限校验开启。网关统一提交 APPRV_PEND,并在写后实时读取本人单据状态:进入待审批时提供撤回引用;按企业流程规则即时通过时返回 completed,不提供撤回引用且禁止重复提交。
1.1.0-rc.9 中薪福通撤回仍标记为实验能力:权限和调用契约已接通,但当前企业真实请求返回 XFTSYS9999,尚未取得成功样本。网关会写后复核并禁止自动重试。面向试用用户时必须保留这一说明;若业务要求撤回必须稳定可用,应等待薪福通定位后再升级为正式能力。
首次凭证配置
把钉钉 AppKey/AppSecret 保存为权限 0600 的 .secrets/dingtalk.json,把薪福通 AuthoritySecret 保存为权限 0600 的密钥文件。然后运行一次:
npm ci
npm run build
node dist/src/admin.js service configure \
--public-base-url https://approval.justconn.top \
--corp-id YOUR_CORP_ID \
--credentials-file .secrets/dingtalk.json \
--xft-open-api-app-id YOUR_XFT_APP_ID \
--xft-open-api-secret-file .secrets/xft-open-api.secret \
--xft-open-api-enterprise-id YOUR_XFT_ENTERPRISE_ID \
--port 3010
不要把这些企业凭证发给终端用户。
MCP_SESSION_SECRET_FILE 与 MCP_APPROVAL_REF_SECRET_FILE 由管理命令生成,内容至少 32 字节且权限必须为 0600(或更严格)。手工配置环境变量时同样强制至少 32 字节;缺失时网关拒绝启动,不再临时生成会导致重启后引用失效的随机密钥。MCP_PUBLIC_BASE_URL 必须是无内嵌凭证、无额外路径/query/hash 的 HTTPS origin,端口范围为 1–65535。
一键安装和更新
首次发布还需要生成一对 Ed25519 发布密钥:
npm run release:keygen
私钥保存在 .secrets/release-signing-private.pem(0600),必须离线备份且禁止提交、复制到 Web 目录或提供给网关进程;公钥位于 distribution/release-signing-public.pem 并随客户端发布。密钥已经存在时脚本会拒绝覆盖,轮换必须另行设计交叉签名迁移,不能直接替换。
首次配置完成后,每次安装、升级或 Bug 修复统一执行:
cd /path/to/dingMCP
./deploy/install.sh
脚本自动测试、构建、用 Ed25519 私钥签署 schema v3 清单、重启服务、原子发布静态文件、安装 Nginx 配置并验证公网健康检查、OAuth 元数据和安装链接。首次签证书时,临时 HTTP 配置只开放 ACME challenge,其余请求返回 403;不会在明文 HTTP 上代理 OAuth、Token 或 MCP。证书通过 Certbot webroot 模式签发,正式配置启用 HTTPS 跳转与不带 includeSubDomains 的 HSTS。已有证书会自动复用;签名私钥缺失、权限不是 0600 或与固定公钥不匹配时发行会失败。
发行物先复制到独立 staging 目录,再通过符号链接原子切换;客户端不会看到新清单配旧 artifact 的中间状态。服务器保留当前版本和最近两个旧版本用于排障,较老版本自动清理。install.md、清单、签名、安装器、Skill、校验文件和测试包均以 no-cache 提供;内容寻址的客户端 tgz 才使用 immutable 缓存。签名清单的 createdAt 超过 180 天时客户端拒绝安装,避免旧签名版本冻结攻击。
卸载:
./deploy/uninstall.sh
这是服务器卸载命令,不是终端用户插件卸载。它默认保留凭证、OAuth 数据库和 Let's Encrypt 证书,重新安装无需重新录入企业密钥。自动化环境可使用 ./deploy/uninstall.sh --yes。
用户分发
部署脚本会生成:
release/
├── install.md
├── manifest.json
├── manifest.sig
├── SHA256SUMS
├── dingtalk-approval-installer.mjs
├── dingtalk-approval-bootstrap.ps1
├── dingtalk-approval-bootstrap.sh
├── release-signing-public.pem
├── dingtalk-approval-client-*.tgz
├── dingtalk-approval-SKILL.md
├── dingmcp-test-bundle.tar.gz
└── agent-test-install.md
终端用户只需要把下面链接发给自己的 Agent:
https://approval.justconn.top/downloads/dingmcp/install.md
Agent 先校验当前平台引导脚本的固定 SHA-256,再执行该引导。平台引导选择并校验 Node 运行时,同时校验首次安装器和发布公钥;安装器随后验证 manifest.sig 和全部 artifact 哈希,将内置运行依赖的客户端以 npm 离线模式安装到稳定用户目录并返回标准 MCP 配置。终端安装过程不会从 npm registry 下载额外代码。Bridge 的稳定绝对路径不会包含 NVM 的具体 Node 版本,配置也不再携带 --url;网关地址由受签名保护的安装状态提供。
公开 install.md 必须保留从平台检查到双来源验收的完整编号步骤,并分别提供 Windows PowerShell 与 Linux/macOS 的独立路径。三平台都必须支持系统没有 Node 的情况:发布流程固定 Node 精确版本及 Windows/Linux/macOS x64/ARM64 的官方 SHA-256;平台引导依次使用阿里云、npmmirror 和 Node 官方精确版本地址,网络失败才换源,哈希不符立即终止。禁止使用 latest 镜像别名、修改系统 PATH 或要求管理员权限。
Windows 安装说明至少要覆盖 Invoke-WebRequest 下载、Get-FileHash 校验、JSON 解析、.cmd stdio 配置、路径空格、连接器信任与完整重启、独立登录续期、doctor --json、更新和卸载。Linux/macOS 必须同时支持 curl/wget 和 sha256sum/shasum,并使用系统 gzip tar 解压私有 Node。教程必须明确禁止把网关 HTTPS URL 直接注册为 MCP。
平台引导下载 Node 时使用 300 秒超时,Node 制品不经过本项目的小型网关,避免占用网关带宽。引导只接受固定官方哈希,且把私有 Node 与 npm 保存在客户端专用目录。进入 Node 安装器后,每个项目发布物仍使用 30 秒超时和 25 MB 流式硬上限,不会先把无界响应完整缓冲到内存;互不依赖的客户端包、Skill 和公钥并行下载。
平台引导与安装器把阶段进度写入 stderr,stdout 只保留最终 JSON。Agent 执行首次安装时必须给予至少 10 分钟工具超时;Windows 不论默认 shell 是什么都必须使用 PowerShell bootstrap,禁止直接调用内部 Node 安装器。安装器优先由当前 Node 直接启动相邻的 npm-cli.js,不会依赖易受 PATH、shell 或 Node 安全加固影响的隐式 npm shim;离线展开限时 180 秒。跨进程安装锁禁止并发写入,同一 PID 存活时要求等待,进程已死亡时自动回收锁并清理残留 staging。
安装器不会识别 Agent 品牌或直接修改其配置。所有 Agent 都使用返回的 skillPath 和标准 mcpConfig,按自身官方方式安装 Skill、注册并信任名为 dingtalk-approval 的本地 stdio MCP;不能热加载连接器时再新建会话或重启。
安装器在返回前通过已安装 Bridge 主动准备登录,并在 JSON 的 postInstall 中返回 authenticated、authorization_required 或 unavailable,同时返回独立的 loginCommand。Agent 必须立即显示其中的短期 directUrl,不得等待首次审批请求;网页授权后调用 approval_login,再按 verifyTool 用 source=all 做双来源只读验证。链接过期时,MCP 已加载就重新调用 approval_login,尚未加载就执行 loginCommand,禁止为刷新链接重跑安装。首次认证成功会返回一次性 onboarding 示例,普通文本宿主也可见。登录准备失败不回滚安装,且整个流程不提交 consent、不读取审批、不执行写操作。
安装后可用稳定 CLI:login --json 只检查登录或生成新授权链接;print-path bridge 默认只输出路径;print-config --format mcp-json 输出可直接消费的标准 MCP JSON;doctor --json 同时检查 Node、npm、运行时、Skill、公钥和网关配置。后续更新直接执行安装结果中 cliPath 对应的 update --json,它会复用已固定的签名清单地址和本地公钥,并保留 OAuth 会话。更新完成后应重新连接该 MCP;Agent 不能单独重启连接器时,再新建会话或重启。
客户端卸载使用安装结果返回的 cliPath uninstall --yes --json,默认删除 OAuth 会话,并安全清理由旧版手工删除留下的四个 npm 入口;只会删除名称精确匹配且目标属于 dingtalk-approval-mcp 的链接或 Windows shim。
运维检查
systemctl --user status dingtalk-approval-mcp.service
curl --fail http://127.0.0.1:3010/healthz
curl --fail https://approval.justconn.top/healthz
curl --fail https://approval.justconn.top/downloads/dingmcp/manifest.json
curl --fail https://approval.justconn.top/downloads/dingmcp/manifest.sig
备份时必须把 .runtime/auth.sqlite、同路径派生的 .runtime/auth.sqlite.invoice-dedup.sqlite 与 .secrets/oauth-session.key 一并迁移。发票查重库只含不可逆 HMAC 和最小状态元数据;审批正文不落库,不需要备份。
每个候选版和正式版发布前执行 发布检查清单。
只读 E2E 脚本没有生产默认地址,也不会自动挑选第一张审批:
DINGTALK_APPROVAL_MCP_URL=https://staging.example.com/mcp \
node scripts/e2e-approval-mcp.mjs dingtalk
读取指定单据时加 --business-no EXACT。只有已取得真实写入授权时,才可再加 --execute-real-write;脚本要求精确业务编号,并在写后重新列待办复核。任何 outcome_uncertain 均不得重试。