From bbc967b45ad39658c7abc55d670db56fbab9c56d Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 17:25:21 +0800 Subject: [PATCH 1/8] Abilities API: Enable MCP Server Setting --- .../class-convertkit-admin-section-mcp.php | 172 ++++++++++++++++++ includes/class-convertkit-settings-mcp.php | 121 ++++++++++++ includes/class-wp-convertkit.php | 44 +++++ wp-convertkit.php | 14 +- 4 files changed, 339 insertions(+), 12 deletions(-) create mode 100644 admin/section/class-convertkit-admin-section-mcp.php create mode 100644 includes/class-convertkit-settings-mcp.php diff --git a/admin/section/class-convertkit-admin-section-mcp.php b/admin/section/class-convertkit-admin-section-mcp.php new file mode 100644 index 000000000..53f64c04b --- /dev/null +++ b/admin/section/class-convertkit-admin-section-mcp.php @@ -0,0 +1,172 @@ + Kit > MCP. + * + * @package ConvertKit + * @author ConvertKit + */ +class ConvertKit_Admin_Section_MCP extends ConvertKit_Admin_Section_Base { + + /** + * Constructor. + * + * @since 3.4.0 + */ + public function __construct() { + + // Define the class that reads/writes settings. + $this->settings = new ConvertKit_Settings_MCP(); + + // Define the settings key. + $this->settings_key = $this->settings::SETTINGS_NAME; + + // Define the programmatic name, Title and Tab Text. + $this->name = 'mcp'; + $this->title = __( 'MCP', 'convertkit' ); + $this->tab_text = __( 'MCP', 'convertkit' ); + + // Identify that this is beta functionality. + $this->is_beta = true; + + // Define settings sections. + $this->settings_sections = array( + 'general' => array( + 'title' => $this->title, + 'callback' => array( $this, 'print_section_info' ), + 'wrap' => true, + ), + ); + + // Register and maybe output notices for this settings screen, and the Intercom messenger. + if ( $this->on_settings_screen( $this->name ) ) { + add_action( 'convertkit_settings_base_render_before', array( $this, 'maybe_output_notices' ) ); + } + + // Enqueue scripts and CSS. + add_action( 'convertkit_admin_settings_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + + parent::__construct(); + + } + + /** + * Enqueues scripts for the Settings > MCP screen. + * + * @since 3.4.0 + * + * @param string $section Settings section / tab (general|tools|restrict-content|broadcasts|mcp). + */ + public function enqueue_scripts( $section ) { + + // Bail if we're not on the MCP section. + if ( $section !== $this->name ) { + return; + } + + // Enqueue JS. + wp_enqueue_script( 'convertkit-admin-settings-conditional-display', CONVERTKIT_PLUGIN_URL . 'resources/backend/js/settings-conditional-display.js', array( 'jquery' ), CONVERTKIT_PLUGIN_VERSION, true ); + + } + + /** + * Registers settings fields for this section. + * + * @since 3.4.0 + */ + public function register_fields() { + + // Enable. + add_settings_field( + 'enabled', + __( 'Enable MCP Server', 'convertkit' ), + array( $this, 'enabled_callback' ), + $this->settings_key, + $this->name, + array( + 'name' => 'enabled', + 'label_for' => 'enabled', + 'label' => __( 'When enabled, allows AI clients to connect to the Kit Plugin using MCP.', 'convertkit' ), + 'description' => '', + ) + ); + + } + + /** + * Prints help info for this section + * + * @since 3.4.0 + */ + public function print_section_info() { + + ?> + +

+ output_checkbox_field( + $args['name'], + 'on', + $this->settings->enabled(), + $args['label'], + $args['description'], + array( 'convertkit-conditional-display' ) + ); + + } + +} + +// Bootstrap. +add_filter( + 'convertkit_admin_settings_register_sections', + function ( $sections ) { + + // Don't register the MCP section if the Abilities API is not available (WordPress < 6.9). + if ( ! function_exists( 'wp_register_ability' ) ) { + return $sections; + } + + // Don't register the MCP section if PHP 7.4+ is not installed. + if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { + return $sections; + } + + $sections['mcp'] = new ConvertKit_Admin_Section_MCP(); + return $sections; + + } +); diff --git a/includes/class-convertkit-settings-mcp.php b/includes/class-convertkit-settings-mcp.php new file mode 100644 index 000000000..9e58b50f1 --- /dev/null +++ b/includes/class-convertkit-settings-mcp.php @@ -0,0 +1,121 @@ +settings = $this->get_defaults(); + } else { + $this->settings = array_merge( $this->get_defaults(), $settings ); + } + + } + + /** + * Returns Plugin settings. + * + * @since 3.4.0 + * + * @return array + */ + public function get() { + + return $this->settings; + + } + + /** + * Returns whether the MCP server is enabled. + * + * @since 3.4.0 + * + * @return bool + */ + public function enabled() { + + return ( $this->settings['enabled'] === 'on' ? true : false ); + + } + + /** + * The default settings, used when the ConvertKit MCP Settings haven't been saved + * e.g. on a new installation. + * + * @since 2.1.0 + * + * @return array + */ + public function get_defaults() { + + $defaults = array( + 'enabled' => '', // blank|on. + ); + + /** + * The default settings, used when the ConvertKit MCP Settings haven't been saved + * e.g. on a new installation. + * + * @since 3.4.0 + * + * @param array $defaults Default settings. + */ + $defaults = apply_filters( 'convertkit_settings_mcp_get_defaults', $defaults ); + + return $defaults; + + } + + /** + * Saves the given array of settings to the WordPress options table. + * + * @since 3.4.0 + * + * @param array $settings Settings. + */ + public function save( $settings ) { + + update_option( self::SETTINGS_NAME, array_merge( $this->get(), $settings ) ); + + } + +} diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index 63cfa5263..0dc5b83b7 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -64,6 +64,7 @@ public function initialize() { $this->initialize_cli_cron(); $this->initialize_frontend(); $this->initialize_global(); + $this->initialize_mcp(); } @@ -218,6 +219,49 @@ private function initialize_global() { } + /** + * Initializes the MCP server if enabled in the Plugin's settings. + * + * @since 3.4.0 + */ + public function initialize_mcp() { + + // Bail if the MCP server is not enabled. + $settings = new ConvertKit_Settings_MCP(); + if ( ! $settings->enabled() ) { + return; + } + + // Bail if the Abilities API is unavailable (WordPress < 6.9). + if ( ! function_exists( 'wp_register_ability' ) ) { + return; + } + + // Bail if PHP 7.4+ is not installed. + if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { + return; + } + + // Bail if the WordPress MCP Adapter is not installed. + if ( ! file_exists( CONVERTKIT_PLUGIN_PATH . '/vendor/autoload.php' ) ) { + return; + } + + // Load MCP Adapter. + require_once CONVERTKIT_PLUGIN_PATH . '/vendor/autoload.php'; + + // Bail if the MCP Adapter class doesn't exist - something went wrong with the autoloader. + if ( ! class_exists( 'WP\\MCP\\Core\\McpAdapter' ) ) { + return; + } + + // Bootstrap the MCP Adapter, per WordPress/mcp-adapter's recommended + // integration pattern. + // @see https://github.com/WordPress/mcp-adapter#using-mcp-adapter-in-your-plugin. + \WP\MCP\Core\McpAdapter::instance(); + + } + /** * Runs the Plugin's initialization and update routines, which checks if * the Plugin has just been updated to a newer version, diff --git a/wp-convertkit.php b/wp-convertkit.php index 1b6d3ea7e..f46a3708d 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -31,18 +31,6 @@ define( 'CONVERTKIT_OAUTH_CLIENT_ID', 'HXZlOCj-K5r0ufuWCtyoyo3f688VmMAYSsKg1eGvw0Y' ); define( 'CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI', 'https://app.kit.com/wordpress/redirect' ); -// Load WordPress MCP Adapter if the Abilities API is available (WordPress 6.9+) -// and PHP 7.4+ is installed. -if ( file_exists( CONVERTKIT_PLUGIN_PATH . '/vendor/autoload.php' ) && function_exists( 'wp_register_ability' ) && version_compare( PHP_VERSION, '7.4', '>=' ) ) { - require_once CONVERTKIT_PLUGIN_PATH . '/vendor/autoload.php'; - - // Bootstrap the MCP Adapter, per WordPress/mcp-adapter's recommended - // integration pattern. - // @see https://github.com/WordPress/mcp-adapter#using-mcp-adapter-in-your-plugin. - if ( class_exists( 'WP\\MCP\\Core\\McpAdapter' ) ) { - \WP\MCP\Core\McpAdapter::instance(); - } -} // Load shared classes, if they have not been included by another Kit Plugin. if ( ! trait_exists( 'ConvertKit_API_Traits' ) && ! trait_exists( 'ConvertKit_API\ConvertKit_API_Traits' ) ) { require_once CONVERTKIT_PLUGIN_PATH . '/vendor/convertkit/convertkit-wordpress-libraries/src/class-convertkit-api-traits.php'; @@ -89,6 +77,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-tags.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings-broadcasts.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings-mcp.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings-restrict-content.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-setup.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-shortcodes.php'; @@ -150,6 +139,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-broadcasts.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-form-entries.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-general.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-mcp.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-oauth.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-restrict-content.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-tools.php'; From 3a6d93b7ede5f8cdef6cc91cd7757ce75c04261a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 17:55:38 +0800 Subject: [PATCH 2/8] Added tests --- .../plugin-screens/PluginSettingsMCPCest.php | 77 +++++++++++++++++++ tests/Integration/MCPTest.php | 34 +++++++- tests/Support/Helper/KitRestrictContent.php | 15 ++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php new file mode 100644 index 000000000..fbc26a5c5 --- /dev/null +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php @@ -0,0 +1,77 @@ + Kit > MCP. + * + * @since 3.4.0 + */ +class PluginSettingsMCPCest +{ + /** + * Run common actions before running the test functions in this class. + * + * @since 3.4.0 + * + * @param EndToEndTester $I Tester. + */ + public function _before(EndToEndTester $I) + { + // Activate Kit Plugin. + $I->activateKitPlugin($I); + + // Setup Plugin. + $I->setupKitPlugin($I); + } + + /** + * Tests that enabling and disabling the MCP server setting works with no errors. + * + * @since 3.4.0 + * + * @param EndToEndTester $I Tester. + */ + public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) + { + // Go to the Plugin's MCP Screen. + $I->loadKitSettingsMCPScreen($I); + + // Enable MCP server. + $I->checkOption('#enabled'); + $I->click('Save Changes'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that the MCP server is enabled. + $I->seeCheckboxIsChecked('#enabled'); + + // Disable MCP server. + $I->uncheckOption('#enabled'); + $I->click('Save Changes'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that the MCP server is disabled. + $I->dontSeeCheckboxIsChecked('#enabled'); + } + + /** + * Deactivate and reset Plugin(s) after each test, if the test passes. + * We don't use _after, as this would provide a screenshot of the Plugin + * deactivation and not the true test error. + * + * @since 3.4.0 + * + * @param EndToEndTester $I Tester. + */ + public function _passed(EndToEndTester $I) + { + $I->deactivateKitPlugin($I); + $I->resetKitPlugin($I); + } +} diff --git a/tests/Integration/MCPTest.php b/tests/Integration/MCPTest.php index 3f79ea1c5..5010224f3 100644 --- a/tests/Integration/MCPTest.php +++ b/tests/Integration/MCPTest.php @@ -40,6 +40,22 @@ public function tearDown(): void parent::tearDown(); } + /** + * Test that the Kit MCP server is not created when the MCP server setting is disabled. + * + * @since 3.4.0 + */ + public function testKitMCPServerNotCreatedWhenDisabled() + { + // Make request. + $request = new \WP_REST_Request('POST', '/kit-mcp/v1'); + $request->set_header('Content-Type', 'application/json'); + $response = rest_get_server()->dispatch($request); + + // Assert response is 404 not found. + $this->assertSame( 404, $response->get_status() ); + } + /** * Test that the /wp-json/kit-mcp/v1 REST API route returns a 401 when the user is not authorized. * @@ -47,6 +63,14 @@ public function tearDown(): void */ public function testWhenUnauthorized() { + // Enable MCP server. + update_option( + '_wp_convertkit_settings_mcp', + [ + 'enabled' => 'on', + ] + ); + // Make request. $request = new \WP_REST_Request( 'GET', '/kit-mcp/v1' ); $response = rest_get_server()->dispatch( $request ); @@ -61,11 +85,19 @@ public function testWhenUnauthorized() * * @since 3.4.0 */ - public function testKitMCPServerCreated() + public function testKitMCPServerCreatedWhenEnabled() { // Create and become administrator. $this->actAsAdministrator(); + // Enable MCP server. + update_option( + '_wp_convertkit_settings_mcp', + [ + 'enabled' => 'on', + ] + ); + // Make request. $request = new \WP_REST_Request('POST', '/kit-mcp/v1'); $request->set_header('Content-Type', 'application/json'); diff --git a/tests/Support/Helper/KitRestrictContent.php b/tests/Support/Helper/KitRestrictContent.php index 7c325e08b..ea1882947 100644 --- a/tests/Support/Helper/KitRestrictContent.php +++ b/tests/Support/Helper/KitRestrictContent.php @@ -43,6 +43,21 @@ public function loadKitSettingsRestrictContentScreen($I) $I->checkNoWarningsAndNoticesOnScreen($I); } + /** + * Helper method to load the Plugin's Settings > MCP screen. + * + * @since 3.4.0 + * + * @param EndToEndTester $I EndToEndTester. + */ + public function loadKitSettingsMCPScreen($I) + { + $I->amOnAdminPage('options-general.php?page=_wp_convertkit_settings&tab=mcp'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + } + /** * Returns the expected default settings for Restricted Content. * From c9233aee55383b7b6a03f5459dc9b8bc6461d6bc Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 19:42:48 +0800 Subject: [PATCH 3/8] Conditionally load MCP Server and Abilities --- includes/class-wp-convertkit.php | 6 ---- includes/mcp/class-convertkit-mcp.php | 23 +++++++++++---- tests/Integration/MCPTest.php | 41 ++++++--------------------- 3 files changed, 27 insertions(+), 43 deletions(-) diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index 0dc5b83b7..b4387f737 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -226,12 +226,6 @@ private function initialize_global() { */ public function initialize_mcp() { - // Bail if the MCP server is not enabled. - $settings = new ConvertKit_Settings_MCP(); - if ( ! $settings->enabled() ) { - return; - } - // Bail if the Abilities API is unavailable (WordPress < 6.9). if ( ! function_exists( 'wp_register_ability' ) ) { return; diff --git a/includes/mcp/class-convertkit-mcp.php b/includes/mcp/class-convertkit-mcp.php index 05e3dd3df..22826e42e 100644 --- a/includes/mcp/class-convertkit-mcp.php +++ b/includes/mcp/class-convertkit-mcp.php @@ -64,11 +64,6 @@ class ConvertKit_MCP { */ public function __construct() { - // Bail if the Abilities API is unavailable (WordPress < 6.9). - if ( ! function_exists( 'wp_register_ability' ) ) { - return; - } - // Register the ability category. add_action( 'wp_abilities_api_categories_init', array( $this, 'register_abilities_category' ) ); @@ -87,6 +82,12 @@ public function __construct() { */ public function register_abilities_category() { + // Bail if the MCP server is not enabled. + $settings = new ConvertKit_Settings_MCP(); + if ( ! $settings->enabled() ) { + return; + } + wp_register_ability_category( self::CATEGORY_SLUG, array( @@ -104,6 +105,12 @@ public function register_abilities_category() { */ public function register_abilities() { + // Bail if the MCP server is not enabled. + $settings = new ConvertKit_Settings_MCP(); + if ( ! $settings->enabled() ) { + return; + } + // Get abilities. $abilities = convertkit_get_abilities(); @@ -136,6 +143,12 @@ public function register_abilities() { */ public function register_mcp_server( $adapter ) { + // Bail if the MCP server is not enabled. + $settings = new ConvertKit_Settings_MCP(); + if ( ! $settings->enabled() ) { + return; + } + // Get abilities. $abilities = convertkit_get_abilities(); diff --git a/tests/Integration/MCPTest.php b/tests/Integration/MCPTest.php index 5010224f3..c3f9a1d95 100644 --- a/tests/Integration/MCPTest.php +++ b/tests/Integration/MCPTest.php @@ -26,6 +26,15 @@ class MCPTest extends WPRestApiTestCase public function setUp(): void { parent::setUp(); + + // Enable MCP server. + update_option( + '_wp_convertkit_settings_mcp', + [ + 'enabled' => 'on', + ] + ); + activate_plugins('convertkit/wp-convertkit.php'); } @@ -40,22 +49,6 @@ public function tearDown(): void parent::tearDown(); } - /** - * Test that the Kit MCP server is not created when the MCP server setting is disabled. - * - * @since 3.4.0 - */ - public function testKitMCPServerNotCreatedWhenDisabled() - { - // Make request. - $request = new \WP_REST_Request('POST', '/kit-mcp/v1'); - $request->set_header('Content-Type', 'application/json'); - $response = rest_get_server()->dispatch($request); - - // Assert response is 404 not found. - $this->assertSame( 404, $response->get_status() ); - } - /** * Test that the /wp-json/kit-mcp/v1 REST API route returns a 401 when the user is not authorized. * @@ -63,14 +56,6 @@ public function testKitMCPServerNotCreatedWhenDisabled() */ public function testWhenUnauthorized() { - // Enable MCP server. - update_option( - '_wp_convertkit_settings_mcp', - [ - 'enabled' => 'on', - ] - ); - // Make request. $request = new \WP_REST_Request( 'GET', '/kit-mcp/v1' ); $response = rest_get_server()->dispatch( $request ); @@ -90,14 +75,6 @@ public function testKitMCPServerCreatedWhenEnabled() // Create and become administrator. $this->actAsAdministrator(); - // Enable MCP server. - update_option( - '_wp_convertkit_settings_mcp', - [ - 'enabled' => 'on', - ] - ); - // Make request. $request = new \WP_REST_Request('POST', '/kit-mcp/v1'); $request->set_header('Content-Type', 'application/json'); From b8d137a581724351849a3ccdc3649f369c286c0a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 20:33:02 +0800 Subject: [PATCH 4/8] Completed tests --- includes/class-wp-convertkit.php | 10 +++- includes/mcp/class-convertkit-mcp.php | 18 ------- tests/EndToEnd.suite.yml | 1 + .../plugin-screens/PluginSettingsMCPCest.php | 9 ++++ tests/Support/Helper/KitPlugin.php | 1 + tests/Support/Helper/WPRestAPI.php | 51 +++++++++++++++++++ 6 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 tests/Support/Helper/WPRestAPI.php diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index b4387f737..1e358c92f 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -226,17 +226,23 @@ private function initialize_global() { */ public function initialize_mcp() { + // Bail if the MCP server is not enabled. + $settings = new ConvertKit_Settings_MCP(); + if ( ! $settings->enabled() ) { + return; + } + // Bail if the Abilities API is unavailable (WordPress < 6.9). if ( ! function_exists( 'wp_register_ability' ) ) { return; } - // Bail if PHP 7.4+ is not installed. + // Bail if PHP 7.4+ is not installed, as this is required for the MCP Adapter classes. if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { return; } - // Bail if the WordPress MCP Adapter is not installed. + // Bail if the WordPress MCP Adapter autoloader is missing. if ( ! file_exists( CONVERTKIT_PLUGIN_PATH . '/vendor/autoload.php' ) ) { return; } diff --git a/includes/mcp/class-convertkit-mcp.php b/includes/mcp/class-convertkit-mcp.php index 22826e42e..4e1930b0b 100644 --- a/includes/mcp/class-convertkit-mcp.php +++ b/includes/mcp/class-convertkit-mcp.php @@ -82,12 +82,6 @@ public function __construct() { */ public function register_abilities_category() { - // Bail if the MCP server is not enabled. - $settings = new ConvertKit_Settings_MCP(); - if ( ! $settings->enabled() ) { - return; - } - wp_register_ability_category( self::CATEGORY_SLUG, array( @@ -105,12 +99,6 @@ public function register_abilities_category() { */ public function register_abilities() { - // Bail if the MCP server is not enabled. - $settings = new ConvertKit_Settings_MCP(); - if ( ! $settings->enabled() ) { - return; - } - // Get abilities. $abilities = convertkit_get_abilities(); @@ -143,12 +131,6 @@ public function register_abilities() { */ public function register_mcp_server( $adapter ) { - // Bail if the MCP server is not enabled. - $settings = new ConvertKit_Settings_MCP(); - if ( ! $settings->enabled() ) { - return; - } - // Get abilities. $abilities = convertkit_get_abilities(); diff --git a/tests/EndToEnd.suite.yml b/tests/EndToEnd.suite.yml index 85037bdcf..0fdb63bbb 100644 --- a/tests/EndToEnd.suite.yml +++ b/tests/EndToEnd.suite.yml @@ -41,6 +41,7 @@ modules: - \Tests\Support\Helper\WPGutenberg - \Tests\Support\Helper\WPMetabox - \Tests\Support\Helper\WPNotices + - \Tests\Support\Helper\WPRestAPI - \Tests\Support\Helper\WPQuickEdit - \Tests\Support\Helper\WPWidget - \Tests\Support\Helper\Xdebug diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php index fbc26a5c5..a2c9a72b5 100644 --- a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php @@ -36,6 +36,9 @@ public function _before(EndToEndTester $I) */ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) { + // Check that the MCP server is not registered. + $I->doesNotHaveRoute($I, '/kit-mcp'); + // Go to the Plugin's MCP Screen. $I->loadKitSettingsMCPScreen($I); @@ -49,6 +52,9 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) // Check that the MCP server is enabled. $I->seeCheckboxIsChecked('#enabled'); + // Check that the MCP server is registered. + $I->hasRoute($I, '/kit-mcp'); + // Disable MCP server. $I->uncheckOption('#enabled'); $I->click('Save Changes'); @@ -58,6 +64,9 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) // Check that the MCP server is disabled. $I->dontSeeCheckboxIsChecked('#enabled'); + + // Check that the MCP server is not registered. + $I->doesNotHaveRoute($I, '/kit-mcp'); } /** diff --git a/tests/Support/Helper/KitPlugin.php b/tests/Support/Helper/KitPlugin.php index 93fee63e9..a656c238b 100644 --- a/tests/Support/Helper/KitPlugin.php +++ b/tests/Support/Helper/KitPlugin.php @@ -575,6 +575,7 @@ public function resetKitPlugin($I) $I->dontHaveOptionInDatabase('_wp_convertkit_settings'); $I->dontHaveOptionInDatabase('_wp_convertkit_settings_restrict_content'); $I->dontHaveOptionInDatabase('_wp_convertkit_settings_broadcasts'); + $I->dontHaveOptionInDatabase('_wp_convertkit_settings_mcp'); $I->dontHaveOptionInDatabase('convertkit_version'); // Resources. diff --git a/tests/Support/Helper/WPRestAPI.php b/tests/Support/Helper/WPRestAPI.php new file mode 100644 index 000000000..26011ebe2 --- /dev/null +++ b/tests/Support/Helper/WPRestAPI.php @@ -0,0 +1,51 @@ +{yourFunctionName}. + * + * @since 3.4.0 + */ +class WPRestAPI extends \Codeception\Module +{ + /** + * Check that the given route is registered in the REST API. + * + * @since 3.4.0 + * + * @param EndToEndTester $I EndToEndTester. + * @param string $route Route. + */ + public function hasRoute($I, $route) + { + $I->assertTrue( in_array( $route, $this->getRoutes(), true ) ); + } + + /** + * Check that the given route is not registered in the REST API. + * + * @since 3.4.0 + * + * @param EndToEndTester $I EndToEndTester. + * @param string $route Route. + */ + public function doesNotHaveRoute($I, $route) + { + $I->assertFalse( in_array( $route, $this->getRoutes(), true ) ); + } + + /** + * Get the routes registered in the REST API. + * + * @since 3.4.0 + * + * @return array + */ + private function getRoutes() + { + $response = wp_remote_get( rest_url() ); + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + return array_keys( $body['routes'] ?? [] ); + } +} From 6a9ff89badd99d8cfeeeeffedb442d12b08b0831 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 20:47:32 +0800 Subject: [PATCH 5/8] Remove MCP unit test --- tests/Integration/MCPTest.php | 118 ---------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 tests/Integration/MCPTest.php diff --git a/tests/Integration/MCPTest.php b/tests/Integration/MCPTest.php deleted file mode 100644 index c3f9a1d95..000000000 --- a/tests/Integration/MCPTest.php +++ /dev/null @@ -1,118 +0,0 @@ - 'on', - ] - ); - - activate_plugins('convertkit/wp-convertkit.php'); - } - - /** - * Performs actions after each test. - * - * @since 3.4.0 - */ - public function tearDown(): void - { - deactivate_plugins('convertkit/wp-convertkit.php'); - parent::tearDown(); - } - - /** - * Test that the /wp-json/kit-mcp/v1 REST API route returns a 401 when the user is not authorized. - * - * @since 3.4.0 - */ - public function testWhenUnauthorized() - { - // Make request. - $request = new \WP_REST_Request( 'GET', '/kit-mcp/v1' ); - $response = rest_get_server()->dispatch( $request ); - - // Assert response is unsuccessful. - $this->assertSame( 401, $response->get_status() ); - } - - /** - * Test that the Kit MCP server is registered with the MCP Adapter and - * exposes its discovery endpoint at /wp-json/kit-mcp/v1. - * - * @since 3.4.0 - */ - public function testKitMCPServerCreatedWhenEnabled() - { - // Create and become administrator. - $this->actAsAdministrator(); - - // Make request. - $request = new \WP_REST_Request('POST', '/kit-mcp/v1'); - $request->set_header('Content-Type', 'application/json'); - $request->set_body( - wp_json_encode( - [ - 'jsonrpc' => '2.0', - 'id' => 1, - 'method' => 'initialize', - 'params' => [ - 'protocolVersion' => '2024-11-05', - 'capabilities' => new \stdClass(), - 'clientInfo' => [ - 'name' => 'test', - 'version' => '1.0', - ], - ], - ] - ) - ); - $response = rest_get_server()->dispatch($request); - - // Assert the discovery endpoint is registered and responds successfully. - $this->assertSame(200, $response->get_status()); - - // Assert the response identifies itself as the Kit MCP server. - $data = $response->get_data(); - $this->assertSame('Kit MCP', $data['result']->serverInfo['name'] ?? null); - } - - /** - * Act as an administrator user. - * - * @since 3.4.0 - */ - private function actAsAdministrator() - { - $administrator_id = static::factory()->user->create( [ 'role' => 'administrator' ] ); - wp_set_current_user( $administrator_id ); - } -} From c2c9044fc11ed7368a6a530cd90b191be1a109c4 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 20:59:57 +0800 Subject: [PATCH 6/8] Change URL to kit/mcp/v1; surface URL to use in AI clients --- admin/section/class-convertkit-admin-section-mcp.php | 6 +++++- includes/mcp/class-convertkit-mcp.php | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/admin/section/class-convertkit-admin-section-mcp.php b/admin/section/class-convertkit-admin-section-mcp.php index 53f64c04b..8cb7f2be8 100644 --- a/admin/section/class-convertkit-admin-section-mcp.php +++ b/admin/section/class-convertkit-admin-section-mcp.php @@ -93,7 +93,11 @@ public function register_fields() { 'name' => 'enabled', 'label_for' => 'enabled', 'label' => __( 'When enabled, allows AI clients to connect to the Kit Plugin using MCP.', 'convertkit' ), - 'description' => '', + 'description' => sprintf( + '%s
%s', + __( 'Go to your AI tool to add a custom connector by pasting this URL to connect to this plugin:', 'convertkit' ), + get_site_url() . '/wp-json/kit/mcp/v1' + ), ) ); diff --git a/includes/mcp/class-convertkit-mcp.php b/includes/mcp/class-convertkit-mcp.php index 4e1930b0b..d16d349ab 100644 --- a/includes/mcp/class-convertkit-mcp.php +++ b/includes/mcp/class-convertkit-mcp.php @@ -37,7 +37,7 @@ class ConvertKit_MCP { * * @var string */ - const SERVER_ID = 'kit-mcp'; + const SERVER_ID = 'kit/mcp'; /** * The REST namespace used by the MCP server. @@ -46,7 +46,7 @@ class ConvertKit_MCP { * * @var string */ - const SERVER_NAMESPACE = 'kit-mcp'; + const SERVER_NAMESPACE = 'kit/mcp'; /** * The REST version number used by the MCP server. @@ -145,7 +145,7 @@ public function register_mcp_server( $adapter ) { self::SERVER_ID, self::SERVER_NAMESPACE, self::SERVER_ROUTE, - __( 'Kit MCP', 'convertkit' ), + __( 'Kit WordPress Plugin MCP', 'convertkit' ), __( 'Exposes Kit Plugin abilities over the Model Context Protocol.', 'convertkit' ), '1.0.0', array( 'WP\\MCP\\Transport\\HttpTransport' ), From 33aa917ee46760c024611c3203c91054c156c872 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 May 2026 21:18:07 +0800 Subject: [PATCH 7/8] PHPStan compat. --- admin/section/class-convertkit-admin-section-base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/section/class-convertkit-admin-section-base.php b/admin/section/class-convertkit-admin-section-base.php index 650b0321d..79d3e38c0 100644 --- a/admin/section/class-convertkit-admin-section-base.php +++ b/admin/section/class-convertkit-admin-section-base.php @@ -47,7 +47,7 @@ abstract class ConvertKit_Admin_Section_Base { * * @since 1.9.6 * - * @var false|ConvertKit_Settings|ConvertKit_ContactForm7_Settings|ConvertKit_Wishlist_Settings|ConvertKit_Settings_Restrict_Content|ConvertKit_Settings_Broadcasts|ConvertKit_Forminator_Settings + * @var false|ConvertKit_Settings|ConvertKit_ContactForm7_Settings|ConvertKit_Wishlist_Settings|ConvertKit_Settings_Restrict_Content|ConvertKit_Settings_Broadcasts|ConvertKit_Forminator_Settings|ConvertKit_Settings_MCP */ public $settings; From 6cd8073140003487d2660088260a4a3e0c0861c2 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 May 2026 12:26:38 +0800 Subject: [PATCH 8/8] Fix test --- .../general/plugin-screens/PluginSettingsMCPCest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php index a2c9a72b5..3d7a009ea 100644 --- a/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php @@ -50,10 +50,12 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) $I->checkNoWarningsAndNoticesOnScreen($I); // Check that the MCP server is enabled. + $I->waitForElementVisible('#enabled'); $I->seeCheckboxIsChecked('#enabled'); // Check that the MCP server is registered. - $I->hasRoute($I, '/kit-mcp'); + $I->hasRoute($I, '/kit/mcp'); + $I->hasRoute($I, '/kit/mcp/v1'); // Disable MCP server. $I->uncheckOption('#enabled'); @@ -63,10 +65,16 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I) $I->checkNoWarningsAndNoticesOnScreen($I); // Check that the MCP server is disabled. + $I->waitForElementVisible('#enabled'); $I->dontSeeCheckboxIsChecked('#enabled'); + // Go to the Plugin's MCP Screen. + $I->loadKitSettingsMCPScreen($I); + $I->wait(2); + // Check that the MCP server is not registered. - $I->doesNotHaveRoute($I, '/kit-mcp'); + $I->doesNotHaveRoute($I, '/kit/mcp'); + $I->doesNotHaveRoute($I, '/kit/mcp/v1'); } /**