admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('speedycache_ajax_nonce'), 'endpoint_url' => trailingslashit(home_url()) . ltrim(self::$ABILITIES_ENDPOINT, '/'), 'i18n' => [ 'installing' => esc_html__('Installing...', 'speedycache'), 'activating' => esc_html__('Activating...', 'speedycache'), 'generating' => esc_html__('Generating...', 'speedycache'), 'testing' => esc_html__('Testing...', 'speedycache'), 'genErrTitle' => esc_html__('Could not generate the password.', 'speedycache'), 'testOkPrefix' => esc_html__('Success:', 'speedycache'), 'testFailPrefix' => esc_html__('Failed:', 'speedycache'), ], ]); // Status probes (kept cheap; safe to run on each render). $abilities_api_available = function_exists('wp_register_ability'); $adapter_active = is_plugin_active('mcp-adapter/mcp-adapter.php'); $adapter_installed = '' !== self::get_installed_mcp_adapter_file(); $has_app_password = self::current_user_has_mcp_app_password(); $test_status = self::get_test_connection_status(); $site_url = trailingslashit(rtrim(home_url(), '/')); $endpoint_url = $site_url . ltrim(self::$ABILITIES_ENDPOINT, '/'); $mcp_server_url = $site_url . ltrim(self::$MCP_SERVER_ENDPOINT, '/'); $username = function_exists('wp_get_current_user') ? wp_get_current_user()->user_login : ''; $app_pass_placeholder = esc_html__('your-application-password', 'speedycache'); $user_placeholder = $username ?: esc_html__('', 'speedycache'); // Setup progress $done_count = 0; $next_step = ''; if(!$abilities_api_available){ $next_step = esc_html__('Abilities registration', 'speedycache'); }elseif(!$adapter_active){ $done_count++; $next_step = esc_html__('MCP Adapter installation', 'speedycache'); }elseif(!$has_app_password){ $done_count += 2; $next_step = esc_html__('Application Password', 'speedycache'); }elseif(empty($test_status['ok'])){ $done_count += 3; $next_step = esc_html__('Connection test', 'speedycache'); }else{ $done_count += 4; } $test_ok = !empty($test_status['ok']); $test_message = !empty($test_status['message']) ? $test_status['message'] : ''; $speedycache_options = get_option('speedycache_options', []); $is_enabled = !empty($speedycache_options['ai_abilities']['enabled']) ? 1 : 0; ?>

' . sprintf(esc_html__('Next up: %s', 'speedycache'), '' . esc_html($next_step) . '') . ''; }else{ echo '' . esc_html__('All steps complete. Your AI client can now connect.', 'speedycache') . ''; } ?>
' . esc_html__('SpeedyCache exposes the abilities listed below. They are picked up automatically by any installed MCP adapter and surfaced to AI clients as tools.', 'speedycache') . '

'; echo wp_kses_post(self::render_abilities_list()); }else{ echo '

' . esc_html__('The WordPress Abilities API ships in WordPress 6.9+. SpeedyCache can still work today — installing the MCP Adapter backfills the API so your AI client can connect immediately. Consider updating WordPress for native abilities support.', 'speedycache') . '

'; } ?>
' . esc_html__('The MCP Adapter plugin is installed and active. Your site now exposes an MCP server that AI clients can connect to.', 'speedycache') . '

'; }elseif($adapter_installed){ echo '

' . esc_html__('The MCP Adapter is installed but not active. Activate it to enable the MCP server.', 'speedycache') . '

'; echo ''; }else{ echo '

' . esc_html__('Install the official WordPress MCP Adapter plugin with a single click. It bridges your site to any MCP-compatible AI client. SpeedyCache requires the MCP Adapter to expose its abilities to AI clients.', 'speedycache') . '

'; echo ''; } ?>

' . esc_html(($test_ok ? __('Success: ', 'speedycache') : __('Failed: ', 'speedycache')) . $test_message) . '
'; }else{ echo '
'; } ?>

