Skip to main content
Glama
project-discovery.test.tsโ€ข14.6 kB
import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; /** * Unit tests for project discovery functionality * * Tests the following functionality: * - get_available_projects tool * - Enhanced project resolution with suggestions * - Dynamic project discovery and fallback logic */ describe('Project Discovery Unit Tests', () => { describe('get_available_projects Tool', () => { it('should validate tool parameters schema', () => { const validParams = { starred: true, publiclyAccessible: false, format: 'formatted', includePaginationInfo: true }; // Optional boolean parameters assert.ok(typeof validParams.starred === 'boolean' || validParams.starred === undefined, 'starred should be boolean or undefined'); assert.ok(typeof validParams.publiclyAccessible === 'boolean' || validParams.publiclyAccessible === undefined, 'publiclyAccessible should be boolean or undefined'); // Format validation assert.ok(['raw', 'formatted'].includes(validParams.format), 'format should be raw or formatted'); // Pagination info flag assert.ok(typeof validParams.includePaginationInfo === 'boolean', 'includePaginationInfo should be boolean'); }); it('should validate project response structure', () => { const mockProjectResponse = { items: [ { id: 7, name: 'Android', key: 'MCP', logoUrl: '/files/18b4939f-37e9-0576-8c73-478c7095192e', createdAt: '2023-09-11T17:43:13.337691Z', leadId: 26, starred: true, publiclyAccessible: true, deleted: false }, { id: 8, name: 'iOS', key: 'MCP', logoUrl: '/files/18b49392-14e6-bd47-bf9d-195db8161702', createdAt: '2023-09-11T17:43:52.711029Z', leadId: null, starred: true, publiclyAccessible: true, deleted: false } ] }; assert.ok(Array.isArray(mockProjectResponse.items), 'should have items array'); mockProjectResponse.items.forEach(project => { assert.ok(typeof project.id === 'number', 'project id should be number'); assert.ok(typeof project.name === 'string', 'project name should be string'); assert.ok(typeof project.key === 'string', 'project key should be string'); assert.ok(typeof project.starred === 'boolean', 'starred should be boolean'); assert.ok(typeof project.publiclyAccessible === 'boolean', 'publiclyAccessible should be boolean'); assert.ok(typeof project.deleted === 'boolean', 'deleted should be boolean'); assert.ok(project.leadId === null || typeof project.leadId === 'number', 'leadId should be number or null'); }); }); it('should validate pagination info response structure', () => { const mockPaginationResponse = { data: { limit: 50, currentTotal: 12 } }; assert.ok(mockPaginationResponse.data, 'should have data object'); assert.ok(typeof mockPaginationResponse.data.limit === 'number', 'limit should be number'); assert.ok(typeof mockPaginationResponse.data.currentTotal === 'number', 'currentTotal should be number'); assert.ok(mockPaginationResponse.data.limit > 0, 'limit should be positive'); assert.ok(mockPaginationResponse.data.currentTotal >= 0, 'currentTotal should be non-negative'); }); it('should validate client-side filtering logic', () => { const mockProjects = [ { id: 1, name: 'Project 1', key: 'PROJ1', starred: true, publiclyAccessible: true, deleted: false }, { id: 2, name: 'Project 2', key: 'PROJ2', starred: false, publiclyAccessible: true, deleted: false }, { id: 3, name: 'Project 3', key: 'PROJ3', starred: true, publiclyAccessible: false, deleted: false }, { id: 4, name: 'Deleted Project', key: 'DEL', starred: false, publiclyAccessible: true, deleted: true } ]; // Test deleted filter (should always exclude deleted) const nonDeletedProjects = mockProjects.filter(p => !p.deleted); assert.equal(nonDeletedProjects.length, 3, 'should exclude deleted projects'); // Test starred filter const starredProjects = nonDeletedProjects.filter(p => p.starred === true); assert.equal(starredProjects.length, 2, 'should filter by starred status'); // Test public accessibility filter const publicProjects = nonDeletedProjects.filter(p => p.publiclyAccessible === true); assert.equal(publicProjects.length, 2, 'should filter by public accessibility'); // Test combined filters const starredPublicProjects = nonDeletedProjects.filter(p => p.starred === true && p.publiclyAccessible === true); assert.equal(starredPublicProjects.length, 1, 'should apply multiple filters correctly'); }); }); describe('Enhanced Project Resolution', () => { it('should validate project resolution logic', () => { const mockAvailableProjects = [ { id: 7, name: 'MCP Project', key: 'MCP' }, { id: 8, name: 'MCP iOS', key: 'MCPIOS' }, { id: 9, name: 'MCP Web', key: 'MCPWEB' }, { id: 3, name: 'MCP Core', key: 'MCPCORE' } ]; const hardcodedAliases = { web: 'MCPWEB', android: 'MCP', ios: 'MCPIOS', api: 'MCPAPI' }; // Test exact key match const exactMatch = mockAvailableProjects.find(p => p.key === 'MCP'); assert.ok(exactMatch, 'should find exact key match'); assert.equal(exactMatch.id, 7, 'should return correct project ID'); // Test case-insensitive match const caseInsensitiveMatch = mockAvailableProjects.find(p => p.key.toLowerCase() === 'mcp'.toLowerCase() ); assert.ok(caseInsensitiveMatch, 'should find case-insensitive match'); // Test name-based match const nameMatch = mockAvailableProjects.find(p => p.name.toLowerCase() === 'MCP Project'.toLowerCase() ); assert.ok(nameMatch, 'should find name-based match'); // Test hardcoded alias resolution const aliasKey = hardcodedAliases['android']; const aliasMatch = mockAvailableProjects.find(p => p.key === aliasKey); assert.ok(aliasMatch, 'should resolve hardcoded aliases'); assert.equal(aliasMatch.key, 'MCP', 'should resolve to correct key'); }); it('should generate helpful suggestions for invalid projects', () => { const mockAvailableProjects = [ { id: 7, name: 'MCP Project', key: 'MCP' }, { id: 8, name: 'MCP iOS', key: 'MCPIOS' }, { id: 9, name: 'MCP Web', key: 'MCPWEB' }, { id: 3, name: 'MCP Core', key: 'MCPCORE' } ]; const generateSuggestions = (input: string, projects: any[]) => { return projects .filter(p => p.key.toLowerCase().includes(input.toLowerCase()) || p.name.toLowerCase().includes(input.toLowerCase()) ) .slice(0, 5) .map(p => `"${p.key}" (${p.name})`); }; // Test partial match suggestions const suggestions1 = generateSuggestions('MCP', mockAvailableProjects); assert.ok(suggestions1.length > 0, 'should generate suggestions for partial matches'); assert.ok(suggestions1.some(s => s.includes('MCP')), 'should include relevant suggestions'); // Test typo suggestions const suggestions2 = generateSuggestions('PROJECT', mockAvailableProjects); assert.ok(suggestions2.length > 0, 'should generate suggestions for typos'); assert.ok(suggestions2.some(s => s.includes('MCP Project')), 'should suggest based on name match'); // Test no match case const suggestions3 = generateSuggestions('INVALID', mockAvailableProjects); assert.equal(suggestions3.length, 0, 'should return empty suggestions for no matches'); }); it('should validate suggestion message formatting', () => { const mockProjects = [ { id: 7, name: 'MCP Project', key: 'MCP' }, { id: 8, name: 'MCP iOS', key: 'MCPIOS' } ]; const formatSuggestionMessage = (input: string, suggestions: string[], allProjects: string[]) => { const suggestionText = suggestions.length > 0 ? `\n\n๐Ÿ’ก Did you mean: ${suggestions.join(', ')}?\n\n๐Ÿ“‹ Available projects: ${allProjects.join(', ')}` : `\n\n๐Ÿ“‹ Available projects: ${allProjects.join(', ')}`; return `Project "${input}" not found.${suggestionText}`; }; const suggestions = ['"MCP" (MCP Project)']; const allProjects = ['"MCP" (MCP Project)', '"MCPIOS" (MCP iOS)']; const message = formatSuggestionMessage('INVALID', suggestions, allProjects); assert.ok(message.includes('Project "INVALID" not found'), 'should include original input'); assert.ok(message.includes('๐Ÿ’ก Did you mean'), 'should include suggestion prompt'); assert.ok(message.includes('๐Ÿ“‹ Available projects'), 'should include full project list'); assert.ok(message.includes('MCP'), 'should include suggested projects'); }); }); describe('Formatted Output Structure', () => { it('should validate formatted project response structure', () => { const mockFormattedResponse = { summary: { totalProjects: 12, starred: true, publiclyAccessible: undefined, systemLimit: 50, systemTotal: 12 }, projects: [ { name: 'MCP Project', key: 'MCP', id: 7, starred: true, publiclyAccessible: true, logoUrl: '/files/18b4939f-37e9-0576-8c73-478c7095192e', createdAt: '2023-09-11T17:43:13.337691Z', leadId: 26 } ], keyToIdMapping: { 'MCP': 7, 'MCPIOS': 8, 'MCPWEB': 9 }, usage: { note: 'Use \'key\' field for project parameter in other tools', examples: [ 'project: "MCP" (for MCP Project)', 'project: "MCPIOS" (for MCP iOS)', 'project: "MCPWEB" (for MCP Web)' ] } }; // Validate summary structure assert.ok(mockFormattedResponse.summary, 'should have summary object'); assert.ok(typeof mockFormattedResponse.summary.totalProjects === 'number', 'totalProjects should be number'); // Validate projects array assert.ok(Array.isArray(mockFormattedResponse.projects), 'projects should be array'); const project = mockFormattedResponse.projects[0]; assert.ok(typeof project.name === 'string', 'project name should be string'); assert.ok(typeof project.key === 'string', 'project key should be string'); assert.ok(typeof project.id === 'number', 'project id should be number'); // Validate key-to-ID mapping assert.ok(typeof mockFormattedResponse.keyToIdMapping === 'object', 'keyToIdMapping should be object'); Object.entries(mockFormattedResponse.keyToIdMapping).forEach(([key, id]) => { assert.ok(typeof key === 'string', 'mapping key should be string'); assert.ok(typeof id === 'number', 'mapping id should be number'); }); // Validate usage information assert.ok(mockFormattedResponse.usage, 'should have usage object'); assert.ok(typeof mockFormattedResponse.usage.note === 'string', 'usage note should be string'); assert.ok(Array.isArray(mockFormattedResponse.usage.examples), 'usage examples should be array'); }); }); describe('Backward Compatibility', () => { it('should maintain hardcoded alias support', () => { const hardcodedAliases = { web: 'MCPWEB', android: 'MCP', ios: 'MCPIOS', api: 'MCPAPI' }; // Test that hardcoded aliases are still valid Object.entries(hardcodedAliases).forEach(([alias, expectedKey]) => { assert.ok(typeof alias === 'string', 'alias should be string'); assert.ok(typeof expectedKey === 'string', 'expected key should be string'); assert.ok(expectedKey.length > 0, 'expected key should not be empty'); }); // Test alias resolution logic const resolveProjectKey = (input: string, aliases: Record<string, string>) => { return aliases[input] || input; }; assert.equal(resolveProjectKey('android', hardcodedAliases), 'MCP', 'should resolve android alias'); assert.equal(resolveProjectKey('MCP', hardcodedAliases), 'MCP', 'should pass through direct keys'); assert.equal(resolveProjectKey('CUSTOM', hardcodedAliases), 'CUSTOM', 'should pass through unknown keys'); }); it('should validate fallback mechanism', () => { const mockProjectResolution = async (input: string | number) => { // Simulate the resolution logic if (typeof input === 'number') { return { projectId: input }; } // Try hardcoded aliases first const hardcodedAliases = { android: 'MCP' }; const projectKey = hardcodedAliases[input as keyof typeof hardcodedAliases] || input; // Simulate API call success/failure const knownProjects = ['MCP', 'MCPIOS', 'MCPWEB']; if (knownProjects.includes(projectKey)) { return { projectId: 7 }; // Mock ID } // Fallback to dynamic discovery would happen here throw new Error(`Project "${input}" not found`); }; // Test numeric input (should pass through) assert.doesNotReject(async () => { const result = await mockProjectResolution(7); assert.equal(result.projectId, 7, 'should handle numeric project IDs'); }); // Test hardcoded alias (should resolve) assert.doesNotReject(async () => { const result = await mockProjectResolution('android'); assert.equal(result.projectId, 7, 'should resolve hardcoded aliases'); }); // Test unknown project (should trigger fallback) assert.rejects(async () => { await mockProjectResolution('UNKNOWN'); }, /Project "UNKNOWN" not found/, 'should reject unknown projects'); }); }); });

Latest Blog Posts

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/maksimsarychau/mcp-zebrunner'

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