{$desc}", '', '## AI Data Endpoints', '', '### Site-Wide', '', "- Organization Schema: {$home}/wp-json/ai/v1/organization", "- Entity Registry: {$home}/wp-json/ai/v1/entities/registry", "- Site Graph: {$home}/wp-json/ai/v1/site-graph", '', '### Per-Page (replace {id} with post ID or slug)', '', "- Context: {$home}/wp-json/ai/v1/context/{id}", "- Entities: {$home}/wp-json/ai/v1/entities/{id}", "- Schema: {$home}/wp-json/ai/v1/schema/{id}", "- Bundle: {$home}/wp-json/ai/v1/bundle/{id}", '', ]; if ( Semantic_Settings::mcp_enabled() ) { $lines[] = '### MCP Server'; $lines[] = ''; $lines[] = "- Model Context Protocol endpoint: {$home}/wp-json/ai/v1/mcp"; $lines[] = ' (Streamable HTTP, stateless — add as a custom connector in Claude / ChatGPT)'; $lines[] = ''; } $markdown_on = Semantic_Settings::markdown_enabled(); if ( $markdown_on ) { $lines[] = '### Markdown'; $lines[] = ''; $lines[] = "- Full content (Markdown): {$home}/llms-full.txt"; $lines[] = '- Per-page Markdown: append `.md` to any content URL below'; $lines[] = ''; } $lines[] = '## Content'; $lines[] = ''; // List published, public pages/posts (excluding password-protected and any // the user has marked out of the data layer) — same gate as every other // public surface. $types = Semantic_Settings::public_post_types(); $posts = empty( $types ) ? [] : get_posts( [ 'post_type' => $types, 'post_status' => 'publish', 'has_password' => false, 'posts_per_page' => 500, 'orderby' => 'menu_order title', 'order' => 'ASC', 'meta_query' => [ [ 'key' => Semantic_Settings::EXCLUDE_META, 'compare' => 'NOT EXISTS' ] ], ] ); foreach ( $posts as $post ) { $permalink = get_permalink( $post ); if ( $markdown_on ) { $md = Semantic_Markdown::md_url( $post ); $lines[] = $md ? sprintf( '- [%s](%s) ([md](%s))', $post->post_title, $permalink, $md ) : sprintf( '- [%s](%s)', $post->post_title, $permalink ); } else { $lines[] = sprintf( '- [%s](%s)', $post->post_title, $permalink ); } } // Canonical entity reference pages — top entities by occurrence, restricted // to entities that appear in currently published, public content (matching // what the entity pages actually serve; non-public entities would 404). global $wpdb; $public_ids = Semantic_Entity_Registry::public_registry_ids(); $entities = []; if ( ! empty( $public_ids ) ) { $in = implode( ',', array_map( 'intval', $public_ids ) ); $entities = $wpdb->get_results( "SELECT entity_id, label FROM {$wpdb->prefix}semantic_entity_registry WHERE id IN ($in) ORDER BY occurrence_count DESC LIMIT 100", ARRAY_A ); } if ( ! empty( $entities ) ) { $lines[] = ''; $lines[] = '## Entities'; $lines[] = ''; foreach ( $entities as $e ) { $lines[] = sprintf( '- [%s](%s)', $e['label'], $home . '/entities/' . $e['entity_id'] ); } } return implode( "\n", $lines ) . "\n"; } }