Files
Jan-Niclas LoosenandClaude Opus 5.5 bee9da3a86 feat(cache): WP Fastest Cache statt WP Super Cache
Die Cache-Integration war für WP Super Cache gebaut, eingesetzt wird aber
WP Fastest Cache. Profil (wpfc), Listener, Drosselung, lit/cache-clear und
Leeren-Probe laufen jetzt über die öffentlichen WPFC-Funktionen
wpfc_clear_post_cache_by_id/wpfc_clear_all_cache; die Probe leert nur die
Wegwerf-Seite (singleDeleteCache ohne Elternseiten) und prüft, dass WPFC
den Hook noch verarbeitet. Smoke fastest-cache-smoke.sh ersetzt den WPSC-Smoke.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 19:57:55 +01:00

150 lines
6.6 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Shared helpers for the MCP integration scripts (mcp-smoke.sh, wp-basis-smoke.sh,
# and Etappe 1b's smoke scripts). Source it from a script that runs INSIDE the DDEV
# web container with /var/www/html as its working directory (cd there before
# sourcing – HOST below shells out to `wp` immediately).
# Requires jq and curl (both present in the DDEV web image).
# Mutual exclusion of ALL smokes (every one sources this file inside the same web container): two at once would
# share one site state – WP Fastest Cache edits wp-config.php, every smoke empties the test application passwords of the
# admin (cleanup_app_passwords_for, harmless only because of this lock), Pods/Divi release options. The lock lives as
# long as the script (fd 9 closes with it); a smoke that runs a second one itself must not source this file twice.
exec 9>/tmp/lit-mcp-smoke.lock
flock -n 9 || { echo "FAIL: another smoke is running (lock /tmp/lit-mcp-smoke.lock)"; exit 1; }
PLUGIN=wp-content/plugins/WP-LIT-MCP
ENDPOINT=http://localhost/wp-json/mcp/mcp-oauth-server
# The site's own Host header (without scheme). A request that bypasses normal WP
# routing (e.g. the raw discovery rewrite target mcp-smoke.sh probes) needs this
# Host header, or WordPress 301s it to the canonical host before it ever reaches
# the code under test.
HOST=$(wp option get home | sed -E 's#^[a-zA-Z]+://##')
# Optional: the Host header MCP requests carry (default: curl's own, "localhost"). A cache plugin that keys its files
# by the request's host (WP Fastest Cache) needs the site's real host here (none of the current smokes needs it).
MCP_HOST=${MCP_HOST:-}
TOKEN=""
SESSION=""
INIT_RESPONSE=""
REQUEST_ID=100
fail() { echo "FAIL: $*"; exit 1; }
# A smoke script must reach its last line: finish() marks that and prints the verdict.
# guard_exit (called first in every EXIT trap) turns any other exit – including a silent
# stop that still returns 0 – into a failure, so a run can never pass without all checks.
SMOKE_FINISHED=0
finish() { SMOKE_FINISHED=1; echo "ALL CHECKS PASSED"; }
guard_exit() {
local rc=$1
if [ "$rc" -eq 0 ] && [ "$SMOKE_FINISHED" != 1 ]; then
echo "FAIL: script stopped before its last check (exit 0 without finish)"
return 1
fi
return "$rc"
}
pass() { echo "ok - $*"; }
# Removes every "lit-mcp-integration-test" application password of a user (of ANY run – safe only under the smoke
# lock above: no other smoke can be using one).
cleanup_app_passwords_for() { # $1 = user_login
local login="$1"
[ -n "$login" ] || return 0
local escaped=${login//\\/\\\\}
escaped=${escaped//\'/\\\'}
wp eval "
\$u = get_user_by('login', '${escaped}');
if (\$u) {
foreach (WP_Application_Passwords::get_user_application_passwords(\$u->ID) as \$pw) {
if ((\$pw['name'] ?? '') === 'lit-mcp-integration-test') {
WP_Application_Passwords::delete_application_password(\$u->ID, \$pw['uuid']);
}
}
}
" >/dev/null 2>&1 || true
}
# Mints an MCP access token for a user, exactly like mcp-oauth's TokenEndpoint does
# after a completed OAuth flow (mint-token.php). Also creates the throwaway
# "lit-mcp-integration-test" application password cleanup_app_passwords_for removes.
mint_token() { # $1 = user_login -> prints the token
wp eval-file "$PLUGIN/tests/Integration/mint-token.php" "$1"
}
# Raw JSON-RPC POST to the MCP endpoint; prints headers+body (-i). $3 (session id)
# is optional. Low-level building block: used directly where a script needs the
# response headers (e.g. to read Mcp-Session-Id off the initialize response) and by
# rpc() below for everything after that.
mcp() { # $1 = token, $2 = json body, $3 = session id (optional)
local session_header=()
[ -n "${3:-}" ] && session_header=(-H "Mcp-Session-Id: $3")
local host_header=()
[ -n "$MCP_HOST" ] && host_header=(-H "Host: $MCP_HOST")
curl -s --max-time 20 -i -X POST "$ENDPOINT" \
"${host_header[@]}" \
-H "Authorization: Bearer $1" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
"${session_header[@]}" -d "$2"
}
# Mints a token for the user and opens an MCP session. Sets TOKEN, SESSION and
# INIT_RESPONSE (raw initialize response incl. headers).
connect() { # $1 = user_login
TOKEN=$(mint_token "$1")
local init
init=$(mcp "$TOKEN" '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}')
SESSION=$(echo "$init" | grep -i '^mcp-session-id:' | awk '{print $2}' | tr -d '\r' || true)
INIT_RESPONSE="$init"
echo "$init" | grep -q '"protocolVersion"' || fail "initialize failed: $init"
}
# JSON-RPC call over the session connect() opened; prints the response body only.
rpc() { # $1 = method, $2 = params JSON
REQUEST_ID=$((REQUEST_ID + 1))
local body
body=$(jq -cn --arg m "$1" --argjson p "$2" --argjson id "$REQUEST_ID" '{jsonrpc: "2.0", id: $id, method: $m, params: $p}')
local session_header=()
[ -n "$SESSION" ] && session_header=(-H "Mcp-Session-Id: $SESSION")
local host_header=()
[ -n "$MCP_HOST" ] && host_header=(-H "Host: $MCP_HOST")
curl -s --max-time 60 -X POST "$ENDPOINT" \
"${host_header[@]}" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
"${session_header[@]}" -d "$body"
}
# Calls a tool; prints structuredContent, or {"__error": "<text>"} for isError results.
tool() { # $1 = tool name, $2 = arguments JSON
local response
response=$(rpc tools/call "$(jq -cn --arg n "$1" --argjson a "$2" '{name: $n, arguments: $a}')")
echo "$response" | jq -e '.result' >/dev/null 2>&1 || fail "$1: protocol error: $response"
if [ "$(echo "$response" | jq -r '.result.isError')" = "true" ]; then
echo "$response" | jq -c '{__error: .result.content[0].text}'
else
echo "$response" | jq -c '.result.structuredContent'
fi
}
# Runs an indirect ability through mcp-adapter-execute-ability; prints its result or {"__error": …}.
ability() { # $1 = ability name (lit/…), $2 = parameters JSON
local out
out=$(tool mcp-adapter-execute-ability "$(jq -cn --arg n "$1" --argjson p "$2" '{ability_name: $n, parameters: $p}')")
if echo "$out" | jq -e 'has("__error")' >/dev/null; then
echo "$out"
elif [ "$(echo "$out" | jq -r '.success')" = "true" ]; then
echo "$out" | jq -c '.data'
else
echo "$out" | jq -c '{__error: .error}'
fi
}
# Prints the lit error code (e.g. rev_mismatch) of a tool()/ability() result, or "".
error_code() { # $1 = result JSON
echo "$1" | jq -r '(.__error // "") | (try fromjson catch {}) | (.code // "")'
}