is_available() && $this->is_enabled(); } public function boot(): void { // Content uses core WP APIs only. } /** * @return array> */ public function get_abilities(): array { return array( self::SITE_INFO => array( 'name' => self::SITE_INFO, 'label' => __( 'Site information', 'agentmatica-mcp-connector' ), 'description' => __( 'Site name, URL, tagline, and language.', 'agentmatica-mcp-connector' ), 'input_schema' => array( 'type' => 'object', 'properties' => new stdClass(), ), 'callback' => array( __CLASS__, 'site_info' ), 'readonly' => true, 'idempotent' => true, ), self::PAGES_LIST => array( 'name' => self::PAGES_LIST, 'label' => __( 'List pages', 'agentmatica-mcp-connector' ), 'description' => __( 'Read-only list of published WordPress pages.', 'agentmatica-mcp-connector' ), 'input_schema' => self::list_input_schema( __( 'Optional page title search term.', 'agentmatica-mcp-connector' ) ), 'callback' => array( __CLASS__, 'list_pages' ), 'readonly' => true, 'idempotent' => true, ), self::POSTS_LIST => array( 'name' => self::POSTS_LIST, 'label' => __( 'List posts', 'agentmatica-mcp-connector' ), 'description' => __( 'Read-only list of published WordPress posts.', 'agentmatica-mcp-connector' ), 'input_schema' => self::list_input_schema( __( 'Optional post title search term.', 'agentmatica-mcp-connector' ) ), 'callback' => array( __CLASS__, 'list_posts' ), 'readonly' => true, 'idempotent' => true, ), self::CONTENT_GET => array( 'name' => self::CONTENT_GET, 'label' => __( 'Get page or post', 'agentmatica-mcp-connector' ), 'description' => __( 'Fetch a single published page or post by id or slug.', 'agentmatica-mcp-connector' ), 'input_schema' => array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'description' => 'Content type.', 'enum' => array( 'page', 'post' ), ), 'id' => array( 'type' => 'integer', 'description' => 'WordPress post id.', 'minimum' => 1, ), 'slug' => array( 'type' => 'string', 'description' => 'Post slug (path segment).', ), ), 'required' => array( 'type' ), ), 'callback' => array( __CLASS__, 'get_content' ), 'readonly' => true, 'idempotent' => true, ), ); } /** * @param string $search_description Search field description. * @return array */ private static function list_input_schema( string $search_description ): array { return array( 'type' => 'object', 'properties' => array( 'per_page' => array( 'type' => 'integer', 'description' => 'Number of items to return (1-50).', 'default' => 20, 'minimum' => 1, 'maximum' => 50, ), 'page' => array( 'type' => 'integer', 'description' => 'Page number.', 'default' => 1, 'minimum' => 1, ), 'search' => array( 'type' => 'string', 'description' => $search_description, ), ), ); } /** * @param array|null $input * @return array */ public static function site_info( $input = null ): array { unset( $input ); return array( 'name' => get_bloginfo( 'name' ), 'description' => get_bloginfo( 'description' ), 'url' => home_url( '/' ), 'language' => get_bloginfo( 'language' ), ); } /** * @param array|null $input * @return array */ public static function list_pages( $input = null ): array { return self::list_content( 'page', $input ); } /** * @param array|null $input * @return array */ public static function list_posts( $input = null ): array { return self::list_content( 'post', $input ); } /** * @param string $post_type * @param array|null $input * @return array */ private static function list_content( string $post_type, $input = null ): array { $input = is_array( $input ) ? $input : array(); $per_page = isset( $input['per_page'] ) ? max( 1, min( 50, (int) $input['per_page'] ) ) : 20; $page = isset( $input['page'] ) ? max( 1, (int) $input['page'] ) : 1; $search = isset( $input['search'] ) ? sanitize_text_field( (string) $input['search'] ) : ''; $query_args = array( 'post_type' => $post_type, 'post_status' => 'publish', 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'date', 'order' => 'DESC', 'ignore_sticky_posts' => true, 'no_found_rows' => false, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ); if ( $search !== '' ) { $query_args['s'] = $search; } $query = new WP_Query( $query_args ); $items = array(); foreach ( $query->posts as $post ) { if ( ! $post instanceof WP_Post ) { continue; } $items[] = self::format_post_summary( $post ); } return array( 'items' => $items, 'result_count' => count( $items ), 'total' => (int) $query->found_posts, 'page' => $page, 'per_page' => $per_page, ); } /** * @param array|null $input * @return array */ public static function get_content( $input = null ): array { $input = is_array( $input ) ? $input : array(); $type = isset( $input['type'] ) ? sanitize_key( (string) $input['type'] ) : ''; if ( ! in_array( $type, array( 'page', 'post' ), true ) ) { return array( 'success' => false, 'message' => __( 'content-get requires type page or post.', 'agentmatica-mcp-connector' ), ); } $post = null; if ( isset( $input['id'] ) ) { $post_id = (int) $input['id']; $candidate = get_post( $post_id ); if ( $candidate instanceof WP_Post && $type === $candidate->post_type && 'publish' === $candidate->post_status ) { $post = $candidate; } } elseif ( isset( $input['slug'] ) ) { $slug = sanitize_title( (string) $input['slug'] ); $candidate = get_page_by_path( $slug, OBJECT, $type ); if ( $candidate instanceof WP_Post && 'publish' === $candidate->post_status ) { $post = $candidate; } } if ( ! $post instanceof WP_Post ) { return array( 'success' => false, 'message' => __( 'Published content was not found.', 'agentmatica-mcp-connector' ), ); } return self::format_post_detail( $post ); } /** * @param WP_Post $post * @return array */ private static function format_post_summary( WP_Post $post ): array { $excerpt_source = $post->post_excerpt ? $post->post_excerpt : $post->post_content; return array( 'id' => (string) $post->ID, 'type' => $post->post_type, 'title' => get_the_title( $post ), 'slug' => $post->post_name, 'url' => get_permalink( $post ), 'excerpt' => wp_trim_words( wp_strip_all_tags( $excerpt_source ), 40, '…' ), 'modified_gmt' => $post->post_modified_gmt, ); } /** * @param WP_Post $post * @return array */ private static function format_post_detail( WP_Post $post ): array { $summary = self::format_post_summary( $post ); return array_merge( $summary, array( 'content_text' => wp_trim_words( wp_strip_all_tags( $post->post_content ), 500, '…' ), 'content_html' => apply_filters( 'the_content', $post->post_content ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core content filter. ) ); } }