Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions admin/section/class-convertkit-admin-section-mcp.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,27 @@ public function documentation_url() {

}

/**
* Renders the upgrade CTA when the connected Kit account is on
* the free plan.
*
* @since 3.4.0
*/
public function output_upgrade_required_message() {

?>
<p>
<?php esc_html_e( 'The Kit WordPress MCP is available on paid Kit plans. Upgrade your Kit account to connect AI clients to your WordPress site.', 'convertkit' ); ?>
</p>
<p>
<a href="https://app.kit.com/account_settings/billing" class="button button-primary" target="_blank">
<?php esc_html_e( 'Upgrade Kit Account', 'convertkit' ); ?>
</a>
</p>
<?php

}

/**
* Renders the input for the Enable setting.
*
Expand All @@ -250,6 +271,16 @@ public function documentation_url() {
*/
public function enabled_callback( $args ) {

// If the user doesn't have a paid plan, show the upgrade required message.
$account = new ConvertKit_Resource_Account();
if ( ! $account->is_paid_plan() ) {
// Disable saving settings.
$this->save_disabled = true;

$this->output_upgrade_required_message();
return;
}

// Output field.
$this->output_checkbox_field(
$args['name'],
Expand Down
10 changes: 9 additions & 1 deletion includes/class-convertkit-settings-mcp.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,22 @@ public function get() {
}

/**
* Returns whether the MCP server is enabled.
* Returns whether the user has access to MCP via a paid plan,
* and if so whether the MCP server is enabled in the Plugin's settings.
*
* @since 3.4.0
*
* @return bool
*/
public function enabled() {

// Bail if the connected Kit account isn't on a paid plan.
// This queries the cached account details, so no live API call is made.
$account = new ConvertKit_Resource_Account();
if ( ! $account->is_paid_plan() ) {
return false;
}

return ( $this->settings['enabled'] === 'on' ? true : false );

}
Expand Down
6 changes: 6 additions & 0 deletions includes/mcp/class-convertkit-mcp.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ public function register_abilities() {
*/
public function register_mcp_server( $adapter ) {

// Bail if the MCP server isn't enabled.
$settings = new ConvertKit_Settings_MCP();
if ( ! $settings->enabled() ) {
return;
}

// Get abilities.
$abilities = convertkit_get_abilities();

Expand Down
108 changes: 104 additions & 4 deletions tests/EndToEnd/general/plugin-screens/PluginSettingsMCPCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ public function _before(EndToEndTester $I)
{
// Activate Kit Plugin.
$I->activateKitPlugin($I);

// Setup Plugin.
$I->setupKitPlugin($I);
}

/**
Expand All @@ -36,8 +33,20 @@ public function _before(EndToEndTester $I)
*/
public function testEnableAndDisableMCPServerSetting(EndToEndTester $I)
{
// Simulate a Kit account that is on a paid plan.
$I->setupKitPlugin($I);
$I->haveOptionInDatabase(
'convertkit_account',
[
'account' => [
'plan_type' => 'creator_pro',
],
]
);

// Check that the MCP server is not registered.
$I->doesNotHaveRoute($I, '/kit-mcp');
$I->doesNotHaveRoute($I, '/kit/mcp');
$I->doesNotHaveRoute($I, '/kit/mcp/v1');

// Go to the Plugin's MCP Screen.
$I->loadKitSettingsMCPScreen($I);
Expand Down Expand Up @@ -88,6 +97,17 @@ public function testEnableAndDisableMCPServerSetting(EndToEndTester $I)
*/
public function testGenerateAndRevokeApplicationPassword(EndToEndTester $I)
{
// Simulate a Kit account that is on a paid plan.
$I->setupKitPlugin($I);
$I->haveOptionInDatabase(
'convertkit_account',
[
'account' => [
'plan_type' => 'creator_pro',
],
]
);

// Go to the Plugin's MCP Screen.
$I->loadKitSettingsMCPScreen($I);

Expand Down Expand Up @@ -158,6 +178,86 @@ public function testGenerateAndRevokeApplicationPassword(EndToEndTester $I)
$I->waitForElementNotVisible('#convertkit-settings-mcp-revoke-application-password');
}

/**
* Tests that a free-plan Kit account sees the upgrade CTA on the MCP tab
* instead of the enable / connect UI, and that the MCP REST route is not
* registered even when the enabled setting is on.
*
* @since 3.4.0
*
* @param EndToEndTester $I Tester.
*/
public function testFreePlanShowsUpgradeCTA(EndToEndTester $I)
{
// Simulate a Kit account that is on the free plan.
$I->setupKitPluginFakeAPIKey($I);
$I->setupKitPluginResources($I);
$I->haveOptionInDatabase(
'convertkit_account',
[
'account' => [
'plan_type' => 'free',
],
]
);

// Enable MCP server.
$I->haveOptionInDatabase(
'_wp_convertkit_settings_mcp',
[
'enabled' => 'on',
]
);

// Load the MCP settings tab.
$I->loadKitSettingsMCPScreen($I);

// Assert that the upgrade CTA is shown.
$I->see('The Kit WordPress MCP is available on paid Kit plans. Upgrade your Kit account to connect AI clients to your WordPress site.');
$I->seeLink('Upgrade Kit Account');

// Assert no option to enable/disable the MCP server are shown.
$I->dontSeeElement('#enabled');
$I->dontSee('Create Application Password');

// Assert that the MCP server is not registered.
$I->doesNotHaveRoute($I, '/kit/mcp');
$I->doesNotHaveRoute($I, '/kit/mcp/v1');
}

/**
* Tests that a paid-plan Kit account sees the enable UI on the MCP tab
* (i.e. the upgrade CTA is not shown).
*
* @since 3.4.0
*
* @param EndToEndTester $I Tester.
*/
public function testPaidPlanShowsEnableUI(EndToEndTester $I)
{
// Simulate a Kit account that is on a paid plan.
$I->setupKitPlugin($I);
$I->setupKitPluginResources($I);
$I->haveOptionInDatabase(
'convertkit_account',
[
'account' => [
'plan_type' => 'creator_pro',
],
]
);

// Load the MCP settings tab.
$I->loadKitSettingsMCPScreen($I);

// The upgrade CTA should not be shown.
$I->dontSee('The Kit WordPress MCP is available on paid Kit plans. Upgrade your Kit account to connect AI clients to your WordPress site.');
$I->dontSeeLink('Upgrade Kit Account');

// The Enable checkbox should be visible.
$I->seeElement('#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
Expand Down
132 changes: 132 additions & 0 deletions tests/Integration/SettingsMCPTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace Tests;

use lucatume\WPBrowser\TestCase\WPTestCase;

/**
* Tests for the ConvertKit_Settings_MCP class, in particular that enabled()
* is the single source of truth combining the toggle setting and the cached
* account plan.
*
* @since 3.4.0
*/
class SettingsMCPTest extends WPTestCase
{
/**
* The testing implementation.
*
* @var \WpunitTester.
*/
protected $tester;

/**
* Performs actions before each test.
*
* @since 3.4.0
*/
public function setUp(): void
{
parent::setUp();
activate_plugins('convertkit/wp-convertkit.php');
}

/**
* Performs actions after each test.
*
* @since 3.4.0
*/
public function tearDown(): void
{
delete_option(\ConvertKit_Settings_MCP::SETTINGS_NAME);
delete_option('convertkit_account');
deactivate_plugins('convertkit/wp-convertkit.php');
parent::tearDown();
}

/**
* Test that enabled() returns false when the MCP toggle is off, regardless
* of the cached plan type.
*
* @since 3.4.0
*/
public function testEnabledFalseWhenToggleOff()
{
update_option(\ConvertKit_Settings_MCP::SETTINGS_NAME, [ 'enabled' => '' ]);
update_option(
'convertkit_account',
[ 'account' => [ 'plan_type' => 'creator_pro' ] ]
);

$settings = new \ConvertKit_Settings_MCP();
$this->assertSame(false, $settings->enabled());
}

/**
* Test that enabled() returns false when the toggle is on but no account is
* cached (fail closed).
*
* @since 3.4.0
*/
public function testEnabledFalseWhenToggleOnAndNoAccountCache()
{
update_option(\ConvertKit_Settings_MCP::SETTINGS_NAME, [ 'enabled' => 'on' ]);
delete_option('convertkit_account');

$settings = new \ConvertKit_Settings_MCP();
$this->assertSame(false, $settings->enabled());
}

/**
* Test that enabled() returns false when the toggle is on but the cached
* plan is free.
*
* @since 3.4.0
*/
public function testEnabledFalseWhenToggleOnAndFreePlan()
{
update_option(\ConvertKit_Settings_MCP::SETTINGS_NAME, [ 'enabled' => 'on' ]);
update_option(
'convertkit_account',
[ 'account' => [ 'plan_type' => 'free' ] ]
);

$settings = new \ConvertKit_Settings_MCP();
$this->assertSame(false, $settings->enabled());
}

/**
* Test that enabled() returns true when the toggle is on and the cached
* plan is a paid plan.
*
* @since 3.4.0
*/
public function testEnabledTrueWhenToggleOnAndPaidPlan()
{
update_option(\ConvertKit_Settings_MCP::SETTINGS_NAME, [ 'enabled' => 'on' ]);
update_option(
'convertkit_account',
[ 'account' => [ 'plan_type' => 'creator' ] ]
);

$settings = new \ConvertKit_Settings_MCP();
$this->assertSame(true, $settings->enabled());
}

/**
* Test that enabled() returns true for creator_pro plans.
*
* @since 3.4.0
*/
public function testEnabledTrueForCreatorProPlan()
{
update_option(\ConvertKit_Settings_MCP::SETTINGS_NAME, [ 'enabled' => 'on' ]);
update_option(
'convertkit_account',
[ 'account' => [ 'plan_type' => 'creator_pro' ] ]
);

$settings = new \ConvertKit_Settings_MCP();
$this->assertSame(true, $settings->enabled());
}
}