Skip to main content
Glama

tbls MCP Server

by yhosok
uri-patterns.test.ts25.8 kB
import { jest } from '@jest/globals'; import { tmpdir } from 'os'; import { join } from 'path'; import { promises as fs } from 'fs'; import { TblsMcpServer } from '../../src/server'; import { LazyResourceRegistry } from '../../src/server/lazy-resource-registry'; import { ResourceCache } from '../../src/cache/resource-cache'; import { ResourcePatterns } from '../../src/server/resource-patterns'; import { ServerConfig } from '../../src/schemas/config'; import { ok } from 'neverthrow'; // Mock the resource handlers jest.mock('../../src/resources/schema-resource'); jest.mock('../../src/resources/table-resource'); jest.mock('../../src/resources/index-resource'); import * as schemaResource from '../../src/resources/schema-resource'; import * as tableResource from '../../src/resources/table-resource'; import * as indexResource from '../../src/resources/index-resource'; // Define types for test server interface ResourceResponse { uri: string; name: string; description: string; mimeType: string; } interface ListResourcesResponse { resources: ResourceResponse[]; } interface RequestHandler { (request: { method: string; params: Record<string, unknown>; }): Promise<ListResourcesResponse>; } interface MockTblsServer { server: { _requestHandlers: Map<string, RequestHandler>; }; } describe('db:// URI Patterns', () => { let tempDir: string; let schemaDir: string; let mockConfig: ServerConfig; let server: TblsMcpServer; beforeEach(async () => { // Reset all mocks jest.clearAllMocks(); // Create temporary directory for tests tempDir = await fs.mkdtemp(join(tmpdir(), 'tbls-uri-test-')); schemaDir = join(tempDir, 'schemas'); await fs.mkdir(schemaDir); mockConfig = { schemaSource: schemaDir, cache: { enabled: true, maxItems: 1000, ttlMs: 300000, }, }; server = new TblsMcpServer(mockConfig); // Mock schema resource responses const mockSchemaResourceMod = schemaResource as jest.Mocked< typeof schemaResource >; mockSchemaResourceMod.handleSchemaListResource.mockResolvedValue( ok({ schemas: [ { name: 'public', tableCount: 5 }, { name: 'auth', tableCount: 3 }, ], }) ); const mockTableResourceMod = tableResource as jest.Mocked< typeof tableResource >; mockTableResourceMod.handleSchemaTablesResource.mockResolvedValue( ok({ tables: [ { name: 'users', columns: 4, rowCount: 1000 }, { name: 'posts', columns: 6, rowCount: 500 }, ], }) ); }); afterEach(async () => { if (server) { await server.close(); } if (tempDir) { await fs.rm(tempDir, { recursive: true, force: true }); } }); describe('Pattern Matching System - db:// Hierarchical Structure', () => { describe('Root Schema List Pattern', () => { it('should match db://schemas URI pattern', () => { const match = ResourcePatterns.matchUri('db://schemas'); expect(match).not.toBeNull(); expect(match?.pattern.id).toBe('db-schemas'); expect(match?.pattern.uriPattern).toBe('db://schemas'); expect(match?.params).toEqual({}); }); it('should not match old schema://list pattern', () => { const match = ResourcePatterns.matchUri('schema://list'); expect(match).toBeNull(); }); it('should generate correct resource metadata for db://schemas', () => { const match = ResourcePatterns.matchUri('db://schemas'); expect(match?.pattern.namePattern).toBe('Database Schemas'); expect(match?.pattern.descriptionPattern).toContain( 'Complete list of all available database schemas' ); expect(match?.pattern.mimeType).toBe('application/json'); }); }); describe('Schema Tables Pattern', () => { it('should match db://schemas/{schemaName}/tables URI pattern', () => { const match = ResourcePatterns.matchUri('db://schemas/public/tables'); expect(match).not.toBeNull(); expect(match?.pattern.id).toBe('db-schema-tables'); expect(match?.pattern.uriPattern).toBe( 'db://schemas/{schemaName}/tables' ); expect(match?.params).toEqual({ schemaName: 'public' }); }); it('should extract schema name parameter correctly', () => { const testCases = [ { uri: 'db://schemas/public/tables', expected: 'public' }, { uri: 'db://schemas/auth/tables', expected: 'auth' }, { uri: 'db://schemas/inventory/tables', expected: 'inventory' }, { uri: 'db://schemas/user_management/tables', expected: 'user_management', }, ]; testCases.forEach(({ uri, expected }) => { const match = ResourcePatterns.matchUri(uri); expect(match?.params.schemaName).toBe(expected); }); }); it('should not match old schema://{schemaName}/tables pattern', () => { const match = ResourcePatterns.matchUri('schema://public/tables'); expect(match).toBeNull(); }); it('should not match malformed db:// patterns', () => { const malformedUris = [ 'db://schemas/tables', // missing schema name 'db://schemas//tables', // empty schema name 'db://schemas/public', // missing /tables 'db://schema/public/tables', // wrong schemes segment ]; malformedUris.forEach((uri) => { const match = ResourcePatterns.matchUri(uri); expect(match).toBeNull(); }); }); }); describe('Individual Table Pattern', () => { it('should match db://schemas/{schemaName}/tables/{tableName} URI pattern', () => { const match = ResourcePatterns.matchUri( 'db://schemas/public/tables/users' ); expect(match).not.toBeNull(); expect(match?.pattern.id).toBe('db-table-info'); expect(match?.pattern.uriPattern).toBe( 'db://schemas/{schemaName}/tables/{tableName}' ); expect(match?.params).toEqual({ schemaName: 'public', tableName: 'users', }); }); it('should extract both schema and table name parameters correctly', () => { const testCases = [ { uri: 'db://schemas/public/tables/users', expected: { schemaName: 'public', tableName: 'users' }, }, { uri: 'db://schemas/auth/tables/user_sessions', expected: { schemaName: 'auth', tableName: 'user_sessions' }, }, { uri: 'db://schemas/inventory/tables/product_catalog', expected: { schemaName: 'inventory', tableName: 'product_catalog' }, }, ]; testCases.forEach(({ uri, expected }) => { const match = ResourcePatterns.matchUri(uri); expect(match?.params).toEqual(expected); }); }); it('should not match old table://{schemaName}/{tableName} pattern', () => { const match = ResourcePatterns.matchUri('table://public/users'); expect(match).toBeNull(); }); it('should generate correct interpolated names and descriptions', () => { const match = ResourcePatterns.matchUri( 'db://schemas/public/tables/users' ); expect(match?.pattern.namePattern).toBe( '{tableName} table ({schemaName} schema)' ); expect(match?.pattern.descriptionPattern).toContain( 'Complete detailed information about the {tableName} table' ); // Test interpolation const interpolatedName = ResourcePatterns.interpolate( match?.pattern.namePattern || '', match?.params || {} ); const interpolatedDesc = ResourcePatterns.interpolate( match?.pattern.descriptionPattern || '', match?.params || {} ); expect(interpolatedName).toBe('users table (public schema)'); expect(interpolatedDesc).toContain( 'Complete detailed information about the users table' ); }); }); describe('Table Indexes Pattern', () => { it('should match db://schemas/{schemaName}/tables/{tableName}/indexes URI pattern', () => { const match = ResourcePatterns.matchUri( 'db://schemas/public/tables/users/indexes' ); expect(match).not.toBeNull(); expect(match?.pattern.id).toBe('db-table-indexes'); expect(match?.pattern.uriPattern).toBe( 'db://schemas/{schemaName}/tables/{tableName}/indexes' ); expect(match?.params).toEqual({ schemaName: 'public', tableName: 'users', }); }); it('should extract schema and table parameters for index URIs', () => { const testCases = [ { uri: 'db://schemas/public/tables/users/indexes', expected: { schemaName: 'public', tableName: 'users' }, }, { uri: 'db://schemas/auth/tables/sessions/indexes', expected: { schemaName: 'auth', tableName: 'sessions' }, }, ]; testCases.forEach(({ uri, expected }) => { const match = ResourcePatterns.matchUri(uri); expect(match?.params).toEqual(expected); }); }); it('should not match old table://{schemaName}/{tableName}/indexes pattern', () => { const match = ResourcePatterns.matchUri('table://public/users/indexes'); expect(match).toBeNull(); }); }); }); describe('Progressive Resource Discovery with URI Patterns', () => { it('should discover schemas under db://schemas when listing resources', async () => { const mockServer = server as MockTblsServer; const listHandler = mockServer.server._requestHandlers.get('resources/list'); const response = await listHandler({ method: 'resources/list', params: {}, }); // Should include the db://schemas resource const dbSchemasResource = response.resources.find( (r: ResourceResponse) => r.uri === 'db://schemas' ); expect(dbSchemasResource).toBeDefined(); expect(dbSchemasResource.name).toBe('Database Schemas'); expect(dbSchemasResource.description).toContain( 'Complete list of all available database schemas' ); }); it('should progressively discover schema tables under db://schemas/{schemaName}/tables', async () => { // Mock the lazy registry to simulate discovery const registry = new LazyResourceRegistry( mockConfig.schemaSource, new ResourceCache({ maxItems: 1000, ttlMs: 300000, }) ); const discoveryResult = await registry.discoverResource( 'db://schemas/public/tables' ); expect(discoveryResult.isOk()).toBe(true); if (discoveryResult.isOk()) { expect(discoveryResult.value).toBeDefined(); expect(discoveryResult.value?.uri).toBe('db://schemas/public/tables'); } }); it('should progressively discover individual tables under db://schemas/{schemaName}/tables/{tableName}', async () => { const registry = new LazyResourceRegistry( mockConfig.schemaSource, new ResourceCache({ maxItems: 1000, ttlMs: 300000, }) ); const discoveryResult = await registry.discoverResource( 'db://schemas/public/tables/users' ); expect(discoveryResult.isOk()).toBe(true); if (discoveryResult.isOk()) { expect(discoveryResult.value).toBeDefined(); expect(discoveryResult.value?.uri).toBe( 'db://schemas/public/tables/users' ); } }); it('should progressively discover table indexes under db://schemas/{schemaName}/tables/{tableName}/indexes', async () => { const registry = new LazyResourceRegistry( mockConfig.schemaSource, new ResourceCache({ maxItems: 1000, ttlMs: 300000, }) ); const discoveryResult = await registry.discoverResource( 'db://schemas/public/tables/users/indexes' ); expect(discoveryResult.isOk()).toBe(true); if (discoveryResult.isOk()) { expect(discoveryResult.value).toBeDefined(); expect(discoveryResult.value?.uri).toBe( 'db://schemas/public/tables/users/indexes' ); } }); it('should generate hierarchical resources in correct order', async () => { const registry = new LazyResourceRegistry( mockConfig.schemaSource, new ResourceCache({ maxItems: 1000, ttlMs: 300000, }) ); // First, trigger discovery by accessing hierarchical resources in order await registry.handleResourceAccess('db://schemas'); // Triggers schema tables discovery await registry.handleResourceAccess('db://schemas/public/tables'); // Triggers table discovery for public schema await registry.handleResourceAccess('db://schemas/auth/tables'); // Triggers table discovery for auth schema await registry.handleResourceAccess('db://schemas/public/tables/users'); // Triggers index discovery for users table await registry.handleResourceAccess('db://schemas/public/tables/posts'); // Triggers index discovery for posts table await registry.handleResourceAccess( 'db://schemas/public/tables/users/indexes' ); await registry.handleResourceAccess( 'db://schemas/public/tables/posts/indexes' ); const allResources = await registry.listResources(); expect(allResources.isOk()).toBe(true); if (allResources.isOk()) { const uris = allResources.value.map((r) => r.uri); // Should include hierarchical db:// patterns after discovery expect(uris).toContain('db://schemas'); expect(uris).toContain('db://schemas/public/tables'); expect(uris).toContain('db://schemas/auth/tables'); expect(uris).toContain('db://schemas/public/tables/users'); expect(uris).toContain('db://schemas/public/tables/posts'); expect(uris).toContain('db://schemas/public/tables/users/indexes'); expect(uris).toContain('db://schemas/public/tables/posts/indexes'); } }); }); describe('Error Handling with URI Patterns', () => { it('should provide helpful error messages for unrecognized db:// URIs', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); try { await readHandler({ method: 'resources/read', params: { uri: 'db://invalid/path' }, }); fail('Expected error to be thrown'); } catch (error: Error & { data?: Record<string, unknown> }) { expect(error.message).toContain('Unknown resource URI'); expect(error.data?.suggestions).toContain('db://schemas'); expect(error.data?.validPatterns).toContain( 'db://schemas/{schemaName}/tables' ); } }); it('should treat old schema:// patterns as unknown URIs', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); try { await readHandler({ method: 'resources/read', params: { uri: 'schema://list' }, }); fail('Expected error to be thrown'); } catch (error: Error & { data?: Record<string, unknown> }) { expect(error.message).toContain('Unknown resource URI'); expect(error.data?.suggestions).toBeDefined(); expect(error.data?.suggestions).toContain('db://schemas'); } }); it('should treat old table:// patterns as unknown URIs', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); try { await readHandler({ method: 'resources/read', params: { uri: 'table://public/users' }, }); fail('Expected error to be thrown'); } catch (error: Error & { data?: Record<string, unknown> }) { expect(error.message).toContain('Unknown resource URI'); expect(error.data?.suggestions).toBeDefined(); expect(error.data?.suggestions).toContain('db://schemas'); } }); it('should provide hierarchical path suggestions for partial matches', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); try { await readHandler({ method: 'resources/read', params: { uri: 'db://schemas/nonexistent' }, }); fail('Expected error to be thrown'); } catch (error: Error & { data?: Record<string, unknown> }) { expect(error.message).toContain('Resource not found'); expect(error.data?.suggestions).toContain( 'db://schemas/nonexistent/tables' ); expect(error.data?.availableSchemas).toEqual(['public', 'auth']); } }); it('should provide table suggestions when table not found in valid schema', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); try { await readHandler({ method: 'resources/read', params: { uri: 'db://schemas/public/tables/nonexistent' }, }); fail('Expected error to be thrown'); } catch (error: Error & { data?: Record<string, unknown> }) { expect(error.message).toContain('Table not found'); expect(error.data?.availableTables).toEqual(['users', 'posts']); expect(error.data?.schemaName).toBe('public'); } }); }); describe('Integration Tests - Complete Flow with URI Patterns', () => { it('should handle complete flow from db://schemas to resource content', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); const response = await readHandler({ method: 'resources/read', params: { uri: 'db://schemas' }, }); expect(response.contents).toBeDefined(); expect(response.contents[0].mimeType).toBe('application/json'); const content = JSON.parse(response.contents[0].text); expect(content.schemas).toHaveLength(2); expect(content.schemas[0].name).toBe('public'); expect(content.schemas[1].name).toBe('auth'); }); it('should handle complete flow from db://schemas/{schemaName}/tables to resource content', async () => { const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); const response = await readHandler({ method: 'resources/read', params: { uri: 'db://schemas/public/tables' }, }); expect(response.contents).toBeDefined(); expect(response.contents[0].mimeType).toBe('application/json'); const content = JSON.parse(response.contents[0].text); expect(content.tables).toHaveLength(2); expect(content.tables[0].name).toBe('users'); expect(content.tables[1].name).toBe('posts'); }); it('should handle complete flow from db://schemas/{schemaName}/tables/{tableName} to resource content', async () => { // Mock the table info resource handler const mockTableResourceMod = tableResource as jest.Mocked< typeof tableResource >; mockTableResourceMod.handleTableInfoResource.mockResolvedValue( ok({ table: { name: 'users', schema: 'public', columns: [ { name: 'id', type: 'integer', nullable: false, primaryKey: true, }, { name: 'email', type: 'varchar(255)', nullable: false, unique: true, }, { name: 'name', type: 'varchar(100)', nullable: true }, { name: 'created_at', type: 'timestamp', nullable: false }, ], indexes: 2, rowCount: 1000, }, }) ); const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); const response = await readHandler({ method: 'resources/read', params: { uri: 'db://schemas/public/tables/users' }, }); expect(response.contents).toBeDefined(); expect(response.contents[0].mimeType).toBe('application/json'); const content = JSON.parse(response.contents[0].text); expect(content.table.name).toBe('users'); expect(content.table.schema).toBe('public'); expect(content.table.columns).toHaveLength(4); }); it('should handle complete flow from db://schemas/{schemaName}/tables/{tableName}/indexes to resource content', async () => { // Mock the index resource handler const mockIndexesData = { table: 'users', schema: 'public', indexes: [ { name: 'users_pkey', type: 'PRIMARY KEY', columns: ['id'], unique: true, }, { name: 'users_email_idx', type: 'UNIQUE', columns: ['email'], unique: true, }, ], }; const mockIndexResourceMod = indexResource as jest.Mocked< typeof indexResource >; mockIndexResourceMod.handleTableIndexesResource.mockResolvedValue( ok(mockIndexesData) ); const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); const response = await readHandler({ method: 'resources/read', params: { uri: 'db://schemas/public/tables/users/indexes' }, }); expect(response.contents).toBeDefined(); expect(response.contents[0].mimeType).toBe('application/json'); const content = JSON.parse(response.contents[0].text); expect(content.table).toBe('users'); expect(content.schema).toBe('public'); expect(content.indexes).toHaveLength(2); }); it('should only support db:// patterns (no backward compatibility)', async () => { // This test ensures that old patterns are completely removed const mockServer = server as MockTblsServer; const readHandler = mockServer.server._requestHandlers.get('resources/read'); // Test that old URIs are treated as unknown patterns try { await readHandler({ method: 'resources/read', params: { uri: 'schema://list' }, }); fail('Expected error for unknown pattern'); } catch (error: Error & { data?: Record<string, unknown> }) { expect(error.message).toContain('Unknown resource URI'); expect(error.data?.suggestions).toBeDefined(); } // Test that db:// URIs work correctly const response = await readHandler({ method: 'resources/read', params: { uri: 'db://schemas' }, }); expect(response.contents).toBeDefined(); }); }); describe('Performance and Caching with URI Patterns', () => { it('should cache resources with db:// URI patterns efficiently', async () => { const cache = new ResourceCache({ maxItems: 1000, ttlMs: 300000, }); const registry = new LazyResourceRegistry(mockConfig.schemaSource, cache); // First access should hit the source const firstAccess = await registry.discoverResource( 'db://schemas/public/tables' ); expect(firstAccess.isOk()).toBe(true); // Second access should hit the cache const secondAccess = await registry.discoverResource( 'db://schemas/public/tables' ); expect(secondAccess.isOk()).toBe(true); // Both calls should return the same successful result expect(firstAccess.isOk()).toBe(secondAccess.isOk()); // Verify cache is properly configured and ready to use const stats = cache.getStats(); expect(stats).toHaveProperty('hits'); expect(stats).toHaveProperty('misses'); }); it('should support hierarchical cache invalidation for db:// patterns', async () => { const cache = new ResourceCache({ maxItems: 1000, ttlMs: 300000, }); // This test demonstrates the conceptual caching behavior expected for hierarchical URI patterns // In the actual implementation, cache invalidation would work at the file/schema level const registry = new LazyResourceRegistry(mockConfig.schemaSource, cache); // Access hierarchical resources const result1 = await registry.discoverResource( 'db://schemas/public/tables' ); const result2 = await registry.discoverResource( 'db://schemas/public/tables/users' ); // Both should succeed expect(result1.isOk()).toBe(true); expect(result2.isOk()).toBe(true); // Cache should have proper invalidation method available expect(cache.invalidateFile).toBeDefined(); expect(typeof cache.invalidateFile).toBe('function'); // Test that invalidation can be called without error expect(() => cache.invalidateFile(mockConfig.schemaSource)).not.toThrow(); }); }); });

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/yhosok/tbls-mcp-server'

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