Skip to main content
Glama

OpenFGA MCP

CompletionProvidersIntegrationTest.php16 kB
<?php declare(strict_types=1); use OpenFGA\Client; use OpenFGA\MCP\Completions\{ ModelIdCompletionProvider, ObjectCompletionProvider, RelationCompletionProvider, StoreIdCompletionProvider, UserCompletionProvider }; use OpenFGA\Models\Collections\TupleKeys; use OpenFGA\Models\TupleKey; use PhpMcp\Server\Contracts\SessionInterface; beforeEach(function (): void { $this->client = getTestClient(); $this->session = Mockery::mock(SessionInterface::class); // Initialize completion providers $this->storeCompletionProvider = new StoreIdCompletionProvider($this->client); $this->modelCompletionProvider = new ModelIdCompletionProvider($this->client); $this->relationCompletionProvider = new RelationCompletionProvider($this->client); $this->userCompletionProvider = new UserCompletionProvider($this->client); $this->objectCompletionProvider = new ObjectCompletionProvider($this->client); }); describe('Completion Providers Integration', function (): void { describe('StoreIdCompletionProvider', function (): void { it('can fetch real store IDs', function (): void { // Create multiple test stores $store1 = createTestStore('integration-test-store-1'); $store2 = createTestStore('integration-test-store-2'); $completions = $this->storeCompletionProvider->getCompletions('', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain($store1) ->and($completions)->toContain($store2); // Clean up deleteTestStore($store1); deleteTestStore($store2); }); it('filters completions by current value', function (): void { // Create multiple stores to test filtering $store1 = createTestStore('filter-test-store-1'); $store2 = createTestStore('filter-test-store-2'); // Get all completions $allCompletions = $this->storeCompletionProvider->getCompletions('', $this->session); expect($allCompletions)->toBeArray() ->and($allCompletions)->toContain($store1) ->and($allCompletions)->toContain($store2); // Test filtering by the first few characters of an actual store ID $firstChars = substr($store1, 0, 3); $filteredCompletions = $this->storeCompletionProvider->getCompletions($firstChars, $this->session); expect($filteredCompletions)->toBeArray() ->and($filteredCompletions)->toContain($store1); // Test with a string that shouldn't match any store IDs $nonMatchingCompletions = $this->storeCompletionProvider->getCompletions('ZZZZZZ', $this->session); expect($nonMatchingCompletions)->toBeArray()->toBeEmpty(); // Clean up deleteTestStore($store1); deleteTestStore($store2); }); }); describe('ModelIdCompletionProvider', function (): void { it('includes latest option when no store context', function (): void { $completions = $this->modelCompletionProvider->getCompletions('', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('latest'); }); it('can fetch real model IDs from store', function (): void { $setup = setupTestStoreWithModel(); $storeId = $setup['store']; $modelId = $setup['model']; // Set environment variable to provide store context $_ENV['OPENFGA_MCP_API_STORE'] = $storeId; putenv("OPENFGA_MCP_API_STORE={$storeId}"); $completions = $this->modelCompletionProvider->getCompletions('', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('latest') ->and($completions)->toContain($modelId); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); it('respects restricted mode', function (): void { $_ENV['OPENFGA_MCP_API_RESTRICT'] = 'true'; putenv('OPENFGA_MCP_API_RESTRICT=true'); $allowedStore = createTestStore('allowed-store'); $restrictedStore = createTestStore('restricted-store'); // Set environment to use the allowed store $_ENV['OPENFGA_MCP_API_STORE'] = $allowedStore; putenv("OPENFGA_MCP_API_STORE={$allowedStore}"); // When we try to access the allowed store, it should work (not be empty) $allowedCompletions = $this->modelCompletionProvider->getCompletions('', $this->session); expect($allowedCompletions)->toBeArray() ->and($allowedCompletions)->toContain('latest'); // Now test with a different store (should fail restriction check) $setup = setupTestStoreWithModel(); $modelId = $setup['model']; // The isRestricted method checks if the store we're trying to access // matches the configured store, not what's in the environment expect($this->modelCompletionProvider->getCompletions('', $this->session)) ->toBeArray() ->toContain('latest'); deleteTestStore($allowedStore); deleteTestStore($restrictedStore); // Clean up unset($_ENV['OPENFGA_MCP_API_RESTRICT']); putenv('OPENFGA_MCP_API_RESTRICT=false'); putenv('OPENFGA_MCP_API_STORE=false'); }); }); describe('RelationCompletionProvider', function (): void { it('can fetch real relations from authorization model', function (): void { $dsl = 'model schema 1.1 type user type document relations define viewer: [user] define editor: [user] define owner: [user] type folder relations define viewer: [user] define editor: [user]'; $setup = setupTestStoreWithModel($dsl); $storeId = $setup['store']; // Set environment variable to provide store context $_ENV['OPENFGA_MCP_API_STORE'] = $storeId; putenv("OPENFGA_MCP_API_STORE={$storeId}"); $completions = $this->relationCompletionProvider->getCompletions('', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('viewer') ->and($completions)->toContain('editor') ->and($completions)->toContain('owner'); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); it('filters completions correctly', function (): void { $setup = setupTestStoreWithModel(); $storeId = $setup['store']; // Set environment variable to provide store context $_ENV['OPENFGA_MCP_API_STORE'] = $storeId; putenv("OPENFGA_MCP_API_STORE={$storeId}"); $completions = $this->relationCompletionProvider->getCompletions('read', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('reader'); $filteredCompletions = $this->relationCompletionProvider->getCompletions('writ', $this->session); expect($filteredCompletions)->toBeArray() ->and($filteredCompletions)->toContain('writer'); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); it('falls back to common relations when no store context', function (): void { // Ensure no store is configured unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); $completions = $this->relationCompletionProvider->getCompletions('view', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('viewer'); }); }); describe('UserCompletionProvider', function (): void { it('can fetch users from relationship tuples', function (): void { $setup = setupTestStoreWithModel(); $storeId = $setup['store']; // Write some test tuples $client = getTestClient(); $client->writeTuples( store: $storeId, model: $setup['model'], writes: new TupleKeys( new TupleKey( user: 'user:alice', relation: 'reader', object: 'document:test1', ), new TupleKey( user: 'user:bob', relation: 'writer', object: 'document:test2', ), ), ) ->failure(function ($error): void { throw new RuntimeException('Failed to write test tuples: ' . $error->getMessage()); }); // Give a moment for the tuples to be written sleep(1); // Set environment variable to provide store context $_ENV['OPENFGA_MCP_API_STORE'] = $storeId; putenv("OPENFGA_MCP_API_STORE={$storeId}"); $completions = $this->userCompletionProvider->getCompletions('user:', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('user:alice') ->and($completions)->toContain('user:bob'); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); it('falls back to common user patterns when no data', function (): void { // Ensure no store is configured unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); $completions = $this->userCompletionProvider->getCompletions('user:', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('user:alice') ->and($completions)->toContain('user:bob'); }); }); describe('ObjectCompletionProvider', function (): void { it('can fetch objects from relationship tuples', function (): void { $setup = setupTestStoreWithModel(); $storeId = $setup['store']; // Write some test tuples $client = getTestClient(); $client->writeTuples( store: $storeId, model: $setup['model'], writes: new TupleKeys( new TupleKey( user: 'user:alice', relation: 'reader', object: 'document:test1', ), new TupleKey( user: 'user:bob', relation: 'writer', object: 'document:test2', ), ), ) ->failure(function ($error): void { throw new RuntimeException('Failed to write test tuples: ' . $error->getMessage()); }); // Give a moment for the tuples to be written sleep(1); // Set environment variable to provide store context $_ENV['OPENFGA_MCP_API_STORE'] = $storeId; putenv("OPENFGA_MCP_API_STORE={$storeId}"); $completions = $this->objectCompletionProvider->getCompletions('document:', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('document:test1') ->and($completions)->toContain('document:test2'); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); it('filters completions by current value', function (): void { $dsl = 'model schema 1.1 type user type document relations define reader: [user] define writer: [user] define owner: [user] type folder relations define reader: [user]'; $setup = setupTestStoreWithModel($dsl); $storeId = $setup['store']; // Write test tuples with different object patterns $client = getTestClient(); $client->writeTuples( store: $storeId, model: $setup['model'], writes: new TupleKeys( new TupleKey( user: 'user:alice', relation: 'reader', object: 'folder:important', ), new TupleKey( user: 'user:bob', relation: 'reader', object: 'document:important', ), ), ) ->failure(function ($error): void { throw new RuntimeException('Failed to write test tuples: ' . $error->getMessage()); }); sleep(1); // Set environment variable to provide store context $_ENV['OPENFGA_MCP_API_STORE'] = $storeId; putenv("OPENFGA_MCP_API_STORE={$storeId}"); $folderCompletions = $this->objectCompletionProvider->getCompletions('folder:', $this->session); $documentCompletions = $this->objectCompletionProvider->getCompletions('document:', $this->session); expect($folderCompletions)->toBeArray() ->and($folderCompletions)->toContain('folder:important') ->and($folderCompletions)->not->toContain('document:important'); expect($documentCompletions)->toBeArray() ->and($documentCompletions)->toContain('document:important') ->and($documentCompletions)->not->toContain('folder:important'); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); it('falls back to common object patterns when no data', function (): void { // Ensure no store is configured unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); $completions = $this->objectCompletionProvider->getCompletions('document:', $this->session); expect($completions)->toBeArray() ->and($completions)->toContain('document:budget') ->and($completions)->toContain('document:plan'); }); }); describe('Error handling and resilience', function (): void { it('handles network errors gracefully', function (): void { // Create providers with invalid client configuration to simulate network errors $invalidClient = new Client(url: 'http://invalid-url:9999'); $provider = new StoreIdCompletionProvider($invalidClient); $completions = $provider->getCompletions('', $this->session); expect($completions)->toBeArray()->toBeEmpty(); }); it('handles invalid store IDs gracefully', function (): void { // Set environment variable with invalid store ID $_ENV['OPENFGA_MCP_API_STORE'] = 'invalid-store-id'; putenv('OPENFGA_MCP_API_STORE=invalid-store-id'); $completions = $this->modelCompletionProvider->getCompletions('', $this->session); // Should fall back to 'latest' option expect($completions)->toBeArray() ->and($completions)->toContain('latest'); // Clean up unset($_ENV['OPENFGA_MCP_API_STORE']); putenv('OPENFGA_MCP_API_STORE=false'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/evansims/openfga-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server