Skip to main content
Glama

1MCP Server

presetManager.test.ts30.6 kB
import { promises as fs } from 'fs'; import os from 'os'; import { McpConfigManager } from '@src/config/mcpConfigManager.js'; import { PresetManager } from '@src/domains/preset/manager/presetManager.js'; import { TagQueryEvaluator } from '@src/domains/preset/parsers/tagQueryEvaluator.js'; import { TagQueryParser } from '@src/domains/preset/parsers/tagQueryParser.js'; import logger from '@src/logger/logger.js'; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; // Mock dependencies vi.mock('fs', () => ({ promises: { readFile: vi.fn(), writeFile: vi.fn(), mkdir: vi.fn(), }, watch: vi.fn(), })); vi.mock('os', () => ({ default: { homedir: vi.fn(), }, homedir: vi.fn(), })); vi.mock('@src/config/mcpConfigManager.js'); vi.mock('../parsing/tagQueryParser.js'); vi.mock('../parsing/tagQueryEvaluator.js'); vi.mock('@src/logger/logger.js'); const mockFs = fs as any; const mockHomedir = os.homedir as Mock; const mockMcpConfig = McpConfigManager as any; const mockTagQueryParser = TagQueryParser as any; const mockTagQueryEvaluator = TagQueryEvaluator as any; describe('PresetManager', () => { let presetManager: PresetManager; let mockConfigManager: any; beforeEach(() => { // Reset all mocks vi.clearAllMocks(); // Mock homedir mockHomedir.mockReturnValue('/mock/home'); // Mock MCP config manager mockConfigManager = { getTransportConfig: vi.fn().mockReturnValue({ server1: { tags: ['web', 'api'] }, server2: { tags: ['database', 'sql'] }, server3: { tags: ['web', 'frontend'] }, }), }; mockMcpConfig.getInstance = vi.fn().mockReturnValue(mockConfigManager); // Mock TagQueryParser mockTagQueryParser.parseSimple = vi.fn().mockImplementation((tags: string) => tags .split(',') .map((t) => t.trim()) .filter((t) => t.length > 0), ); mockTagQueryParser.parseAdvanced = vi.fn().mockReturnValue({ type: 'or', children: [ { type: 'tag', value: 'web' }, { type: 'tag', value: 'api' }, ], }); mockTagQueryParser.evaluate = vi.fn().mockReturnValue(true); // Mock TagQueryEvaluator mockTagQueryEvaluator.validateQuery = vi.fn().mockReturnValue({ isValid: true, errors: [], }); mockTagQueryEvaluator.queryToString = vi.fn().mockReturnValue(''); mockTagQueryEvaluator.evaluate = vi.fn().mockReturnValue(true); // Mock fs operations mockFs.mkdir = vi.fn().mockResolvedValue(undefined); mockFs.readFile = vi.fn(); mockFs.writeFile = vi.fn().mockResolvedValue(undefined); // Get fresh instance for each test (PresetManager as any).instance = null; presetManager = PresetManager.getInstance(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('getInstance', () => { it('should return singleton instance', () => { const instance1 = PresetManager.getInstance(); const instance2 = PresetManager.getInstance(); expect(instance1).toBe(instance2); }); }); describe('initialize', () => { it('should initialize successfully with existing preset file', async () => { const mockPresetData = { version: '1.0.0', presets: { dev: { name: 'dev', description: 'Development preset', strategy: 'or' as const, servers: ['server1', 'server2'], tagExpression: 'web,api,database', created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); expect(mockFs.mkdir).toHaveBeenCalledWith('/mock/home/.config/1mcp', { recursive: true }); expect(mockFs.readFile).toHaveBeenCalledWith('/mock/home/.config/1mcp/presets.json', 'utf-8'); expect(logger.info).toHaveBeenCalledWith('PresetManager initialized successfully', { presetsLoaded: 1, configPath: '/mock/home/.config/1mcp/presets.json', }); }); it('should create empty preset file if none exists', async () => { const enoentError = new Error('File not found') as any; enoentError.code = 'ENOENT'; mockFs.readFile.mockRejectedValue(enoentError); await presetManager.initialize(); expect(mockFs.writeFile).toHaveBeenCalledWith( '/mock/home/.config/1mcp/presets.json', JSON.stringify({ version: '1.0.0', presets: {} }, null, 2), 'utf-8', ); }); it('should throw error if preset file is corrupted', async () => { mockFs.readFile.mockResolvedValue('invalid json'); await expect(presetManager.initialize()).rejects.toThrow('Failed to load presets'); }); }); describe('savePreset', () => { beforeEach(async () => { mockFs.readFile.mockResolvedValue('{"version":"1.0.0","presets":{}}'); await presetManager.initialize(); }); it('should save valid preset', async () => { const config = { description: 'Test preset', strategy: 'or' as const, tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, }; await presetManager.savePreset('test', config); expect(mockFs.writeFile).toHaveBeenCalled(); const writeCall = mockFs.writeFile.mock.calls.find( (call: any) => call[0] === '/mock/home/.config/1mcp/presets.json', ); expect(writeCall).toBeDefined(); const savedData = JSON.parse(writeCall[1]); expect(savedData.presets.test).toMatchObject({ name: 'test', description: 'Test preset', strategy: 'or', tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, }); expect(savedData.presets.test.created).toBeDefined(); expect(savedData.presets.test.lastModified).toBeDefined(); }); it('should validate preset before saving', async () => { const invalidConfig = { description: 'Invalid preset', strategy: 'invalid' as any, tagQuery: {}, }; await expect(presetManager.savePreset('invalid', invalidConfig)).rejects.toThrow('Invalid preset'); }); it('should update existing preset', async () => { // Mock Date constructor to control timestamps const realDate = Date; let callCount = 0; const mockDates = [ '2025-01-01T10:00:00.000Z', // First save timestamp '2025-01-01T10:01:00.000Z', // Second save timestamp (1 minute later) ]; // @ts-ignore - Mock Date constructor global.Date = class extends realDate { constructor(...args: any[]) { if (args.length === 0) { super(mockDates[Math.min(callCount++, mockDates.length - 1)]); } else { super(...(args as [string | number | Date])); } } static now() { return realDate.now(); } toISOString() { return mockDates[Math.min(callCount - 1, mockDates.length - 1)]; } }; const config1 = { description: 'First version', strategy: 'or' as const, servers: ['server1'], tagExpression: 'web', tagQuery: { tag: 'web' }, }; await presetManager.savePreset('test', config1); // Update with new config (will use second timestamp) const config2 = { description: 'Updated version', strategy: 'and' as const, servers: ['server1', 'server2'], tagExpression: 'web,api', tagQuery: { $and: [{ tag: 'web' }, { tag: 'api' }] }, }; await presetManager.savePreset('test', config2); // Get the latest (most recent) writeFile call for the preset file const writeCalls = mockFs.writeFile.mock.calls.filter( (call: any) => call[0] === '/mock/home/.config/1mcp/presets.json', ); const lastWriteCall = writeCalls[writeCalls.length - 1]; const savedData = JSON.parse(lastWriteCall[1]); expect(savedData.presets.test.description).toBe('Updated version'); expect(savedData.presets.test.strategy).toBe('and'); expect(savedData.presets.test.created).toBe(mockDates[0]); expect(savedData.presets.test.lastModified).toBe(mockDates[1]); expect(savedData.presets.test.lastModified).not.toBe(savedData.presets.test.created); // Restore real Date global.Date = realDate; }); }); describe('getPreset', () => { beforeEach(async () => { const mockPresetData = { version: '1.0.0', presets: { dev: { name: 'dev', description: 'Development preset', strategy: 'or' as const, servers: ['server1'], tagExpression: 'web,api', created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); }); it('should return existing preset', () => { const preset = presetManager.getPreset('dev'); expect(preset).toMatchObject({ name: 'dev', description: 'Development preset', strategy: 'or', servers: ['server1'], tagExpression: 'web,api', }); }); it('should return null for non-existent preset', () => { const preset = presetManager.getPreset('nonexistent'); expect(preset).toBeNull(); }); }); describe('deletePreset', () => { beforeEach(async () => { const mockPresetData = { version: '1.0.0', presets: { dev: { name: 'dev', strategy: 'or' as const, tagQuery: { tag: 'web' }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, prod: { name: 'prod', strategy: 'and' as const, tagQuery: { tag: 'database' }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); }); it('should delete existing preset', async () => { const result = await presetManager.deletePreset('dev'); expect(result).toBe(true); const writeCall = mockFs.writeFile.mock.calls.find( (call: any) => call[0] === '/mock/home/.config/1mcp/presets.json', ); const savedData = JSON.parse(writeCall[1]); expect(savedData.presets.dev).toBeUndefined(); expect(savedData.presets.prod).toBeDefined(); }); it('should return false for non-existent preset', async () => { const result = await presetManager.deletePreset('nonexistent'); expect(result).toBe(false); }); }); describe('validatePreset', () => { beforeEach(async () => { mockFs.readFile.mockResolvedValue('{"version":"1.0.0","presets":{}}'); await presetManager.initialize(); }); it('should validate correct preset', async () => { const config = { description: 'Valid preset', strategy: 'or' as const, servers: ['server1', 'server2'], tagExpression: 'web,api', tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, }; const result = await presetManager.validatePreset('test', config); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should reject invalid preset name', async () => { const config = { strategy: 'or' as const, servers: ['server1'], tagExpression: 'web', tagQuery: { tag: 'web' }, }; const result = await presetManager.validatePreset('invalid name!', config); expect(result.isValid).toBe(false); expect(result.errors).toContain('Preset name can only contain letters, numbers, hyphens, and underscores'); }); it('should reject invalid strategy', async () => { const config = { strategy: 'invalid' as any, servers: ['server1'], tagExpression: 'web', tagQuery: { tag: 'web' }, }; const result = await presetManager.validatePreset('test', config); expect(result.isValid).toBe(false); expect(result.errors).toContain('Strategy must be one of: or, and, advanced'); }); it('should validate tag query structure', async () => { mockTagQueryEvaluator.validateQuery.mockReturnValue({ isValid: false, errors: ['Invalid query structure'], }); const config = { strategy: 'or' as const, tagQuery: { invalid: 'structure' }, }; const result = await presetManager.validatePreset('test', config); expect(result.isValid).toBe(false); expect(result.errors).toContain('Tag query: Invalid query structure'); }); it('should validate empty tag queries with warning', async () => { mockTagQueryEvaluator.queryToString.mockReturnValue(''); const config = { strategy: 'advanced' as const, tagQuery: {}, }; const result = await presetManager.validatePreset('test', config); expect(result.isValid).toBe(true); expect(result.warnings).toContain('Tag query produces no meaningful filter'); }); }); describe('testPreset', () => { beforeEach(async () => { const mockPresetData = { version: '1.0.0', presets: { 'web-preset': { name: 'web-preset', strategy: 'or' as const, tagQuery: { $or: [{ tag: 'web' }, { tag: 'frontend' }] }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); // Mock evaluate to return true for servers with web or frontend tags mockTagQueryEvaluator.evaluate.mockImplementation((_query: any, serverTags: string[]) => { return serverTags.includes('web') || serverTags.includes('frontend'); }); }); it('should test preset against available servers', async () => { const result = await presetManager.testPreset('web-preset'); expect(result.servers).toEqual(['server1', 'server3']); expect(result.tags).toEqual(['api', 'database', 'frontend', 'sql', 'web']); }); it('should throw error for non-existent preset', async () => { await expect(presetManager.testPreset('nonexistent')).rejects.toThrow("Preset 'nonexistent' not found"); }); }); describe('resolvePresetToExpression', () => { beforeEach(async () => { const mockPresetData = { version: '1.0.0', presets: { dev: { name: 'dev', strategy: 'or' as const, tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); }); it('should resolve existing preset to expression', () => { mockTagQueryEvaluator.queryToString.mockReturnValue('web OR api'); const expression = presetManager.resolvePresetToExpression('dev'); expect(expression).toBe('web OR api'); }); it('should return null for non-existent preset', () => { const expression = presetManager.resolvePresetToExpression('nonexistent'); expect(expression).toBeNull(); }); }); describe('notification callbacks', () => { beforeEach(async () => { mockFs.readFile.mockResolvedValue('{"version":"1.0.0","presets":{}}'); await presetManager.initialize(); }); it('should register and call notification callbacks', async () => { const callback1 = vi.fn().mockResolvedValue(undefined); const callback2 = vi.fn().mockResolvedValue(undefined); presetManager.onPresetChange(callback1); presetManager.onPresetChange(callback2); const config = { strategy: 'or' as const, servers: ['server1'], tagExpression: 'web', tagQuery: { tag: 'web' }, }; await presetManager.savePreset('test', config); expect(callback1).toHaveBeenCalledWith('test'); expect(callback2).toHaveBeenCalledWith('test'); }); it('should remove notification callbacks', async () => { const callback = vi.fn().mockResolvedValue(undefined); presetManager.onPresetChange(callback); presetManager.offPresetChange(callback); const config = { strategy: 'or' as const, servers: ['server1'], tagExpression: 'web', tagQuery: { tag: 'web' }, }; await presetManager.savePreset('test', config); expect(callback).not.toHaveBeenCalled(); }); it('should handle callback errors gracefully', async () => { const callback = vi.fn().mockRejectedValue(new Error('Callback error')); presetManager.onPresetChange(callback); const config = { strategy: 'or' as const, servers: ['server1'], tagExpression: 'web', tagQuery: { tag: 'web' }, }; // Should not throw despite callback error await expect(presetManager.savePreset('test', config)).resolves.not.toThrow(); expect(callback).toHaveBeenCalledWith('test'); }); }); describe('error handling improvements', () => { beforeEach(async () => { const mockPresetData = { version: '1.0.0', presets: { 'valid-preset': { name: 'valid-preset', strategy: 'or' as const, tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, 'error-preset': { name: 'error-preset', strategy: 'advanced' as const, tagQuery: { $advanced: 'invalid_expression' }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, 'empty-query-preset': { name: 'empty-query-preset', strategy: 'or' as const, tagQuery: {}, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); }); describe('testPreset error handling', () => { it('should handle TagQueryEvaluator evaluation errors gracefully', async () => { // Mock TagQueryEvaluator.evaluate to throw an error mockTagQueryEvaluator.evaluate.mockImplementation((_query: any, _serverTags: string[]) => { throw new Error('Invalid query expression'); }); // Mock TagQueryParser for legacy advanced query conversion mockTagQueryParser.advancedQueryToJSON = vi.fn().mockImplementation(() => { throw new Error('Invalid advanced query'); }); const result = await presetManager.testPreset('error-preset'); // Should return empty servers list when evaluation fails expect(result.servers).toEqual([]); expect(result.tags).toEqual(['api', 'database', 'frontend', 'sql', 'web']); // Should log warning about failed evaluation expect(logger.warn).toHaveBeenCalledWith('Failed to evaluate preset against server', { preset: 'error-preset', server: expect.any(String), error: 'Invalid advanced query', tagQuery: { $advanced: 'invalid_expression' }, serverTags: expect.any(Array), }); }); it('should handle individual server evaluation failures', async () => { let callCount = 0; mockTagQueryEvaluator.evaluate.mockImplementation((_query: any, serverTags: string[]) => { callCount++; if (callCount === 2) { // Fail on second server evaluation throw new Error('Evaluation error on server2'); } return serverTags.includes('web'); }); const result = await presetManager.testPreset('valid-preset'); // Should still return results for servers that evaluated successfully expect(result.servers).toEqual(['server1', 'server3']); // Should log warning for failed server evaluation expect(logger.warn).toHaveBeenCalledWith('Failed to evaluate preset against server', { preset: 'valid-preset', server: 'server2', error: 'Evaluation error on server2', tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, serverTags: ['database', 'sql'], }); }); }); describe('resolvePresetToExpression error handling', () => { it('should handle non-existent preset gracefully', () => { const result = presetManager.resolvePresetToExpression('non-existent'); expect(result).toBeNull(); expect(logger.warn).toHaveBeenCalledWith('Attempted to resolve non-existent preset', { name: 'non-existent', }); }); it('should handle TagQueryEvaluator.queryToString errors', () => { mockTagQueryEvaluator.queryToString.mockImplementation(() => { throw new Error('Query to string conversion failed'); }); const result = presetManager.resolvePresetToExpression('valid-preset'); expect(result).toBeNull(); expect(logger.error).toHaveBeenCalledWith('Failed to resolve preset to expression', { name: 'valid-preset', error: 'Query to string conversion failed', tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, }); }); it('should handle empty query expressions', () => { mockTagQueryEvaluator.queryToString.mockReturnValue(''); const result = presetManager.resolvePresetToExpression('empty-query-preset'); expect(result).toBeNull(); expect(logger.warn).toHaveBeenCalledWith('Preset resolved to empty expression', { name: 'empty-query-preset', tagQuery: {}, }); }); it('should handle whitespace-only query expressions', () => { mockTagQueryEvaluator.queryToString.mockReturnValue(' \t\n '); const result = presetManager.resolvePresetToExpression('valid-preset'); expect(result).toBeNull(); expect(logger.warn).toHaveBeenCalledWith('Preset resolved to empty expression', { name: 'valid-preset', tagQuery: { $or: [{ tag: 'web' }, { tag: 'api' }] }, }); }); }); describe('change detector error handling', () => { it('should handle change detector updateServerList errors', async () => { // Create a mock change detector with failing updateServerList const mockChangeDetector = { updateServerList: vi.fn().mockImplementation((presetName: string, _servers: string[]) => { if (presetName === 'valid-preset') { throw new Error('Change detector update failed'); } }), getTrackedPresets: vi.fn().mockReturnValue(['valid-preset']), removePreset: vi.fn(), clear: vi.fn(), }; // Replace the change detector in the preset manager instance (presetManager as any).changeDetector = mockChangeDetector; // Mock testPreset to succeed mockTagQueryEvaluator.evaluate.mockReturnValue(true); // Trigger a file change to test error handling await (presetManager as any).reloadAndNotifyChanges(); expect(logger.error).toHaveBeenCalledWith('Failed to update change detector for preset', { presetName: 'valid-preset', error: 'Change detector update failed', }); }); }); }); describe('memory leak prevention', () => { let cleanupManager: PresetManager; beforeEach(async () => { mockFs.readFile.mockResolvedValue('{"version":"1.0.0","presets":{}}'); (PresetManager as any).instance = null; cleanupManager = PresetManager.getInstance(); await cleanupManager.initialize(); }); it('should clean up all resources during cleanup', async () => { const callback1 = vi.fn().mockResolvedValue(undefined); const callback2 = vi.fn().mockResolvedValue(undefined); // Add notification callbacks cleanupManager.onPresetChange(callback1); cleanupManager.onPresetChange(callback2); // Mock the change detector with clear method const mockChangeDetector = { clear: vi.fn(), updateServerList: vi.fn(), getTrackedPresets: vi.fn().mockReturnValue([]), removePreset: vi.fn(), }; (cleanupManager as any).changeDetector = mockChangeDetector; // Mock watcher const mockWatcher = { close: vi.fn(), }; (cleanupManager as any).watcher = mockWatcher; // Set a timeout to simulate pending operation (cleanupManager as any).reloadTimeout = setTimeout(() => {}, 1000); await cleanupManager.cleanup(); // Verify all resources were cleaned up expect(mockWatcher.close).toHaveBeenCalled(); expect(mockChangeDetector.clear).toHaveBeenCalled(); expect((cleanupManager as any).watcher).toBeNull(); expect((cleanupManager as any).reloadTimeout).toBeNull(); expect((cleanupManager as any).notificationCallbacks.size).toBe(0); expect((cleanupManager as any).presets.size).toBe(0); expect(logger.debug).toHaveBeenCalledWith('PresetManager cleanup completed successfully'); }); it('should handle cleanup errors gracefully', async () => { const mockChangeDetector = { clear: vi.fn().mockImplementation(() => { throw new Error('Cleanup failed'); }), updateServerList: vi.fn(), getTrackedPresets: vi.fn().mockReturnValue([]), removePreset: vi.fn(), }; (cleanupManager as any).changeDetector = mockChangeDetector; // Should not throw despite internal cleanup errors await expect(cleanupManager.cleanup()).resolves.not.toThrow(); expect(logger.error).toHaveBeenCalledWith('Error during PresetManager cleanup', { error: expect.any(Error), }); }); it('should handle change detector without clear method', async () => { const mockChangeDetector = { updateServerList: vi.fn(), getTrackedPresets: vi.fn().mockReturnValue([]), removePreset: vi.fn(), // No clear method }; (cleanupManager as any).changeDetector = mockChangeDetector; // Should not throw when clear method doesn't exist await expect(cleanupManager.cleanup()).resolves.not.toThrow(); expect(logger.debug).toHaveBeenCalledWith('PresetManager cleanup completed successfully'); }); }); describe('resetInstance for testing', () => { it('should reset singleton instance and cleanup resources', async () => { // Setup mock data for initialization mockFs.readFile.mockResolvedValue( JSON.stringify({ version: '1.0.0', presets: {}, }), ); // Initialize a preset manager const instance = PresetManager.getInstance(); await instance.initialize(); // Add some state const callback = vi.fn(); instance.onPresetChange(callback); // Spy on cleanup method const cleanupSpy = vi.spyOn(instance, 'cleanup').mockResolvedValue(); // Reset instance PresetManager.resetInstance(); expect(cleanupSpy).toHaveBeenCalled(); // Verify new instance is different const newInstance = PresetManager.getInstance(); expect(newInstance).not.toBe(instance); }); it('should handle cleanup failure during reset gracefully', async () => { // Setup mock data for initialization mockFs.readFile.mockResolvedValue( JSON.stringify({ version: '1.0.0', presets: {}, }), ); const instance = PresetManager.getInstance(); await instance.initialize(); // Mock cleanup to fail vi.spyOn(instance, 'cleanup').mockRejectedValue(new Error('Cleanup failed')); // Mock console.warn const mockWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); // Should not throw despite cleanup failure expect(() => PresetManager.resetInstance()).not.toThrow(); // Wait for the async cleanup promise to reject and console.warn to be called await new Promise((resolve) => setTimeout(resolve, 0)); // Should log warning about cleanup failure expect(mockWarn).toHaveBeenCalledWith('Failed to cleanup PresetManager during reset:', expect.any(Error)); mockWarn.mockRestore(); }); }); describe('utility methods', () => { beforeEach(async () => { const mockPresetData = { version: '1.0.0', presets: { dev: { name: 'dev', strategy: 'or' as const, tagQuery: { tag: 'web' }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, prod: { name: 'prod', strategy: 'and' as const, tagQuery: { tag: 'database' }, created: '2025-01-01T00:00:00Z', lastModified: '2025-01-01T00:00:00Z', }, }, }; mockFs.readFile.mockResolvedValue(JSON.stringify(mockPresetData)); await presetManager.initialize(); }); it('should check if preset exists', () => { expect(presetManager.hasPreset('dev')).toBe(true); expect(presetManager.hasPreset('nonexistent')).toBe(false); }); it('should get preset names', () => { const names = presetManager.getPresetNames(); expect(names).toEqual(['dev', 'prod']); }); it('should get preset list', () => { const list = presetManager.getPresetList(); expect(list).toHaveLength(2); expect(list[0]).toMatchObject({ name: 'dev', strategy: 'or', tagQuery: { tag: 'web' }, }); }); it('should get configuration path', () => { const configPath = presetManager.getConfigPath(); expect(configPath).toBe('/mock/home/.config/1mcp/presets.json'); }); }); });

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/1mcp-app/agent'

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