UserCompletionProvider.php•3.51 kB
<?php
declare(strict_types=1);
namespace OpenFGA\MCP\Completions;
use OpenFGA\Models\TupleKey;
use OpenFGA\Responses\ReadTuplesResponseInterface;
use Override;
use PhpMcp\Server\Contracts\SessionInterface;
use Throwable;
use function array_slice;
use function assert;
final readonly class UserCompletionProvider extends AbstractCompletions
{
/**
* Get completion suggestions for user identifiers from OpenFGA relationship tuples.
*
* @param string $currentValue
* @param SessionInterface $session
* @return array<string>
*/
#[Override]
public function getCompletions(string $currentValue, SessionInterface $session): array
{
// Return common user patterns in offline mode
if ($this->isOffline()) {
return $this->getCommonUserPatterns($currentValue);
}
try {
// Try to get store ID from session context
$storeId = $this->extractStoreIdFromSession($session);
if (null === $storeId) {
return $this->getCommonUserPatterns($currentValue);
}
// Check if access to this store is restricted
if ($this->isRestricted($storeId)) {
return [];
}
// Read relationship tuples to extract users
$users = [];
// Use wildcard tuple (empty strings act as wildcards) to read all tuples
$tuple = new TupleKey(
user: '',
relation: '',
object: '',
);
$this->client->readTuples(
store: $storeId,
tuple: $tuple,
pageSize: 50,
)
->failure(static function (): void {
// If we can't fetch tuples, users will remain empty
})
->success(static function (mixed $response) use (&$users): void {
assert($response instanceof ReadTuplesResponseInterface);
$tuples = $response->getTuples();
foreach ($tuples as $tuple) {
$user = $tuple->getKey()->getUser();
if ('' !== $user) {
$users[] = $user;
}
}
});
if ([] === $users) {
return $this->getCommonUserPatterns($currentValue);
}
// Remove duplicates and sort
$users = array_unique($users);
sort($users);
// Limit to reasonable number for performance
$users = array_slice($users, 0, 50);
return $this->filterCompletions($users, $currentValue);
} catch (Throwable) {
// Handle any unexpected errors gracefully
return $this->getCommonUserPatterns($currentValue);
}
}
/**
* Get common user identifier patterns as fallback.
*
* @param string $currentValue
* @return array<string>
*/
private function getCommonUserPatterns(string $currentValue): array
{
$commonPatterns = [
'user:alice',
'user:bob',
'user:admin',
'user:guest',
'group:admins',
'group:users',
'group:viewers',
'service:api',
'service:backend',
];
return $this->filterCompletions($commonPatterns, $currentValue);
}
}