Files
WP-LIT-MCP/tests/Integration/mcp-smoke.sh
T

103 lines
5.3 KiB
Bash
Raw 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
# End-to-end checks of the OAuth-protected MCP endpoint.
# Runs INSIDE the DDEV web container (wp-cli and curl available there):
# cd /home/janniclas/Projekte/WordPress && ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/mcp-smoke.sh
set -euo pipefail
PLUGIN=wp-content/plugins/WP-LIT-MCP
ENDPOINT=http://localhost/wp-json/mcp/mcp-oauth-server
cd /var/www/html
fail() { echo "FAIL: $*"; exit 1; }
pass() { echo "ok - $*"; }
# --- Cleanup: remove every "lit-mcp-integration-test" application password
# this script mints (for every user it mints one for) and the throwaway
# subscriber it creates. Registered up front so it runs on any exit path
# (success, fail(), or an unexpected error under `set -e`).
ADMIN_LOGIN=""
cleanup_app_passwords_for() { # $1 = user_login
local login="$1"
[ -n "$login" ] || return 0
local escaped=${login//\'/\\\'}
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
}
cleanup() {
cleanup_app_passwords_for "$ADMIN_LOGIN"
cleanup_app_passwords_for lit-smoke-sub
wp user delete lit-smoke-sub --yes >/dev/null 2>&1 || true
}
trap cleanup EXIT
mcp() { # $1 token, $2 json body, $3 session id (optional) -> prints headers+body
local session_header=()
[ -n "${3:-}" ] && session_header=(-H "Mcp-Session-Id: $3")
curl -s -i -X POST "$ENDPOINT" \
-H "Authorization: Bearer $1" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
"${session_header[@]}" -d "$2"
}
# 1. Discovery documents
curl -s http://localhost/.well-known/oauth-protected-resource | grep -q '"resource"' \
|| fail "protected-resource metadata missing"
pass "RFC 9728 protected-resource metadata served"
curl -s http://localhost/.well-known/oauth-authorization-server | grep -q 'client_id_metadata_document_supported' \
|| fail "authorization-server metadata missing CIMD flag"
pass "RFC 8414 authorization-server metadata advertises CIMD"
# 2. Unauthenticated request -> 401 with WWW-Authenticate
UNAUTH=$(curl -s -i -X POST "$ENDPOINT" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}')
echo "$UNAUTH" | head -1 | grep -q ' 401' || fail "expected 401 without token"
echo "$UNAUTH" | grep -qi '^www-authenticate: Bearer' || fail "missing WWW-Authenticate header"
pass "401 + WWW-Authenticate without token"
# 3. Administrator: initialize, tools/list, tools/call
ADMIN_LOGIN=$(wp user list --role=administrator --field=user_login | head -1)
TOKEN=$(wp eval-file "$PLUGIN/tests/Integration/mint-token.php" "$ADMIN_LOGIN")
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')
echo "$INIT" | grep -q 'LIT MCP pflegt' || fail "instructions missing in initialize result"
pass "initialize returns LIT instructions (session: ${SESSION:-none})"
LIST=$(mcp "$TOKEN" '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' "$SESSION")
for tool in lit-guide lit-site-status mcp-adapter-execute-ability; do
echo "$LIST" | grep -q "\"$tool\"" || fail "tools/list lacks $tool"
done
pass "tools/list contains direct tools and meta-tools"
CALL=$(mcp "$TOKEN" '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"lit-site-status","arguments":{}}}' "$SESSION")
echo "$CALL" | grep -q '"isError":false' || fail "lit-site-status failed: $CALL"
echo "$CALL" | grep -q 'the-events-calendar' || fail "site-status lacks components"
pass "tools/call lit-site-status works"
# 4. Token must not authenticate other REST routes (mcp-oauth issue #62)
ME=$(curl -s -o /dev/null -w '%{http_code}' http://localhost/wp-json/wp/v2/users/me -H "Authorization: Bearer $TOKEN")
[ "$ME" = "401" ] || fail "MCP token authenticates /wp/v2/users/me (HTTP $ME) – scope filter needed"
pass "MCP token is rejected on /wp/v2/users/me"
# 5. Role gate: subscriber has a valid token but must not use tools
wp user create lit-smoke-sub lit-smoke-sub@example.invalid --role=subscriber >/dev/null
SUB_TOKEN=$(wp eval-file "$PLUGIN/tests/Integration/mint-token.php" lit-smoke-sub)
SUB_INIT=$(mcp "$SUB_TOKEN" '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}')
SUB_SESSION=$(echo "$SUB_INIT" | grep -i '^mcp-session-id:' | awk '{print $2}' | tr -d '\r')
SUB_CALL=$(mcp "$SUB_TOKEN" '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"lit-site-status","arguments":{}}}' "$SUB_SESSION")
echo "$SUB_CALL" | grep -q '"isError":true\|"error"' || fail "subscriber could call lit-site-status: $SUB_CALL"
SUB_META=$(mcp "$SUB_TOKEN" '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"mcp-adapter-execute-ability","arguments":{"ability_name":"lit/site-status","parameters":{}}}}' "$SUB_SESSION")
echo "$SUB_META" | grep -q '"isError":true\|"error"' || fail "subscriber could use execute-ability: $SUB_META"
pass "role gate blocks subscriber (direct tool and meta-tool)"
echo "ALL CHECKS PASSED"