Skip to content

Commit 1f736e7

Browse files
committed
WordPress.org Abilities: Add validate-readme tool and refactor base class.
Add the validate-readme MCP tool for AI agents to validate plugin readme files using the plugin directory's existing Validator class. Results are returned as structured JSON with HTML messages converted to markdown. Rename Resource_Base to Ability_Base since it now serves as the shared base for tools, resources, and prompts. Move the plugin-directory autoloader bootstrap and HTML-to-markdown conversion into it, removing duplication from Reserved_Slugs. Remove redundant auth guidance from the server description since the MCP adapter enforces authentication at the session level. git-svn-id: https://meta.svn.wordpress.org/sites/trunk@14709 74240141-8908-4e6f-9713-ba540dce6ec7
1 parent f1b24b9 commit 1f736e7

10 files changed

Lines changed: 147 additions & 44 deletions

File tree

wordpress.org/public_html/wp-content/plugins/wporg-abilities/class-mcp-server.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ public static function register( McpAdapter $adapter ): void {
5454
array(
5555
'WordPress.org MCP Server — provides tools, resources, and prompts for interacting with WordPress.org services.',
5656
'Use prompts/list to discover available workflows for specific tasks. Browse wporg://* resources for reference documentation.',
57-
'All write operations require authentication via application password. To set up authentication, visit: https://login.wordpress.org/?action=authorize_application&app_id=c4c73a54-96d7-47b9-9bdc-1a66b9b04505',
5857
)
5958
),
6059
'v1.0.0',

