WriteFailed maps to lit_write_failed / lit_partial_write (partial writes are logged with result partial); old template read before the update; row marker set before wp_update_post; created terms deleted on rollback; parent must be a readable non-descendant of a hierarchical type; featured image must be readable; snapshot keeps empty values; meta comparison after sanitize_meta; follow-up failures (listener, change log) only add warnings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
99 lines
3.5 KiB
PHP
99 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Abilities;
|
|
|
|
use LIT\MCP\Access;
|
|
use LIT\MCP\Integrations\IntegrationRegistry;
|
|
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)
|
|
{
|
|
}
|
|
|
|
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
|
|
{
|
|
foreach ($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) {
|
|
$message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e);
|
|
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
return Errors::internal();
|
|
}
|
|
},
|
|
'execute_callback' => static function (mixed $input = null) use ($ability): mixed {
|
|
try {
|
|
return $ability->execute(is_array($input) ? $input : []);
|
|
} catch (WriteFailed $e) {
|
|
$message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e);
|
|
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
return $e->toError();
|
|
} catch (Throwable $e) {
|
|
$message = sprintf('[wp-lit-mcp] %s failed: %s', $ability->name(), $e);
|
|
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
return Errors::internal();
|
|
}
|
|
},
|
|
'meta' => [
|
|
'annotations' => $ability->annotations(),
|
|
'show_in_rest' => false,
|
|
'mcp' => ['public' => true, 'type' => 'tool'],
|
|
],
|
|
];
|
|
|
|
$output = $ability->outputSchema();
|
|
if ($output !== null) {
|
|
$args['output_schema'] = $output;
|
|
}
|
|
|
|
return $args;
|
|
}
|
|
}
|