self::CAPABILITY ); } } /** * Security audit 2026-11 (B3): the adapter's execute-ability and get-ability-info reach every ability with * meta.mcp.public, from any plugin – past the Tools tab ("aus", "nur lesen"), which knows only lit/ tools. * On the OAuth-protected server they therefore name lit/ abilities only. * * @param array $args * @return array */ public function limitMetaToolsToOwnAbilities(array $args, string $name): array { if (! in_array($name, self::META_ABILITIES_BY_NAME, true)) { return $args; } $inner = $args['permission_callback'] ?? null; $args['permission_callback'] = static function (mixed $input = null) use ($inner): bool|WP_Error { $target = is_array($input) && is_string($input['ability_name'] ?? null) ? $input['ability_name'] : ''; if (RequestContext::isOAuthServerRequest() && ! str_starts_with($target, 'lit/')) { return Errors::forbidden( 'Über diesen Server sind nur LIT-MCP-Tools nutzbar.', 'Nur Abilities mit dem Präfix lit/ ausführen oder abfragen.' ); } return is_callable($inner) ? $inner($input) : false; }; return $args; } /** * @return list */ public function allowedRoles(): array { $roles = get_option(self::OPTION, self::DEFAULT_ROLES); if (! is_array($roles)) { $roles = self::DEFAULT_ROLES; } $roles = apply_filters('lit_mcp_allowed_roles', $roles); if (! is_array($roles)) { $roles = self::DEFAULT_ROLES; } return array_values(array_filter($roles, 'is_string')); } /** * @param array $allcaps * @param list $caps * @param array $args * @return array */ public function grantCapability( array $allcaps, array $caps, array $args, WP_User $user ): array { if (! in_array(self::CAPABILITY, $caps, true)) { return $allcaps; } if (array_intersect($user->roles, $this->allowedRoles()) !== []) { $allcaps[self::CAPABILITY] = true; } return $allcaps; } public static function currentUserMayUse(): bool { return current_user_can(self::CAPABILITY); } }