__( 'WebMCP', 'agentgate-for-webmcp' ), 'description' => __( 'Tools available to AI agents through the WebMCP browser API.', 'agentgate-for-webmcp' ), ] ); } public function setup_tools(): void { if ( ! apply_filters( 'webmcp_register_default_tools', true ) ) { return; } $this->register_search(); $this->register_post(); $this->register_categories(); } // --- wp/search-posts -------------------------------------------------- private function register_search(): void { wp_register_ability( 'wp/search-posts', [ 'label' => __( 'Search Posts', 'agentgate-for-webmcp' ), 'description' => __( 'Find published posts matching a keyword. Returns title, excerpt, and link for each result.', 'agentgate-for-webmcp' ), 'category' => self::CAT, 'input_schema' => [ 'type' => 'object', 'required' => [ 'query' ], 'properties' => [ 'query' => [ 'type' => 'string', 'description' => __( 'Search term.', 'agentgate-for-webmcp' ) ], 'limit' => [ 'type' => 'integer', 'description' => __( 'Max results (1-50).', 'agentgate-for-webmcp' ), 'default' => 10, 'minimum' => 1, 'maximum' => 50 ], ], ], 'execute_callback' => [ $this, 'handle_search' ], 'permission_callback' => '__return_true', 'meta' => [ 'webmcp_readonly' => true, 'webmcp_public' => true ], ] ); } public function handle_search( array $p ) { $q = sanitize_text_field( $p['query'] ?? '' ); if ( $q === '' ) { return new \WP_Error( 'missing_query', __( 'A search query is required.', 'agentgate-for-webmcp' ) ); } $n = isset( $p['limit'] ) ? max( 1, min( 50, intval( $p['limit'] ) ) ) : 10; return array_map( [ $this, 'format_post_summary' ], get_posts( [ 'post_status' => 'publish', 'posts_per_page' => $n, 's' => $q ] ) ); } // --- wp/get-post ------------------------------------------------------ private function register_post(): void { wp_register_ability( 'wp/get-post', [ 'label' => __( 'Get Post', 'agentgate-for-webmcp' ), 'description' => __( 'Retrieve a single published post by ID or slug. Returns full content, categories, and tags.', 'agentgate-for-webmcp' ), 'category' => self::CAT, 'input_schema' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'integer', 'description' => __( 'Post ID.', 'agentgate-for-webmcp' ) ], 'slug' => [ 'type' => 'string', 'description' => __( 'Post URL slug.', 'agentgate-for-webmcp' ) ], ], ], 'execute_callback' => [ $this, 'handle_post' ], 'permission_callback' => '__return_true', 'meta' => [ 'webmcp_readonly' => true, 'webmcp_public' => true ], ] ); } public function handle_post( array $p ) { $post = $this->find_post( $p ); if ( ! $post ) { return new \WP_Error( 'not_found', __( 'Post not found.', 'agentgate-for-webmcp' ), [ 'status' => 404 ] ); } $cats = wp_get_post_categories( $post->ID, [ 'fields' => 'names' ] ); $tags = wp_get_post_tags( $post->ID, [ 'fields' => 'names' ] ); return [ 'id' => $post->ID, 'title' => get_the_title( $post ), 'content' => wp_strip_all_tags( apply_filters( 'the_content', $post->post_content ) ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core WordPress filter. 'excerpt' => wp_strip_all_tags( get_the_excerpt( $post ) ), 'published' => get_the_date( 'c', $post ), 'link' => get_permalink( $post ), 'categories' => is_array( $cats ) ? array_values( $cats ) : [], 'tags' => is_array( $tags ) ? array_values( $tags ) : [], ]; } private function find_post( array $p ): ?\WP_Post { if ( ! empty( $p['id'] ) ) { $post = get_post( intval( $p['id'] ) ); } elseif ( ! empty( $p['slug'] ) ) { $hits = get_posts( [ 'name' => sanitize_title( $p['slug'] ), 'post_status' => 'publish', 'numberposts' => 1 ] ); $post = $hits[0] ?? null; } else { return null; } if ( ! $post instanceof \WP_Post ) { return null; } if ( $post->post_status !== 'publish' && ! current_user_can( 'read_post', $post->ID ) ) { return null; } return $post; } // --- wp/get-categories ------------------------------------------------ private function register_categories(): void { wp_register_ability( 'wp/get-categories', [ 'label' => __( 'Get Categories', 'agentgate-for-webmcp' ), 'description' => __( 'List all post categories including name, description, and post count.', 'agentgate-for-webmcp' ), 'category' => self::CAT, 'input_schema' => [ 'type' => 'object', 'properties' => new \stdClass() ], 'execute_callback' => [ $this, 'handle_categories' ], 'permission_callback' => '__return_true', 'meta' => [ 'webmcp_readonly' => true, 'webmcp_public' => true ], ] ); } public function handle_categories( array $p ): array { $cats = get_categories( [ 'hide_empty' => false, 'orderby' => 'name' ] ); if ( ! is_array( $cats ) ) { return []; } return array_map( fn( \WP_Term $c ) => [ 'id' => $c->term_id, 'name' => $c->name, 'slug' => $c->slug, 'description' => wp_strip_all_tags( $c->description ), 'post_count' => (int) $c->count, 'link' => get_category_link( $c->term_id ), ], array_values( $cats ) ); } // --- shared helpers --------------------------------------------------- private function format_post_summary( \WP_Post $p ): array { return [ 'id' => $p->ID, 'title' => get_the_title( $p ), 'snippet' => wp_strip_all_tags( get_the_excerpt( $p ) ), 'link' => get_permalink( $p ), 'published' => get_the_date( 'c', $p ), ]; } }