diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b1a497..adde5f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,11 @@ jobs: shell: pwsh run: ./skills/scripts/test-workflow-title-safety.ps1 + - name: Client-neutral bootstrap/discovery (Windows PowerShell 5.1) + if: runner.os == 'Windows' + shell: powershell + run: ./skills/scripts/test-client-neutral-bootstrap.ps1 + - name: Offline sample case contract (Windows PowerShell 5.1) if: runner.os == 'Windows' shell: powershell @@ -129,6 +134,10 @@ jobs: echo "syntax OK: $f" done < <(git ls-files '*.sh') + - name: Client-neutral bootstrap/discovery (Bash) + shell: bash + run: bash skills/scripts/test-client-neutral-bootstrap.sh + - name: Structured routing parity (Bash) shell: bash run: | diff --git a/.github/workflows/macos-bash-compat.yml b/.github/workflows/macos-bash-compat.yml index 9b26bfa..b9a160a 100644 --- a/.github/workflows/macos-bash-compat.yml +++ b/.github/workflows/macos-bash-compat.yml @@ -37,6 +37,10 @@ jobs: echo "syntax OK: $f" done + - name: Client-neutral bootstrap/discovery + shell: bash + run: /bin/bash skills/scripts/test-client-neutral-bootstrap.sh + - name: Exercise structured router and case contract shell: bash run: | diff --git a/README_AI.md b/README_AI.md index 0816f8c..052f295 100644 --- a/README_AI.md +++ b/README_AI.md @@ -108,6 +108,15 @@ bash skills/scripts/case-guard.sh --case-root work/my-sample bash skills/scripts/bootstrap-reverse.sh --list ``` +MCP client registration is opt-in. Bootstrap defaults to installing or preparing the capability without writing client-global configuration. Select the target explicitly when registration is required: + +```text +Windows: powershell -File skills/scripts/bootstrap-reverse.ps1 -Capability jshookmcp -McpHostTarget Codex +Linux/macOS: bash skills/scripts/bootstrap-reverse.sh jshookmcp --mcp-host=codex +``` + +Use `Claude` / `claude` or `Both` / `both` for other supported targets. + Kali users should use the dedicated Kali bootstrap entrypoint: ```bash @@ -371,7 +380,7 @@ Whether you use Claude Code, Codex CLI, Cursor, Cline, Windsurf, or another code } ``` -The bootstrap command enables bearer authentication for Anything Analyzer and registers the generated token for supported clients. Manual configurations must include the `Authorization` header shown above. +The bootstrap command enables bearer authentication for Anything Analyzer. It registers the generated token only when an MCP host is explicitly selected (`-McpHostTarget` or `--mcp-host`). Manual configurations must include the `Authorization` header shown above. ### Minimum Prompt Requirements @@ -602,4 +611,4 @@ This project (`reverse-skill`) is primarily licensed under the **MIT License**. This package is intended only for legally authorized security research, learning, and CTF competitions. - Users must ensure all operations are within legal boundaries - Unauthorized penetration testing against other people's systems is illegal -- The package author is not responsible for misuse \ No newline at end of file +- The package author is not responsible for misuse diff --git a/docs/platforms/linux.md b/docs/platforms/linux.md index a24b60e..8fda80b 100644 --- a/docs/platforms/linux.md +++ b/docs/platforms/linux.md @@ -41,7 +41,7 @@ python3 -m pipx ensurepath | Ghidra | GitHub release ZIP | Flatpak / distro package | Java required. | | IDA Pro | manual Linux installer | — | Commercial tool; set `IDADIR` or document local path. | | BurpSuite | manual installer / jar | distro package if available | Load `burp-mcp-full` extension manually. | -| jshookmcp | `npx -y @jshookmcp/jshook@0.3.4` | MCP config command | Requires Node/npm/npx. | +| jshookmcp | `npx -y @jshookmcp/jshook@0.3.4` | MCP config command | Requires Node/npm/npx and explicit MCP registration. | | anything-analyzer | project clone + `pnpm install` | custom local service | Register its MCP endpoint in the Agent client. | | nuclei | GitHub release / `go install` | distro package if available | Often absent in Ubuntu apt. | | SecLists | `git clone https://github.com/danielmiessler/SecLists ~/tools/SecLists` | distro package if available | Keep path in tool index. | @@ -114,6 +114,16 @@ adb version ## MCP setup notes +The core bootstrap is client-neutral by default. It **does not write** `~/.claude/mcp.json` or `~/.codex/config.toml` unless an MCP host is explicitly selected. For MCP capabilities, use one of: + +```bash +--mcp-host=claude +--mcp-host=codex +--mcp-host=both +``` + +Without an explicit host, the bootstrap may prepare the runtime but reports the MCP capability as `registration-required`. Override the explicit adapter paths with `CLAUDE_MCP_CONFIG` or `CODEX_CONFIG_PATH` when needed. + ### BurpSuite MCP Build the extension: @@ -167,12 +177,18 @@ From the repository root, list all bootstrap capabilities: bash skills/scripts/bootstrap-reverse.sh --list ``` -Install/configure capabilities with the same core names as the Windows version: +Install ordinary capabilities without selecting an Agent client: ```bash bash skills/scripts/bootstrap-reverse.sh jadx apktool frida -bash skills/scripts/bootstrap-reverse.sh jshookmcp anything-analyzer -bash skills/scripts/bootstrap-reverse.sh idapro --start-services +``` + +For MCP registration, select the target client explicitly: + +```bash +bash skills/scripts/bootstrap-reverse.sh jshookmcp --mcp-host=codex +bash skills/scripts/bootstrap-reverse.sh anything-analyzer --mcp-host=claude +bash skills/scripts/bootstrap-reverse.sh idapro --start-services --mcp-host=both ``` Refresh the tool index only: @@ -189,7 +205,7 @@ skills/tool-index.md skills/tool-index.json ``` -The bootstrap script installs or configures supported capabilities where possible using the same core capability names as Windows. The refresh script detects common Linux/macOS tools and records install hints. Manual-only tools such as IDA Pro and BurpSuite still require local installation and app-specific setup. +The bootstrap script installs or prepares supported capabilities where possible using the same core capability names as Windows. MCP client registration is performed only when `--mcp-host=...` is explicit. The refresh script detects common Linux/macOS tools and discovers supported MCP registrations without choosing a default Agent client. Manual-only tools such as IDA Pro and BurpSuite still require local installation and app-specific setup. ## Validation checklist @@ -216,4 +232,4 @@ Use [`../../kali/README-kali.md`](../../kali/README-kali.md) when: - you want Kali-native security tooling and MCP integrations; - you need Metasploit, NetExec, responder, BloodHound, Certipy, HexStrike, or other offensive-security distributions pre-wired. -For Ubuntu / Debian, keep this generic Linux guide as the default and only borrow Kali scripts when the tool is known to exist on your system. +For Ubuntu / Debian, keep this generic Linux guide as the default and only borrow Kali scripts when the tool is known to exist on your system. \ No newline at end of file diff --git a/docs/platforms/macos.md b/docs/platforms/macos.md index 9ec2ab0..3b598d4 100644 --- a/docs/platforms/macos.md +++ b/docs/platforms/macos.md @@ -45,7 +45,7 @@ python3 -m pipx ensurepath | Ghidra | `brew install ghidra` or `brew install --cask ghidra` | GitHub release ZIP | Formula/cask availability may vary. | | IDA Pro | manual app install | — | Usually under `/Applications/IDA Professional*.app`. | | BurpSuite | `brew install --cask burp-suite` | manual jar / installer | Load `burp-mcp-full` extension manually. | -| jshookmcp | `npx -y @jshookmcp/jshook@0.3.4` | MCP config command | Requires Node/npm/npx. | +| jshookmcp | `npx -y @jshookmcp/jshook@0.3.4` | MCP config command | Requires Node/npm/npx and explicit MCP registration. | | anything-analyzer | project clone + `pnpm install` | custom local service | Register its MCP endpoint. | | nuclei | `brew install nuclei` | GitHub release / Go install | Optional security scanner. | | SecLists | Git clone | — | Usually clone to `~/tools/SecLists`. | @@ -100,6 +100,16 @@ If the service uses a custom port or token, update your Agent client's MCP confi ## MCP setup notes +The core bootstrap is client-neutral by default. It **does not write** `~/.claude/mcp.json` or `~/.codex/config.toml` unless an MCP host is explicitly selected. For MCP capabilities, use one of: + +```bash +--mcp-host=claude +--mcp-host=codex +--mcp-host=both +``` + +Without an explicit host, the bootstrap may prepare the runtime but reports the MCP capability as `registration-required`. Override the explicit adapter paths with `CLAUDE_MCP_CONFIG` or `CODEX_CONFIG_PATH` when needed. + ### BurpSuite MCP Build the extension: @@ -167,12 +177,18 @@ From the repository root, list the same core capability names as the Windows Pow The generic bootstrap is compatible with the system `/bin/bash` shipped by macOS (Bash 3.2); Homebrew Bash is not required. -Install or configure supported capabilities with the generic Bash bootstrap: +Install ordinary capabilities without selecting an Agent client: ```bash /bin/bash skills/scripts/bootstrap-reverse.sh jadx apktool frida -/bin/bash skills/scripts/bootstrap-reverse.sh jshookmcp anything-analyzer -/bin/bash skills/scripts/bootstrap-reverse.sh burpsuite-mcp +``` + +For MCP registration, select the target client explicitly: + +```bash +/bin/bash skills/scripts/bootstrap-reverse.sh jshookmcp --mcp-host=codex +/bin/bash skills/scripts/bootstrap-reverse.sh anything-analyzer --mcp-host=claude +/bin/bash skills/scripts/bootstrap-reverse.sh burpsuite-mcp --mcp-host=both ``` Refresh the local tool index only: @@ -188,7 +204,7 @@ skills/tool-index.md skills/tool-index.json ``` -`bootstrap-reverse.sh` installs/configures supported capabilities where possible on macOS, using Homebrew, `pipx`, `npm`, GitHub releases, and MCP registration. `refresh-tool-index.sh` is detection-only. Manual-only tools such as IDA Pro and BurpSuite still require local app installation and app-specific setup. +`bootstrap-reverse.sh` installs or prepares supported capabilities where possible on macOS using Homebrew, `pipx`, `npm`, and GitHub releases. MCP client registration occurs only when `--mcp-host=...` is explicit. `refresh-tool-index.sh` is detection-only and discovers supported MCP registrations without choosing a default Agent client. Manual-only tools such as IDA Pro and BurpSuite still require local app installation and app-specific setup. ## Validation checklist @@ -211,4 +227,4 @@ bash skills/scripts/refresh-tool-index.sh - GUI app paths vary by edition and version. Do not hard-code IDA or Burp paths unless you verified them locally. - Some security tools are Linux-first. Prefer Homebrew formulae first, then GitHub releases, then source builds. -- iOS analysis may require additional signing, device, and jailbreak-specific setup; keep those steps in a dedicated mobile reverse Skill rather than this generic platform page. +- iOS analysis may require additional signing, device, and jailbreak-specific setup; keep those steps in a dedicated mobile reverse Skill rather than this generic platform page. \ No newline at end of file diff --git a/skills/js-reverse/SKILL.md b/skills/js-reverse/SKILL.md index c2d57c5..458f247 100644 --- a/skills/js-reverse/SKILL.md +++ b/skills/js-reverse/SKILL.md @@ -31,7 +31,7 @@ description: 在使用 js-reverse-mcp 做前端 JavaScript 逆向时使用,适 如果当前任务明确提到 `jshookmcp`、`JS hook`、`CDP`、浏览器断点、网络拦截、SourceMap 或 AST 去混淆,也仍然走本 skill;只是把底层 MCP 面切到 `jshookmcp`,而不是把它当成一个新的总入口。 -前提条件:`jshookmcp` 不是本地裸命令工具,而是一个要先下载/注册/启用的 MCP server。只有在 Claude MCP 配置里接入并启用后,相关工具面才真的可调用。 +前提条件:`jshookmcp` 不是本地裸命令工具,而是一个要先下载、显式注册并启用的 MCP server。只有在所选客户端(Claude、Codex 等)的 MCP 配置里接入并启用后,相关工具面才真的可调用。 常用映射: @@ -180,29 +180,30 @@ description: 在使用 js-reverse-mcp 做前端 JavaScript 逆向时使用,适 ## 按需自举(On-Demand Bootstrap) -本 skill 依赖的 MCP 能力可通过统一自举系统自动注册。 +本 skill 依赖的 MCP 能力可通过统一自举系统安装;MCP 客户端注册必须显式选择目标,默认不会写任何客户端全局配置。 ### 自动化能力边界 | 能力 | 可自动注册 | 方式 | 说明 | |------|-----------|------|------| -| jshookmcp | ✓ | npm-mcp(npx 启动) | 自动写入 Claude MCP 配置 | -| anything-analyzer | ✓ | local-http-mcp | 自动注册 + 可自动启动服务 | +| jshookmcp | ✓ | npm-mcp(npx 启动) | 显式选择 Claude / Codex / Both 后注册 | +| anything-analyzer | ✓ | local-http-mcp | 可自动启动服务;客户端注册须显式选择 | | Node.js | ✓ | winget 安装 | 运行时依赖 | ### 自举方式 ```powershell -# 注册 jshookmcp 到 MCP 配置 -powershell -File "\scripts\bootstrap-reverse.ps1" -Capability @('jshookmcp') +# 安装并注册 jshookmcp;Codex 可替换为 Claude 或 Both +powershell -File "\scripts\bootstrap-reverse.ps1" -Capability @('jshookmcp') -McpHostTarget Codex # 注册并启动 anything-analyzer -powershell -File "\scripts\bootstrap-reverse.ps1" -Capability @('anything-analyzer') -StartServices +powershell -File "\scripts\bootstrap-reverse.ps1" -Capability @('anything-analyzer') -StartServices -McpHostTarget Codex ``` ### 注意事项 - `jshookmcp` 注册后仍需在 AI 客户端中**启用**该 MCP server 才能调用 +- 不传 `-McpHostTarget` 时只安装/准备能力并返回 registration-required,不修改 Claude 或 Codex 配置 - `anything-analyzer` 需要 pnpm 和项目源码,bootstrap 会自动 clone 并安装依赖 - 如果 Node.js 未安装,bootstrap 会先通过 winget 安装 Node.js 22 diff --git a/skills/pentest-tools/SKILL.md b/skills/pentest-tools/SKILL.md index bbba3d5..73ba24a 100644 --- a/skills/pentest-tools/SKILL.md +++ b/skills/pentest-tools/SKILL.md @@ -147,9 +147,11 @@ docker run -d -p 8080:8080 pentestmcp Reqable 桌面客户端可通过官方 [Reqable MCP Server](https://github.com/reqable/reqable-mcp-server) 暴露本地抓包、API、断点和规则能力。先单独安装并启动 Reqable,再登记 MCP: ```powershell -powershell -NoProfile -ExecutionPolicy Bypass -File skills\scripts\bootstrap-reverse.ps1 -Capability reqable-mcp +powershell -NoProfile -ExecutionPolicy Bypass -File skills\scripts\bootstrap-reverse.ps1 -Capability reqable-mcp -McpHostTarget Codex ``` +将 `Codex` 替换为 `Claude` 或 `Both` 可选择对应客户端;省略 `-McpHostTarget` 时不会写任何客户端全局配置。 + 登记后的 stdio 配置为: ```json diff --git a/skills/scripts/bootstrap-reverse.ps1 b/skills/scripts/bootstrap-reverse.ps1 index 0c12862..757eaa1 100644 --- a/skills/scripts/bootstrap-reverse.ps1 +++ b/skills/scripts/bootstrap-reverse.ps1 @@ -9,8 +9,8 @@ param( [switch]$StartServices, - [ValidateSet('Claude', 'Codex', 'Both')] - [string]$McpHostTarget = 'Both' + [ValidateSet('None', 'Claude', 'Codex', 'Both')] + [string]$McpHostTarget = 'None' ) # 临时目录统一入口($env:TEMP 在 Linux/macOS 上可能未设置) @@ -96,7 +96,8 @@ function Get-McpHostTargets { switch ($McpHostTarget) { 'Claude' { return @('Claude') } 'Codex' { return @('Codex') } - default { return @('Claude', 'Codex') } + 'Both' { return @('Claude', 'Codex') } + default { return @() } } } @@ -834,7 +835,7 @@ function Ensure-Capability { if ($definition.PSObject.Properties['canAutoInstall'] -and $definition.canAutoInstall -eq $false) { $hint = if ($definition.PSObject.Properties['manualInstallHint']) { $definition.manualInstallHint } else { "Please install $Name manually. Docs: $($definition.docsUrl)" } Write-Warning "MANUAL_INSTALL_REQUIRED: $Name — $hint" - # Still try to register MCP URL if applicable + # Still try to register MCP URL if applicable and a host was explicitly selected. if ($definition.PSObject.Properties['mcpNames'] -and $definition.PSObject.Properties['mcpUrl']) { Ensure-McpServer -ServerName $definition.mcpNames[0] -ServerDefinition @{ url = $definition.mcpUrl } } diff --git a/skills/scripts/bootstrap-reverse.sh b/skills/scripts/bootstrap-reverse.sh index fae4b01..7f27937 100644 --- a/skills/scripts/bootstrap-reverse.sh +++ b/skills/scripts/bootstrap-reverse.sh @@ -5,12 +5,12 @@ # Supports the same capability names and the same high-level modes: # - dependency expansion # - package / release / pipx / npm installation -# - MCP registration hints / config writing +# - optional, explicit MCP host registration # - optional service start with --start-services # - refresh tool index unless --skip-refresh # # Usage: -# bash skills/scripts/bootstrap-reverse.sh [capability2] ... [--start-services] [--skip-refresh] +# bash skills/scripts/bootstrap-reverse.sh [capability2] ... [--start-services] [--skip-refresh] [--mcp-host=none|claude|codex|both] # bash skills/scripts/bootstrap-reverse.sh --list set -euo pipefail @@ -26,7 +26,9 @@ if [[ -z "$TOOLS_ROOT" || "$TOOLS_ROOT" == "/" || "$TOOLS_ROOT" == "$HOME" ]]; t echo "Unsafe REVERSE_SKILL_TOOLS_DIR: $TOOLS_ROOT" >&2 exit 2 fi -MCP_CONFIG_PATH="${CLAUDE_MCP_CONFIG:-$HOME/.claude/mcp.json}" +CLAUDE_MCP_CONFIG_PATH="${CLAUDE_MCP_CONFIG:-$HOME/.claude/mcp.json}" +CODEX_MCP_CONFIG_PATH="${CODEX_CONFIG_PATH:-$HOME/.codex/config.toml}" +MCP_HOST_TARGET="none" MANIFEST_PATH="$SCRIPT_DIR/bootstrap-manifest.json" UNAME_S="$(uname -s 2>/dev/null || echo unknown)" @@ -42,6 +44,7 @@ LIST_ONLY=false MANUAL_REQUIRED=false FAILED=false LAST_CAPABILITY_MANUAL=false +LAST_CAPABILITY_REGISTRATION_REQUIRED=false CAPABILITIES=() for arg in "$@"; do @@ -50,6 +53,10 @@ for arg in "$@"; do --skip-refresh) SKIP_REFRESH=true ;; --list|-l) LIST_ONLY=true ;; --help|-h) CAPABILITIES+=("__help__") ;; + --mcp-host=none|--mcp-host=claude|--mcp-host=codex|--mcp-host=both) + MCP_HOST_TARGET="${arg#--mcp-host=}" + ;; + --mcp-host=*) echo "Invalid MCP host target: ${arg#--mcp-host=}" >&2; exit 2 ;; -*) echo "Unknown option: $arg" >&2; exit 2 ;; *) CAPABILITIES+=("$arg") ;; esac @@ -170,7 +177,7 @@ platform_doc() { print_usage() { cat <<'EOF' Usage: - bash skills/scripts/bootstrap-reverse.sh [capability2] ... [--start-services] [--skip-refresh] + bash skills/scripts/bootstrap-reverse.sh [capability2] ... [--start-services] [--skip-refresh] [--mcp-host=none|claude|codex|both] bash skills/scripts/bootstrap-reverse.sh --list Capabilities (parity with bootstrap-reverse.ps1): @@ -180,14 +187,16 @@ Capabilities (parity with bootstrap-reverse.ps1): Examples: bash skills/scripts/bootstrap-reverse.sh jadx apktool frida - bash skills/scripts/bootstrap-reverse.sh jshookmcp reqable-mcp - bash skills/scripts/bootstrap-reverse.sh idapro --start-services + bash skills/scripts/bootstrap-reverse.sh jshookmcp --mcp-host=codex + bash skills/scripts/bootstrap-reverse.sh reqable-mcp --mcp-host=claude + bash skills/scripts/bootstrap-reverse.sh idapro --start-services --mcp-host=both bash skills/scripts/bootstrap-reverse.sh burpsuite-mcp Notes: - This script supports Linux and macOS. - - It writes MCP config to ~/.claude/mcp.json by default. - - Override with CLAUDE_MCP_CONFIG=/path/to/mcp.json. + - MCP host registration is opt-in. The default is --mcp-host=none and does not write client-global config. + - Explicit Claude registration uses CLAUDE_MCP_CONFIG or ~/.claude/mcp.json. + - Explicit Codex registration uses CODEX_CONFIG_PATH or ~/.codex/config.toml. - Override install root with REVERSE_SKILL_TOOLS_DIR=~/tools. EOF } @@ -490,11 +499,11 @@ PY fi } -write_mcp_server() { +write_claude_mcp_server() { local name="$1" local json_payload="$2" - ensure_dir "$(dirname "$MCP_CONFIG_PATH")" - python3 - "$MCP_CONFIG_PATH" "$name" "$json_payload" <<'PY' + ensure_dir "$(dirname "$CLAUDE_MCP_CONFIG_PATH")" + python3 - "$CLAUDE_MCP_CONFIG_PATH" "$name" "$json_payload" <<'PY' import json, pathlib, sys path = pathlib.Path(sys.argv[1]) name = sys.argv[2] @@ -508,9 +517,84 @@ else: data = {} data.setdefault('mcpServers', {})[name] = payload path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8') -print(path) PY - log_ok "MCP server '$name' registered in $MCP_CONFIG_PATH" + log_ok "MCP server '$name' registered for Claude in $CLAUDE_MCP_CONFIG_PATH" +} + +write_codex_mcp_server() { + local name="$1" + local json_payload="$2" + ensure_dir "$(dirname "$CODEX_MCP_CONFIG_PATH")" + python3 - "$CODEX_MCP_CONFIG_PATH" "$name" "$json_payload" <<'PY' +import json, pathlib, re, sys +path = pathlib.Path(sys.argv[1]) +name = sys.argv[2] +payload = json.loads(sys.argv[3]) +lines = path.read_text(encoding='utf-8').splitlines() if path.exists() else [] +header = re.compile(r'^\s*\[mcp_servers\.([^\].]+)(?:\.env)?\]\s*$') +out = [] +skip = False +for line in lines: + match = header.match(line) + if line.lstrip().startswith('['): + if match and match.group(1) == name: + skip = True + continue + if skip: + skip = False + if not skip: + out.append(line) +while out and not out[-1].strip(): + out.pop() +if out: + out.append('') + +def literal(value): + if isinstance(value, bool): + return 'true' if value else 'false' + if isinstance(value, (int, float)): + return str(value) + if isinstance(value, list): + return '[' + ', '.join(literal(v) for v in value) + ']' + text = str(value).replace('\\', '\\\\').replace('"', '\\"') + return f'"{text}"' + +out.append(f'[mcp_servers.{name}]') +for key in ('type', 'url', 'command', 'args', 'bearer_token_env_var'): + if key in payload: + out.append(f'{key} = {literal(payload[key])}') +for key in sorted(k for k in payload if k not in {'type', 'url', 'command', 'args', 'bearer_token_env_var', 'env', 'headers'}): + out.append(f'{key} = {literal(payload[key])}') +env = payload.get('env') +if isinstance(env, dict) and env: + out.append('') + out.append(f'[mcp_servers.{name}.env]') + for key in sorted(env): + out.append(f'{key} = {literal(env[key])}') +path.write_text('\n'.join(out) + '\n', encoding='utf-8') +PY + log_ok "MCP server '$name' registered for Codex in $CODEX_MCP_CONFIG_PATH" +} + +write_mcp_server() { + local name="$1" + local json_payload="$2" + case "$MCP_HOST_TARGET" in + none) + LAST_CAPABILITY_REGISTRATION_REQUIRED=true + log_warn "MCP registration skipped for '$name' (client-neutral default). Re-run with --mcp-host=claude, codex, or both." + ;; + claude) + write_claude_mcp_server "$name" "$json_payload" + ;; + codex) + write_codex_mcp_server "$name" "$json_payload" + ;; + both) + write_claude_mcp_server "$name" "$json_payload" + write_codex_mcp_server "$name" "$json_payload" + ;; + esac } test_tcp_port() { @@ -855,7 +939,7 @@ import json, sys print(json.dumps({'command':'docker','args':['run','--rm','-i',sys.argv[1],'mcp','serve']})) PY )" - log_warn "pentestswarm Go install failed or produced no runnable binary; registered Docker fallback $docker_image" + log_warn "pentestswarm Go install failed or produced no runnable binary; prepared Docker fallback $docker_image" else manual_required pentestswarm "Install Go 1.24+ or Docker, then install Pentest-Swarm-AI and ensure pentestswarm is on PATH." fi @@ -959,7 +1043,7 @@ while IFS= read -r capability; do EXPANDED+=("$capability") done < <(expand_capabilities "${CAPABILITIES[@]}") -log_info "platform=$PLATFORM doc=$(platform_doc) tools_root=$TOOLS_ROOT" +log_info "platform=$PLATFORM doc=$(platform_doc) tools_root=$TOOLS_ROOT mcp_host=$MCP_HOST_TARGET" if ! ensure_python_interpreter; then log_err "Python 3 is required to read bootstrap-manifest.json; no capability was executed." @@ -969,9 +1053,12 @@ fi for cap in "${EXPANDED[@]}"; do log_info "ensure $cap" LAST_CAPABILITY_MANUAL=false + LAST_CAPABILITY_REGISTRATION_REQUIRED=false if ensure_capability "$cap"; then if $LAST_CAPABILITY_MANUAL; then status_json_line "$cap" "manual-required" "see $(platform_doc)" >> "$RESULTS_FILE" + elif $LAST_CAPABILITY_REGISTRATION_REQUIRED; then + status_json_line "$cap" "registration-required" "re-run with --mcp-host=claude, codex, or both" >> "$RESULTS_FILE" else status_json_line "$cap" "ready" >> "$RESULTS_FILE" fi diff --git a/skills/scripts/lib/ToolDiscovery.ps1 b/skills/scripts/lib/ToolDiscovery.ps1 index 62fe437..9a60dc4 100644 --- a/skills/scripts/lib/ToolDiscovery.ps1 +++ b/skills/scripts/lib/ToolDiscovery.ps1 @@ -360,22 +360,18 @@ function Get-ReverseToolCatalog { [pscustomobject]@{ Name = 'jshookmcp' Skill = 'js-reverse' - Purpose = '通过 npx 启动 @jshookmcp/jshook MCP(仍需先配置并启用 MCP server)' + Purpose = '通过 npx 启动 @jshookmcp/jshook MCP(需 MCP 注册;npx 本身不代表该能力已安装)' FixedVersion = '@jshookmcp/jshook@0.3.4' VersionArgs = @() - Fallbacks = @( - [pscustomobject]@{ Type = 'command'; Value = 'npx' } - ) + Fallbacks = @() } [pscustomobject]@{ Name = 'reqable-mcp' Skill = 'pentest-tools' - Purpose = '通过 npx 启动 Reqable 桌面客户端 MCP(仍需先安装并启动 Reqable)' + Purpose = '通过 npx 启动 Reqable 桌面客户端 MCP(需 MCP 注册与 Reqable;npx 本身不代表该能力已安装)' FixedVersion = 'reqable-mcp-server@1.0.1' VersionArgs = @() - Fallbacks = @( - [pscustomobject]@{ Type = 'command'; Value = 'npx' } - ) + Fallbacks = @() } [pscustomobject]@{ Name = 'agent-browser' @@ -896,6 +892,17 @@ function Get-ReverseCapabilityState { $toolReady = $false } + $runtimeReady = $toolReady + if ($definition.bootstrapKind -eq 'npm-mcp') { + try { + $runtimeSpec = Resolve-ReverseToolSpec -Name 'npx' + $runtimeReady = [bool]$runtimeSpec.Available + } + catch { + $runtimeReady = $false + } + } + $verificationMode = if ($definition.PSObject.Properties['verificationMode']) { [string]$definition.verificationMode } else { '' } $ready = $toolReady if ($definition.PSObject.Properties['mcpNames']) { @@ -908,7 +915,7 @@ function Get-ReverseCapabilityState { } default { if ($definition.bootstrapKind -eq 'npm-mcp') { - $ready = $registered -and $toolReady + $ready = $registered -and $runtimeReady } else { $ready = $registered -or $toolReady @@ -924,6 +931,7 @@ function Get-ReverseCapabilityState { DocsUrl = [string]$definition.docsUrl Ready = $ready Registered = $registered + RuntimeAvailable = $runtimeReady ServiceOnline = $serviceOnline McpHttpVerified = $mcpHttpVerified } diff --git a/skills/scripts/refresh-tool-index.ps1 b/skills/scripts/refresh-tool-index.ps1 index ef62cbd..31acb18 100644 --- a/skills/scripts/refresh-tool-index.ps1 +++ b/skills/scripts/refresh-tool-index.ps1 @@ -77,8 +77,8 @@ $markdownLines = @( '', "- 扫描时间: $generatedAt", '- 路由入口: `SKILL.md` → `routing.md` → 对应子 skill', - '- 说明: 本表由 `skills/scripts/refresh-tool-index.ps1` 自动生成,优先用于 Claude 路由和工具路径确认。', - '- 注意: 对于 jshookmcp 这类 MCP server,`yes` 只表示本机具备通过 node/npx 拉起它的条件,不表示它已经在 MCP 配置里注册并启用。', + '- 说明: 本表由 `skills/scripts/refresh-tool-index.ps1` 自动生成,用于各 Agent 客户端的路由和工具路径确认。', + '- 注意: MCP-only 能力的工具可用性与运行时分开计算;`npx` 只代表 npm MCP 的运行条件,不能单独让 jshookmcp / reqable-mcp 变成可用或 Ready。', '', '| 工具 | 归属 skill | 作用 | 可用 | 路径 | 版本 | 来源 | 脚本引用 |', '|---|---|---|---|---|---|---|---|' @@ -180,4 +180,3 @@ $jsonPayload | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $OutputJson -E "markdown=$OutputMarkdown" "json=$OutputJson" "tools=$($reports.Count)" - diff --git a/skills/scripts/refresh-tool-index.sh b/skills/scripts/refresh-tool-index.sh index 578fb41..daafe4f 100644 --- a/skills/scripts/refresh-tool-index.sh +++ b/skills/scripts/refresh-tool-index.sh @@ -129,12 +129,11 @@ TOOLS=( "nuclei|pentest-tools|Template-based vulnerability scanner|nuclei|nuclei -version|" "binwalk|firmware-pentest|Firmware extraction and analysis|binwalk|binwalk --version|" "seclists|pentest-tools|Security wordlists|none|none|$HOME/tools/SecLists;/usr/share/seclists" - "jshookmcp|js-reverse|JS/CDP/Hook MCP runtime via npx|npx|npx --version|" - "reqable-mcp|pentest-tools|Reqable desktop MCP runtime via npx|npx|npx --version|" + "jshookmcp|js-reverse|JS/CDP/Hook MCP capability (requires registration + npx runtime)|none|none|" + "reqable-mcp|pentest-tools|Reqable MCP capability (requires registration + npx runtime)|none|none|" "jeb-pro|apk-reverse|Commercial Android/ARM decompiler (manual licensed install)|jeb,jeb_wincon|jeb --version|$HOME/tools/JEB/jeb;$HOME/JEB/jeb;/opt/jeb/jeb" "anything-analyzer|browser-automation|Browser/HTTP analyzer MCP project|none|none|$HOME/tools/anything-analyzer;$REPO_ROOT/../anything-analyzer" "burp-mcp-full|burp-mcp|Local Burp MCP extension and stdio bridge|none|none|$REPO_ROOT/burp-mcp-full/mcp-bridge.js" - "binwalk|firmware-pentest|Firmware extraction and analysis|binwalk|binwalk --version|" "yara|malware-analysis|Malware rule matching engine|yara|yara --version|" "pwntools|reverse-engineering|CTF pwn exploit development framework|pwn|pwn --version|" ) @@ -224,41 +223,40 @@ done } >> "$OUTPUT_MD" # --- Capability status view ------------------------------------------------- -# Parity with skills/scripts/refresh-tool-index.ps1 (the "能力状态视图" block). -# Computed by parsing skills/scripts/bootstrap-manifest.json plus probing the -# local MCP config and each declared servicePort. All logic is contained in the -# Python heredoc below so this script keeps its existing bash dependencies. +# Parity with skills/scripts/refresh-tool-index.ps1. Registration is discovered +# across supported host adapters rather than assuming one default AI client. MANIFEST_PATH="$SCRIPT_DIR/bootstrap-manifest.json" -MCP_CONFIG_PATH_FOR_CAP="${CLAUDE_MCP_CONFIG:-$HOME/.claude/mcp.json}" +CLAUDE_MCP_CONFIG_PATH_FOR_CAP="${CLAUDE_MCP_CONFIG:-$HOME/.claude/mcp.json}" +CODEX_MCP_CONFIG_PATH_FOR_CAP="${CODEX_CONFIG_PATH:-$HOME/.codex/config.toml}" CAP_RECORDS_TMP="$(mktemp)" -# Replace earlier single-file trap with one that also cleans this capability tmp file. trap 'rm -f "$records_tmp" "$CAP_RECORDS_TMP"' EXIT if [[ -f "$MANIFEST_PATH" ]]; then - # Pass tool availability info (collected earlier) so the capability view can - # reflect the same "tool ready" judgement as the tool table above. - python3 - "$MANIFEST_PATH" "$MCP_CONFIG_PATH_FOR_CAP" "$records_tmp" "$CAP_RECORDS_TMP" <<'PY' -import json, pathlib, socket, sys, urllib.request + python3 - "$MANIFEST_PATH" "$CLAUDE_MCP_CONFIG_PATH_FOR_CAP" "$CODEX_MCP_CONFIG_PATH_FOR_CAP" "$records_tmp" "$CAP_RECORDS_TMP" <<'PY' +import json, pathlib, re, socket, sys, urllib.request -manifest_path, mcp_config_path, tool_records_path, out_path = sys.argv[1:5] +manifest_path, claude_config_path, codex_config_path, tool_records_path, out_path = sys.argv[1:6] -# Load capability definitions try: manifest = json.loads(pathlib.Path(manifest_path).read_text(encoding='utf-8')) except Exception: manifest = {'capabilities': []} capabilities = manifest.get('capabilities', []) -# Load currently registered MCP server names registered_names = set() try: - mcp_data = json.loads(pathlib.Path(mcp_config_path).read_text(encoding='utf-8')) - registered_names = set(mcp_data.get('mcpServers', {}).keys()) + mcp_data = json.loads(pathlib.Path(claude_config_path).read_text(encoding='utf-8')) + registered_names.update(mcp_data.get('mcpServers', {}).keys()) +except Exception: + pass +try: + codex_text = pathlib.Path(codex_config_path).read_text(encoding='utf-8') + pattern = re.compile(r'^\s*\[mcp_servers\.([^\].]+)\]\s*$', re.MULTILINE) + registered_names.update(pattern.findall(codex_text)) except Exception: pass -# Load tool availability from the table we already wrote (best-effort match by name) tool_available = {} try: with open(tool_records_path, encoding='utf-8') as f: @@ -311,6 +309,7 @@ for cap in capabilities: mcp_http_verified = mcp_http_handshake(service_port) tool_ready = bool(tool_available.get(name, False)) + runtime_ready = bool(tool_available.get('npx', False)) if bootstrap_kind == 'npm-mcp' else tool_ready if mcp_names: if verification_mode == 'service-and-registration': @@ -318,7 +317,7 @@ for cap in capabilities: elif verification_mode == 'service-or-registration': ready = registered or service_online elif bootstrap_kind == 'npm-mcp': - ready = registered and tool_ready + ready = registered and runtime_ready else: ready = registered or tool_ready else: @@ -327,6 +326,7 @@ for cap in capabilities: rows.append({ 'name': name, 'tool_available': tool_ready, + 'runtime_available': runtime_ready, 'ready': ready, 'mcp_registered': registered if mcp_names else None, 'service_online': service_online if service_port else None, @@ -339,7 +339,6 @@ with open(out_path, 'w', encoding='utf-8') as f: json.dump(rows, f, ensure_ascii=False) PY - # Append the markdown capability table (mirrors the headings used in the ps1 version) { echo "" echo "---" @@ -353,9 +352,9 @@ PY python3 - "$CAP_RECORDS_TMP" "$OUTPUT_MD" <<'PY' import json, sys rows = json.loads(open(sys.argv[1], encoding='utf-8').read()) -def yn(v): # True->✓, False->✗ +def yn(v): return '✓' if v else '✗' -def opt(v): # True->✓, False->—, None->— +def opt(v): if v is True: return '✓' if v is False: return '—' return '—' @@ -367,7 +366,7 @@ with open(sys.argv[2], 'a', encoding='utf-8') as out: f"{opt(r['mcp_http_verified'])} | {yn(r['can_auto_install'])} | " f"{r['bootstrap_kind'] or '—'} |\n" ) - out.write("\n> ✓ = 是 | ✗ = 否 | — = 不适用或未检测\n\n") + out.write("\n> ✓ = 是 | ✗ = 否 | — = 不适用或未检测。npm-mcp 的 Ready 使用 MCP 注册状态 + npx runtime;npx 本身不会让某个 MCP capability 变成工具可用。\n\n") PY else CAP_RECORDS_TMP="" diff --git a/skills/scripts/test-bootstrap-manifest.sh b/skills/scripts/test-bootstrap-manifest.sh index 080e43a..3e09023 100644 --- a/skills/scripts/test-bootstrap-manifest.sh +++ b/skills/scripts/test-bootstrap-manifest.sh @@ -75,7 +75,7 @@ chmod +x "$STUB_BIN/python3" json_value() { "$REAL_PYTHON" - "$MANIFEST" "$1" "$2" <<'PY' import json, pathlib, sys -d=json.loads(pathlib.Path(sys.argv[1]).read_text()) +d=json.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8')) if sys.argv[2] == 'dependency': v=d['bootstrapDependencies'][sys.argv[3]]['package'] else: v=next(x for x in d['capabilities'] if x['name']==sys.argv[2])[sys.argv[3]] print(v) @@ -142,9 +142,9 @@ mkdir -p "$BROKEN_DIR" cp "$BOOTSTRAP" "$BROKEN_DIR/bootstrap-reverse.sh" "$REAL_PYTHON" - "$MANIFEST" "$BROKEN_DIR/bootstrap-manifest.json" <<'PY' import json, pathlib, sys -data = json.loads(pathlib.Path(sys.argv[1]).read_text()) +data = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8')) next(x for x in data['capabilities'] if x['name'] == 'agent-browser')['npmPackage'] = '' -pathlib.Path(sys.argv[2]).write_text(json.dumps(data)) +pathlib.Path(sys.argv[2]).write_text(json.dumps(data), encoding='utf-8') PY : > "$CALL_LOG" set +e diff --git a/skills/scripts/test-client-neutral-bootstrap.ps1 b/skills/scripts/test-client-neutral-bootstrap.ps1 new file mode 100644 index 0000000..581eb89 --- /dev/null +++ b/skills/scripts/test-client-neutral-bootstrap.ps1 @@ -0,0 +1,63 @@ +#requires -Version 5 + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$bootstrap = Join-Path $scriptDir 'bootstrap-reverse.ps1' +$toolDiscovery = Join-Path $scriptDir 'lib\ToolDiscovery.ps1' +$scratch = Join-Path ([System.IO.Path]::GetTempPath()) ('reverse-client-neutral-' + [guid]::NewGuid().ToString('n')) +$binDir = Join-Path $scratch 'bin' +$clientDir = Join-Path $scratch 'client' +$claudeConfig = Join-Path $clientDir 'claude.json' +$codexConfig = Join-Path $clientDir 'codex.toml' + +New-Item -ItemType Directory -Force -Path $binDir, $clientDir | Out-Null +try { + foreach ($name in @('node', 'npm', 'npx')) { + $cmdPath = Join-Path $binDir ($name + '.cmd') + @('@echo off', 'if "%1"=="--version" echo 1.0.0', 'exit /b 0') | Set-Content -LiteralPath $cmdPath -Encoding ascii + } + + $oldPath = $env:PATH + $oldClaudeConfig = $env:CLAUDE_MCP_CONFIG + $oldCodexConfig = $env:CODEX_CONFIG_PATH + $env:PATH = "$binDir;$oldPath" + $env:CLAUDE_MCP_CONFIG = $claudeConfig + $env:CODEX_CONFIG_PATH = $codexConfig + + $defaultOutput = (& $bootstrap -Capability jshookmcp -SkipRefresh | Out-String) + if (Test-Path -LiteralPath $claudeConfig) { throw 'default bootstrap wrote Claude global config' } + if (Test-Path -LiteralPath $codexConfig) { throw 'default bootstrap wrote Codex global config' } + if ($defaultOutput -notmatch 'configured-not-ready') { throw 'default MCP bootstrap did not report configured-not-ready' } + + $codexOutput = (& $bootstrap -Capability jshookmcp -SkipRefresh -McpHostTarget Codex | Out-String) + if (Test-Path -LiteralPath $claudeConfig) { throw 'Codex-only bootstrap wrote Claude config' } + if (-not (Test-Path -LiteralPath $codexConfig)) { throw 'Codex-only bootstrap did not write Codex config' } + if ((Get-Content -LiteralPath $codexConfig -Raw) -notmatch '(?m)^\[mcp_servers\.jshook\]\r?$') { throw 'Codex config missing jshook MCP block' } + if ($codexOutput -notmatch '"status"\s*:\s*"ready"') { throw 'Codex-only bootstrap did not report ready' } + + . $toolDiscovery + $tool = Resolve-ReverseToolSpec -Name 'jshookmcp' + if ($tool.Available) { throw 'npx must not masquerade as jshookmcp tool availability' } + $state = Get-ReverseCapabilityState -Name 'jshookmcp' + if (-not $state.Registered) { throw 'Codex-only registration was not discovered' } + if (-not $state.RuntimeAvailable) { throw 'npx runtime was not detected' } + if (-not $state.Ready) { throw 'Codex registration plus npx runtime should be ready' } + + Remove-Item -LiteralPath $codexConfig -Force + $claudeOutput = (& $bootstrap -Capability jshookmcp -SkipRefresh -McpHostTarget Claude | Out-String) + if (-not (Test-Path -LiteralPath $claudeConfig)) { throw 'Claude-only bootstrap did not write Claude config' } + if (Test-Path -LiteralPath $codexConfig) { throw 'Claude-only bootstrap wrote Codex config' } + $json = Get-Content -LiteralPath $claudeConfig -Raw -Encoding UTF8 | ConvertFrom-Json + if ($null -eq $json.mcpServers.jshook) { throw 'Claude config missing jshook MCP server' } + if ($claudeOutput -notmatch '"status"\s*:\s*"ready"') { throw 'Claude-only bootstrap did not report ready' } + + Write-Host 'client-neutral PowerShell bootstrap/discovery regression passed' +} +finally { + if ($null -ne $oldPath) { $env:PATH = $oldPath } + $env:CLAUDE_MCP_CONFIG = $oldClaudeConfig + $env:CODEX_CONFIG_PATH = $oldCodexConfig + Remove-Item -LiteralPath $scratch -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/skills/scripts/test-client-neutral-bootstrap.sh b/skills/scripts/test-client-neutral-bootstrap.sh new file mode 100644 index 0000000..2b2c641 --- /dev/null +++ b/skills/scripts/test-client-neutral-bootstrap.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BOOTSTRAP="$SCRIPT_DIR/bootstrap-reverse.sh" +REFRESH="$SCRIPT_DIR/refresh-tool-index.sh" +SCRATCH="$(mktemp -d /tmp/reverse-client-neutral-XXXXXX)" +trap 'rm -rf "$SCRATCH"' EXIT + +HOME_DIR="$SCRATCH/home" +BIN_DIR="$SCRATCH/bin" +TOOLS_DIR="$SCRATCH/tools" +CLAUDE_CFG="$SCRATCH/client/claude.json" +CODEX_CFG="$SCRATCH/client/codex.toml" +mkdir -p "$HOME_DIR" "$BIN_DIR" "$TOOLS_DIR" "$(dirname "$CLAUDE_CFG")" + +for name in node npm npx; do + cat > "$BIN_DIR/$name" <<'STUB' +#!/usr/bin/env bash +if [[ "${1:-}" == "--version" ]]; then echo 1.0.0; fi +exit 0 +STUB + chmod +x "$BIN_DIR/$name" +done + +export PATH="$BIN_DIR:$PATH" +export HOME="$HOME_DIR" +export REVERSE_SKILL_TOOLS_DIR="$TOOLS_DIR" +export CLAUDE_MCP_CONFIG="$CLAUDE_CFG" +export CODEX_CONFIG_PATH="$CODEX_CFG" + +MD="$SCRATCH/tool-index.md" +JSON="$SCRATCH/tool-index.json" +bash "$REFRESH" "$MD" "$JSON" >/dev/null + +python3 - "$JSON" <<'PY' +import json, sys +data = json.load(open(sys.argv[1], encoding='utf-8')) +tools = data['tools'] +assert sum(t['name'] == 'binwalk' for t in tools) == 1, 'binwalk must appear exactly once' +by_tool = {t['name']: t for t in tools} +assert by_tool['npx']['available'] is True +assert by_tool['jshookmcp']['available'] is False, 'npx must not masquerade as jshookmcp' +assert by_tool['reqable-mcp']['available'] is False, 'npx must not masquerade as reqable-mcp' +by_cap = {c['name']: c for c in data['capabilities']} +assert by_cap['jshookmcp']['ready'] is False +assert by_cap['reqable-mcp']['ready'] is False +PY + +cat > "$CODEX_CFG" <<'EOF' +[mcp_servers.jshook] +command = "npx" +args = ["-y", "@jshookmcp/jshook@0.3.4"] +EOF +bash "$REFRESH" "$MD" "$JSON" >/dev/null +python3 - "$JSON" <<'PY' +import json, sys +data = json.load(open(sys.argv[1], encoding='utf-8')) +cap = {c['name']: c for c in data['capabilities']}['jshookmcp'] +assert cap['mcp_registered'] is True, 'Codex-only MCP registration must be discovered' +assert cap['runtime_available'] is True +assert cap['ready'] is True, 'registered npm MCP + npx runtime should be ready' +PY + +rm -f "$CLAUDE_CFG" "$CODEX_CFG" +default_out="$(bash "$BOOTSTRAP" jshookmcp --skip-refresh)" +[[ "$default_out" == *'"status":"registration-required"'* || "$default_out" == *'"status": "registration-required"'* ]] +[[ ! -e "$CLAUDE_CFG" ]] +[[ ! -e "$CODEX_CFG" ]] + +codex_out="$(bash "$BOOTSTRAP" jshookmcp --skip-refresh --mcp-host=codex)" +[[ "$codex_out" == *'"status":"ready"'* || "$codex_out" == *'"status": "ready"'* ]] +[[ ! -e "$CLAUDE_CFG" ]] +grep -Eq '^\[mcp_servers\.jshook\]$' "$CODEX_CFG" + +rm -f "$CLAUDE_CFG" "$CODEX_CFG" +claude_out="$(bash "$BOOTSTRAP" jshookmcp --skip-refresh --mcp-host=claude)" +[[ "$claude_out" == *'"status":"ready"'* || "$claude_out" == *'"status": "ready"'* ]] +[[ -f "$CLAUDE_CFG" ]] +[[ ! -e "$CODEX_CFG" ]] +python3 - "$CLAUDE_CFG" <<'PY' +import json, sys +data = json.load(open(sys.argv[1], encoding='utf-8')) +assert 'jshook' in data.get('mcpServers', {}) +PY + +echo 'client-neutral Bash bootstrap/discovery regression passed'