diff --git a/specs/098-tools-preflight/quickstart.md b/specs/098-tools-preflight/quickstart.md index def45d7b..e5b98212 100644 --- a/specs/098-tools-preflight/quickstart.md +++ b/specs/098-tools-preflight/quickstart.md @@ -13,17 +13,33 @@ SCRATCH=$(mktemp -d) API_KEY=$(jq -r .api_key "$SCRATCH/config.json") ``` -Add fixture upstreams (QA harness ctl-server; DESC_FILE enables the rug-pull cell): +Add a fixture upstream. The repo ships a zero-dependency stdio fixture at +`internal/server/testdata/preflight_fixture_server.js` (the one the sabotage E2E +uses). It serves whatever `FIXTURE_TOOLS_FILE` points at — re-read on every +tools/list, which is what makes the rug-pull and new-tool cells reproducible — +and has two more knobs: `FIXTURE_INIT_DELAY_MS` (parks the proxy in +connecting/discovering) and `FIXTURE_FAIL_FILE` (exists at startup ⇒ the process +exits, so a failure sticks across reconnects). ```bash +cat > "$SCRATCH/tools.json" <<'EOF' +[ + {"name": "echo", "description": "echo back", "inputSchema": {"type": "object"}}, + {"name": "wipe", "description": "dangerous cleanup", "inputSchema": {"type": "object"}, + "annotations": {"destructiveHint": true}} +] +EOF curl -s -X POST -H "X-API-Key: $API_KEY" localhost:18098/api/v1/servers \ - -d '{"name":"ctl","command":"node","args":["internal/testdata/ctl-server.js"],"protocol":"stdio","enabled":true}' + -d "{\"name\":\"ctl\",\"command\":\"node\", + \"args\":[\"$PWD/internal/server/testdata/preflight_fixture_server.js\"], + \"env\":{\"FIXTURE_TOOLS_FILE\":\"$SCRATCH/tools.json\"}, + \"protocol\":\"stdio\",\"enabled\":true}" ``` ## 2. Happy path ```bash -./mcpproxy tools preflight ctl:echo ctl:add --output json ; echo "exit=$?" +./mcpproxy tools preflight ctl:echo ctl:wipe --output json ; echo "exit=$?" # expect: verdict ready, exit=0, both tools status=ready curl -s -X POST -H "X-API-Key: $API_KEY" localhost:18098/api/v1/preflight \ -d '{"tools":[{"id":"ctl:echo"}]}' | jq . @@ -35,16 +51,16 @@ curl -s -X POST -H "X-API-Key: $API_KEY" localhost:18098/api/v1/preflight \ |---|---|---| | server_disabled | `mcpproxy upstream disable ctl` | 11 / server_disabled / action enable | | server_quarantined | quarantine via API | 11 / server_quarantined / approve | -| tool_pending_approval | add new tool on ctl (DESC_FILE) before approval | 11 / tool_pending_approval | -| tool_changed | rewrite tool description via DESC_FILE after approval | 11 / tool_changed | +| tool_pending_approval | append a new tool to `$SCRATCH/tools.json`, restart ctl (kill the child; the proxy reconnects) | 11 / tool_pending_approval | +| tool_changed | rewrite `echo`'s description in `$SCRATCH/tools.json`, restart ctl | 11 / tool_changed | | tool_blocked_by_user | `POST /servers/ctl/tools/block` | 11 / tool_blocked_by_user | | tool_denied_by_config | add `disabled_tools:["ctl:echo"]` | 11 / tool_denied_by_config | | oauth_required | fixture server in PendingAuth | 11 / oauth_required / login | -| server_unhealthy | SIGSTOP the ctl child | 10 / server_unhealthy | -| server_initializing | preflight immediately after enable | 10 / server_initializing | +| server_unhealthy | `touch "$SCRATCH/fail"` (with `FIXTURE_FAIL_FILE=$SCRATCH/fail` in the server env), then kill the ctl child — the failure sticks across reconnects | 10 / server_unhealthy | +| server_initializing | add the server with `FIXTURE_INIT_DELAY_MS=8000` in env and preflight immediately | 10 / server_initializing | | hash_mismatch | `--pin ctl:echo=sha256/v2:deadbeef` | 11 / hash_mismatch | -| missing_annotation | `--read-only-only` against ctl (no annotations) | 11 / missing_annotation | -| policy_filtered | `--exclude-destructive` vs tool with destructiveHint=true | 11 / policy_filtered | +| missing_annotation | `--read-only-only ctl:echo` (no annotations on echo) | 11 / missing_annotation | +| policy_filtered | `--exclude-destructive ctl:wipe` (explicit destructiveHint:true) | 11 / policy_filtered | | not_found | `ctl:nope` | 12 / not_found (+did_you_mean) | | server_not_configured | `ghost:echo` | 12 / server_not_configured | | scope tiers | profile excluding ctl: operator → server_not_in_scope; agent token → not_found | 11 vs 12, byte-indistinguishable not_found |