wordpress.org/public_html/wp-content/plugins/wporg-abilities/class-registrar.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public static function register_abilities(): void {
5353
Plugin_Directory\Resources\Plugin_Check_Guide::register();
5454
Plugin_Directory\Resources\Plugin_FAQ::register();
5555

56+
// Tools.
57+
Plugin_Directory\Tools\Validate_Readme::register();
58+
5659
// Prompts.
5760
Plugin_Directory\Prompts\Prepare_Plugin::register();
5861
Plugin_Directory\Prompts\Run_Plugin_Check::register();

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/class-resource-base.php renamed to wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/class-ability-base.php

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?php
22
/**
3-
* Base class for plugin directory resource abilities.
3+
* Base class for plugin directory abilities.
44
*
5-
* Provides shared helpers for resources that pull content from
6-
* developer.wordpress.org (blog ID 33).
5+
* Provides shared utilities for abilities that interact with the
6+
* plugin-directory plugin or developer.wordpress.org content.
77
*
88
* @package WordPressdotorg\Abilities\Plugins\Plugin_Directory
99
*/
@@ -15,9 +15,9 @@
1515
defined( 'ABSPATH' ) || exit;
1616

1717
/**
18-
* Resource_Base class.
18+
* Ability_Base class.
1919
*/
20-
class Resource_Base {
20+
class Ability_Base {
2121

2222
/**
2323
* Blog ID for developer.wordpress.org in the multisite network.
@@ -26,6 +26,51 @@ class Resource_Base {
2626
*/
2727
const DEVELOPER_BLOG_ID = 33;
2828

29+
/**
30+
* Register the plugin-directory autoloader if it hasn't been loaded yet.
31+
*
32+
* This is needed when abilities reference classes from the plugin-directory
33+
* plugin (e.g. Readme\Validator, Trademarks) that may not be autoloaded yet.
34+
*/
35+
protected static function maybe_load_plugin_directory(): void {
36+
static $loaded = false;
37+
38+
if ( $loaded ) {
39+
return;
40+
}
41+
42+
$autoloader = WP_PLUGIN_DIR . '/plugin-directory/class-autoloader.php';
43+
44+
if ( ! file_exists( $autoloader ) ) {
45+
return;
46+
}
47+
48+
require_once $autoloader;
49+
\WordPressdotorg\Plugin_Directory\Autoloader\register_class_path(
50+
'WordPressdotorg\\Plugin_Directory',
51+
WP_PLUGIN_DIR . '/plugin-directory'
52+
);
53+
54+
$loaded = true;
55+
}
56+
57+
/**
58+
* Convert HTML to markdown when available, falling back to plain text.
59+
*
60+
* Uses the wp_html_to_markdown() function from the html-to-md mu-plugin
61+
* when available. Falls back to stripping tags if the converter is not loaded.
62+
*
63+
* @param string $html The HTML string to convert.
64+
* @return string The converted text.
65+
*/
66+
protected static function html_to_text( string $html ): string {
67+
if ( function_exists( 'wp_html_to_markdown' ) ) {
68+
return wp_html_to_markdown( $html );
69+
}
70+
71+
return wp_strip_all_tags( $html );
72+
}
73+
2974
/**
3075
* Fetch post content from developer.wordpress.org, converting to markdown when available.
3176
*
@@ -62,12 +107,8 @@ protected static function get_devhub_post_content( int $post_id, string $uri ):
62107
restore_current_blog();
63108
}
64109

65-
$mime_type = 'text/html';
66-
67-
if ( function_exists( 'wp_html_to_markdown' ) ) {
68-
$text = wp_html_to_markdown( $text );
69-
$mime_type = 'text/markdown';
70-
}
110+
$text = self::html_to_text( $text );
111+
$mime_type = function_exists( 'wp_html_to_markdown' ) ? 'text/markdown' : 'text/plain';
71112

72113
return array(
73114
array(

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/prompts/class-prepare-plugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static function execute( array $input ): array {
8686
8787
## Step 3: Validate readme.txt
8888
89-
Use the `wporg/validate-readme` tool with the contents of `{$plugin_path}/readme.txt`.
89+
Use the `wporg/plugins/plugin-directory/validate-readme` tool with the contents of `{$plugin_path}/readme.txt`.
9090
9191
If no readme.txt exists, create one following the `wporg://plugins/plugin-directory/readme-standard` resource.
9292

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/resources/class-plugin-faq.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010
namespace WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resources;
1111

12-
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resource_Base;
12+
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Ability_Base;
1313

1414
defined( 'ABSPATH' ) || exit;
1515

1616
/**
1717
* Plugin_FAQ class.
1818
*/
19-
class Plugin_FAQ extends Resource_Base {
19+
class Plugin_FAQ extends Ability_Base {
2020

2121
/**
2222
* Register this resource as an ability.

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/resources/class-plugin-guidelines.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010
namespace WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resources;
1111

12-
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resource_Base;
12+
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Ability_Base;
1313

1414
defined( 'ABSPATH' ) || exit;
1515

1616
/**
1717
* Plugin_Guidelines class.
1818
*/
19-
class Plugin_Guidelines extends Resource_Base {
19+
class Plugin_Guidelines extends Ability_Base {
2020

2121
/**
2222
* Register this resource as an ability.

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/resources/class-plugin-headers.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010
namespace WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resources;
1111

12-
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resource_Base;
12+
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Ability_Base;
1313

1414
defined( 'ABSPATH' ) || exit;
1515

1616
/**
1717
* Plugin_Headers class.
1818
*/
19-
class Plugin_Headers extends Resource_Base {
19+
class Plugin_Headers extends Ability_Base {
2020

2121
/**
2222
* Register this resource as an ability.

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/resources/class-readme-standard.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010
namespace WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resources;
1111

12-
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resource_Base;
12+
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Ability_Base;
1313

1414
defined( 'ABSPATH' ) || exit;
1515

1616
/**
1717
* Readme_Standard class.
1818
*/
19-
class Readme_Standard extends Resource_Base {
19+
class Readme_Standard extends Ability_Base {
2020

2121
/**
2222
* Register this resource as an ability.

wordpress.org/public_html/wp-content/plugins/wporg-abilities/plugins/plugin-directory/resources/class-reserved-slugs.php

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace WordPressdotorg\Abilities\Plugins\Plugin_Directory\Resources;
1111

12+
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Ability_Base;
1213
use WordPressdotorg\Plugin_Directory\Admin\Metabox\Review_Tools;
1314
use WordPressdotorg\Plugin_Directory\Trademarks;
1415

@@ -17,7 +18,7 @@
1718
/**
1819
* Reserved_Slugs class.
1920
*/
20-
class Reserved_Slugs {
21+
class Reserved_Slugs extends Ability_Base {
2122

2223
/**
2324
* Register this resource as an ability.
@@ -45,7 +46,7 @@ public static function register(): void {
4546
* @return array MCP resource contents array.
4647
*/
4748
public static function execute(): array {
48-
self::maybe_load_plugin_directory_autoloader();
49+
self::maybe_load_plugin_directory();
4950

5051
if ( class_exists( Trademarks::class ) && class_exists( Review_Tools::class ) ) {
5152
$text = self::get_dynamic_content();
@@ -62,27 +63,6 @@ public static function execute(): array {
6263
);
6364
}
6465

65-
/**
66-
* Register the plugin-directory autoloader if it hasn't been loaded yet.
67-
*/
68-
private static function maybe_load_plugin_directory_autoloader(): void {
69-
if ( class_exists( Trademarks::class, false ) ) {
70-
return;
71-
}
72-
73-
$autoloader = WP_PLUGIN_DIR . '/plugin-directory/class-autoloader.php';
74-
75-
if ( ! file_exists( $autoloader ) ) {
76-
return;
77-
}
78-
79-
require_once $autoloader;
80-
\WordPressdotorg\Plugin_Directory\Autoloader\register_class_path(
81-
'WordPressdotorg\\Plugin_Directory',
82-
WP_PLUGIN_DIR . '/plugin-directory'
83-
);
84-
}
85-
8666
/**
8767
* Build content dynamically from plugin directory source classes.
8868
*
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
/**
3+
* Validate Readme tool.
4+
*
5+
* @package WordPressdotorg\Abilities\Plugins\Plugin_Directory\Tools
6+
*/
7+
8+
declare( strict_types = 1 );
9+
10+
namespace WordPressdotorg\Abilities\Plugins\Plugin_Directory\Tools;
11+
12+
use WordPressdotorg\Abilities\Plugins\Plugin_Directory\Ability_Base;
13+
use WordPressdotorg\Plugin_Directory\Readme\Validator;
14+
15+
defined( 'ABSPATH' ) || exit;
16+
17+
/**
18+
* Validate_Readme class.
19+
*/
20+
class Validate_Readme extends Ability_Base {
21+
22+
/**
23+
* Register this tool as an ability.
24+
*/
25+
public static function register(): void {
26+
wp_register_ability(
27+
'wporg/plugins/plugin-directory/validate-readme',
28+
array(
29+
'label' => 'Validate Plugin Readme',
30+
'description' => 'Validates a WordPress plugin readme.txt file and returns errors, warnings, and notes.',
31+
'category' => 'wporg-plugins-plugin-directory',
32+
'input_schema' => array(
33+
'type' => 'object',
34+
'properties' => array(
35+
'content' => array(
36+
'type' => 'string',
37+
'description' => 'The full text content of the readme.txt or readme.md file to validate.',
38+
),
39+
),
40+
'required' => array( 'content' ),
41+
),
42+
'execute_callback' => array( __CLASS__, 'execute' ),
43+
'permission_callback' => '__return_true',
44+
'meta' => array(
45+
'mcp' => array( 'type' => 'tool' ),
46+
'annotations' => array( 'readonly' => true ),
47+
),
48+
)
49+
);
50+
}
51+
52+
/**
53+
* Validate the provided readme content.
54+
*
55+
* @param array $input The tool input containing 'content'.
56+
* @return array MCP tool result.
57+
*/
58+
public static function execute( array $input ): array {
59+
self::maybe_load_plugin_directory();
60+
61+
if ( ! class_exists( Validator::class ) ) {
62+
return array(
63+
'error' => 'The plugin directory readme validator is not available.',
64+
);
65+
}
66+
67+
$results = Validator::instance()->validate_content( $input['content'] );
68+
69+
// Convert HTML messages to markdown/plain text for AI agent consumption.
70+
foreach ( $results as $type => $items ) {
71+
foreach ( $items as $code => $message ) {
72+
if ( is_string( $message ) ) {
73+
$results[ $type ][ $code ] = self::html_to_text( $message );
74+
}
75+
}
76+
}
77+
78+
return $results;
79+
}
80+
}

0 commit comments

Comments
 (0)