Files
deta-agent-plugins/plugins/studio/bin/deta-mcp.sh
T
ringo 1d1000ddf5
CI / contract (push) Has been cancelled
feat(hosts): WorkBuddy 接入(install-workbuddy:本机 stdio + 技能安装 + 握手自证)
WorkBuddy 自定义 MCP 支持 command/args/env(取证:其二进制 calculateConfigHash 先判 entry.command),因此走与 Codex 同款的本机 stdio full 工具面,不退远程引擎面。

落地:① 启动器多维护一个 MCP 稳定入口 ~/.deta/bin/deta-mcp-<档位>(指向当前插件副本,升级自动刷新;WB 信任哈希按命令行算,路径稳定=升级不重弹信任);② deta-cli 新增 install-workbuddy [--http]——备份并合并 ~/.workbuddy/mcp.json(同名旧条目与旧远程条目替换掉,防双工具面)、把插件内全部技能装进 ~/.workbuddy/skills/(单一事实源,带 icon.svg)、用宿主将执行的同一条命令做真实握手自证;③ aml-design 技能两处 Codex 特有措辞改宿主中立;④ 契约校验加 E7(装配源齐备 + README 指引);⑤ 探针 scripts/verify-workbuddy.sh(配置形态/稳定入口/技能/握手/WB 日志,5 项断言)。

实测:studio 0.3.16 已装副本重跑幂等通过(握手 39 工具);writer 0.1.7 在沙箱 HOME 验证(deta_writer + report-authoring 技能 + 握手 39 工具)。WB 侧需重启并点一次信任确认。
2026-09-20 17:13:15 +08:00

