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 '
'; if ( ! $this->settings->enabled() ) { // The MCP server isn't enabled; tell the user how to enable it. ?>

application_passwords_available() ) { // Get the Application Password for this Plugin, if one exists. $application_password = $this->get_application_password(); ?>
  1. ' . esc_url( ConvertKit_MCP::get_server_url() ) . '' ); ?>

  2. output_application_password( $application_password ); } else { $this->output_create_application_password(); } ?>
  3. output_client_instructions(); } ?>
output_capabilities_summary(); } echo '
'; } /** * Returns whether WordPress' Application Passwords feature is available for * the site and the current user, outputting an error message if it isn't. * * WordPress disables Application Passwords when the site isn't served over * HTTPS and isn't a local environment, meaning no AI client can authenticate. * * @since 3.4.1 * * @return bool Application Passwords are available. */ private function application_passwords_available() { // Application Passwords are disabled for the site. if ( ! wp_is_application_passwords_available() ) { $this->output_error( sprintf( /* translators: %1$s: Site URL, %2$s: WP_ENVIRONMENT_TYPE constant. */ __( 'Application Passwords are disabled on this site, so AI clients cannot authenticate. WordPress disables Application Passwords when a site is not served over HTTPS. Serve %1$s over HTTPS, or set %2$s to local on a development site, and then reload this screen.', 'convertkit' ), home_url(), 'WP_ENVIRONMENT_TYPE' ) ); return false; } // Application Passwords are disabled for this user. if ( ! wp_is_application_passwords_available_for_user( get_current_user_id() ) ) { $this->output_error( __( 'Application Passwords are disabled for your WordPress user, so you cannot create the password an AI client needs. Ask an administrator to enable Application Passwords for your user.', 'convertkit' ) ); return false; } return true; } /** * Renders the Create Application Password button, which sends the user to * WordPress' authorize-application.php screen. * * @since 3.4.1 */ private function output_create_application_password() { // Build the WordPress authorize-application.php URL. // See: https://developer.wordpress.org/advanced-administration/security/application-passwords/. // We don't use add_query_arg(), as rawurlencode() is needed for authorize-application.php's JS to work correctly. $authorize_url = admin_url( 'authorize-application.php' ) . '?app_name=' . rawurlencode( CONVERTKIT_MCP_APP_NAME ) . '&success_url=' . rawurlencode( $this->get_settings_url( array( '_convertkit_settings_mcp_create_application_password' => wp_create_nonce( 'convertkit-mcp-create-application-password' ), ) ) ) . '&reject_url=' . rawurlencode( $this->get_settings_url() ); ?>

' . 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

output_code_block( (string) $claude_desktop_config ); ?>

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; } );