site-relative path (no leading slash). * * @var array */ const ARTIFACTS = array( 'llms_txt' => 'llms.txt', 'llms_full_txt' => 'llms-full.txt', 'mcp_json' => '.well-known/mcp.json', 'mcp_manifest' => '.well-known/mcp', 'mcp_server_card' => '.well-known/mcp/server-card.json', 'mcp_server_wk' => '.well-known/mcp-server', ); /** * Hook everything up. */ public function register_hooks() { add_action( 'ael42a_daily', array( $this, 'refresh' ) ); add_action( 'ael42a_detect_existing', array( $this, 'refresh' ) ); } /** * Whether the current request is one of our own detection probes. * Serve handlers bail when this is true so the probe can see what the * rest of the stack would answer. * * @return bool */ public static function is_probe_request() { return isset( $_SERVER['HTTP_X_42A_AEL_PROBE'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- presence check only, value unused. } /** * Whether an artifact is already provided by the site from another * source, meaning this plugin must not serve its own version. * * @param string $key Artifact key (see self::ARTIFACTS). * @return bool */ public static function is_external( $key ) { if ( ! isset( self::ARTIFACTS[ $key ] ) ) { return false; } /** * Filter whether the plugin should respect existing content for an * artifact. Return false to force the plugin to serve its own * version even when existing content was detected. * * @param bool $respect Whether to respect existing content. * @param string $key Artifact key. */ if ( ! apply_filters( 'ael42a_respect_existing', true, $key ) ) { return false; } if ( self::physical_file_exists( self::ARTIFACTS[ $key ] ) ) { return true; } $state = get_option( self::OPTION, array() ); return ! empty( $state[ $key ]['external'] ); } /** * Human-readable detail for an artifact's detection state ('' if the * artifact is not externally provided). * * @param string $key Artifact key. * @return string */ public static function detail( $key ) { if ( ! isset( self::ARTIFACTS[ $key ] ) ) { return ''; } if ( self::physical_file_exists( self::ARTIFACTS[ $key ] ) ) { return sprintf( /* translators: %s: file path. */ __( 'A physical %s file exists in the document root.', 'ael-42a' ), '/' . self::ARTIFACTS[ $key ] ); } $state = get_option( self::OPTION, array() ); if ( ! empty( $state[ $key ]['external'] ) && ! empty( $state[ $key ]['detail'] ) ) { return (string) $state[ $key ]['detail']; } return ''; } /** * Artifact keys currently detected as externally provided. * * @return string[] */ public static function external_keys() { $keys = array(); foreach ( array_keys( self::ARTIFACTS ) as $key ) { if ( self::is_external( $key ) ) { $keys[] = $key; } } return $keys; } /** * Re-probe every artifact and persist the result. Flushes rewrite rules * when the detection state changed, so rewrites for externally-provided * artifacts are dropped (and re-added when the external source goes * away). */ public function refresh() { $old = get_option( self::OPTION, array() ); $new = array(); foreach ( self::ARTIFACTS as $key => $path ) { $probe = $this->probe( $path ); // A probe cannot distinguish a physical file from a foreign // handler (both are 200s without our header). Physical files are // already handled fresh at serve time, so never persist them as // external - otherwise a deleted file would linger as "external" // (blocking our own version) until the next refresh. if ( $probe['external'] && self::physical_file_exists( $path ) ) { $probe = array( 'external' => false, 'detail' => '', 'checked_at' => time(), ); } $new[ $key ] = $probe; } update_option( self::OPTION, $new, true ); if ( $this->external_map( $old ) !== $this->external_map( $new ) ) { flush_rewrite_rules(); } } /** * Probe one artifact path with our serve handlers disabled. * * @param string $path Site-relative path (no leading slash). * @return array{external: bool, detail: string, checked_at: int} */ private function probe( $path ) { // The cache-busting query param keeps full-page caches and CDNs from // answering with a stale copy of OUR response minus its headers // (which would read as external content). Static files and foreign // handlers ignore the query string. $url = add_query_arg( 'ael42a_probe', (string) time(), home_url( '/' . $path ) ); $response = wp_remote_get( $url, array( 'timeout' => 5, 'redirection' => 0, 'sslverify' => false, // Loopback request - local certs are often self-signed. 'headers' => array( self::PROBE_HEADER => AEL42A_VERSION ), ) ); $result = array( 'external' => false, 'detail' => '', 'checked_at' => time(), ); if ( is_wp_error( $response ) ) { // Loopback failed (blocked on this host): assume nothing external // and keep serving ours. return $result; } $code = (int) wp_remote_retrieve_response_code( $response ); $ours = (string) wp_remote_retrieve_header( $response, 'x-42a-ael' ); $body = (string) wp_remote_retrieve_body( $response ); // Strip a UTF-8 BOM before sniffing. if ( 0 === strpos( $body, "\xEF\xBB\xBF" ) ) { $body = substr( $body, 3 ); } $body = ltrim( $body ); // Only a successful response that did NOT come from this plugin // counts. And it must positively look like the artifact in question: // HTML fallback pages (home, custom 404), cache-prepended comment // wrappers, and 200 maintenance bodies must all default to "keep // serving ours", never to silently standing down. if ( 200 !== $code || '' !== $ours || '' === $body ) { return $result; } if ( ! $this->looks_like_artifact( $path, $body ) ) { return $result; } $result['external'] = true; $result['detail'] = sprintf( /* translators: %s: URL. */ __( '%s is already served by this site (a physical file or another plugin).', 'ael-42a' ), '/' . $path ); return $result; } /** * Whether a response body positively looks like the artifact a path is * supposed to carry. llms*.txt files are markdown starting with an H1; * MCP discovery documents are JSON objects with discovery keys. * * @param string $path Site-relative artifact path. * @param string $body Response body (BOM/whitespace-trimmed). * @return bool */ private function looks_like_artifact( $path, $body ) { if ( '.txt' === substr( $path, -4 ) ) { return '# ' === substr( $body, 0, 2 ); } $json = json_decode( $body, true ); if ( ! is_array( $json ) ) { return false; } foreach ( array( 'endpoint', 'url', 'servers', 'mcpServers' ) as $field ) { if ( array_key_exists( $field, $json ) ) { return true; } } return false; } /** * Whether a physical file for the given site-relative path exists in * the document root (checked against ABSPATH and DOCUMENT_ROOT to cover * subdirectory installs). Memoized for the request lifetime - callers * hit this on every init. * * @param string $path Site-relative path (no leading slash). * @return bool */ private static function physical_file_exists( $path ) { static $memo = array(); if ( array_key_exists( $path, $memo ) ) { return $memo[ $path ]; } $memo[ $path ] = self::physical_file_lookup( $path ); return $memo[ $path ]; } /** * Uncached physical-file lookup behind physical_file_exists(). * * @param string $path Site-relative path (no leading slash). * @return bool */ private static function physical_file_lookup( $path ) { $candidates = array( ABSPATH . $path ); if ( ! empty( $_SERVER['DOCUMENT_ROOT'] ) ) { $doc_root = realpath( sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) ); if ( $doc_root ) { $candidates[] = trailingslashit( $doc_root ) . $path; } } foreach ( array_unique( $candidates ) as $candidate ) { if ( is_file( $candidate ) ) { return true; } } return false; } /** * Reduce a stored state array to key => external bool for comparison. * * @param array $state Stored state. * @return array */ private function external_map( $state ) { $map = array(); foreach ( array_keys( self::ARTIFACTS ) as $key ) { $map[ $key ] = ! empty( $state[ $key ]['external'] ); } return $map; } }