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 '
' . esc_html__('Activate MCP Adapter', 'speedycache') . ' ';
}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__('Install MCP Adapter', 'speedycache') . ' ';
}
?>
' . 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 '' . esc_html($data['label']) . wp_kses_post($badge) . ' ';
$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,
]);
}
}