' . wp_json_encode([ 'siteUrl' => $site_url, 'mcpServerUrl' => $mcp_server_url, 'endpointUrl' => $endpoint_url, 'username' => $username, 'passwordHolder' => $app_pass_placeholder, 'userPlaceholder' => $user_placeholder, 'serverName' => 'speedycache-site', 'adapterActive' => $adapter_active, 'hasAppPassword' => $has_app_password, 'i18n' => [ 'copy' => esc_html__('Copy snippet', 'speedycache'), 'copied' => esc_html__('Copied!', 'speedycache'), 'installing' => esc_html__('Installing...', 'speedycache'), 'activating' => esc_html__('Activating...', 'speedycache'), 'generating' => esc_html__('Generating...', 'speedycache'), 'testing' => esc_html__('Testing...', 'speedycache'), 'genErrTitle' => esc_html__('Could not generate the password.', 'speedycache'), 'testOkPrefix' => esc_html__('Success:', 'speedycache'), 'testFailPrefix' => esc_html__('Failed:', 'speedycache'), ], ]) . ''; $clients = [ 'claude-desktop' => ['label' => esc_html__('Claude Desktop', 'speedycache')], 'claude-code' => ['label' => esc_html__('Claude Code CLI', 'speedycache')], 'cursor' => ['label' => esc_html__('Cursor', 'speedycache')], 'vscode' => ['label' => esc_html__('VS Code', 'speedycache')], 'antigravity' => ['label' => esc_html__('Antigravity', 'speedycache')], 'opencode' => ['label' => esc_html__('OpenCode', 'speedycache'), 'icon' => 'OC'], 'gemini-cli' => ['label' => esc_html__('Gemini CLI', 'speedycache'), 'deprecated' => true], ]; echo '
'; $first = true; foreach($clients as $id => $data){ $badge = ''; if(!empty($data['deprecated'])){ $badge = ' ' . esc_html__('Deprecated', 'speedycache') . ''; } echo ''; $first = false; } ?>

									
								

    ' . esc_html__('No SpeedyCache abilities are currently exposed. Activate the MCP Adapter to surface them.', 'speedycache') . '

    '; } $html = '
    '; foreach($groups as $group => $items){ $html .= '
    '; $html .= '

    ' . esc_html($group) . '

    '; $html .= '
      '; foreach($items as $ability){ $html .= '
    • '; $html .= '' . esc_html($ability['label']) . ''; if(!empty($ability['description'])){ $html .= '' . esc_html($ability['description']) . ''; } if(!empty($ability['pro'])){ $html .= '' . esc_html__('Pro', 'speedycache') . ''; } $html .= '
    • '; } $html .= '
    '; $html .= '
    '; } $html .= '
    '; return $html; } /** * The catalogue of SpeedyCache abilities exposed to MCP clients. Mirrors * the abilities registered in main/abilitiesregister.php (Free) and * main/abilitiespro.php in Pro. The Pro section is only merged when * the SpeedyCache Pro plugin is installed and active. * * @return array */ static function get_registered_abilities(){ $free = [ esc_html__('Settings', 'speedycache') => [ [ 'label' => esc_html__('Get SpeedyCache Settings', 'speedycache'), 'description' => esc_html__('Returns global cache settings: status, gzip, mobile, preload, lifespan, Varnish and more.', 'speedycache'), ], ], esc_html__('Cache', 'speedycache') => [ [ 'label' => esc_html__('Delete All Cache', 'speedycache'), 'description' => esc_html__('Purges the entire page cache and optionally the minified assets, fonts and gravatars.', 'speedycache'), ], [ 'label' => esc_html__('Delete Cache for a URL', 'speedycache'), 'description' => esc_html__('Purges the cached page(s) for the given URL(s).', 'speedycache'), ], [ 'label' => esc_html__('Delete Cache for a Post', 'speedycache'), 'description' => esc_html__('Purges the cached page for a given post ID (0 = homepage).', 'speedycache'), ], [ 'label' => esc_html__('Get Cache Statistics', 'speedycache'), 'description' => esc_html__('Returns basic cache stats: HTML file count and total size.', 'speedycache'), ], ], esc_html__('File Optimization', 'speedycache') => [ [ 'label' => esc_html__('Get File Optimization Settings', 'speedycache'), 'description' => esc_html__('Returns minify HTML/CSS/JS, combine, delay JS, render-blocking, lazy load and speculation toggles.', 'speedycache'), ], ], esc_html__('CDN', 'speedycache') => [ [ 'label' => esc_html__('Get CDN Settings', 'speedycache'), 'description' => esc_html__('Returns the CDN configuration: enabled, URL, file types and include/exclude keywords.', 'speedycache'), ], ], esc_html__('Excludes', 'speedycache') => [ [ 'label' => esc_html__('List Cache Excludes', 'speedycache'), 'description' => esc_html__('Returns the list of cache exclusion rules (type, prefix, content).', 'speedycache'), ], [ 'label' => esc_html__('Delete a Cache Exclude Rule', 'speedycache'), 'description' => esc_html__('Deletes a single cache exclusion rule by its index.', 'speedycache'), ], ], esc_html__('Preload', 'speedycache') => [ [ 'label' => esc_html__('Get Preload Status', 'speedycache'), 'description' => esc_html__('Returns whether preloading is enabled and the queued URL list.', 'speedycache'), ], [ 'label' => esc_html__('Build the Preload List', 'speedycache'), 'description' => esc_html__('Re-builds the preload list and kicks off the preload cron.', 'speedycache'), ], ], esc_html__('Import / Export', 'speedycache') => [ [ 'label' => esc_html__('Export SpeedyCache Settings', 'speedycache'), 'description' => esc_html__('Exports the full settings tree as JSON. Object-cache credentials are scrubbed.', 'speedycache'), ], ], ]; // Allow SpeedyCache Pro to register additional abilities. return apply_filters('speedycache_registered_abilities', $free); } /** * Scan installed plugins for the MCP Adapter (folder prefix: mcp-adapter/). * * @return string */ static function get_installed_mcp_adapter_file(){ if(!function_exists('get_plugins')){ require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); $target_file = 'mcp-adapter/mcp-adapter.php'; if(isset($all_plugins[ $target_file ])){ return $target_file; } return ''; } /** * Does the current user already have an MCP app password created by SpeedyCache? * * @return bool */ static function current_user_has_mcp_app_password(){ $user_id = get_current_user_id(); if(!$user_id || !class_exists('WP_Application_Passwords')){ return false; } foreach(\WP_Application_Passwords::get_user_application_passwords($user_id) as $app){ if(!empty($app['app_id']) && $app['app_id'] === self::$APP_PASSWORD_APP_ID){ return true; } } return false; } /** * Read the saved test-connection status for the current user. * * @return array */ static function get_test_connection_status(){ $user_id = get_current_user_id(); if(!$user_id){ return ['ok' => false, 'message' => '']; } $stored = get_user_meta($user_id, self::$TEST_STATUS_META, true); if(!is_array($stored)){ return ['ok' => false, 'message' => '']; } return [ 'ok' => !empty($stored['ok']), 'message' => !empty($stored['message']) ? $stored['message'] : '', ]; } /** * Persist the test-connection status for the current user. * * @param bool $ok * @param string $message * @return void */ static function save_test_connection_status($ok, $message){ $user_id = get_current_user_id(); if(!$user_id){ return; } update_user_meta($user_id, self::$TEST_STATUS_META, [ 'ok' => !empty($ok), 'message' => (string)$message, ]); } }