self::SLUG ), $args ), admin_url( 'admin.php' ) ); } public static function menu() { add_menu_page( __( '100pixel AI Agent Connector', '100pixel-ai-agent-connector' ), __( 'AI Agent Connector', '100pixel-ai-agent-connector' ), MCP100P_Settings::key_capability(), self::SLUG, array( __CLASS__, 'render' ), self::menu_icon(), 80 ); } /** * Monochrome plugin mark as a data URI; WordPress recolors it to match the admin color scheme. */ private static function menu_icon() { $svg = (string) file_get_contents( MCP100P_DIR . 'assets/mcp100p-mark.svg' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } public static function row_meta( $links, $file ) { if ( plugin_basename( MCP100P_FILE ) === $file ) { $links[] = '' . esc_html__( 'Visit 100pixel.com', '100pixel-ai-agent-connector' ) . ''; } return $links; } public static function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Connect AI', '100pixel-ai-agent-connector' ) . '' ); return $links; } public static function assets( $hook ) { if ( 'toplevel_page_' . self::SLUG !== $hook ) { return; } // Version by file time so browsers and caches pick up every change. $css = MCP100P_DIR . 'assets/admin.css'; $js = MCP100P_DIR . 'assets/admin.js'; wp_enqueue_style( 'mcp100p-admin', MCP100P_URL . 'assets/admin.css', array(), MCP100P_VERSION . '.' . filemtime( $css ) ); wp_enqueue_script( 'mcp100p-admin', MCP100P_URL . 'assets/admin.js', array(), MCP100P_VERSION . '.' . filemtime( $js ), true ); wp_localize_script( 'mcp100p-admin', 'mcp100p', array( 'copied' => __( 'Copied!', '100pixel-ai-agent-connector' ), 'copy' => __( 'Copy', '100pixel-ai-agent-connector' ), 'revoke' => __( 'Revoke this key? Assistants using it will be disconnected.', '100pixel-ai-agent-connector' ), ) ); } /* ---------- Form handlers ---------- */ public static function create_key() { check_admin_referer( 'mcp100p_create_key' ); if ( ! current_user_can( MCP100P_Settings::key_capability() ) ) { wp_die( esc_html__( 'You are not allowed to create API keys.', '100pixel-ai-agent-connector' ) ); } $label = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : ''; $label = '' !== $label ? $label : __( 'AI agent', '100pixel-ai-agent-connector' ); $key = MCP100P_Auth::create( get_current_user_id(), $label ); set_transient( 'mcp100p_new_key_' . get_current_user_id(), $key, 10 * MINUTE_IN_SECONDS ); wp_safe_redirect( self::page_url( array( 'mcp100p_msg' => 'created' ) ) . '#mcp100p-connect' ); exit; } public static function revoke_key() { check_admin_referer( 'mcp100p_revoke_key' ); $id = isset( $_POST['key_id'] ) ? sanitize_key( wp_unslash( $_POST['key_id'] ) ) : ''; $ok = MCP100P_Auth::revoke( $id ); wp_safe_redirect( self::page_url( array( 'mcp100p_msg' => $ok ? 'revoked' : 'revoke_failed' ) ) ); exit; } public static function save_settings() { check_admin_referer( 'mcp100p_save_settings' ); if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to change these settings.', '100pixel-ai-agent-connector' ) ); } // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified above. $groups = array(); foreach ( array_keys( MCP100P_Settings::groups() ) as $group ) { $groups[ $group ] = empty( $_POST['groups'][ $group ] ) ? 0 : 1; } $capability = isset( $_POST['key_capability'] ) ? sanitize_key( wp_unslash( $_POST['key_capability'] ) ) : 'manage_options'; update_option( MCP100P_Settings::OPTION, array( 'enabled' => empty( $_POST['enabled'] ) ? 0 : 1, 'allow_url_key' => empty( $_POST['allow_url_key'] ) ? 0 : 1, 'read_only' => empty( $_POST['read_only'] ) ? 0 : 1, 'key_capability' => isset( MCP100P_Settings::access_levels()[ $capability ] ) ? $capability : 'manage_options', 'groups' => $groups, ) ); // phpcs:enable wp_safe_redirect( self::page_url( array( 'mcp100p_msg' => 'saved' ) ) . '#mcp100p-settings' ); exit; } /* ---------- Client setup snippets ---------- */ /** * Setup instructions for each AI client. */ private static function clients( $key ) { $url = MCP100P_Server::endpoint(); $url_key = MCP100P_Server::endpoint( $key ); $bearer = 'Bearer ' . $key; $json = function ( $data ) { return wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); }; $headers = array( 'Authorization' => $bearer ); $clients = array( 'claude-ai' => array( 'label' => 'Claude', 'kind' => 'Web & desktop app', 'icon' => 'claude', 'url_key' => true, 'steps' => array( 'Open Claude → Settings → Connectors and click Add custom connector.', 'Name it after your site and paste the URL below as the Remote MCP server URL. Leave the OAuth fields empty.', 'In a chat, open the + / tools menu and enable the connector.', ), 'code' => $url_key, ), 'chatgpt' => array( 'label' => 'ChatGPT', 'kind' => 'Paste a URL', 'icon' => 'openai', 'url_key' => true, 'steps' => array( 'In ChatGPT, open Plugins and click the Add button.', 'Choose Create MCP app and enter a name and description.', 'Under Authentication, choose No authentication.', 'Paste the URL below and click Connect.', ), 'code' => $url_key, ), 'claude-code' => array( 'label' => 'Claude Code', 'kind' => 'Terminal command', 'icon' => 'claude', 'steps' => array( 'Run this in your terminal:' ), 'code' => sprintf( 'claude mcp add --transport http wordpress %s --header "Authorization: %s"', $url, $bearer ), ), 'claude-desktop' => array( 'label' => 'Claude Desktop', 'kind' => 'Config file', 'icon' => 'claude', 'steps' => array( 'Alternative to the Connectors screen. Requires Node.js. Open Settings → Developer → Edit Config and add:', ), 'code' => $json( array( 'mcpServers' => array( 'wordpress' => array( 'command' => 'npx', 'args' => array( '-y', 'mcp-remote', $url, '--header', 'Authorization:${WP_AUTH}' ), 'env' => array( 'WP_AUTH' => $bearer ), ), ), ) ), ), 'cursor' => array( 'label' => 'Cursor', 'kind' => 'Config file', 'icon' => 'cursor', 'steps' => array( 'Add this to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):' ), 'code' => $json( array( 'mcpServers' => array( 'wordpress' => array( 'url' => $url, 'headers' => $headers, ), ), ) ), ), 'vscode' => array( 'label' => 'VS Code', 'kind' => 'Copilot · config file', 'icon' => 'visualstudiocode', 'steps' => array( 'Add this to .vscode/mcp.json in your workspace:' ), 'code' => $json( array( 'servers' => array( 'wordpress' => array( 'type' => 'http', 'url' => $url, 'headers' => $headers, ), ), ) ), ), 'gemini' => array( 'label' => 'Gemini CLI', 'kind' => 'Config file', 'icon' => 'googlegemini', 'steps' => array( 'Add this to ~/.gemini/settings.json:' ), 'code' => $json( array( 'mcpServers' => array( 'wordpress' => array( 'httpUrl' => $url, 'headers' => $headers, ), ), ) ), ), 'windsurf' => array( 'label' => 'Windsurf', 'kind' => 'Config file', 'icon' => 'windsurf', 'steps' => array( 'Add this to ~/.codeium/windsurf/mcp_config.json:' ), 'code' => $json( array( 'mcpServers' => array( 'wordpress' => array( 'serverUrl' => $url, 'headers' => $headers, ), ), ) ), ), 'other' => array( 'label' => 'Other', 'kind' => 'Any MCP client · test', 'icon' => 'modelcontextprotocol', 'steps' => array( 'Any MCP client that supports the Streamable HTTP transport works. Send the key as Authorization: Bearer <key> or X-API-Key: <key>. Test the connection with:', ), 'code' => sprintf( "curl -s %s \\\n -H \"Content-Type: application/json\" \\\n -H \"Accept: application/json, text/event-stream\" \\\n -H \"X-API-Key: %s\" \\\n -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}'", $url, $key ), ), ); // Setup prompts: a chat message the user pastes into an AI app that can run commands or // edit files, so the agent connects itself. Not possible for Claude web/desktop or ChatGPT, // whose connectors can only be added in their settings screens. $verify = 'Once the server is loaded (after any restart or refresh), list the tools the "wordpress" server provides and call wp_site_info to confirm which WordPress site you are connected to.'; // Node-based clients give each address only 250 ms to connect when a host has both IPv4 and // IPv6 records, so an unreachable IPv6 plus a slow IPv4 handshake fails as "fetch failed: AggregateError". $host = wp_parse_url( $url, PHP_URL_HOST ); $verify .= "\n\nIf the connection fails with \"fetch failed\", \"AggregateError\", \"ETIMEDOUT\" or \"ENETUNREACH\" even though the URL opens in a browser, the cause is usually on my computer, not the site: " . $host . " has an IPv6 address my network cannot reach, and the IPv4 connection takes longer than the 250 ms that Node.js gives each address. Fix it like this:\n" . '1. Look up the IPv4 address of ' . $host . " (for example with nslookup, dig or ping -4).\n" . "2. Check the diagnosis: a plain Node.js fetch() of the server URL fails, but the same request run with NODE_OPTIONS=--network-family-autoselection-attempt-timeout=2000 gets an HTTP response.\n" . '3. Ask me to add the line " ' . $host . "\" to my hosts file (/etc/hosts on macOS and Linux, C:\\Windows\\System32\\drivers\\etc\\hosts on Windows). It needs administrator rights, so give me the exact command or steps instead of running sudo yourself.\n" . '4. After I have done that, have me restart or reload the app and connect again.'; $clients['claude-code']['prompt'] = "Connect Claude Code to my WordPress site through its MCP server (100pixel AI Agent Connector).\n\n" . "1. Run this command in the terminal:\n\n" . $clients['claude-code']['code'] . "\n\n" . "2. Run `claude mcp list` and check that the \"wordpress\" server shows as connected.\n" . "3. Tell me to restart Claude Code (or start a new session) so the WordPress tools load.\n\n" . $verify; $clients['cursor']['prompt'] = "Connect Cursor to my WordPress site through its MCP server (100pixel AI Agent Connector).\n\n" . "Create the file .cursor/mcp.json in this project, or update it if it already exists. Keep any servers that are already in it, and add this \"wordpress\" server to \"mcpServers\":\n\n" . $clients['cursor']['code'] . "\n\n" . "Then tell me to open Cursor Settings → MCP and enable the \"wordpress\" server if it is not already on.\n\n" . $verify; $clients['vscode']['prompt'] = "Connect VS Code (GitHub Copilot agent mode) to my WordPress site through its MCP server (100pixel AI Agent Connector).\n\n" . "Create the file .vscode/mcp.json in this workspace, or update it if it already exists. Keep any servers that are already in it, and add this \"wordpress\" server to \"servers\":\n\n" . $clients['vscode']['code'] . "\n\n" . "Then tell me to click Start above the \"wordpress\" entry in mcp.json (or run \"MCP: List Servers\" and start it).\n\n" . $verify; $clients['gemini']['prompt'] = "Connect Gemini CLI to my WordPress site through its MCP server (100pixel AI Agent Connector).\n\n" . "Update ~/.gemini/settings.json (create it if it does not exist). Keep every existing setting and server, and add this \"wordpress\" server to \"mcpServers\":\n\n" . $clients['gemini']['code'] . "\n\n" . "Then tell me to restart Gemini CLI and run /mcp to check that \"wordpress\" is connected.\n\n" . $verify; $clients['windsurf']['prompt'] = "Connect Windsurf to my WordPress site through its MCP server (100pixel AI Agent Connector).\n\n" . "Update ~/.codeium/windsurf/mcp_config.json (create it if it does not exist). Keep any servers that are already in it, and add this \"wordpress\" server to \"mcpServers\":\n\n" . $clients['windsurf']['code'] . "\n\n" . "Then tell me to refresh the MCP servers in Cascade (Plugins/MCP panel → Refresh) so the WordPress tools load.\n\n" . $verify; $clients['other']['prompt'] = "Connect to my WordPress site's MCP server (100pixel AI Agent Connector) and add it to your MCP configuration as a server named \"wordpress\".\n\n" . "- Transport: Streamable HTTP\n" . '- URL: ' . $url . "\n" . '- Header: Authorization: ' . $bearer . "\n" . '- If your client cannot send headers, use this URL instead (the key is included): ' . $url_key . "\n\n" . "Edit your own MCP config file or run your MCP add command, keeping any existing servers. Tell me if I need to restart or refresh anything.\n\n" . $verify; return $clients; } /* ---------- Page ---------- */ /** * Inline a bundled brand icon (assets/icons/.svg). */ private static function icon( $name ) { $file = MCP100P_DIR . 'assets/icons/' . sanitize_file_name( $name ) . '.svg'; if ( ! is_readable( $file ) ) { return ''; } $svg = (string) file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $svg = preg_replace( '#.*?#s', '', $svg ); return str_replace( '
array( 'success', __( 'API key revoked.', '100pixel-ai-agent-connector' ) ), 'revoke_failed' => array( 'error', __( 'The key could not be revoked.', '100pixel-ai-agent-connector' ) ), 'saved' => array( 'success', __( 'Settings saved.', '100pixel-ai-agent-connector' ) ), ); ?>

">
$key ) : $owner = get_userdata( (int) $key['user_id'] ); ?>
user_login : '—' ); ?> mcp100p__…

$client ) : ?>
$client ) : ?>
>

  • SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

$info ) : $note = ''; if ( 'woocommerce' === $group && ! MCP100P_Tools_WooCommerce::available() ) { $note = __( 'WooCommerce not active', '100pixel-ai-agent-connector' ); } elseif ( 'abilities' === $group && ! MCP100P_Tools_Abilities::available() ) { $note = __( 'Requires WordPress 6.9+', '100pixel-ai-agent-connector' ); } ?>