' . esc_html__('All steps complete. Your AI client can now connect.', 'siteseo') . '
';
}
?>
' . esc_html__('SiteSEO registers 12 SEO abilities across 5 categories. They are picked up automatically by any installed MCP adapter and surfaced to AI clients as tools.', 'siteseo') . '';
echo wp_kses_post( self::render_abilities_list() );
}else{
echo '
' . esc_html__('The WordPress Abilities API ships in WordPress 6.9+. SiteSEO 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.', 'siteseo') . '
';
}
?>
' . esc_html__('The MCP Adapter plugin is installed and active. Your site now exposes an MCP server that AI clients can connect to.', 'siteseo') . '';
}elseif($adapter_installed){
echo '
' . esc_html__('The MCP Adapter is installed but not active. Activate it to enable the MCP server.', 'siteseo') . '
';
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.', 'siteseo') . '
';
return $html;
}
// The catalogue of SiteSEO abilities exposed to MCP clients. Mirrors the
// abilities registered in main/abilitiesregister.php so the UI listing
// stays in sync with the real wp_register_ability() calls. Kept in one
// place so future AI integrations can simply append a new entry here.
static function get_registered_abilities(){
return [
esc_html__('Posts', 'siteseo') => [
[
'label' => esc_html__('Get Post SEO Data', 'siteseo'),
'description' => esc_html__('Returns the SEO snapshot for a post: title, meta description, focus keyphrase, robots flags, canonical URL and social meta.', 'siteseo'),
],
[
'label' => esc_html__('Update Post SEO Data', 'siteseo'),
'description' => esc_html__('Updates SEO fields for a post: title, description, focus keyphrase, robots flags, canonical URL and social meta.', 'siteseo'),
],
[
'label' => esc_html__('List Posts Missing SEO Data', 'siteseo'),
'description' => esc_html__('Returns posts where one or more SEO fields are unset (title, description, or focus keyphrase).', 'siteseo'),
],
[
'label' => esc_html__('List Posts by SEO Score', 'siteseo'),
'description' => esc_html__('Returns posts with their cached SEO content-analysis score.', 'siteseo'),
],
],
esc_html__('Settings', 'siteseo') => [
[
'label' => esc_html__('Get SiteSEO Settings', 'siteseo'),
'description' => esc_html__('Returns the SiteSEO settings tree (titles, social, advanced, sitemap, analytics).', 'siteseo'),
],
],
esc_html__('Sitemap', 'siteseo') => [
[
'label' => esc_html__('Get Sitemap Status', 'siteseo'),
'description' => esc_html__('Returns whether the XML sitemap is enabled and the sitemap index URL.', 'siteseo'),
],
],
esc_html__('Robots.txt', 'siteseo') => [
[
'label' => esc_html__('Get Robots.txt Output', 'siteseo'),
'description' => esc_html__('Returns the active robots.txt content served for this site.', 'siteseo'),
],
[
'label' => esc_html__('List Robots.txt Rules', 'siteseo'),
'description' => esc_html__('Lists the parsed custom robots.txt rules.', 'siteseo'),
],
[
'label' => esc_html__('Add Robots.txt Rule', 'siteseo'),
'description' => esc_html__('Adds a new custom robots.txt rule for a given user agent.', 'siteseo'),
],
[
'label' => esc_html__('Delete Robots.txt Rule', 'siteseo'),
'description' => esc_html__('Deletes a custom robots.txt rule from the physical robots.txt file.', 'siteseo'),
],
],
esc_html__('Audit', 'siteseo') => [
[
'label' => esc_html__('Get Homepage SEO Audit', 'siteseo'),
'description' => esc_html__('Returns an SEO snapshot of the homepage: title, meta description, robots flags and a content-analysis score.', 'siteseo'),
],
[
'label' => esc_html__('Get Site SEO Audit', 'siteseo'),
'description' => esc_html__('Returns a site-wide SEO health snapshot: homepage score, sitemap state, robots rule count and public post types.', 'siteseo'),
],
],
];
}
// Human readable label for the current adapter state.
protected static function adapter_status_label($state){
switch($state){
case 'active':
return esc_html__('Active', 'siteseo');
case 'inactive':
return esc_html__('Inactive', 'siteseo');
default:
return esc_html__('Not installed', 'siteseo');
}
}
// Scan installed plugins for the MCP Adapter (folder prefix: mcp-adapter/).
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 SiteSEO?
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 installed MCP Adapter version from the plugin headers.
*
* @return string Empty string when the adapter is not installed.
*/
static function get_installed_mcp_adapter_version(){
$file = self::get_installed_mcp_adapter_file();
if('' === $file){
return '';
}
if(!function_exists('get_plugins')){
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$all_plugins = get_plugins();
if(!isset($all_plugins[ $file ]['Version'])){
return '';
}
return (string)$all_plugins[ $file ]['Version'];
}
// Read the saved test-connection status for the current user.
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.
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,
]);
}
}