Files
WP-LIT-MCP/src/Abilities/AbilityRegistrar.php
T
janniclas.loosenandClaude Opus 5.5 b6984d3f9a refactor: one logging helper LIT\MCP\Log (M6)
Log::failure(context, detail) keeps the '[wp-lit-mcp] <context> failed: <detail>'
format of the nine former error_log() sites; Log::line() covers the change log line.
One phpcs ignore instead of eleven.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:36:01 +02:00

104 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Abilities;
use LIT\MCP\Access;
use LIT\MCP\Integrations\IntegrationRegistry;
use LIT\MCP\Log;
use LIT\MCP\Server\RequestContext;
use Throwable;
use WP_Error;
/**
* Registers all abilities of available integrations with the Abilities API and
* wraps them with the role gate and neutral error handling.
*/
final class AbilityRegistrar
{
public const CATEGORY = 'lit-mcp';
public function __construct(private IntegrationRegistry $registry, private ?ExposurePolicy $policy = null)
{
}
public function register(): void
{
add_action('wp_abilities_api_categories_init', [$this, 'registerCategory']);
add_action('wp_abilities_api_init', [$this, 'registerAbilities']);
}
public function registerCategory(): void
{
wp_register_ability_category(self::CATEGORY, [
'label' => 'LIT MCP',
'description' => 'Website-Pflege für WordPress, Divi und Pods über MCP.',
]);
}
public function registerAbilities(): void
{
$policy = $this->policy ?? new ExposurePolicy();
foreach ($policy->enabled($this->registry->abilities()) as $ability) {
wp_register_ability($ability->name(), $this->argsFor($ability));
}
}
/** @return array<string, mixed> */
public function argsFor(Ability $ability): array
{
$args = [
'label' => $ability->label(),
'description' => $ability->description(),
'category' => self::CATEGORY,
'input_schema' => $ability->inputSchema(),
'permission_callback' => static function (mixed $input = null) use ($ability): bool|WP_Error {
if (! Access::currentUserMayUse()) {
return Errors::forbidden('Dein Benutzerkonto ist für LIT MCP nicht freigegeben.');
}
if (! RequestContext::isOAuthServerRequest()) {
return Errors::forbidden('LIT MCP ist nur über den OAuth-geschützten MCP-Server nutzbar.');
}
try {
return $ability->permission(is_array($input) ? $input : []);
} catch (Throwable $e) {
Log::failure($ability->name(), $e);
return Errors::internal();
}
},
'execute_callback' => static function (mixed $input = null) use ($ability): mixed {
try {
return $ability->execute(is_array($input) ? $input : []);
} catch (WriteFailed $e) {
Log::failure($ability->name(), $e);
return $e->toError();
} catch (Throwable $e) {
Log::failure($ability->name(), $e);
return Errors::internal();
}
},
'meta' => [
'annotations' => $ability->annotations(),
'show_in_rest' => false,
// mcp.public only governs the adapter's meta-tools (discover/get-info/execute);
// direct tools are listed on the server explicitly. Direct-only abilities
// (image/resource results) would not work through execute, so hide them there.
'mcp' => [
'public' => ! in_array($ability->name(), ExposurePolicy::DIRECT_ONLY, true),
'type' => 'tool',
],
],
];
$output = $ability->outputSchema();
if ($output !== null) {
$args['output_schema'] = $output;
}
return $args;
}
}