1#!/usr/bin/env bash
Checks launch.sh the way a client meets it: a bare environment, scripted stdio, timed. Runs against a scratch copy of the layout, so the real run/bin is never moved (a live client may be reconnecting through it).
5set -euo pipefail
7REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" 8scratch="$(mktemp -d)" 9trap 'rm -rf "$scratch"' EXIT 10mkdir -p "$scratch/tools/mcp" "$scratch/run" 11cp "$REPO_ROOT/tools/mcp/launch.sh" "$scratch/tools/mcp/" 12 13INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}' 14READY='{"jsonrpc":"2.0","method":"notifications/initialized"}' 15LIST='{"jsonrpc":"2.0","id":2,"method":"tools/list"}' 16failed=0
$1 = case name, $2 = the id the last line must answer, $3 = what it must hold
19check() { 20 local name="$1" id="$2" want="$3" start reply ms 21 start="$EPOCHREALTIME" 22 coproc LAUNCH { env -i HOME="$HOME" PATH=/usr/bin:/bin "$scratch/tools/mcp/launch.sh" 2>/dev/null; } 23 printf '%s\n' "$INIT" >&"${LAUNCH[1]}" 24 [[ "$id" == 2 ]] && IFS= read -r -t 5 reply <&"${LAUNCH[0]}" && printf '%s\n%s\n' "$READY" "$LIST" >&"${LAUNCH[1]}" 25 reply="" 26 IFS= read -r -t 5 reply <&"${LAUNCH[0]}" || true 27 ms=$(( (${EPOCHREALTIME/./} - ${start/./}) / 1000 )) 28 kill "$LAUNCH_PID" 2>/dev/null || true 29 wait "$LAUNCH_PID" 2>/dev/null || true 30 if [[ "$reply" == *"\"id\":$id"* && "$reply" == *"$want"* && "$ms" -lt 1000 ]]; then 31 echo "ok $name (${ms} ms)" 32 else 33 echo "FAIL $name (${ms} ms): ${reply:-no reply}" 34 failed=1 35 fi 36}
38check "no binary: initialize gets an error naming install.sh" 1 'install.sh' 39 40if [[ -x "$REPO_ROOT/run/bin/native" ]]; then 41 ln -s "$REPO_ROOT/run/bin" "$scratch/run/bin" 42 check "installed: tools/list answers" 2 '"result":{"tools":' 43else 44 echo "skip installed case: no run/bin/native (run tools/mcp/install.sh)" 45fi 46exit "$failed"