Merge pull request #93 from jhuang-tw/fix/client-neutral-bootstrap-capability
fix: make MCP bootstrap client-neutral by default Owner integration: preserve all prior CI contracts, document explicit host selection on Windows, and make the Bash manifest regression UTF-8 deterministic.
This commit is contained in:
@@ -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: |
|
||||
|
||||
@@ -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: |
|
||||
|
||||
+11
-2
@@ -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
|
||||
- The package author is not responsible for misuse
|
||||
|
||||
+22
-6
@@ -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.
|
||||
+22
-6
@@ -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.
|
||||
@@ -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 "<skill-root>\scripts\bootstrap-reverse.ps1" -Capability @('jshookmcp')
|
||||
# 安装并注册 jshookmcp;Codex 可替换为 Claude 或 Both
|
||||
powershell -File "<skill-root>\scripts\bootstrap-reverse.ps1" -Capability @('jshookmcp') -McpHostTarget Codex
|
||||
|
||||
# 注册并启动 anything-analyzer
|
||||
powershell -File "<skill-root>\scripts\bootstrap-reverse.ps1" -Capability @('anything-analyzer') -StartServices
|
||||
powershell -File "<skill-root>\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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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 <capability1> [capability2] ... [--start-services] [--skip-refresh]
|
||||
# bash skills/scripts/bootstrap-reverse.sh <capability1> [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 <capability1> [capability2] ... [--start-services] [--skip-refresh]
|
||||
bash skills/scripts/bootstrap-reverse.sh <capability1> [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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user