claude-plugin-wordpress-manager 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +19 -0
- package/.mcp.json +19 -0
- package/CHANGELOG.md +62 -0
- package/LICENSE +69 -0
- package/README.md +213 -0
- package/agents/wp-content-strategist.md +148 -0
- package/agents/wp-deployment-engineer.md +93 -0
- package/agents/wp-performance-optimizer.md +198 -0
- package/agents/wp-security-auditor.md +161 -0
- package/agents/wp-site-manager.md +109 -0
- package/commands/wp-audit.md +37 -0
- package/commands/wp-backup.md +45 -0
- package/commands/wp-deploy.md +38 -0
- package/commands/wp-setup.md +64 -0
- package/commands/wp-status.md +53 -0
- package/docs/GUIDE.md +1190 -0
- package/hooks/hooks.json +57 -0
- package/hooks/scripts/backup-reminder.sh +29 -0
- package/hooks/scripts/pre-deploy-check.sh +49 -0
- package/package.json +46 -0
- package/scripts/health-check.sh +110 -0
- package/scripts/validate-wp-operation.sh +115 -0
- package/servers/wp-rest-bridge/build/server.d.ts +2 -0
- package/servers/wp-rest-bridge/build/server.js +74 -0
- package/servers/wp-rest-bridge/build/tools/comments.d.ts +227 -0
- package/servers/wp-rest-bridge/build/tools/comments.js +192 -0
- package/servers/wp-rest-bridge/build/tools/index.d.ts +919 -0
- package/servers/wp-rest-bridge/build/tools/index.js +30 -0
- package/servers/wp-rest-bridge/build/tools/media.d.ts +174 -0
- package/servers/wp-rest-bridge/build/tools/media.js +247 -0
- package/servers/wp-rest-bridge/build/tools/plugin-repository.d.ts +62 -0
- package/servers/wp-rest-bridge/build/tools/plugin-repository.js +149 -0
- package/servers/wp-rest-bridge/build/tools/plugins.d.ts +153 -0
- package/servers/wp-rest-bridge/build/tools/plugins.js +175 -0
- package/servers/wp-rest-bridge/build/tools/search.d.ts +44 -0
- package/servers/wp-rest-bridge/build/tools/search.js +44 -0
- package/servers/wp-rest-bridge/build/tools/unified-content.d.ts +328 -0
- package/servers/wp-rest-bridge/build/tools/unified-content.js +628 -0
- package/servers/wp-rest-bridge/build/tools/unified-taxonomies.d.ts +244 -0
- package/servers/wp-rest-bridge/build/tools/unified-taxonomies.js +492 -0
- package/servers/wp-rest-bridge/build/tools/users.d.ts +269 -0
- package/servers/wp-rest-bridge/build/tools/users.js +226 -0
- package/servers/wp-rest-bridge/build/types.d.ts +151 -0
- package/servers/wp-rest-bridge/build/types.js +2 -0
- package/servers/wp-rest-bridge/build/wordpress.d.ts +48 -0
- package/servers/wp-rest-bridge/build/wordpress.js +305 -0
- package/servers/wp-rest-bridge/package.json +27 -0
- package/skills/wordpress-router/SKILL.md +78 -0
- package/skills/wordpress-router/references/decision-tree.md +88 -0
- package/skills/wp-abilities-api/SKILL.md +97 -0
- package/skills/wp-abilities-api/references/php-registration.md +67 -0
- package/skills/wp-abilities-api/references/rest-api.md +13 -0
- package/skills/wp-audit/SKILL.md +114 -0
- package/skills/wp-audit/references/performance-checklist.md +113 -0
- package/skills/wp-audit/references/security-checklist.md +95 -0
- package/skills/wp-audit/references/seo-checklist.md +128 -0
- package/skills/wp-backup/SKILL.md +87 -0
- package/skills/wp-backup/references/backup-strategies.md +116 -0
- package/skills/wp-backup/references/restore-procedures.md +129 -0
- package/skills/wp-block-development/SKILL.md +176 -0
- package/skills/wp-block-development/references/attributes-and-serialization.md +22 -0
- package/skills/wp-block-development/references/block-json.md +49 -0
- package/skills/wp-block-development/references/creating-new-blocks.md +46 -0
- package/skills/wp-block-development/references/debugging.md +36 -0
- package/skills/wp-block-development/references/deprecations.md +24 -0
- package/skills/wp-block-development/references/dynamic-rendering.md +23 -0
- package/skills/wp-block-development/references/inner-blocks.md +25 -0
- package/skills/wp-block-development/references/registration.md +30 -0
- package/skills/wp-block-development/references/supports-and-wrappers.md +18 -0
- package/skills/wp-block-development/references/tooling-and-testing.md +21 -0
- package/skills/wp-block-development/scripts/list_blocks.mjs +121 -0
- package/skills/wp-block-themes/SKILL.md +118 -0
- package/skills/wp-block-themes/references/creating-new-block-theme.md +37 -0
- package/skills/wp-block-themes/references/debugging.md +24 -0
- package/skills/wp-block-themes/references/patterns.md +18 -0
- package/skills/wp-block-themes/references/style-variations.md +14 -0
- package/skills/wp-block-themes/references/templates-and-parts.md +16 -0
- package/skills/wp-block-themes/references/theme-json.md +59 -0
- package/skills/wp-block-themes/scripts/detect_block_themes.mjs +117 -0
- package/skills/wp-content/SKILL.md +103 -0
- package/skills/wp-content/references/content-templates.md +230 -0
- package/skills/wp-content/references/seo-optimization.md +169 -0
- package/skills/wp-deploy/SKILL.md +52 -0
- package/skills/wp-deploy/references/hostinger-deploy.md +51 -0
- package/skills/wp-deploy/references/ssh-deploy.md +63 -0
- package/skills/wp-interactivity-api/SKILL.md +181 -0
- package/skills/wp-interactivity-api/references/debugging.md +29 -0
- package/skills/wp-interactivity-api/references/directives-quickref.md +30 -0
- package/skills/wp-interactivity-api/references/server-side-rendering.md +310 -0
- package/skills/wp-migrate/SKILL.md +100 -0
- package/skills/wp-migrate/references/cross-platform.md +104 -0
- package/skills/wp-migrate/references/hostinger-migration.md +86 -0
- package/skills/wp-performance/SKILL.md +148 -0
- package/skills/wp-performance/references/autoload-options.md +24 -0
- package/skills/wp-performance/references/cron.md +20 -0
- package/skills/wp-performance/references/database.md +20 -0
- package/skills/wp-performance/references/http-api.md +15 -0
- package/skills/wp-performance/references/measurement.md +21 -0
- package/skills/wp-performance/references/object-cache.md +24 -0
- package/skills/wp-performance/references/query-monitor-headless.md +38 -0
- package/skills/wp-performance/references/server-timing.md +22 -0
- package/skills/wp-performance/references/wp-cli-doctor.md +24 -0
- package/skills/wp-performance/references/wp-cli-profile.md +32 -0
- package/skills/wp-performance/scripts/perf_inspect.mjs +128 -0
- package/skills/wp-phpstan/SKILL.md +99 -0
- package/skills/wp-phpstan/references/configuration.md +52 -0
- package/skills/wp-phpstan/references/third-party-classes.md +76 -0
- package/skills/wp-phpstan/references/wordpress-annotations.md +124 -0
- package/skills/wp-phpstan/scripts/phpstan_inspect.mjs +263 -0
- package/skills/wp-playground/SKILL.md +103 -0
- package/skills/wp-playground/references/blueprints.md +36 -0
- package/skills/wp-playground/references/cli-commands.md +39 -0
- package/skills/wp-playground/references/debugging.md +16 -0
- package/skills/wp-plugin-development/SKILL.md +114 -0
- package/skills/wp-plugin-development/references/data-and-cron.md +19 -0
- package/skills/wp-plugin-development/references/debugging.md +19 -0
- package/skills/wp-plugin-development/references/lifecycle.md +33 -0
- package/skills/wp-plugin-development/references/security.md +29 -0
- package/skills/wp-plugin-development/references/settings-api.md +22 -0
- package/skills/wp-plugin-development/references/structure.md +16 -0
- package/skills/wp-plugin-development/scripts/detect_plugins.mjs +122 -0
- package/skills/wp-project-triage/SKILL.md +40 -0
- package/skills/wp-project-triage/references/triage.schema.json +143 -0
- package/skills/wp-project-triage/scripts/detect_wp_project.mjs +592 -0
- package/skills/wp-rest-api/SKILL.md +116 -0
- package/skills/wp-rest-api/references/authentication.md +18 -0
- package/skills/wp-rest-api/references/custom-content-types.md +20 -0
- package/skills/wp-rest-api/references/discovery-and-params.md +20 -0
- package/skills/wp-rest-api/references/responses-and-fields.md +30 -0
- package/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
- package/skills/wp-rest-api/references/schema.md +22 -0
- package/skills/wp-wpcli-and-ops/SKILL.md +125 -0
- package/skills/wp-wpcli-and-ops/references/automation.md +30 -0
- package/skills/wp-wpcli-and-ops/references/cron-and-cache.md +23 -0
- package/skills/wp-wpcli-and-ops/references/debugging.md +17 -0
- package/skills/wp-wpcli-and-ops/references/multisite.md +22 -0
- package/skills/wp-wpcli-and-ops/references/packages-and-updates.md +22 -0
- package/skills/wp-wpcli-and-ops/references/safety.md +30 -0
- package/skills/wp-wpcli-and-ops/references/search-replace.md +40 -0
- package/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +90 -0
- package/skills/wpds/SKILL.md +60 -0
- package/skills/wpds/references/wpds-mcp-setup.md +59 -0
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "mcp__wp-rest-bridge__delete_content|mcp__wp-rest-bridge__delete_media|mcp__wp-rest-bridge__delete_user|mcp__wp-rest-bridge__delete_term",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "prompt",
|
|
9
|
+
"prompt": "The user is about to DELETE WordPress content. This operation may be irreversible if force=true. Verify the user explicitly requested this deletion and confirm the target (site, content type, ID). If uncertain, respond with 'deny' and ask the user to confirm. Respond with 'approve' only if the deletion was clearly and explicitly requested."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "mcp__wp-rest-bridge__deactivate_plugin",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "prompt",
|
|
18
|
+
"prompt": "The user is about to DEACTIVATE a WordPress plugin. This could break site functionality if other plugins depend on it. Confirm the user explicitly requested this. Respond 'approve' if intentional, 'deny' if it seems accidental."
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"matcher": "mcp__hostinger-mcp__hosting_importWordpressWebsite",
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/backup-reminder.sh",
|
|
28
|
+
"timeout": 5
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "prompt",
|
|
32
|
+
"prompt": "The user is about to IMPORT a full WordPress site, which will OVERWRITE the existing installation. This is a major destructive operation. Verify the user explicitly requested this and understands the consequences. Respond 'approve' only if clearly intentional."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"matcher": "mcp__hostinger-mcp__DNS_updateDNSRecordsV1|mcp__hostinger-mcp__DNS_resetDNSRecordsV1",
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "prompt",
|
|
41
|
+
"prompt": "The user is about to MODIFY DNS records. Incorrect DNS changes can make the site unreachable. Confirm the user explicitly requested this change and verify the record details look correct. Respond 'approve' if intentional."
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"matcher": "mcp__hostinger-mcp__hosting_deployWordpressPlugin|mcp__hostinger-mcp__hosting_deployWordpressTheme|mcp__hostinger-mcp__hosting_deployStaticWebsite",
|
|
47
|
+
"hooks": [
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/pre-deploy-check.sh",
|
|
51
|
+
"timeout": 15
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# backup-reminder.sh — Backup reminder hook for destructive operations
|
|
3
|
+
# Prints a warning to stderr reminding the user to backup before major operations.
|
|
4
|
+
# This is an advisory hook (always exits 0) — it doesn't block operations.
|
|
5
|
+
# Called as a PreToolUse command hook for hosting_importWordpressWebsite.
|
|
6
|
+
#
|
|
7
|
+
# Claude Code PreToolUse hooks receive tool input as JSON on stdin.
|
|
8
|
+
# We read the tool name from stdin if jq is available, otherwise fall back.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
# Read tool name from stdin JSON (Claude Code passes {"tool_name": "...", "tool_input": {...}})
|
|
13
|
+
TOOL_NAME="unknown"
|
|
14
|
+
if command -v jq &>/dev/null; then
|
|
15
|
+
INPUT=$(cat)
|
|
16
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null || echo "unknown")
|
|
17
|
+
else
|
|
18
|
+
# Consume stdin to avoid broken pipe
|
|
19
|
+
cat > /dev/null
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
cat <<MSG >&2
|
|
23
|
+
[wordpress-manager] Reminder: You are about to run '$TOOL_NAME'.
|
|
24
|
+
Consider creating a backup first if you haven't recently:
|
|
25
|
+
/wordpress-manager:wp-backup create
|
|
26
|
+
MSG
|
|
27
|
+
|
|
28
|
+
# Advisory only — always allow
|
|
29
|
+
exit 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# pre-deploy-check.sh — Pre-deployment validation hook
|
|
3
|
+
# Ensures the target site is reachable and authenticated before deploy operations.
|
|
4
|
+
# Called as a PreToolUse command hook for deploy-related tools.
|
|
5
|
+
# Exit 0 = allow, Exit 2 = block with message
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# Consume stdin (Claude Code sends tool input as JSON on stdin to command hooks)
|
|
10
|
+
cat > /dev/null
|
|
11
|
+
|
|
12
|
+
# Resolve site URL from WP_SITES_CONFIG
|
|
13
|
+
if [ -z "${WP_SITES_CONFIG:-}" ]; then
|
|
14
|
+
echo "WARN: WP_SITES_CONFIG not set, cannot validate target site"
|
|
15
|
+
exit 0 # Allow — can't validate without config
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
SITE_URL=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['url'])" 2>/dev/null || echo "")
|
|
19
|
+
CREDS=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; c=json.load(sys.stdin)[0]; print(c['username']+':'+c['password'])" 2>/dev/null || echo "")
|
|
20
|
+
|
|
21
|
+
if [ -z "$SITE_URL" ]; then
|
|
22
|
+
exit 0 # Can't determine site, allow operation
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Check site reachable
|
|
26
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$SITE_URL" 2>/dev/null || echo "000")
|
|
27
|
+
if [ "$HTTP_CODE" = "000" ]; then
|
|
28
|
+
echo "BLOCKED: Target site $SITE_URL is unreachable (connection timeout). Deploy aborted."
|
|
29
|
+
exit 2
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check REST API
|
|
33
|
+
API_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$SITE_URL/wp-json/wp/v2/" 2>/dev/null || echo "000")
|
|
34
|
+
if [ "$API_CODE" != "200" ]; then
|
|
35
|
+
echo "BLOCKED: WordPress REST API not available at $SITE_URL (HTTP $API_CODE). Deploy aborted."
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Check authentication
|
|
40
|
+
if [ -n "$CREDS" ]; then
|
|
41
|
+
AUTH_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 -u "$CREDS" "$SITE_URL/wp-json/wp/v2/users/me" 2>/dev/null || echo "000")
|
|
42
|
+
if [ "$AUTH_CODE" != "200" ]; then
|
|
43
|
+
echo "BLOCKED: Authentication failed for $SITE_URL (HTTP $AUTH_CODE). Check Application Password."
|
|
44
|
+
exit 2
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# All checks passed
|
|
49
|
+
exit 0
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-plugin-wordpress-manager",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Unified WordPress management and development plugin for Claude Code. Orchestrates Hostinger MCP, WP REST API bridge, and WordPress.com MCP with 18 skills, 5 agents, and security hooks.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "vinmor",
|
|
7
|
+
"email": "morreale.v@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"claude-code",
|
|
12
|
+
"claude-code-plugin",
|
|
13
|
+
"wordpress",
|
|
14
|
+
"hostinger",
|
|
15
|
+
"mcp",
|
|
16
|
+
"deployment",
|
|
17
|
+
"cms",
|
|
18
|
+
"multi-site",
|
|
19
|
+
"gutenberg",
|
|
20
|
+
"block-development",
|
|
21
|
+
"wp-cli"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/morrealev/wordpress-manager.git"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/morrealev/wordpress-manager#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/morrealev/wordpress-manager/issues"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
".claude-plugin/",
|
|
33
|
+
".mcp.json",
|
|
34
|
+
"agents/",
|
|
35
|
+
"commands/",
|
|
36
|
+
"skills/",
|
|
37
|
+
"hooks/",
|
|
38
|
+
"scripts/",
|
|
39
|
+
"servers/wp-rest-bridge/build/",
|
|
40
|
+
"servers/wp-rest-bridge/package.json",
|
|
41
|
+
"docs/",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"CHANGELOG.md",
|
|
44
|
+
"README.md"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# health-check.sh — WordPress site health check
|
|
3
|
+
# Tests REST API reachability, authentication, SSL validity, and Hostinger API status.
|
|
4
|
+
# Usage: ./health-check.sh [site-url] [username:app-password]
|
|
5
|
+
# ./health-check.sh (uses WP_SITES_CONFIG env var)
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
NC='\033[0m'
|
|
13
|
+
|
|
14
|
+
pass() { echo -e " ${GREEN}PASS${NC} $1"; }
|
|
15
|
+
fail() { echo -e " ${RED}FAIL${NC} $1"; }
|
|
16
|
+
warn() { echo -e " ${YELLOW}WARN${NC} $1"; }
|
|
17
|
+
|
|
18
|
+
# Resolve site config
|
|
19
|
+
if [ $# -ge 1 ]; then
|
|
20
|
+
SITE_URL="$1"
|
|
21
|
+
# Ensure URL has https:// prefix
|
|
22
|
+
if [[ ! "$SITE_URL" =~ ^https?:// ]]; then
|
|
23
|
+
SITE_URL="https://$SITE_URL"
|
|
24
|
+
fi
|
|
25
|
+
CREDS="${2:-}"
|
|
26
|
+
elif [ -n "${WP_SITES_CONFIG:-}" ]; then
|
|
27
|
+
SITE_URL=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['url'])" 2>/dev/null)
|
|
28
|
+
CREDS=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; c=json.load(sys.stdin)[0]; print(c['username']+':'+c['password'])" 2>/dev/null)
|
|
29
|
+
else
|
|
30
|
+
echo "Usage: $0 [site-url] [username:app-password]"
|
|
31
|
+
echo " Or set WP_SITES_CONFIG environment variable"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
echo "=== WordPress Health Check ==="
|
|
36
|
+
echo "Site: $SITE_URL"
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
# 1. HTTP reachability
|
|
40
|
+
echo "[1/5] Site Reachability"
|
|
41
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$SITE_URL" 2>/dev/null || echo "000")
|
|
42
|
+
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "301" ] || [ "$HTTP_CODE" = "302" ]; then
|
|
43
|
+
pass "Site responds (HTTP $HTTP_CODE)"
|
|
44
|
+
elif [ "$HTTP_CODE" = "000" ]; then
|
|
45
|
+
fail "Site unreachable (connection timeout/refused)"
|
|
46
|
+
else
|
|
47
|
+
warn "Site responds with HTTP $HTTP_CODE"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 2. SSL Certificate
|
|
51
|
+
echo "[2/5] SSL Certificate"
|
|
52
|
+
SSL_EXPIRY=$(echo | openssl s_client -servername "${SITE_URL#https://}" -connect "${SITE_URL#https://}:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
|
53
|
+
if [ -n "$SSL_EXPIRY" ]; then
|
|
54
|
+
EXPIRY_EPOCH=$(date -d "$SSL_EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$SSL_EXPIRY" +%s 2>/dev/null || echo "0")
|
|
55
|
+
NOW_EPOCH=$(date +%s)
|
|
56
|
+
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
|
|
57
|
+
if [ "$DAYS_LEFT" -gt 30 ]; then
|
|
58
|
+
pass "SSL valid ($DAYS_LEFT days remaining, expires: $SSL_EXPIRY)"
|
|
59
|
+
elif [ "$DAYS_LEFT" -gt 0 ]; then
|
|
60
|
+
warn "SSL expiring soon ($DAYS_LEFT days remaining)"
|
|
61
|
+
else
|
|
62
|
+
fail "SSL certificate expired!"
|
|
63
|
+
fi
|
|
64
|
+
else
|
|
65
|
+
fail "Could not retrieve SSL certificate"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# 3. WordPress REST API
|
|
69
|
+
echo "[3/5] WordPress REST API"
|
|
70
|
+
API_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$SITE_URL/wp-json/wp/v2/" 2>/dev/null || echo "000")
|
|
71
|
+
if [ "$API_CODE" = "200" ]; then
|
|
72
|
+
pass "REST API reachable (HTTP $API_CODE)"
|
|
73
|
+
else
|
|
74
|
+
fail "REST API not reachable (HTTP $API_CODE)"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# 4. Authentication
|
|
78
|
+
echo "[4/5] Authentication"
|
|
79
|
+
if [ -n "$CREDS" ]; then
|
|
80
|
+
AUTH_RESP=$(curl -s --max-time 10 -u "$CREDS" "$SITE_URL/wp-json/wp/v2/users/me" 2>/dev/null)
|
|
81
|
+
USER_ID=$(echo "$AUTH_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
|
|
82
|
+
if [ -n "$USER_ID" ] && [ "$USER_ID" != "" ]; then
|
|
83
|
+
USER_NAME=$(echo "$AUTH_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('name','?'))" 2>/dev/null)
|
|
84
|
+
pass "Authenticated as '$USER_NAME' (ID: $USER_ID)"
|
|
85
|
+
else
|
|
86
|
+
ERROR=$(echo "$AUTH_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('message','Unknown error'))" 2>/dev/null || echo "Parse error")
|
|
87
|
+
fail "Authentication failed: $ERROR"
|
|
88
|
+
fi
|
|
89
|
+
else
|
|
90
|
+
warn "No credentials provided, skipping auth test"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# 5. Hostinger API (if token available)
|
|
94
|
+
echo "[5/5] Hostinger API"
|
|
95
|
+
if [ -n "${HOSTINGER_API_TOKEN:-}" ]; then
|
|
96
|
+
HAPI_RESP=$(curl -s --max-time 10 -H "Authorization: Bearer $HOSTINGER_API_TOKEN" "https://developers.hostinger.com/api/hosting/v1/websites" 2>/dev/null)
|
|
97
|
+
HAPI_CODE=$(echo "$HAPI_RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('data',d)) if isinstance(d,dict) and 'message' not in d else d.get('message','error'))" 2>/dev/null || echo "error")
|
|
98
|
+
if [[ "$HAPI_CODE" =~ ^[0-9]+$ ]]; then
|
|
99
|
+
pass "Hostinger API reachable ($HAPI_CODE sites found)"
|
|
100
|
+
elif [ "$HAPI_CODE" = "Unauthenticated." ]; then
|
|
101
|
+
fail "Hostinger API: Unauthenticated (token invalid or expired)"
|
|
102
|
+
else
|
|
103
|
+
warn "Hostinger API: HTTP $HAPI_CODE"
|
|
104
|
+
fi
|
|
105
|
+
else
|
|
106
|
+
warn "HOSTINGER_API_TOKEN not set, skipping Hostinger check"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
echo ""
|
|
110
|
+
echo "=== Health Check Complete ==="
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# validate-wp-operation.sh — Pre-flight validation for WordPress operations
|
|
3
|
+
# Checks that a site is reachable and authenticated before proceeding.
|
|
4
|
+
# Returns exit code 0 (safe to proceed) or 1 (abort).
|
|
5
|
+
# Usage: ./validate-wp-operation.sh <operation> [site-url]
|
|
6
|
+
#
|
|
7
|
+
# Operations: deploy, delete, import, dns-change, plugin-deactivate, backup, migrate
|
|
8
|
+
# Used by command-type hooks and manual pre-flight checks.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
OPERATION="${1:-unknown}"
|
|
13
|
+
SITE_URL="${2:-}"
|
|
14
|
+
|
|
15
|
+
# Resolve site URL from config if not provided
|
|
16
|
+
if [ -z "$SITE_URL" ] && [ -n "${WP_SITES_CONFIG:-}" ]; then
|
|
17
|
+
SITE_URL=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['url'])" 2>/dev/null || echo "")
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if [ -z "$SITE_URL" ]; then
|
|
21
|
+
echo "ERROR: No site URL provided and WP_SITES_CONFIG not set"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
ERRORS=0
|
|
26
|
+
|
|
27
|
+
check() {
|
|
28
|
+
local label="$1"
|
|
29
|
+
local result="$2"
|
|
30
|
+
if [ "$result" = "ok" ]; then
|
|
31
|
+
echo " [OK] $label"
|
|
32
|
+
else
|
|
33
|
+
echo " [FAIL] $label: $result"
|
|
34
|
+
ERRORS=$((ERRORS + 1))
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
echo "Pre-flight check: $OPERATION on $SITE_URL"
|
|
39
|
+
echo "---"
|
|
40
|
+
|
|
41
|
+
# Always check: site reachable
|
|
42
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$SITE_URL" 2>/dev/null || echo "000")
|
|
43
|
+
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "301" ] || [ "$HTTP_CODE" = "302" ]; then
|
|
44
|
+
check "Site reachable" "ok"
|
|
45
|
+
else
|
|
46
|
+
check "Site reachable" "HTTP $HTTP_CODE"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Always check: REST API available
|
|
50
|
+
API_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$SITE_URL/wp-json/wp/v2/" 2>/dev/null || echo "000")
|
|
51
|
+
if [ "$API_CODE" = "200" ]; then
|
|
52
|
+
check "REST API available" "ok"
|
|
53
|
+
else
|
|
54
|
+
check "REST API available" "HTTP $API_CODE"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Operation-specific checks
|
|
58
|
+
case "$OPERATION" in
|
|
59
|
+
deploy|import|migrate)
|
|
60
|
+
# Check authentication (needed for write operations)
|
|
61
|
+
if [ -n "${WP_SITES_CONFIG:-}" ]; then
|
|
62
|
+
CREDS=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; c=json.load(sys.stdin)[0]; print(c['username']+':'+c['password'])" 2>/dev/null || echo "")
|
|
63
|
+
if [ -n "$CREDS" ]; then
|
|
64
|
+
AUTH_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -u "$CREDS" "$SITE_URL/wp-json/wp/v2/users/me" 2>/dev/null || echo "000")
|
|
65
|
+
if [ "$AUTH_CODE" = "200" ]; then
|
|
66
|
+
check "Authentication" "ok"
|
|
67
|
+
else
|
|
68
|
+
check "Authentication" "HTTP $AUTH_CODE"
|
|
69
|
+
fi
|
|
70
|
+
else
|
|
71
|
+
check "Authentication" "Could not parse credentials"
|
|
72
|
+
fi
|
|
73
|
+
else
|
|
74
|
+
check "Authentication" "WP_SITES_CONFIG not set"
|
|
75
|
+
fi
|
|
76
|
+
;;
|
|
77
|
+
dns-change)
|
|
78
|
+
# Check Hostinger API
|
|
79
|
+
if [ -n "${HOSTINGER_API_TOKEN:-}" ]; then
|
|
80
|
+
HAPI_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -H "Authorization: Bearer $HOSTINGER_API_TOKEN" "https://api.hostinger.com/api/dns/v1/zones" 2>/dev/null || echo "000")
|
|
81
|
+
if [ "$HAPI_CODE" = "200" ]; then
|
|
82
|
+
check "Hostinger API" "ok"
|
|
83
|
+
elif [ "$HAPI_CODE" = "530" ]; then
|
|
84
|
+
check "Hostinger API" "Site Frozen (HTTP 530)"
|
|
85
|
+
else
|
|
86
|
+
check "Hostinger API" "HTTP $HAPI_CODE"
|
|
87
|
+
fi
|
|
88
|
+
else
|
|
89
|
+
check "Hostinger API" "HOSTINGER_API_TOKEN not set"
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
delete|plugin-deactivate)
|
|
93
|
+
# Lighter check — just confirm auth works
|
|
94
|
+
if [ -n "${WP_SITES_CONFIG:-}" ]; then
|
|
95
|
+
CREDS=$(echo "$WP_SITES_CONFIG" | python3 -c "import json,sys; c=json.load(sys.stdin)[0]; print(c['username']+':'+c['password'])" 2>/dev/null || echo "")
|
|
96
|
+
if [ -n "$CREDS" ]; then
|
|
97
|
+
AUTH_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -u "$CREDS" "$SITE_URL/wp-json/wp/v2/users/me" 2>/dev/null || echo "000")
|
|
98
|
+
if [ "$AUTH_CODE" = "200" ]; then
|
|
99
|
+
check "Authentication" "ok"
|
|
100
|
+
else
|
|
101
|
+
check "Authentication" "HTTP $AUTH_CODE"
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
|
|
108
|
+
echo "---"
|
|
109
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
110
|
+
echo "RESULT: $ERRORS check(s) failed — operation NOT safe to proceed"
|
|
111
|
+
exit 1
|
|
112
|
+
else
|
|
113
|
+
echo "RESULT: All checks passed — safe to proceed"
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/server.ts - WP REST Bridge MCP Server (multi-site)
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { allTools, toolHandlers } from './tools/index.js';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: 'wp-rest-bridge',
|
|
9
|
+
version: '1.1.0',
|
|
10
|
+
});
|
|
11
|
+
// Register multi-site management tools
|
|
12
|
+
server.tool('switch_site', { site_id: z.string().describe('Site ID to switch to (e.g., "opencactus", "bioinagro")') }, async (args) => {
|
|
13
|
+
const { switchSite } = await import('./wordpress.js');
|
|
14
|
+
try {
|
|
15
|
+
const newSite = switchSite(args.site_id);
|
|
16
|
+
return { content: [{ type: 'text', text: `Switched to site: ${newSite}` }] };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
server.tool('list_sites', {}, async () => {
|
|
23
|
+
const { listSites, getActiveSite } = await import('./wordpress.js');
|
|
24
|
+
const sites = listSites();
|
|
25
|
+
const active = getActiveSite();
|
|
26
|
+
const result = sites.map(s => `${s === active ? '● ' : ' '}${s}`).join('\n');
|
|
27
|
+
return { content: [{ type: 'text', text: `Configured sites:\n${result}` }] };
|
|
28
|
+
});
|
|
29
|
+
server.tool('get_active_site', {}, async () => {
|
|
30
|
+
const { getActiveSite } = await import('./wordpress.js');
|
|
31
|
+
return { content: [{ type: 'text', text: getActiveSite() }] };
|
|
32
|
+
});
|
|
33
|
+
// Register all WordPress content tools from the ported modules
|
|
34
|
+
for (const tool of allTools) {
|
|
35
|
+
const handler = toolHandlers[tool.name];
|
|
36
|
+
if (!handler)
|
|
37
|
+
continue;
|
|
38
|
+
const wrappedHandler = async (args) => {
|
|
39
|
+
const result = await handler(args);
|
|
40
|
+
return {
|
|
41
|
+
content: result.toolResult.content.map((item) => ({
|
|
42
|
+
...item,
|
|
43
|
+
type: 'text',
|
|
44
|
+
})),
|
|
45
|
+
isError: result.toolResult.isError,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const zodSchema = z.object(tool.inputSchema.properties);
|
|
49
|
+
server.tool(tool.name, zodSchema.shape, wrappedHandler);
|
|
50
|
+
}
|
|
51
|
+
async function main() {
|
|
52
|
+
const { logToStderr, initWordPress } = await import('./wordpress.js');
|
|
53
|
+
logToStderr('Starting WP REST Bridge MCP server...');
|
|
54
|
+
try {
|
|
55
|
+
await initWordPress();
|
|
56
|
+
const transport = new StdioServerTransport();
|
|
57
|
+
await server.connect(transport);
|
|
58
|
+
logToStderr('WP REST Bridge MCP server running on stdio');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logToStderr(`Failed to initialize: ${error}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
66
|
+
process.on('SIGINT', () => process.exit(0));
|
|
67
|
+
process.on('uncaughtException', (error) => {
|
|
68
|
+
process.stderr.write(`Uncaught exception: ${error}\n`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
main().catch((error) => {
|
|
72
|
+
process.stderr.write(`Startup error: ${error}\n`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|