Kit > MCP.
*
* @package ConvertKit
* @author ConvertKit
*/
class ConvertKit_Admin_Section_MCP extends ConvertKit_Admin_Section_Base {
/**
* The authorization header to display on screen.
*
* @since 3.4.0
*
* @var bool|string
*/
private $authorization_header = false;
/**
* Whether the Kit account is on a paid plan, once queried.
*
* @since 3.4.1
*
* @var bool|null
*/
private $is_paid_plan = null;
/**
* 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,
),
'connect' => array(
'title' => __( 'Connect an AI client', 'convertkit' ),
'callback' => array( $this, 'print_section_info_connect' ),
'wrap' => true,
),
);
$this->maybe_generate_authentication_header();
$this->maybe_revoke_application_password();
// 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();
}
/**
* Generates the authentication header to display on screen, if the user
* has just created an Application Password.
*
* @since 3.4.0
*/
private function maybe_generate_authentication_header() {
// Bail if we're not on the settings screen.
if ( ! $this->on_settings_screen( $this->name ) ) {
return;
}
// Bail if nonce verification fails.
if ( ! isset( $_REQUEST['_convertkit_settings_mcp_create_application_password'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_convertkit_settings_mcp_create_application_password'] ), 'convertkit-mcp-create-application-password' ) ) {
return;
}
// Bail if the user login and password are not included in the request.
if ( ! isset( $_REQUEST['user_login'] ) || ! isset( $_REQUEST['password'] ) ) {
return;
}
// Build the authorization header to display on screen.
$user_login = sanitize_text_field( wp_unslash( $_REQUEST['user_login'] ) );
$password = sanitize_text_field( wp_unslash( $_REQUEST['password'] ) );
$this->authorization_header = base64_encode( $user_login . ':' . $password ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
/**
* Revokes the Application Password, if the user clicked the Revoke Application Password button.
*
* @since 3.4.0
*/
private function maybe_revoke_application_password() {
// Bail if we're not on the settings screen.
if ( ! $this->on_settings_screen( $this->name ) ) {
return;
}
// Bail if nonce verification fails.
if ( ! isset( $_REQUEST['_convertkit_settings_mcp_revoke_application_password'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_convertkit_settings_mcp_revoke_application_password'] ), 'convertkit-mcp-revoke-application-password' ) ) {
return;
}
// Get the Application Password UUID.
$application_password_uuid = $this->get_application_password_uuid();
// Bail if no Application Password UUID exists.
if ( ! $application_password_uuid ) {
return;
}
// Revoke the Application Password.
$result = WP_Application_Passwords::delete_application_password( get_current_user_id(), $application_password_uuid );
if ( is_wp_error( $result ) ) {
$this->output_error( $result->get_error_message() );
return;
}
// Reload the settings screen.
wp_safe_redirect( $this->get_settings_url() );
exit();
}
/**
* 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 );
wp_enqueue_script( 'convertkit-admin-ui', CONVERTKIT_PLUGIN_URL . 'resources/backend/js/ui.js', array(), CONVERTKIT_PLUGIN_VERSION, true );
// Localize the strings displayed when copying a code block to the clipboard.
wp_localize_script(
'convertkit-admin-ui',
'convertkit_ui',
array(
'copy' => __( 'Copy', 'convertkit' ),
'copied' => __( 'Copied', 'convertkit' ),
'failed' => __( 'Press Ctrl/Cmd + C to copy', 'convertkit' ),
)
);
}
/**
* 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' => sprintf(
'%s%s',
__( 'MCP server URL:', 'convertkit' ),
esc_url( ConvertKit_MCP::get_server_url() )
),
)
);
}
/**
* Prints help info for this section
*
* @since 3.4.0
*/
public function print_section_info() {
?>
is_paid_plan() ) { // Disable saving settings. $this->save_disabled = true; $this->output_upgrade_required_message(); return; } // Output field. $this->output_checkbox_field( $args['name'], 'on', $this->settings->enabled(), $args['label'], $args['description'], array( 'convertkit-conditional-display' ) ); } /** * Renders the connection instructions, comprising of the steps the user needs * to complete to connect an AI client to this site's MCP server. * * @since 3.4.1 */ public function print_section_info_connect() { // Don't output anything if the Kit account isn't on a paid plan, as the // upgrade message is displayed in the section above. if ( ! $this->is_paid_plan() ) { return; } // The output is wrapped in its own container, so that the settings screen's // styles for a beta section's immediate children aren't applied to it. echo '
application_passwords_available() ) { // Get the Application Password for this Plugin, if one exists. $application_password = $this->get_application_password(); ?>
' . esc_url( ConvertKit_MCP::get_server_url() ) . '' ); ?>
output_client_instructions(); } ?>
' . esc_html( wp_get_current_user()->display_name ) . '' ); ?>
get_settings_url( array( '_convertkit_settings_mcp_revoke_application_password' => wp_create_nonce( 'convertkit-mcp-revoke-application-password' ) ) ); // Define the date and time format used for the Application Password's dates. $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' ); ?>' . esc_html( wp_get_current_user()->display_name ) . '', esc_html( (string) wp_date( $date_format, $application_password['created'] ) ) ); if ( ! empty( $application_password['last_used'] ) ) { echo ' '; printf( /* translators: %s: Date and time the Application Password was last used. */ esc_html__( 'Last used %s.', 'convertkit' ), esc_html( (string) wp_date( $date_format, $application_password['last_used'] ) ) ); } else { echo ' '; esc_html_e( 'Not yet used by an AI client.', 'convertkit' ); } ?>
authorization_header ) { ?>output_code_block( 'Basic ' . $this->authorization_header, 'kit-authorization-header' ); ?>
BASE64_ENCODED_USERNAME_AND_APPLICATION_PASSWORD' ); ?>
authorization_header ? $this->authorization_header : 'BASE64_ENCODED_USERNAME_AND_APPLICATION_PASSWORD' ); // Claude Desktop JSON. // Claude Desktop only supports remote MCP servers that authenticate using OAuth, so // mcp-remote is used to proxy requests to the MCP server, adding the authorization header. $claude_desktop_config = wp_json_encode( array( 'mcpServers' => array( 'kit-wordpress' => array( 'command' => 'npx', 'args' => array( '-y', 'mcp-remote', $server_url, '--header', 'Authorization: ' . $auth_header, ), ), ), ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // Claude Code command. $claude_code_command = sprintf( 'claude mcp add --transport http kit-wordpress %s --header "Authorization: %s"', $server_url, $auth_header ); // Cursor JSON. $cursor_config = wp_json_encode( array( 'mcpServers' => array( 'kit-wordpress' => array( 'url' => $server_url, 'headers' => array( 'Authorization' => $auth_header, ), ), ), ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // Codex TOML. $codex_config = '[mcp_servers.kit_wordpress]' . "\n" . 'url = "' . $server_url . '"' . "\n" . 'http_headers = { "Authorization" = "' . $auth_header . '" }'; // Define the clients to display, in the order they should be displayed. $clients = array( 'claude-desktop' => __( 'Claude Desktop', 'convertkit' ), 'claude-code' => __( 'Claude Code', 'convertkit' ), 'cursor' => __( 'Cursor', 'convertkit' ), 'codex' => __( 'Codex', 'convertkit' ), 'other' => __( 'Other clients', 'convertkit' ), ); ?>
claude_desktop_config.json'
);
?>
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
mcp-remote', 'Node.js' ); ?>
output_code_block( $claude_code_command ); ?>
~/.cursor/mcp.json' ); ?>
output_code_block( (string) $cursor_config ); ?>~/.codex/config.toml' ); ?>
output_code_block( $codex_config ); ?>experimental_use_rmcp_client = true' ); ?>
output_code_block( $server_url ); ?>
output_code_block( $auth_header ); ?>
documentation_url() === '#' ) { return; } ?> '_wp_convertkit_settings', 'tab' => $this->name, ), $query_args ), admin_url( 'options-general.php' ) ); } /** * Returns whether the Kit account is on a paid plan. * * @since 3.4.1 * * @return bool */ private function is_paid_plan() { // If the result has already been fetched for this request, return it. if ( ! is_null( $this->is_paid_plan ) ) { return $this->is_paid_plan; } // Fetch the account resource and return the result. $account = new ConvertKit_Resource_Account(); $this->is_paid_plan = $account->is_paid_plan(); return $this->is_paid_plan; } /** * Finds the most recently-created Application Password for this Plugin, belonging * to the currently logged in user. * * @since 3.4.1 * * @return bool|array */ private function get_application_password() { // Get the user's Application Passwords. $passwords = WP_Application_Passwords::get_user_application_passwords( get_current_user_id() ); // Return false if no Application Passwords exist. if ( empty( $passwords ) ) { return false; } // Iterate through the Application Passwords and return the password that matches the app name. foreach ( $passwords as $password ) { if ( $password['name'] === CONVERTKIT_MCP_APP_NAME ) { return $password; } } return false; } /** * Finds the UUID of the most recently-created Application Password for the * currently logged in user * * @since 3.4.0 * * @return bool|string */ private function get_application_password_uuid() { // Get the Application Password for this Plugin. $password = $this->get_application_password(); // Return false if no Application Password exists. if ( ! is_array( $password ) ) { return false; } return $password['uuid']; } } // 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; } );