Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* Autoloader for wporg-abilities.
*
* phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed
*
* @package WordPressdotorg\Abilities
*/

namespace WordPressdotorg\Abilities\Autoloader;

/**
* An Autoloader which respects WordPress's filename standards.
*/
class Autoloader {

/**
* Namespace separator.
*/
const NS_SEPARATOR = '\\';

/**
* The prefix to compare classes against.
*
* @var string
*/
protected $prefix;

/**
* Length of the prefix string.
*
* @var int
*/
protected $prefix_length;

/**
* Path to the files to be loaded.
*
* @var string
*/
protected $path;

/**
* Constructor.
*
* @param string $prefix Prefix all classes have in common.
* @param string $path Path to the files to be loaded.
*/
public function __construct( $prefix, $path ) {
$this->prefix = $prefix;
$this->prefix_length = strlen( $prefix );
$this->path = trailingslashit( $path );
}

/**
* Loads a class if it starts with `$this->prefix`.
*
* @param string $class The class to be loaded.
*/
public function load( $class ) {
if ( strpos( $class, $this->prefix . self::NS_SEPARATOR ) !== 0 ) {
return;
}

// Strip prefix from the start (ala PSR-4).
$class = substr( $class, $this->prefix_length + 1 );
$class = strtolower( $class );
$file = '';

$last_ns_pos = strripos( $class, self::NS_SEPARATOR );
if ( false !== $last_ns_pos ) {
$namespace = substr( $class, 0, $last_ns_pos );
$namespace = str_replace( '_', '-', $namespace );
$class = substr( $class, $last_ns_pos + 1 );
$file = str_replace( self::NS_SEPARATOR, DIRECTORY_SEPARATOR, $namespace ) . DIRECTORY_SEPARATOR;
}

$file .= 'class-' . str_replace( '_', '-', $class ) . '.php';

$path = $this->path . $file;

if ( file_exists( $path ) ) {
require $path;
}
}
}

/**
* Registers Autoloader's autoload function.
*
* @param string $prefix Namespace prefix.
* @param string $path Path to class files.
*/
function register_class_path( $prefix, $path ) {
$loader = new Autoloader( $prefix, $path );
spl_autoload_register( array( $loader, 'load' ) );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* MCP Server for WordPress.org.
*
* Creates a custom MCP server that exposes all WordPress.org abilities as tools.
*
* @package WordPressdotorg\Abilities
*/

namespace WordPressdotorg\Abilities;

use WP\MCP\Core\McpAdapter;
use WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler;
use WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler;
use WP\MCP\Transport\HttpTransport;

defined( 'ABSPATH' ) || exit;

/**
* MCP Server class.
*/
class MCP_Server {

/**
* Server ID.
*
* @var string
*/
const SERVER_ID = 'wporg-mcp-server';

/**
* Register the WordPress.org MCP server.
*
* Called on the 'mcp_adapter_init' action.
*
* @param McpAdapter $adapter The MCP adapter instance.
*/
public static function register( McpAdapter $adapter ): void {
$tools = self::get_wporg_ability_names();

if ( empty( $tools ) ) {
return;
}

$adapter->create_server(
self::SERVER_ID,
'mcp',
'wporg',
'WordPress.org MCP Server',
'MCP server for WordPress.org services.',
'v1.0.0',
array( HttpTransport::class ),
ErrorLogMcpErrorHandler::class,
NullMcpObservabilityHandler::class,
$tools,
array(), // Resources.
array(), // Prompts.
array( __CLASS__, 'check_permission' )
);
}

/**
* Get all registered WordPress.org ability names.
*
* Discovers abilities by querying the WordPress ability registry
* for any ability whose name starts with 'wporg/'.
*
* @return string[] Array of ability names.
*/
private static function get_wporg_ability_names(): array {
$abilities = wp_get_abilities();
$tools = array();

foreach ( $abilities as $ability ) {
$name = $ability->get_name();

if ( 0 === strpos( $name, 'wporg/' ) ) {
$tools[] = $name;
}
}

return $tools;
}

/**
* Transport-level permission callback.
*
* Requires an authenticated WordPress user. Individual abilities
* have their own permission callbacks for fine-grained access control.
*
* @param \WP_REST_Request $request The REST request object.
* @return bool True if the user has permission.
*/
public static function check_permission( \WP_REST_Request $request ): bool {
return is_user_logged_in();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* Registrar for WordPress.org abilities.
*
* Handles registration of ability categories and individual abilities.
*
* @package WordPressdotorg\Abilities
*/

namespace WordPressdotorg\Abilities;

defined( 'ABSPATH' ) || exit;

/**
* Registrar class.
*/
class Registrar {

/**
* Initialize abilities registration.
*/
public static function init(): void {
add_action( 'wp_abilities_api_categories_init', array( __CLASS__, 'register_categories' ) );
add_action( 'wp_abilities_api_init', array( __CLASS__, 'register_abilities' ) );
}

/**
* Register ability categories.
*/
public static function register_categories(): void {
}

/**
* Register abilities.
*/
public static function register_abilities(): void {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "wporg/wporg-abilities",
"description": "WordPress.org Abilities API integration with MCP server.",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"require": {
"wordpress/mcp-adapter": "^0.4"
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit1055b8d9e51a9a4b1dace3c6ffc091f5::getLoader();
Loading