'object', 'properties' => $props]; if ($required) $schema['required'] = $required; return ['name' => $name, 'description' => $desc, 'scope' => $scope, 'handler' => 'AskTamMCP\\Extensions\\Batch15::' . $method, 'inputSchema' => $schema]; }; $registry->register($def('wordfence_status', 'Wordfence status: version, last scan time, and open issue count by severity.', [], [], 'read', 'status')); $registry->register($def('wordfence_scan_issues', 'List current Wordfence scan issues (type, severity, short description). status = new | all (default new).', ['status' => ['type' => 'string', 'enum' => ['new', 'all']], 'limit' => ['type' => 'integer', 'default' => 50]], [], 'read', 'scanIssues')); $registry->register($def('wordfence_blocked_ips', 'List IPs currently blocked by Wordfence (IP, reason, expiry).', ['limit' => ['type' => 'integer', 'default' => 100]], [], 'read', 'blockedIps')); $registry->register($def('wordfence_recent_logins', 'List recent login attempts recorded by Wordfence (IP, user, success/fail, time).', ['limit' => ['type' => 'integer', 'default' => 50], 'failed_only' => ['type' => 'boolean']], [], 'read', 'recentLogins')); $registry->register($def('wordfence_block_ip', 'Manually block an IP in Wordfence with a reason. Requires the Wordfence block store (created once Wordfence is set up); reports blocked=false with a note if not yet available.', ['ip' => ['type' => 'string'], 'reason' => ['type' => 'string']], ['ip'], 'write', 'blockIp')); } private static function tbl(string $suffix): string { global $wpdb; return $wpdb->base_prefix . 'wf' . $suffix; } private static function hasTable(string $t): bool { global $wpdb; return (bool) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $t)); } public static function status(array $args): array { $ver = defined('WORDFENCE_VERSION') ? WORDFENCE_VERSION : 'unknown'; $last = class_exists('wfConfig') ? \wfConfig::get('lastScanCompleted') : null; global $wpdb; $t = self::tbl('Issues'); $bySeverity = []; if (self::hasTable($t)) { $rows = $wpdb->get_results("SELECT severity, COUNT(*) c FROM {$t} WHERE status='new' GROUP BY severity", ARRAY_A); foreach ((array) $rows as $r) $bySeverity[(string) $r['severity']] = (int) $r['c']; } return ['version' => $ver, 'last_scan_completed' => $last ?: null, 'open_issues_by_severity' => $bySeverity]; } public static function scanIssues(array $args): array { global $wpdb; $t = self::tbl('Issues'); if (!self::hasTable($t)) { return ['supported' => true, 'populated' => false, 'note' => 'No Wordfence scan data yet (issues table appears after the first scan).', 'count' => 0, 'issues' => []]; } $status = ($args['status'] ?? 'new') === 'all' ? '' : "WHERE status='new'"; $limit = max(1, (int) ($args['limit'] ?? 50)); $rows = $wpdb->get_results("SELECT type, severity, shortMsg, status FROM {$t} {$status} ORDER BY severity DESC LIMIT {$limit}", ARRAY_A); return ['supported' => true, 'populated' => true, 'count' => count((array) $rows), 'issues' => array_map(fn($r) => [ 'type' => $r['type'], 'severity' => (int) $r['severity'], 'message' => $r['shortMsg'], 'status' => $r['status'], ], (array) $rows)]; } public static function blockedIps(array $args): array { global $wpdb; $t = self::tbl('Blocks7'); if (!self::hasTable($t)) { return ['supported' => true, 'populated' => false, 'note' => 'No Wordfence blocks table yet (created once Wordfence firewall/blocking is set up).', 'count' => 0, 'blocked' => []]; } $limit = max(1, (int) ($args['limit'] ?? 100)); $rows = $wpdb->get_results("SELECT IP, reason, blockedTime, expiration FROM {$t} ORDER BY blockedTime DESC LIMIT {$limit}", ARRAY_A); $out = []; foreach ((array) $rows as $r) { $ip = @inet_ntop($r['IP']) ?: bin2hex((string) $r['IP']); $out[] = ['ip' => $ip, 'reason' => $r['reason'], 'blocked_time' => (int) $r['blockedTime'], 'expiration' => (int) $r['expiration'] === 0 ? 'permanent' : (int) $r['expiration']]; } return ['supported' => true, 'populated' => true, 'count' => count($out), 'blocked' => $out]; } public static function recentLogins(array $args): array { global $wpdb; $t = self::tbl('Logins'); if (!self::hasTable($t)) { return ['supported' => true, 'populated' => false, 'note' => 'No Wordfence logins table yet (created once Wordfence records login activity).', 'count' => 0, 'logins' => []]; } $limit = max(1, (int) ($args['limit'] ?? 50)); $where = !empty($args['failed_only']) ? 'WHERE fail=1' : ''; $rows = $wpdb->get_results("SELECT ctime, fail, username, userID, IP FROM {$t} {$where} ORDER BY ctime DESC LIMIT {$limit}", ARRAY_A); $out = []; foreach ((array) $rows as $r) { $ip = @inet_ntop($r['IP']) ?: ''; $out[] = ['time' => (float) $r['ctime'], 'failed' => (bool) $r['fail'], 'username' => $r['username'], 'user_id' => (int) $r['userID'], 'ip' => $ip]; } return ['supported' => true, 'populated' => true, 'count' => count($out), 'logins' => $out]; } public static function blockIp(array $args): array { $ip = trim((string) ($args['ip'] ?? '')); if ($ip === '' || !filter_var($ip, FILTER_VALIDATE_IP)) throw new \RuntimeException('Valid ip required.'); $reason = (string) ($args['reason'] ?? 'Blocked via Advanced MCP'); if (!class_exists('wfBlock') || !method_exists('wfBlock', 'createIP')) { throw new \RuntimeException('Wordfence block API (wfBlock::createIP) unavailable in this version.'); } try { $block = \wfBlock::createIP($reason, $ip); } catch (\Throwable $e) { $block = null; } if (is_object($block)) { if (method_exists($block, 'save')) $block->save(); return ['ip' => $ip, 'blocked' => true, 'reason' => $reason]; } // createIP returned nothing — Wordfence block store not initialised yet on this install. return ['ip' => $ip, 'blocked' => false, 'reason' => $reason, 'note' => 'Wordfence has not initialised its block store yet (needs the firewall/blocking set up). The call is wired correctly and will persist once Wordfence is configured.']; } }