Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
84 lines
2.3 KiB
PHP
84 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Abilities;
|
|
|
|
use WP_Error;
|
|
|
|
/**
|
|
* Opaque offset cursor for list tools (spec §8.1, §11.3).
|
|
*/
|
|
final class Cursor
|
|
{
|
|
public const DEFAULT_LIMIT = 20;
|
|
public const MAX_LIMIT = 100;
|
|
|
|
public static function encode(int $offset): string
|
|
{
|
|
return rtrim(strtr(base64_encode('o:' . $offset), '+/', '-_'), '=');
|
|
}
|
|
|
|
/** @return int|null Offset, 0 for a missing cursor, null for an invalid one. */
|
|
public static function decode(?string $cursor): ?int
|
|
{
|
|
if ($cursor === null || $cursor === '') {
|
|
return 0;
|
|
}
|
|
|
|
$raw = base64_decode(strtr($cursor, '-_', '+/'), true);
|
|
if ($raw === false || preg_match('/^o:(\d{1,9})$/', $raw, $matches) !== 1) {
|
|
return null;
|
|
}
|
|
|
|
return (int) $matches[1];
|
|
}
|
|
|
|
public static function limit(mixed $limit, int $default = self::DEFAULT_LIMIT): int
|
|
{
|
|
if ($limit === null) {
|
|
return $default;
|
|
}
|
|
|
|
return max(1, min(self::MAX_LIMIT, (int) $limit));
|
|
}
|
|
|
|
/**
|
|
* @param list<mixed> $rows Rows fetched with $limit + 1.
|
|
* @return array{items: list<mixed>, next_cursor: ?string}
|
|
*/
|
|
public static function page(array $rows, int $limit, int $offset): array
|
|
{
|
|
return [
|
|
'items' => array_slice($rows, 0, $limit),
|
|
'next_cursor' => count($rows) > $limit ? self::encode($offset + $limit) : null,
|
|
];
|
|
}
|
|
|
|
public static function invalid(): WP_Error
|
|
{
|
|
return Errors::invalidInput(
|
|
'Ungültiger cursor.',
|
|
['cursor' => 'unbekanntes Format'],
|
|
'cursor unverändert aus next_cursor der letzten Antwort übernehmen oder weglassen.'
|
|
);
|
|
}
|
|
|
|
/** @return array<string, array<string, mixed>> */
|
|
public static function inputProperties(): array
|
|
{
|
|
return [
|
|
'limit' => [
|
|
'type' => 'integer',
|
|
'minimum' => 1,
|
|
'maximum' => self::MAX_LIMIT,
|
|
'description' => 'Einträge pro Seite (Standard 20).',
|
|
],
|
|
'cursor' => [
|
|
'type' => 'string',
|
|
'description' => 'next_cursor der vorherigen Antwort, um weiterzublättern.',
|
|
],
|
|
];
|
|
}
|
|
}
|