mirror of
https://github.com/chainbase-labs/Agentkey.git
synced 2026-09-20 14:20:23 +08:00
feat(installer): unify skill + MCP agent registration (16 agents) (#41)
## Summary - Drive both `npx skills add -a` and `@agentkey/mcp --auth-login --only` from a single detected-agent list. MCP registration now follows the same per-host auto-detection that skill install already does, expanding MCP auto-registration from 3 clients to **16**. - Fix the longstanding `claude-code` marker bug: it included Claude Desktop's config dir, causing skills CLI to target a nonexistent Claude Code on Desktop-only machines. `claude-desktop` is now its own id (MCP-only) — passed to `--auth-login --only` but never to `skills add`. - Detect Claude Desktop via `/Applications/Claude.app` / `%LOCALAPPDATA%\AnthropicClaude` so "installed but never launched" still registers (Linux still requires the config dir). - New `scripts/dev-smoke.sh` — sandboxed 4-phase regression suite, 42 assertions, ~10s, never touches real `$HOME`. Run before any PR touching install/uninstall scripts. ## Depends on [chainbase-labs/AgentKey-Server#9](https://github.com/chainbase-labs/AgentKey-Server/pull/9) — adds `--only <ids>` to `@agentkey/mcp --auth-login`. Older CLI versions silently ignore the flag, so this PR is forward-compatible either way. ## Uninstaller (the bigger gap before this) The previous uninstaller only cleaned 3 config paths and only knew the `mcpServers.<name>` JSON shape. With 13 new agents using 4 different schema dialects, that left AgentKey configured everywhere after uninstall. - Expanded MCP cleanup to **14 JSON paths + codex TOML**, covering all 16 auto-registered agents - Schema-agnostic JSON scrub: walks the tree and drops dict keys whose name EXACTLY matches our server names. Handles `mcpServers.<name>` / `mcp.<name>` / `amp.mcpServers.<name>` / `projects.X.mcpServers.<name>` in one pass - Codex TOML splice via awk / PowerShell (no parser dep) — drops `[mcp_servers.agentkey]` + legacy quoted block, preserves sibling sections - `droid mcp remove` + `openclaw mcp unset` for CLI-registered agents - **Exact-match** server names (not substring) so user keys like `agentkey-helper` are preserved (regression test included in dev-smoke) ## Bugs fixed during review | Where | Bug | |---|---| | install.sh:487 | Unbound `$TARGETS` variable (renamed during refactor) — `set -u` would have made this fatal | | install.ps1 | `$SkillTargets.Count` used where `$AllTargets.Count` was meant — diverged from install.sh behavior | | install.sh | `--only claude-desktop` ran `skills add -a` with no filter, defeating the user's `--only` intent. Now correctly skips the skill step | | install.sh helpers | Leaked-scope loop vars (`_ids`, `_id`) — declared `local -a` | ## Test plan - [x] `scripts/dev-smoke.sh` — 42 passing / 0 failing (Phase 1 unit tests + Phase 2 installer + Phase 3 writer schemas + Phase 4 uninstaller w/ false-positive guard) - [x] `bash -n` clean for install.sh + uninstall.sh - [x] `--list-agents` correctly lists `claude-desktop` as a separate id - [x] `--only claude-desktop --skip-mcp --yes` walks the new "MCP-only, skip skill" branch - [x] Auto-detect path tested via `bash -x` trace under `set -u` (no unbound-variable explosion) - [x] Uninstaller decoy fixtures: `agentkey-helper`, `other-svr`, `[mcp_servers.other]`, `[unrelated_section]` all preserved after scrub - [ ] Windows: `install.ps1` / `uninstall.ps1` syntax-checked but not runtime-tested (no Windows box handy — happy to test if reviewer has one) --------- Co-authored-by: Bruce <bruce@checkabc.me>
This commit is contained in:
+148
-40
@@ -27,23 +27,33 @@ NODE_MIN_MAJOR=18
|
||||
# pre-detected — the user can pass --all-agents or --only to include them.
|
||||
# Sync source: https://github.com/vercel-labs/skills (Supported Agents table).
|
||||
#
|
||||
# IMPORTANT: ids here MUST match the `--only` ids accepted by both
|
||||
# `npx skills add -a` and `npx -y @agentkey/cli --auth-login --only`.
|
||||
# That alignment is what lets the installer drive both halves with one list.
|
||||
#
|
||||
# `claude-desktop` is the documented exception — it isn't in the skills CLI
|
||||
# (Desktop installs skills into a sandbox path the CLI can't write), but
|
||||
# Desktop's MCP config IS auto-writable, so we list it separately and pass
|
||||
# it ONLY to the MCP --only filter (see SKILL_TARGETS / MCP_TARGETS below).
|
||||
#
|
||||
# Format: <agent-id>|<marker>[,<marker>...]
|
||||
# marker types: cmd:foo — `command -v foo`
|
||||
# path:/abs/or/~path — file or dir exists (~ expands to $HOME)
|
||||
AGENT_MARKERS=(
|
||||
"claude-code|path:~/.claude.json,cmd:claude,path:~/Library/Application Support/Claude,path:~/.config/Claude"
|
||||
"claude-code|path:~/.claude.json,cmd:claude"
|
||||
"claude-desktop|path:/Applications/Claude.app,path:~/Applications/Claude.app,path:~/Library/Application Support/Claude/claude_desktop_config.json,path:~/Library/Application Support/Claude,path:~/.config/Claude/claude_desktop_config.json,path:~/.config/Claude"
|
||||
"cursor|path:~/.cursor,cmd:cursor"
|
||||
"codex|path:~/.codex,cmd:codex"
|
||||
"gemini-cli|path:~/.gemini,cmd:gemini"
|
||||
"opencode|path:~/.opencode,cmd:opencode"
|
||||
"openclaw|path:~/.openclaw"
|
||||
"opencode|path:~/.config/opencode,path:~/.opencode,cmd:opencode"
|
||||
"openclaw|path:~/.openclaw,cmd:openclaw"
|
||||
"qwen-code|path:~/.qwen,cmd:qwen"
|
||||
"iflow-cli|path:~/.iflow,cmd:iflow"
|
||||
"windsurf|path:~/.windsurf,cmd:windsurf"
|
||||
"windsurf|path:~/.codeium/windsurf,path:~/.windsurf,cmd:windsurf"
|
||||
"warp|path:~/.warp,path:~/Library/Application Support/dev.warp.Warp-Stable"
|
||||
"amp|cmd:amp"
|
||||
"crush|cmd:crush"
|
||||
"goose|cmd:goose"
|
||||
"amp|path:~/.config/amp,cmd:amp"
|
||||
"crush|path:~/.config/crush,cmd:crush"
|
||||
"goose|path:~/.config/goose,cmd:goose"
|
||||
"droid|cmd:droid"
|
||||
"kode|cmd:kode"
|
||||
"kilo|cmd:kilo"
|
||||
@@ -51,6 +61,20 @@ AGENT_MARKERS=(
|
||||
"kiro-cli|path:~/.kiro,cmd:kiro"
|
||||
)
|
||||
|
||||
# Agent ids that are MCP-only (no skill install path). These get passed to
|
||||
# `--auth-login --only` but NEVER to `npx skills add -a`.
|
||||
MCP_ONLY_AGENTS=(claude-desktop)
|
||||
|
||||
# Agent ids whose MCP registration the installer can drive automatically.
|
||||
# Skipped agents (goose / kode / kilo) still get the skill, but the user
|
||||
# must register MCP manually for them. Keep this in sync with
|
||||
# AGENT_REGISTRY in AgentKey-Server/cli/src/lib/mcp-clients.ts.
|
||||
MCP_AUTO_AGENTS=(
|
||||
claude-code claude-desktop cursor codex gemini-cli opencode
|
||||
qwen-code iflow-cli kimi-cli kiro-cli windsurf warp
|
||||
amp crush droid openclaw
|
||||
)
|
||||
|
||||
# ── Colors (only if stdout is a TTY) ─────────────────────────────────────
|
||||
# Use $'...' so variables hold real ESC bytes — otherwise heredoc output prints
|
||||
# the literal string "\033[1m" instead of applying the SGR code.
|
||||
@@ -160,6 +184,35 @@ detect_agents() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Membership helper: is "$1" in the rest of the argument list?
|
||||
_in_list() {
|
||||
local needle="$1"; shift
|
||||
local item
|
||||
for item in "$@"; do
|
||||
[ "$item" = "$needle" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Filter a comma-separated id list, keeping only ids that are passed in the
|
||||
# remaining arguments. Output is comma-separated. Short-circuits on empty
|
||||
# input so callers don't have to guard.
|
||||
_filter_csv() {
|
||||
local csv="$1"; shift
|
||||
[ -z "$csv" ] && return 0
|
||||
local id
|
||||
local -a ids=() out=()
|
||||
IFS=',' read -ra ids <<<"$csv"
|
||||
for id in "${ids[@]}"; do
|
||||
if _in_list "$id" "$@"; then
|
||||
out+=("$id")
|
||||
fi
|
||||
done
|
||||
if [ ${#out[@]} -gt 0 ]; then
|
||||
printf '%s\n' "${out[@]}" | paste -sd, -
|
||||
fi
|
||||
}
|
||||
|
||||
install_node() {
|
||||
local platform="$1"
|
||||
ui_info "Installing Node.js v$NODE_MIN_MAJOR+ ..."
|
||||
@@ -339,42 +392,73 @@ main() {
|
||||
|
||||
command -v npx >/dev/null 2>&1 || die "npx not found after Node install — please reinstall Node.js"
|
||||
|
||||
# ── Resolve target agent list ─────────────────────────────────────────
|
||||
# Used by step 2 (skill) and step 3 (MCP). Computed once here so both
|
||||
# halves see the same source of truth — that's the invariant the unified
|
||||
# install+register design depends on. Two derived lists:
|
||||
#
|
||||
# ALL_TARGETS — every detected agent, including MCP-only ones (claude-desktop)
|
||||
# SKILL_TARGETS — ALL_TARGETS minus MCP-only ids (those would error in `skills add`)
|
||||
# MCP_TARGETS — ALL_TARGETS filtered to ids the MCP CLI knows how to write
|
||||
local ALL_TARGETS=""
|
||||
if [ -n "$ONLY_AGENTS" ]; then
|
||||
ALL_TARGETS="$ONLY_AGENTS"
|
||||
ui_info "Targeting agents from --only: $ALL_TARGETS"
|
||||
elif $ALL_AGENTS; then
|
||||
ui_info "Installing for every agent the 'skills' CLI detects (--all-agents)"
|
||||
else
|
||||
ALL_TARGETS="$(detect_agents)"
|
||||
if [ -n "$ALL_TARGETS" ]; then
|
||||
ui_ok "Detected agents on this host: $ALL_TARGETS"
|
||||
ui_muted "(override with --only <ids>, or use --all-agents)"
|
||||
else
|
||||
ui_info "No agents auto-detected — letting 'skills' CLI scan."
|
||||
fi
|
||||
fi
|
||||
|
||||
local SKILL_TARGETS=""
|
||||
local MCP_TARGETS=""
|
||||
if [ -n "$ALL_TARGETS" ]; then
|
||||
# SKILL_TARGETS: drop MCP-only ids (would fail in `skills add -a`).
|
||||
local _id
|
||||
local -a _id_list=() _kept=()
|
||||
IFS=',' read -ra _id_list <<<"$ALL_TARGETS"
|
||||
for _id in "${_id_list[@]}"; do
|
||||
if ! _in_list "$_id" "${MCP_ONLY_AGENTS[@]}"; then
|
||||
_kept+=("$_id")
|
||||
fi
|
||||
done
|
||||
if [ ${#_kept[@]} -gt 0 ]; then
|
||||
SKILL_TARGETS="$(printf '%s\n' "${_kept[@]}" | paste -sd, -)"
|
||||
fi
|
||||
# MCP_TARGETS: keep only ids the MCP CLI knows how to register.
|
||||
MCP_TARGETS="$(_filter_csv "$ALL_TARGETS" "${MCP_AUTO_AGENTS[@]}")"
|
||||
fi
|
||||
|
||||
# ── 2. Install the AgentKey skill ─────────────────────────────────────
|
||||
if ! $SKIP_SKILL; then
|
||||
if $SKIP_SKILL; then
|
||||
ui_step "2. Install the AgentKey skill"
|
||||
ui_muted "Skipped (--skip-skill)"
|
||||
elif [ -n "$ALL_TARGETS" ] && [ -z "$SKILL_TARGETS" ]; then
|
||||
# User explicitly selected only MCP-only ids (e.g. `--only claude-desktop`).
|
||||
# There's nothing for `skills add` to do — skip the step entirely
|
||||
# rather than fall through to "install for every detected agent."
|
||||
ui_step "2. Install the AgentKey skill"
|
||||
ui_muted "Skipped — selected targets ($ALL_TARGETS) are MCP-only (no skill install path)."
|
||||
else
|
||||
ui_step "2. Install the AgentKey skill"
|
||||
|
||||
# Resolve target agent list:
|
||||
# 1. --only wins (manual override)
|
||||
# 2. else --all-agents ⇒ no -a (let skills CLI auto-detect everything)
|
||||
# 3. else our auto-detection ⇒ -a <detected list>
|
||||
# 4. else (nothing detected) ⇒ no -a (fall back to skills CLI default)
|
||||
local TARGETS=""
|
||||
if [ -n "$ONLY_AGENTS" ]; then
|
||||
TARGETS="$ONLY_AGENTS"
|
||||
ui_info "Targeting agents from --only: $TARGETS"
|
||||
elif $ALL_AGENTS; then
|
||||
ui_info "Installing for every agent the 'skills' CLI detects (--all-agents)"
|
||||
else
|
||||
TARGETS="$(detect_agents)"
|
||||
if [ -n "$TARGETS" ]; then
|
||||
ui_ok "Detected agents on this host: $TARGETS"
|
||||
ui_muted "(override with --only <ids>, or use --all-agents)"
|
||||
else
|
||||
ui_info "No agents auto-detected — letting 'skills' CLI scan."
|
||||
fi
|
||||
fi
|
||||
|
||||
local SKILLS_ARGS=(-y skills add "$SKILL_REPO" -g)
|
||||
if [ -n "$TARGETS" ]; then
|
||||
if [ -n "$SKILL_TARGETS" ]; then
|
||||
# `skills` CLI accepts -a as either repeated or comma-separated.
|
||||
# We pass each ID individually for maximum compatibility.
|
||||
local AGENT_LIST=()
|
||||
IFS=',' read -ra AGENT_LIST <<<"$TARGETS"
|
||||
IFS=',' read -ra AGENT_LIST <<<"$SKILL_TARGETS"
|
||||
SKILLS_ARGS+=(-a "${AGENT_LIST[@]}")
|
||||
fi
|
||||
# Always pass -y in noninteractive mode AND when we already resolved
|
||||
# an explicit target list — there's nothing left to ask the user.
|
||||
if [ "$MODE" = noninteractive ] || [ -n "$TARGETS" ]; then
|
||||
if [ "$MODE" = noninteractive ] || [ -n "$ALL_TARGETS" ]; then
|
||||
SKILLS_ARGS+=(-y)
|
||||
fi
|
||||
|
||||
@@ -404,16 +488,19 @@ main() {
|
||||
"$HOME/.qwen/skills/agentkey" \
|
||||
"$HOME/.iflow/skills/agentkey" \
|
||||
"$HOME/.windsurf/skills/agentkey" \
|
||||
"$HOME/.warp/skills/agentkey"; do
|
||||
"$HOME/.warp/skills/agentkey" \
|
||||
"$HOME/.config/amp/skills/agentkey" \
|
||||
"$HOME/.config/crush/skills/agentkey" \
|
||||
"$HOME/.config/goose/skills/agentkey" \
|
||||
"$HOME/.config/opencode/skills/agentkey" \
|
||||
"$HOME/.kimi/skills/agentkey" \
|
||||
"$HOME/.kiro/skills/agentkey"; do
|
||||
[ -f "$_dir/SKILL.md" ] && { _agentkey_found=true; break; }
|
||||
done
|
||||
if ! $_agentkey_found; then
|
||||
die "Skill install reported success but no agentkey SKILL.md was created — likely a network or git clone failure. Retry: npx -y skills add $SKILL_REPO -g -y"
|
||||
fi
|
||||
ui_ok "Skill installed"
|
||||
else
|
||||
ui_step "2. Install the AgentKey skill"
|
||||
ui_muted "Skipped (--skip-skill)"
|
||||
fi
|
||||
|
||||
# ── 3. MCP authentication ────────────────────────────────────────────
|
||||
@@ -424,10 +511,31 @@ main() {
|
||||
if $SKIP_MCP; then
|
||||
ui_step "3. Register the MCP server"
|
||||
ui_muted "Skipped (--skip-mcp)"
|
||||
elif [ -n "$ALL_TARGETS" ] && [ -z "$MCP_TARGETS" ]; then
|
||||
# User selected ONLY MCP-incompatible agents (goose / kode / kilo
|
||||
# via --only). Running auth-login without --only would silently
|
||||
# register MCP in every detected agent — overriding the user's
|
||||
# explicit scope. Skip rather than over-register. See PR #41 B1.
|
||||
ui_step "3. Register the MCP server"
|
||||
ui_muted "Skipped — selected agents ($ALL_TARGETS) need manual MCP setup (see SKILL.md Fallback section)."
|
||||
else
|
||||
# Pin MCP registration to the same agent list the skill step
|
||||
# targeted. When MCP_TARGETS is empty (auto-detect found nothing),
|
||||
# let `@agentkey/cli` do its own detection — same fallback we use
|
||||
# for skill install. Older CLI versions silently ignore --only,
|
||||
# so this is forward-compatible.
|
||||
local AUTH_ARGS=(--auth-login)
|
||||
if [ -n "$MCP_TARGETS" ]; then
|
||||
AUTH_ARGS+=(--only "$MCP_TARGETS")
|
||||
fi
|
||||
|
||||
ui_step "3. Register the MCP server"
|
||||
ui_info "Opening your browser for AgentKey device authentication ..."
|
||||
ui_muted "If a browser doesn't open (SSH / Docker / headless), the auth URL is also printed below — open it on any device to finish."
|
||||
if [ -n "$MCP_TARGETS" ]; then
|
||||
ui_muted "Will register MCP in: $MCP_TARGETS"
|
||||
else
|
||||
ui_muted "If a browser doesn't open (SSH / Docker / headless), the auth URL is also printed below — open it on any device to finish."
|
||||
fi
|
||||
echo
|
||||
|
||||
# Telemetry context for `install_completed`. Opt-out is honored at
|
||||
@@ -446,14 +554,14 @@ main() {
|
||||
done
|
||||
export AGENTKEY_INSTALL_SOURCE="one_liner"
|
||||
export AGENTKEY_DETECTED_AGENTS="$(detect_agents)"
|
||||
export AGENTKEY_SELECTED_AGENTS="${TARGETS:-}"
|
||||
export AGENTKEY_SELECTED_AGENTS="${ALL_TARGETS:-}"
|
||||
export AGENTKEY_INSTALLER_FLAGS="$_flags"
|
||||
export AGENTKEY_DEVICE_FINGERPRINT="$(compute_device_fingerprint "$PLATFORM")"
|
||||
fi
|
||||
|
||||
if ! npx -y "$CLI_PACKAGE" --auth-login; then
|
||||
if ! npx -y "$CLI_PACKAGE" "${AUTH_ARGS[@]}"; then
|
||||
ui_error "MCP auth failed."
|
||||
ui_muted "Retry manually: npx -y $CLI_PACKAGE --auth-login"
|
||||
ui_muted "Retry manually: npx -y $CLI_PACKAGE ${AUTH_ARGS[*]}"
|
||||
exit 1
|
||||
fi
|
||||
ui_ok "MCP server registered"
|
||||
|
||||
Reference in New Issue
Block a user