'text/plain; charset=utf-8',
'robots.txt' => 'text/plain; charset=utf-8',
'agenticweb.md' => 'text/markdown; charset=utf-8',
'schema.jsonld' => 'application/ld+json; charset=utf-8',
'llm-info.html' => 'text/html; charset=utf-8',
'webmcp-registration.js' => 'application/javascript; charset=utf-8',
'.well-known/agent-permissions.json' => 'application/json; charset=utf-8',
'.well-known/agent-card.json' => 'application/json; charset=utf-8',
'.well-known/agent-skills/index.json' => 'application/json; charset=utf-8',
'.well-known/mcp/server-card.json' => 'application/json; charset=utf-8',
'.well-known/api-catalog' => 'application/linkset+json; charset=utf-8',
'.well-known/ucp' => 'application/json; charset=utf-8',
'sitemap.xml' => 'application/xml; charset=utf-8',
];
// Conditional-source package files. The generator ships sitemap.xml only
// when the customer's live site has no sitemap; it never clobbers an
// existing one. These files are accepted when present but never required,
// and their rewrite routes are registered only when actually installed so
// we do not shadow an existing Yoast/Rank Math/core sitemap.
private const OPTIONAL_FILES = [
'sitemap.xml',
];
private const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36';
// Must stay a named, self-identifying agent. Cloudflare returns 403 / error 1010
// to `Python-urllib/*` on every zone it fronts, including cloudflare.com, so that
// string reports a block the site owner has no setting to clear.
private const LIBRARY_UA = 'AgentReadyWebsiteBot/1.0 (+https://agentreadywebsite.io/)';
public static function boot(): void {
add_action('init', [__CLASS__, 'add_rewrite_rules']);
add_filter('query_vars', [__CLASS__, 'add_query_vars']);
add_action('template_redirect', [__CLASS__, 'serve_virtual_file']);
add_filter('robots_txt', [__CLASS__, 'serve_robots_txt'], PHP_INT_MAX, 2);
add_action('admin_menu', [__CLASS__, 'add_admin_page']);
add_action('admin_post_agentreadywebsite_upload_package', [__CLASS__, 'handle_upload']);
add_action('wp_head', [__CLASS__, 'print_schema_jsonld'], 5);
}
public static function activate(): void {
self::add_rewrite_rules();
flush_rewrite_rules();
}
public static function deactivate(): void {
flush_rewrite_rules();
}
public static function add_rewrite_rules(): void {
$installed = self::package_files();
foreach (array_keys(self::PACKAGE_FILES) as $path) {
if ($path === 'robots.txt') {
continue;
}
// Never-clobber: optional files (e.g. sitemap.xml) only get a route
// when we actually installed them, so an existing site sitemap is
// left untouched.
if (in_array($path, self::OPTIONAL_FILES, true) && !isset($installed[$path])) {
continue;
}
add_rewrite_rule(
'^' . preg_quote($path, '/') . '$',
'index.php?' . self::QUERY_VAR . '=' . rawurlencode($path),
'top'
);
}
}
public static function add_query_vars(array $vars): array {
$vars[] = self::QUERY_VAR;
return $vars;
}
public static function add_admin_page(): void {
add_management_page(
'AgentReadyWebsite Installer',
'AgentReadyWebsite',
'manage_options',
'agentreadywebsite-installer',
[__CLASS__, 'render_admin_page']
);
}
public static function render_admin_page(): void {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You do not have permission to manage AgentReadyWebsite files.', 'agentreadywebsite-installer'));
}
$package = self::package_files();
$meta = self::package_meta();
$notice_input = filter_input(INPUT_GET, 'agentready_notice', FILTER_UNSAFE_RAW);
$notice = is_string($notice_input) ? sanitize_key(wp_unslash($notice_input)) : '';
?>
AgentReadyWebsite Installer
Upload the AgentReadyWebsite delivery zip. The plugin validates the package and serves each file at the canonical URL without requiring FTP or root-directory access.
AgentReadyWebsite package installed successfully.
The uploaded zip did not match the expected AgentReadyWebsite package shape.
The package could not be uploaded. Please try again or contact support.
Install package
Installed status
No AgentReadyWebsite package is installed yet.
Installed at:
| File |
Status |
Public URL |
$content_type) : ?>
|
|
|
gmdate('c'),
'file_count' => count($files),
], false);
self::add_rewrite_rules();
flush_rewrite_rules();
self::redirect_with_notice('installed');
}
public static function serve_virtual_file(): void {
$path = get_query_var(self::QUERY_VAR);
if (!$path) {
return;
}
$path = rawurldecode((string) $path);
$package = self::package_files();
if (!isset(self::PACKAGE_FILES[$path], $package[$path])) {
status_header(404);
exit;
}
self::send_file_response($path, $package[$path]);
}
public static function serve_robots_txt(string $output, bool $public): string {
$package = self::package_files();
return isset($package['robots.txt']) ? $package['robots.txt'] : $output;
}
public static function print_schema_jsonld(): void {
if (is_admin()) {
return;
}
$package = self::package_files();
$raw = $package['schema.jsonld'] ?? '';
if (!is_string($raw) || $raw === '') {
return;
}
$decoded = json_decode($raw);
if (json_last_error() !== JSON_ERROR_NONE) {
return;
}
echo '' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private static function send_file_response(string $path, string $content): void {
nocache_headers();
header('Content-Type: ' . self::PACKAGE_FILES[$path]);
header('X-AgentReadyWebsite-Installer: active');
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
exit;
}
private static function extract_valid_package(string $zip_path): array {
if (!class_exists('ZipArchive')) {
throw new RuntimeException('ZipArchive is not available.');
}
$zip = new ZipArchive();
if ($zip->open($zip_path) !== true) {
throw new RuntimeException('Unable to open zip.');
}
$files = [];
for ($index = 0; $index < $zip->numFiles; $index++) {
$name = self::normalize_zip_name((string) $zip->getNameIndex($index));
if (!$name || str_ends_with($name, '/')) {
continue;
}
if (!array_key_exists($name, self::PACKAGE_FILES)) {
$zip->close();
throw new RuntimeException('Unexpected file in package.');
}
$content = $zip->getFromIndex($index);
if ($content === false) {
$zip->close();
throw new RuntimeException('Unable to read package file.');
}
$files[$name] = $content;
}
$zip->close();
$required = array_diff(array_keys(self::PACKAGE_FILES), self::OPTIONAL_FILES);
$missing = array_diff($required, array_keys($files));
if ($missing) {
throw new RuntimeException('Missing required package files.');
}
self::validate_json_file($files, 'schema.jsonld');
self::validate_json_file($files, '.well-known/agent-permissions.json');
self::validate_json_file($files, '.well-known/agent-card.json');
self::validate_json_file($files, '.well-known/agent-skills/index.json');
self::validate_json_file($files, '.well-known/mcp/server-card.json');
self::validate_json_file($files, '.well-known/api-catalog');
self::validate_json_file($files, '.well-known/ucp');
return $files;
}
private static function validate_json_file(array $files, string $path): void {
json_decode($files[$path], true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('Invalid JSON in package file.');
}
}
private static function normalize_zip_name(string $name): string {
$name = str_replace('\\', '/', $name);
$name = ltrim($name, '/');
if (str_contains($name, '../') || str_starts_with($name, '../')) {
return '';
}
return $name;
}
private static function package_files(): array {
$package = get_option(self::OPTION_PACKAGE, []);
return is_array($package) ? $package : [];
}
public static function probe_edge_agent_access(): array {
$url = home_url('/');
$browser = self::request_status($url, self::BROWSER_UA);
$library = self::request_status($url, self::LIBRARY_UA);
$blocked = ($browser['status'] >= 200 && $browser['status'] < 400 && in_array($library['status'], [401, 403], true));
$cloudflare = $browser['cloudflare'] || $library['cloudflare'] || self::plugin_is_active('cloudflare/cloudflare.php');
$wordfence = self::plugin_is_active('wordfence/wordfence.php');
$sucuri = self::plugin_is_active('sucuri-scanner/sucuri.php');
$steps = [];
if ($blocked) {
if ($cloudflare) {
$steps[] = 'Open Cloudflare Security > Settings and filter Bot traffic. Turn Bot fight mode Off, set the legacy Block AI bots switch to Do not block, set Search, Agent, and Training AI bot policies to Allow, and turn AI Labyrinth Off.';
}
if ($wordfence) {
$steps[] = 'Wordfence: Tools > Live Traffic, then Firewall options. Stop blocking non-browser user agents.';
}
if ($sucuri) {
$steps[] = 'Sucuri: Firewall / Allowlist. Allow the automated readers you want.';
}
if (!$steps) {
$steps[] = 'Check your host WAF, under-attack mode, or security plugin. A 403 happens before robots.txt is read.';
}
}
return [
'blocked' => $blocked,
'browser_status' => $browser['status'],
'library_status' => $library['status'],
'steps' => $steps,
];
}
private static function request_status(string $url, string $user_agent): array {
$response = wp_remote_get(
$url,
[
'timeout' => 8,
'redirection' => 3,
'sslverify' => true,
'user-agent' => $user_agent,
'headers' => [
'Accept' => 'text/html,*/*',
],
]
);
if (is_wp_error($response)) {
return ['status' => 0, 'cloudflare' => false];
}
$status = (int) wp_remote_retrieve_response_code($response);
$headers = wp_remote_retrieve_headers($response);
$cf_ray = '';
$server = '';
if (is_array($headers)) {
$cf_ray = (string) ($headers['cf-ray'] ?? $headers['CF-RAY'] ?? '');
$server = strtolower((string) ($headers['server'] ?? $headers['Server'] ?? ''));
} elseif (is_object($headers)) {
$cf_ray = (string) ($headers['cf-ray'] ?? '');
$server = strtolower((string) ($headers['server'] ?? ''));
}
return [
'status' => $status,
'cloudflare' => ($cf_ray !== '' || str_contains($server, 'cloudflare')),
];
}
private static function plugin_is_active(string $plugin): bool {
if (!function_exists('is_plugin_active')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return is_plugin_active($plugin);
}
private static function package_meta(): array {
$meta = get_option(self::OPTION_META, []);
return is_array($meta) ? $meta : [];
}
private static function redirect_with_notice(string $notice): void {
wp_safe_redirect(add_query_arg(
['page' => 'agentreadywebsite-installer', 'agentready_notice' => $notice],
admin_url('tools.php')
));
exit;
}
}
AgentReadyWebsite_Installer::boot();
register_activation_hook(__FILE__, ['AgentReadyWebsite_Installer', 'activate']);
register_deactivation_hook(__FILE__, ['AgentReadyWebsite_Installer', 'deactivate']);