PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.3.1
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.3.1
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
notificationx / includes / MCP / SelfTest.php

SelfTest.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar 3.3.1, at includes/MCP/SelfTest.php

125 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Loopback self-test for the NotificationX MCP server.
4 *
5 * Calls the site's own MCP endpoint the way an AI client would and reports the
6 * first thing that is wrong, so an admin gets an actionable answer ("HTTPS
7 * mismatch", "0 tools") instead of a silent "connected but useless" state.
8 *
9 * @package NotificationX\MCP
10 */
11
12 namespace NotificationX\MCP;
13
14 use NotificationX\GetInstance;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * @method static SelfTest get_instance( $args = null )
22 */
23 class SelfTest {
24
25 use GetInstance;
26
27 /**
28 * Run the staged diagnostic.
29 *
30 * @return array { ok:bool, step:string, message:string, tools:int }
31 */
32 public function run() {
33 if ( ! Manager::get_instance()->is_enabled() ) {
34 return $this->fail( 'disabled', __( 'MCP is turned off.', 'notificationx' ) );
35 }
36
37 $pairing = Pairing::get_instance();
38 if ( ! $pairing->is_connected() ) {
39 return $this->fail( 'not_connected', __( 'No connection token has been generated yet.', 'notificationx' ) );
40 }
41
42 $endpoint = home_url( '/notificationx/mcp' );
43 $token = $pairing->site_token();
44
45 // 1) Authenticated tools/list must succeed and return at least one tool.
46 $response = wp_remote_post( $endpoint, array(
47 'timeout' => 15,
48 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core filter name.
49 'headers' => array(
50 'Content-Type' => 'application/json',
51 'Authorization' => 'Bearer ' . $token,
52 ),
53 'body' => wp_json_encode( array(
54 'jsonrpc' => '2.0',
55 'id' => 1,
56 'method' => 'tools/list',
57 'params' => (object) array(),
58 ) ),
59 ) );
60
61 if ( is_wp_error( $response ) ) {
62 return $this->fail( 'unreachable', sprintf(
63 /* translators: %s: error detail. */
64 __( 'The MCP endpoint could not be reached from the server itself: %s', 'notificationx' ),
65 $response->get_error_message()
66 ) );
67 }
68
69 $code = wp_remote_retrieve_response_code( $response );
70 if ( 401 === (int) $code || 403 === (int) $code ) {
71 return $this->fail( 'auth', __( 'The endpoint rejected the connection token.', 'notificationx' ) );
72 }
73
74 $body = json_decode( wp_remote_retrieve_body( $response ), true );
75 $tools = isset( $body['result']['tools'] ) && is_array( $body['result']['tools'] ) ? count( $body['result']['tools'] ) : 0;
76
77 if ( $tools < 1 ) {
78 return $this->fail( 'no_tools', __( 'The server responded but exposed no tools.', 'notificationx' ) );
79 }
80
81 // 2) An unauthenticated call must be challenged (401 + WWW-Authenticate).
82 $probe = wp_remote_post( $endpoint, array(
83 'timeout' => 15,
84 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core filter name.
85 'headers' => array( 'Content-Type' => 'application/json' ),
86 'body' => wp_json_encode( array( 'jsonrpc' => '2.0', 'id' => 2, 'method' => 'ping' ) ),
87 ) );
88
89 if ( ! is_wp_error( $probe ) ) {
90 $probe_code = (int) wp_remote_retrieve_response_code( $probe );
91 $challenge = wp_remote_retrieve_header( $probe, 'www-authenticate' );
92 if ( 401 !== $probe_code || empty( $challenge ) ) {
93 return $this->fail( 'challenge', __( 'The endpoint did not correctly challenge an unauthenticated request; OAuth discovery may fail for some clients.', 'notificationx' ) );
94 }
95 }
96
97 return array(
98 'ok' => true,
99 'step' => 'ok',
100 'message' => sprintf(
101 /* translators: %d: number of tools. */
102 _n( 'MCP is working. %d tool is available.', 'MCP is working. %d tools are available.', $tools, 'notificationx' ),
103 $tools
104 ),
105 'tools' => $tools,
106 );
107 }
108
109 /**
110 * Build a failure result.
111 *
112 * @param string $step Failing step id.
113 * @param string $message Human message.
114 * @return array
115 */
116 protected function fail( $step, $message ) {
117 return array(
118 'ok' => false,
119 'step' => $step,
120 'message' => $message,
121 'tools' => 0,
122 );
123 }
124 }
125