168 lines
8.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# DETA 产品插件的本机 MCP stdio 启动器(唯一入口,多产品共用)
#
# 两个宿主(Codex 插件 / dsh 组合包)都通过本脚本拉起同一个领域服务;
# 产品差异只由 --profile 决定(工作区默认位置、工具面),宿主侧只声明"调哪个脚本、带哪个档位"。
#
# 用法:deta-mcp.sh [--profile <studio|writer>]
#
# 依赖:node + 已构建的领域服务运行时
#
# 环境变量:
# DETA_SERVER_ROOT 领域服务运行时根目录(未设时自动查找,见下)
# DETA_WORKSPACE AML 工作目录(引擎侧工作区真值;缺省 ~/.deta/<profile>/workspace)
# ANT_HEADLESS_WORKSPACE 同上(兼容旧名,DETA_WORKSPACE 优先)
# DETA_TOKEN deta.city 门户令牌(可选;不注入则云类工具返回 auth_required)
# ANT_WORKSPACE_BRIDGE_URL 网关基址(可选;与项目 ID 一起启用 Studio 执行器中继)
# DETA_SURFACE 工具面覆盖(缺省 full)
set -euo pipefail
# ── 产品档位:宿主在声明里传 --profile;缺省 studio ──
PROFILE="${DETA_PROFILE:-studio}"
while [ $# -gt 0 ]; do
case "$1" in
--profile) PROFILE="${2:?--profile 需要一个值}"; shift 2 ;;
*) break ;;
esac
done
PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# ── 运行时自备:插件包内自带打包产物(同事机器没有开发检出,也拿不到私有 registry)──
# 缺失或与插件包不一致时自动安装到 $HOME/.deta/runtime;只比 sha256,命中即跳过(4MB 哈希约 20ms)。
# 为什么随插件分发:私有 registry 匿名读 401,走 npm 会让每个同事机器都要配 token。
RUNTIME_DIR="$HOME/.deta/runtime"
BUNDLED="$PLUGIN_ROOT/runtime/dist/deta-mcp.mjs"
if [ -f "$BUNDLED" ]; then
WANT_SHA="$(shasum -a 256 "$BUNDLED" 2>/dev/null | cut -d' ' -f1)"
HAVE_SHA=""
if [ -f "$RUNTIME_DIR/dist/deta-mcp.mjs" ]; then
HAVE_SHA="$(shasum -a 256 "$RUNTIME_DIR/dist/deta-mcp.mjs" 2>/dev/null | cut -d' ' -f1)"
fi
if [ -n "$WANT_SHA" ] && [ "$WANT_SHA" != "$HAVE_SHA" ]; then
mkdir -p "$RUNTIME_DIR/dist" 2>/dev/null || true
if cp "$BUNDLED" "$RUNTIME_DIR/dist/deta-mcp.mjs" 2>/dev/null; then
if [ -f "$PLUGIN_ROOT/runtime/manifest.json" ]; then
cp "$PLUGIN_ROOT/runtime/manifest.json" "$RUNTIME_DIR/manifest.json" 2>/dev/null || true
fi
echo "[deta] 已从插件包安装运行时到 ${RUNTIME_DIR}" >&2
fi
fi
fi
# ── 运行时解析:显式 DETA_SERVER_ROOT → 本机引擎包安装位置 → 本机开发检出(打印提示)──
ROOT="${DETA_SERVER_ROOT:-}"
if [ -z "$ROOT" ]; then
for cand in "$HOME/.deta/runtime" "$HOME/Dev/ant/ant-copilot"; do
if [ -f "$cand/dist/deta-mcp.mjs" ] || [ -f "$cand/dist/src/headless/mcp-server.js" ]; then ROOT="$cand"; break; fi
done
fi
if ! command -v node >/dev/null 2>&1; then
echo "[deta] 未找到 node,请先安装 Node.js" >&2
exit 1
fi
# 入口优先取 esbuild 打包形态:tsc 产物在 Node ESM 下会因依赖包的目录 import 直接失败
ENTRY=""
if [ -n "$ROOT" ]; then
if [ -f "$ROOT/dist/deta-mcp.mjs" ]; then
ENTRY="$ROOT/dist/deta-mcp.mjs"
elif [ -f "$ROOT/dist/src/headless/mcp-server.js" ]; then
ENTRY="$ROOT/dist/src/headless/mcp-server.js"
echo "[deta] 警告:回落到未打包产物,请先在该运行时目录执行 npm run build:mcp" >&2
fi
fi
if [ -z "$ENTRY" ]; then
echo "[deta] 未找到领域服务运行时(已尝试:DETA_SERVER_ROOT、\$HOME/.deta/runtime、\$HOME/Dev/ant/ant-copilot)" >&2
echo "[deta] 请安装本机引擎包,或设置 DETA_SERVER_ROOT 指向已构建的领域服务仓库" >&2
exit 1
fi
if [ "$ROOT" = "$HOME/Dev/ant/ant-copilot" ]; then
echo "[deta] 提示:使用本机开发检出 ${ROOT}(非打包运行时)" >&2
fi
# ── 工作目录:宿主不会传项目目录(cwd 通常是插件根),按产品档位定稳定位置 ──
WS="${DETA_WORKSPACE:-${ANT_HEADLESS_WORKSPACE:-$HOME/.deta/$PROFILE/workspace}}"
mkdir -p "$WS"
export ANT_HEADLESS_WORKSPACE="$WS"
# ── 工具面:产品档位缺省全量面;DETA_SURFACE 可覆盖(如 engine)──
export DETA_TOOL_SURFACE="${DETA_SURFACE:-full}"
# ── 稳定入口:把命令行门面注册为 ~/.deta/bin/deta(幂等,路径随插件更新自动刷新)──
# 插件装在宿主缓存目录,用户看不见这个路径;而用户与工具回执都需要一条可复制的命令。
BIN_DIR="$HOME/.deta/bin"
SHIM="$BIN_DIR/deta"
SHIM_BODY="#!/bin/sh
# DETA 命令行门面(由插件启动器自动维护,勿手工编辑)
exec \"$(command -v node)\" \"$PLUGIN_ROOT/bin/deta-cli.mjs\" \"\$@\"
"
mkdir -p "$BIN_DIR" 2>/dev/null || true
if [ ! -f "$SHIM" ] || [ "$(cat "$SHIM" 2>/dev/null)" != "$SHIM_BODY" ]; then
printf '%s' "$SHIM_BODY" > "$SHIM" 2>/dev/null && chmod 0755 "$SHIM" 2>/dev/null || true
fi
# ── MCP 稳定入口:~/.deta/bin/deta-mcp-<档位>(供不支持插件市场的宿主以 stdio 接入,如 WorkBuddy)──
# 指向当前插件副本;升级后下一次启动自动刷新。宿主对 MCP 的信任按命令行算哈希,
# 路径稳定 ⇒ 升级插件不再重弹信任确认。PATH 前缀兜住 GUI 拉起时找不到 homebrew node 的情况。
MCP_SHIM="$BIN_DIR/deta-mcp-${PROFILE}"
MCP_SHIM_BODY="#!/bin/bash
# DETA MCP 稳定入口(由插件启动器自动维护,勿手工编辑)
export PATH=\"/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:\${PATH:-}\"
exec bash \"$PLUGIN_ROOT/bin/deta-mcp.sh\" --profile ${PROFILE} \"\$@\"
"
if [ ! -f "$MCP_SHIM" ] || [ "$(cat "$MCP_SHIM" 2>/dev/null)" != "$MCP_SHIM_BODY" ]; then
printf '%s' "$MCP_SHIM_BODY" > "$MCP_SHIM" 2>/dev/null && chmod 0755 "$MCP_SHIM" 2>/dev/null || true
fi
# ── 云令牌:显式环境变量 → ~/.deta/env → ~/.deta/credentials.json ──
# 桌面应用(Finder/Dock 启动)不会继承终端的 export,也不是每个人都有 launchctl 注入,
# 因此登录助手写下的凭据必须由启动器自己解析——这是"登录一次即可用"的关键路径。
TOKEN_SOURCE=""
if [ -n "${DETA_TOKEN:-}" ]; then
TOKEN_SOURCE="环境变量"
fi
if [ -z "${DETA_TOKEN:-}" ] && [ -f "$HOME/.deta/env" ]; then
# shellcheck disable=SC1090
. "$HOME/.deta/env" >/dev/null 2>&1 || true
if [ -n "${DETA_TOKEN:-}" ]; then TOKEN_SOURCE="$HOME/.deta/env"; fi
fi
if [ -z "${DETA_TOKEN:-}" ] && [ -f "$HOME/.deta/credentials.json" ]; then
DETA_TOKEN="$(node -e "try{const c=JSON.parse(require('node:fs').readFileSync(process.env.HOME+'/.deta/credentials.json','utf8'));if(c&&typeof c.token==='string')process.stdout.write(c.token)}catch(e){}" 2>/dev/null || true)"
export DETA_TOKEN
if [ -n "${DETA_TOKEN:-}" ]; then TOKEN_SOURCE="$HOME/.deta/credentials.json"; fi
fi
# ── 网关桥(云项目工作区读写 / host-call 中继):显式 env → 本机网关 → 生产网关 ──
# 缺它时 build_model 无法把模型写进云项目(会退回只写本地工作区),用户会以为已同步。
if [ -z "${ANT_WORKSPACE_BRIDGE_URL:-}" ]; then
if (exec 3<>/dev/tcp/127.0.0.1/3180) 2>/dev/null; then
ANT_WORKSPACE_BRIDGE_URL="http://127.0.0.1:3180"
BRIDGE_STATE="本机网关 ${ANT_WORKSPACE_BRIDGE_URL}"
else
ANT_WORKSPACE_BRIDGE_URL="https://deta.city/workspace-api"
BRIDGE_STATE="生产网关 ${ANT_WORKSPACE_BRIDGE_URL}"
fi
export ANT_WORKSPACE_BRIDGE_URL
else
BRIDGE_STATE="环境变量 ${ANT_WORKSPACE_BRIDGE_URL}"
fi
if [ -n "${DETA_TOKEN:-}" ]; then
CLOUD_STATE="已注入(来源=${TOKEN_SOURCE})"
else
CLOUD_STATE="未注入——云类工具返回 auth_required 时调 login 工具(浏览器点一下即可);无该工具的形态用 ~/.deta/bin/deta login"
fi
echo "[deta] 档位=${PROFILE} 运行时=${ROOT} 工作区=${WS} 工具面=${DETA_TOOL_SURFACE} 云令牌=${CLOUD_STATE} 网关桥=${BRIDGE_STATE}" >&2
# ── 可选依赖提示:缺 @resvg/resvg-js 只影响候选预览图,其余能力不受影响 ──
if [ ! -f "$ROOT/node_modules/@resvg/resvg-js/package.json" ]; then
echo "[deta] 提示:候选预览图需要 @resvg/resvg-js;运行 ~/.deta/bin/deta install-runtime 补装(其余能力不受影响)" >&2
fi
exec node "$ENTRY" "$@"