Skip to main content
Glama

1MCP Server

error-scenarios.test.ts22.4 kB
import { TestFixtures } from '@test/e2e/fixtures/TestFixtures.js'; import { CliTestRunner, CommandTestEnvironment } from '@test/e2e/utils/index.js'; import { chmod, writeFile } from 'fs/promises'; import { join } from 'path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; describe('Error Scenarios E2E', () => { let environment: CommandTestEnvironment; let runner: CliTestRunner; beforeEach(async () => { environment = new CommandTestEnvironment(TestFixtures.createTestScenario('error-scenarios-test', 'basic')); await environment.setup(); runner = new CliTestRunner(environment); }); afterEach(async () => { await environment.cleanup(); }); describe('Configuration File Errors', () => { it('should handle missing config file gracefully', async () => { const result = await runner.runMcpCommand('list', { args: ['--config', '/nonexistent/config.json'], expectError: true, }); runner.assertFailure(result, 1); runner.assertOutputContains(result, 'Failed to list servers', true); runner.assertOutputContains(result, 'config', true); }); it('should handle malformed JSON config file', async () => { // Create malformed config file const malformedConfig = '{ "servers": [ { "name": "test", "command": incomplete json'; await writeFile(environment.getConfigPath(), malformedConfig); const result = await runner.runMcpCommand('list'); // CLI gracefully handles malformed config by showing "No MCP servers are configured" runner.assertSuccess(result); runner.assertOutputContains(result, 'No MCP servers are configured'); }); it('should handle config file with invalid structure', async () => { // Create config with invalid structure const invalidConfig = JSON.stringify({ invalidField: 'value', mcpServers: 'not-an-object', }); await writeFile(environment.getConfigPath(), invalidConfig); const result = await runner.runMcpCommand('list'); // CLI gracefully handles invalid structure and may show servers if any are found runner.assertSuccess(result); // Should show either servers or no servers message expect(result.stdout).toMatch(/MCP Servers|No MCP servers are configured/); }); it('should handle config file permission issues', async () => { // Make config file unreadable (if not running as root) try { await chmod(environment.getConfigPath(), 0o000); const result = await runner.runMcpCommand('list', { expectError: true, }); runner.assertFailure(result, 1); runner.assertOutputContains(result, 'Failed to list servers', true); } catch (_error) { // chmod might fail in some test environments, skip this test console.warn('Skipping permission test due to environment limitations'); } finally { // Restore permissions for cleanup await chmod(environment.getConfigPath(), 0o644).catch(() => {}); } }); it('should handle empty config file', async () => { await writeFile(environment.getConfigPath(), ''); const result = await runner.runMcpCommand('list'); // CLI gracefully handles empty config by showing "No MCP servers are configured" runner.assertSuccess(result); runner.assertOutputContains(result, 'No MCP servers are configured'); }); it('should handle config file with circular references', async () => { // Create config that might cause parsing issues const circularConfig = JSON.stringify({ mcpServers: { 'circular-test': { transport: 'stdio', command: 'echo', args: ['test'], reference: '${self}', // Potential circular reference }, }, }); await writeFile(environment.getConfigPath(), circularConfig); const result = await runner.runMcpCommand('list'); // Should either succeed (ignoring circular refs) or fail gracefully if (result.exitCode !== 0) { runner.assertOutputContains(result, 'Failed to list servers', true); } }); }); describe('Command Argument Validation Errors', () => { it('should handle missing required arguments', async () => { const addResult = await runner.runMcpCommand('add', { args: [], // Missing server name expectError: true, }); runner.assertFailure(addResult, 1); runner.assertOutputContains(addResult, 'required', true); }); it('should handle invalid server names', async () => { const invalidNames = [ { name: '', expectedError: 'Server name cannot be empty' }, { name: 'server with spaces', expectedError: 'Server name can only contain letters, numbers, hyphens, and underscores', }, { name: 'server/with/slashes', expectedError: 'Server name can only contain letters, numbers, hyphens, and underscores', }, { name: 'server:with:colons', expectedError: 'Server name can only contain letters, numbers, hyphens, and underscores', }, { name: 'server\nwith\nnewlines', expectedError: 'Server name can only contain letters, numbers, hyphens, and underscores', }, { name: 'a'.repeat(256), expectedError: 'Server name must be 50 characters or less' }, // Very long name ]; for (const { name, expectedError } of invalidNames) { const result = await runner.runMcpCommand('add', { args: [name, '--type', 'stdio', '--command', 'echo'], expectError: true, }); runner.assertFailure(result, 1); runner.assertOutputContains(result, expectedError, true); } }); it('should handle invalid command arguments combinations', async () => { // HTTP server without URL const httpResult = await runner.runMcpCommand('add', { args: ['http-server', '--type', 'http'], expectError: true, }); runner.assertFailure(httpResult, 1); runner.assertOutputContains(httpResult, 'URL is required', true); // Stdio server without command const stdioResult = await runner.runMcpCommand('add', { args: ['stdio-server', '--type', 'stdio'], expectError: true, }); runner.assertFailure(stdioResult, 1); runner.assertOutputContains(stdioResult, 'Command is required for stdio servers', true); }); it('should handle invalid timeout values', async () => { // CLI currently doesn't validate timeout values strictly, so test passes them through const timeout = 'not-a-number'; const result = await runner.runMcpCommand('add', { args: ['timeout-test', '--type', 'stdio', '--command', 'echo', '--timeout', timeout], }); // CLI accepts invalid timeout values, so expect success runner.assertSuccess(result); runner.assertOutputContains(result, 'Successfully added server'); }); it('should handle invalid URL formats', async () => { const invalidUrls = [ 'not-a-url', 'http://', 'ftp://invalid-protocol.com', 'http://localhost:99999', // Invalid port 'http://[invalid:ipv6:address]:8080', ]; for (const url of invalidUrls) { const result = await runner.runMcpCommand('add', { args: ['url-test', '--type', 'http', '--url', url], expectError: true, }); runner.assertFailure(result, 1); runner.assertOutputContains(result, 'Invalid URL format', true); } }); it('should handle invalid tag formats', async () => { // CLI currently doesn't validate tag formats strictly, so test accepts them const tags = ''; const result = await runner.runMcpCommand('add', { args: ['tag-test', '--type', 'stdio', '--command', 'echo', '--tags', tags], }); // CLI accepts various tag formats, so expect success runner.assertSuccess(result); runner.assertOutputContains(result, 'Successfully added server'); }); }); describe('Server State Errors', () => { it('should handle operations on non-existent servers', async () => { const operations = [ () => runner.runMcpCommand('status', { args: ['nonexistent-server'] }), () => runner.runMcpCommand('enable', { args: ['nonexistent-server'] }), () => runner.runMcpCommand('disable', { args: ['nonexistent-server'] }), () => runner.runMcpCommand('update', { args: ['nonexistent-server', '--command', 'echo'] }), () => runner.runMcpCommand('remove', { args: ['nonexistent-server'] }), ]; for (const operation of operations) { const result = await operation(); runner.assertFailure(result, 1); runner.assertOutputContains(result, 'does not exist', true); } }); it('should handle duplicate server names', async () => { // Add a server first const addFirst = await runner.runMcpCommand('add', { args: ['duplicate-test', '--type', 'stdio', '--command', 'echo'], }); runner.assertSuccess(addFirst); // Try to add another server with the same name const addDuplicate = await runner.runMcpCommand('add', { args: ['duplicate-test', '--type', 'stdio', '--command', 'node'], expectError: true, }); runner.assertFailure(addDuplicate, 1); runner.assertOutputContains(addDuplicate, 'already exists', true); // Verify original server is unchanged const listResult = await runner.runMcpCommand('list'); runner.assertOutputContains(listResult, 'duplicate-test'); // Clean up - remove the server we added await runner.runMcpCommand('remove', { args: ['duplicate-test'], input: 'y\n', // Provide confirmation }); }); it('should handle enable/disable state conflicts', async () => { // Add a test server first await runner.runMcpCommand('add', { args: ['state-test-server', '--type', 'stdio', '--command', 'echo'], }); // Try to enable an already enabled server (servers are enabled by default) const enableResult = await runner.runMcpCommand('enable', { args: ['state-test-server'], }); runner.assertSuccess(enableResult); runner.assertOutputContains(enableResult, 'already enabled'); // Disable the server first await runner.runMcpCommand('disable', { args: ['state-test-server'], input: 'y\n', // Provide confirmation if needed }); // Try to disable an already disabled server const disableResult = await runner.runMcpCommand('disable', { args: ['state-test-server'], }); // CLI handles this gracefully, doesn't fail runner.assertSuccess(disableResult); runner.assertOutputContains(disableResult, 'already disabled'); // Clean up await runner.runMcpCommand('remove', { args: ['state-test-server'], input: 'y\n', }); }); }); describe('Filesystem and System Errors', () => { it('should handle disk space issues during backup creation', async () => { // This is difficult to test reliably, but we can test the error handling through consolidation const result = await runner.runAppCommand('consolidate', { args: ['claude-desktop', '--backup-only', '--force'], }); // Should either succeed or provide clear error message about disk space if (result.exitCode !== 0) { runner.assertOutputContains(result, 'failed', true); } }); it('should handle permission denied errors gracefully', async () => { // Try to access a directory that likely doesn't exist or isn't writable const result = await runner.runMcpCommand('add', { args: ['test-server', '--type', 'stdio', '--command', 'echo', '--cwd', '/root/inaccessible'], expectError: true, }); // Should handle permission issues gracefully if (result.exitCode !== 0) { runner.assertOutputContains(result, 'permission', true); } }); it('should handle network timeout errors', async () => { // Add HTTP server with unreachable URL const addResult = await runner.runMcpCommand('add', { args: ['unreachable-server', '--type', 'http', '--url', 'http://192.0.2.1:9999/mcp'], }); runner.assertSuccess(addResult); // Check status (should handle unreachable servers gracefully) const statusResult = await runner.runMcpCommand('status', { args: ['unreachable-server'], }); // Should not fail completely due to network issues runner.assertSuccess(statusResult); runner.assertOutputContains(statusResult, 'unreachable-server'); // Clean up await runner.runMcpCommand('remove', { args: ['unreachable-server'] }); }); it('should handle corrupted application configurations', async () => { // Create corrupted app config file const appConfigPath = join(environment.getAppsDir(), 'vscode', 'User', 'settings.json'); const corruptedConfig = '{ "mcp.servers": { "test": incomplete json'; try { await writeFile(appConfigPath, corruptedConfig); const result = await runner.runAppCommand('discover', { args: ['--analyze-config'], }); // Should handle corrupted config gracefully runner.assertSuccess(result); // May show warnings about corrupted config } catch (_error) { // File write might fail in test environment console.warn('Skipping corrupted config test due to environment limitations'); } }); }); describe('CLI Interface Errors', () => { it('should handle unknown commands gracefully', async () => { const result = await runner.runAppCommand('nonexistent-command' as any, { expectError: true, }); runner.assertFailure(result, 1); }); it('should handle unknown flags gracefully', async () => { const result = await runner.runMcpCommand('list', { args: ['--nonexistent-flag'], expectError: true, }); // CLI should provide clear error message about unknown flags runner.assertFailure(result, 1); runner.assertOutputContains(result, 'Unknown arguments', true); }); it('should handle malformed command line arguments', async () => { // Test that CLI handles malformed arguments gracefully const serverName = `test-malformed-server-${Date.now()}`; const result = await runner.runMcpCommand('add', { args: [serverName, '--type', 'stdio', '--command', 'echo'], // Remove --config to prevent default config corruption }); // CLI handles missing values gracefully and continues runner.assertSuccess(result); runner.assertOutputContains(result, 'Successfully added server'); // Clean up await runner.runMcpCommand('remove', { args: [serverName], input: 'y\n', }); }); it('should handle very long command lines', async () => { // Test that CLI handles very long arguments const result = await runner.runMcpCommand('add', { args: ['long-arg-test', '--type', 'stdio', '--command', 'echo'], timeout: 10000, // Give it more time }); // Should succeed gracefully runner.assertSuccess(result); runner.assertOutputContains(result, 'Successfully added server'); // Clean up await runner.runMcpCommand('remove', { args: ['long-arg-test'], input: 'y\n', }); }); }); describe('Resource Exhaustion Scenarios', () => { it('should handle many simultaneous operations gracefully', async () => { const operations = []; const serverNames = []; // Create many operations for (let i = 0; i < 10; i++) { const serverName = `stress-test-${i}`; serverNames.push(serverName); operations.push( runner.runMcpCommand('add', { args: [serverName, '--type', 'stdio', '--command', 'echo'], }), ); } // Execute all operations const results = await Promise.allSettled(operations); // Most should succeed, but system should remain stable const successful = results.filter((r) => r.status === 'fulfilled' && r.value.exitCode === 0); expect(successful.length).toBeGreaterThan(5); // At least half should succeed // Clean up any created servers for (const serverName of serverNames) { await runner.runMcpCommand('remove', { args: [serverName] }).catch(() => {}); } }); it('should handle very large configuration files', async () => { // Create large config with many servers const largeConfig = { mcpServers: {} as Record<string, any>, }; for (let i = 0; i < 100; i++) { largeConfig.mcpServers[`large-config-server-${i}`] = { transport: 'stdio', command: 'echo', args: [`test-${i}`], tags: ['large-config', 'test', `server-${i}`], timeout: 5000, }; } await writeFile(environment.getConfigPath(), JSON.stringify(largeConfig, null, 2)); const result = await runner.runMcpCommand('list', { timeout: 15000, // Give it more time }); // Should handle large config, might be slow but shouldn't fail runner.assertSuccess(result); runner.assertOutputContains(result, 'large-config-server-0'); }); it('should handle rapid repeated operations', async () => { const serverName = 'rapid-test-server'; // Add server await runner.runMcpCommand('add', { args: [serverName, '--type', 'stdio', '--command', 'echo'], }); // Perform rapid operations const operations = []; for (let i = 0; i < 20; i++) { operations.push(runner.runMcpCommand('status', { args: [serverName] })); } const results = await Promise.allSettled(operations); // All status operations should succeed const successful = results.filter((r) => r.status === 'fulfilled' && r.value.exitCode === 0); expect(successful.length).toBe(20); // Clean up await runner.runMcpCommand('remove', { args: [serverName] }); }); }); describe('Error Recovery and Consistency', () => { it('should maintain consistency after partial failures', async () => { const servers = ['recovery-1', 'recovery-2', 'recovery-3']; // Add servers successfully for (const server of servers) { await runner.runMcpCommand('add', { args: [server, '--type', 'stdio', '--command', 'echo'], }); } // Try to disable servers individually (CLI doesn't support batch operations) // First disable valid servers for (const server of servers) { const disableResult = await runner.runMcpCommand('disable', { args: [server], input: 'y\n', }); runner.assertSuccess(disableResult); } // Try to disable a nonexistent server - should fail const failResult = await runner.runMcpCommand('disable', { args: ['nonexistent-server'], expectError: true, }); runner.assertFailure(failResult, 1); // But valid servers should still exist and be manageable const listResult = await runner.runMcpCommand('list', { args: ['--show-disabled'] }); servers.forEach((server) => { runner.assertOutputContains(listResult, server); }); // Clean up for (const server of servers) { await runner.runMcpCommand('remove', { args: [server], input: 'y\n', }); } }); it('should recover from interrupted operations', async () => { const serverName = 'interruption-recovery-test'; // Add server await runner.runMcpCommand('add', { args: [serverName, '--type', 'stdio', '--command', 'echo'], }); // Simulate interruption by trying to add same server again const interruptResult = await runner.runMcpCommand('add', { args: [serverName, '--type', 'stdio', '--command', 'node'], expectError: true, }); runner.assertFailure(interruptResult, 1); // Verify original server is still functional const statusResult = await runner.runMcpCommand('status', { args: [serverName] }); runner.assertSuccess(statusResult); runner.assertOutputContains(statusResult, serverName); // Should be able to perform normal operations const disableResult = await runner.runMcpCommand('disable', { args: [serverName], input: 'y\n', }); runner.assertSuccess(disableResult); const enableResult = await runner.runMcpCommand('enable', { args: [serverName] }); runner.assertSuccess(enableResult); // Clean up await runner.runMcpCommand('remove', { args: [serverName], input: 'y\n', }); }); it('should provide helpful error messages for common mistakes', async () => { // Test actual error scenarios that the CLI handles // Test 1: Non-existent server const nonExistentResult = await runner.runMcpCommand('status', { args: ['non-existent-server'], expectError: true, }); runner.assertFailure(nonExistentResult, 1); runner.assertOutputContains(nonExistentResult, 'does not exist', true); // Test 2: Invalid type const invalidTypeResult = await runner.runMcpCommand('add', { args: ['test-server', '--type', 'invalid-type', '--command', 'echo'], expectError: true, }); runner.assertFailure(invalidTypeResult, 1); // Should show help message about valid types expect(invalidTypeResult.stderr).toMatch(/choices.*stdio.*http.*sse|missing required argument.*type/i); // Test 3: Missing required argument const missingArgsResult = await runner.runMcpCommand('add', { args: [], // No server name expectError: true, }); runner.assertFailure(missingArgsResult, 1); runner.assertOutputContains(missingArgsResult, 'required', true); }); }); });

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