command -v misses claude when not in PATH (e.g. brew install without shell init). Now also checks /opt/homebrew/bin/claude and /usr/local/bin/claude. Uses $CLAUDE_BIN variable for all invocations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
246 lines
8.8 KiB
Bash
Executable File
246 lines
8.8 KiB
Bash
Executable File
#!/bin/bash
|
||
# ai-proj-helper 初始化脚本
|
||
# 配置 MCP 连接 + 安装技能到 ~/.claude/skills/
|
||
|
||
set -e
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
MCP_CONFIG="$HOME/.claude/.mcp.json"
|
||
MCP_BRIDGE_DIR="$SCRIPT_DIR/mcp-task-bridge"
|
||
CONFIG_FILE="$SCRIPT_DIR/claude-config.yaml"
|
||
API_BASE="https://ai.pipexerp.com/api/v1"
|
||
SSE_URL="${API_BASE}/mcp/sse"
|
||
|
||
# Default values
|
||
MODE=""
|
||
TOKEN=""
|
||
|
||
# ── Parse command line arguments ──────────────────────────────────────
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
--mode) MODE="$2"; shift 2 ;;
|
||
--token) TOKEN="$2"; shift 2 ;;
|
||
-h|--help)
|
||
echo "Usage: ./init.sh [--mode remote|stdio] [--token MCP_API_KEY]"
|
||
echo ""
|
||
echo "Options:"
|
||
echo " --mode MCP connection mode: remote (Streamable HTTP, recommended) or stdio (local Node.js)"
|
||
echo " --token MCP API Key (aiproj_pk_xxx)"
|
||
echo ""
|
||
echo "Without arguments, runs in interactive mode."
|
||
exit 0
|
||
;;
|
||
*) echo "Unknown option: $1"; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
# ── Read config defaults ─────────────────────────────────────────────
|
||
read_config_value() {
|
||
local key="$1"
|
||
local default="$2"
|
||
if [ -f "$CONFIG_FILE" ]; then
|
||
local val
|
||
val=$(grep "^ ${key}:" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/^[^:]*: *//' | sed 's/ *#.*//' | tr -d '"' | tr -d "'")
|
||
if [ -n "$val" ]; then
|
||
echo "$val"
|
||
return
|
||
fi
|
||
fi
|
||
echo "$default"
|
||
}
|
||
|
||
CONFIG_MODE=$(read_config_value "mode" "remote")
|
||
|
||
# Normalize mode aliases: sse → remote
|
||
if [ "$MODE" = "sse" ]; then
|
||
MODE="remote"
|
||
fi
|
||
|
||
# ── Detect claude CLI ────────────────────────────────────────────────
|
||
CLAUDE_BIN=""
|
||
if command -v claude &>/dev/null; then
|
||
CLAUDE_BIN="claude"
|
||
elif [ -x "/opt/homebrew/bin/claude" ]; then
|
||
CLAUDE_BIN="/opt/homebrew/bin/claude"
|
||
elif [ -x "/usr/local/bin/claude" ]; then
|
||
CLAUDE_BIN="/usr/local/bin/claude"
|
||
fi
|
||
HAS_CLAUDE=false
|
||
if [ -n "$CLAUDE_BIN" ]; then
|
||
HAS_CLAUDE=true
|
||
fi
|
||
|
||
# ── Interactive mode selection ────────────────────────────────────────
|
||
if [ -z "$MODE" ]; then
|
||
echo "┌─────────────────────────────────────┐"
|
||
echo "│ ai-proj MCP 初始化 │"
|
||
echo "└─────────────────────────────────────┘"
|
||
echo ""
|
||
echo "MCP 连接模式:"
|
||
echo " 1) remote - 远程连接 (推荐,零依赖,Streamable HTTP)"
|
||
echo " 2) stdio - 本地进程 (需要 Node.js + mcp-task-bridge)"
|
||
echo ""
|
||
read -p "选择模式 [1/2] (default: $([ "$CONFIG_MODE" = "stdio" ] && echo "2" || echo "1")): " mode_choice
|
||
case "$mode_choice" in
|
||
2|stdio) MODE="stdio" ;;
|
||
*) MODE="remote" ;;
|
||
esac
|
||
fi
|
||
|
||
# ── Token input ───────────────────────────────────────────────────────
|
||
if [ -z "$TOKEN" ]; then
|
||
echo ""
|
||
echo "请输入 MCP API Key:"
|
||
echo " 格式: aiproj_pk_xxxxxxxx"
|
||
echo " 获取: 登录 AI-Proj → 设置 → MCP API Keys → 创建"
|
||
echo ""
|
||
read -sp "API Key: " TOKEN
|
||
echo ""
|
||
fi
|
||
|
||
if [ -z "$TOKEN" ]; then
|
||
echo "❌ API Key 不能为空"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "配置信息:"
|
||
echo " 模式: $MODE"
|
||
echo " Claude CLI: $($HAS_CLAUDE && echo '检测到' || echo '未检测到 (回退写 .mcp.json)')"
|
||
if [ "$MODE" = "remote" ]; then
|
||
echo " URL: $SSE_URL"
|
||
else
|
||
echo " API: $API_BASE"
|
||
fi
|
||
echo ""
|
||
|
||
# ── Configure MCP ────────────────────────────────────────────────────
|
||
if [ "$MODE" = "remote" ]; then
|
||
if $HAS_CLAUDE; then
|
||
# 新版 Claude Code: 用 claude mcp add 注册(transport=http,即 Streamable HTTP)
|
||
# 先移除旧配置(忽略错误)
|
||
"$CLAUDE_BIN" mcp remove -s user ai-proj 2>/dev/null || true
|
||
"$CLAUDE_BIN" mcp add ai-proj "$SSE_URL" -t http -s user -H "X-API-Key: $TOKEN" 2>&1
|
||
echo "✅ 已通过 claude mcp add 注册 (Streamable HTTP)"
|
||
else
|
||
# 旧版或无 CLI: 回退写 .mcp.json(使用 http transport)
|
||
mkdir -p "$(dirname "$MCP_CONFIG")"
|
||
cat > "$MCP_CONFIG" << EOF
|
||
{
|
||
"mcpServers": {
|
||
"ai-proj": {
|
||
"type": "http",
|
||
"url": "$SSE_URL",
|
||
"headers": {
|
||
"X-API-Key": "$TOKEN"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
EOF
|
||
echo "✅ 已生成 $MCP_CONFIG (Streamable HTTP 模式)"
|
||
fi
|
||
|
||
elif [ "$MODE" = "stdio" ]; then
|
||
if [ ! -d "$MCP_BRIDGE_DIR" ]; then
|
||
echo "📦 mcp-task-bridge 未找到,正在克隆..."
|
||
git clone https://gitea.pipexerp.com/pipexerp/mcp-task-bridge.git "$MCP_BRIDGE_DIR"
|
||
fi
|
||
|
||
if [ ! -f "$MCP_BRIDGE_DIR/dist/index.js" ]; then
|
||
echo "🔨 构建 mcp-task-bridge..."
|
||
cd "$MCP_BRIDGE_DIR"
|
||
npm install
|
||
npm run build
|
||
cd "$SCRIPT_DIR"
|
||
fi
|
||
|
||
BRIDGE_ENTRY="$MCP_BRIDGE_DIR/dist/index.js"
|
||
|
||
if $HAS_CLAUDE; then
|
||
"$CLAUDE_BIN" mcp remove -s user ai-proj 2>/dev/null || true
|
||
"$CLAUDE_BIN" mcp add ai-proj -s user -e "NODE_ENV=production" -e "TASK_API_BASE=$API_BASE" -e "TASK_API_TOKEN=$TOKEN" -e "MCP_SERVER_NAME=ai-proj" -- node "$BRIDGE_ENTRY" 2>&1
|
||
echo "✅ 已通过 claude mcp add 注册 (stdio)"
|
||
else
|
||
mkdir -p "$(dirname "$MCP_CONFIG")"
|
||
cat > "$MCP_CONFIG" << EOF
|
||
{
|
||
"mcpServers": {
|
||
"ai-proj": {
|
||
"command": "node",
|
||
"args": ["$BRIDGE_ENTRY"],
|
||
"env": {
|
||
"NODE_ENV": "production",
|
||
"TASK_API_BASE": "$API_BASE",
|
||
"TASK_API_TOKEN": "$TOKEN",
|
||
"MCP_SERVER_NAME": "ai-proj"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
EOF
|
||
echo "✅ 已生成 $MCP_CONFIG (stdio 模式)"
|
||
fi
|
||
fi
|
||
|
||
# ── Install skills to ~/.claude/skills/ ──────────────────────────────
|
||
echo "📦 安装技能到 ~/.claude/skills/ ..."
|
||
SKILLS_DIR="$HOME/.claude/skills"
|
||
mkdir -p "$SKILLS_DIR"
|
||
|
||
SKILL_COUNT=0
|
||
for plugin_dir in "$SCRIPT_DIR"/skills-*/; do
|
||
for skill_path in "$plugin_dir"*-plugin/; do
|
||
[ -d "$skill_path" ] || continue
|
||
skill_md="$skill_path/skills/SKILL.md"
|
||
[ -f "$skill_md" ] || continue
|
||
|
||
# Extract skill name: ai-proj-plugin -> ai-proj
|
||
dir_name=$(basename "$skill_path")
|
||
skill_name="${dir_name%-plugin}"
|
||
|
||
target_dir="$SKILLS_DIR/$skill_name"
|
||
mkdir -p "$target_dir"
|
||
|
||
# Copy SKILL.md (overwrite if exists)
|
||
cp "$skill_md" "$target_dir/SKILL.md"
|
||
SKILL_COUNT=$((SKILL_COUNT + 1))
|
||
done
|
||
done
|
||
echo " 已安装 $SKILL_COUNT 个技能"
|
||
echo "✅ 技能安装完成 → $SKILLS_DIR"
|
||
|
||
# ── Verify MCP connection ────────────────────────────────────────────
|
||
echo ""
|
||
echo "🔍 验证 MCP 连接..."
|
||
if [ "$MODE" = "remote" ]; then
|
||
VERIFY_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$SSE_URL" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-API-Key: $TOKEN" \
|
||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"init-verify","version":"1.0"}}}' \
|
||
--connect-timeout 10 --max-time 15 2>/dev/null)
|
||
if [ "$VERIFY_RESPONSE" = "200" ]; then
|
||
echo " ✅ MCP 服务端连接正常 (HTTP $VERIFY_RESPONSE)"
|
||
else
|
||
echo " ⚠️ MCP 服务端连接异常 (HTTP $VERIFY_RESPONSE)"
|
||
echo " 请检查: 1) API Key 是否正确 2) 服务端是否运行 3) 网络是否通畅"
|
||
echo " 测试命令: curl -s -X POST '$SSE_URL' -H 'X-API-Key: \$TOKEN' -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}'"
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
echo "┌─────────────────────────────────────┐"
|
||
echo "│ ✅ 初始化完成! │"
|
||
echo "└─────────────────────────────────────┘"
|
||
echo ""
|
||
echo "已完成配置:"
|
||
if $HAS_CLAUDE; then
|
||
echo " ✅ MCP 服务器 (claude mcp add, transport=http)"
|
||
else
|
||
echo " ✅ MCP 服务器 → $MCP_CONFIG"
|
||
fi
|
||
echo " ✅ 技能 ($SKILL_COUNT 个) → $SKILLS_DIR"
|
||
echo ""
|
||
echo "重启 Claude Code 即可使用。"
|
||
echo "如需更改配置,编辑 claude-config.yaml 后重新运行 ./init.sh"
|