Skip to main content
Glama

MCP Xcode

by Stefan-Nitu
ERROR-HANDLING.md8.16 kB
# Error Handling and Presentation Patterns ## Overview This document describes the error handling patterns and presentation conventions used across the MCP Xcode Server codebase. Following these patterns ensures consistent user experience and maintainable error handling. ## Core Principles ### 1. Separation of Concerns - **Domain Layer**: Creates typed error objects with data only (no formatting) - **Use Cases**: Return domain errors without formatting messages - **Presentation Layer**: Formats errors for user display with consistent styling ### 2. Typed Domain Errors Each domain has its own error types that extend a base error class: ```typescript // Base class for domain-specific errors export abstract class BootError extends Error {} // Specific error types with relevant data export class SimulatorNotFoundError extends BootError { constructor(public readonly deviceId: string) { super(deviceId); // Just store the data, no formatting this.name = 'SimulatorNotFoundError'; } } export class BootCommandFailedError extends BootError { constructor(public readonly stderr: string) { super(stderr); // Just store the stderr output this.name = 'BootCommandFailedError'; } } ``` **Important**: Domain errors should NEVER contain user-facing messages. They should only contain data. The presentation layer is responsible for formatting messages based on the error type and data. ### 3. Error Type Checking Use `instanceof` to check error types in the presentation layer: ```typescript if (error instanceof SimulatorNotFoundError) { return `❌ Simulator not found: ${error.deviceId}`; } if (error instanceof BootCommandFailedError) { return `❌ ${ErrorFormatter.format(error)}`; } ``` ## Presentation Patterns ### Visual Indicators (Emojis) All tools use consistent emoji prefixes for different outcomes: - **✅ Success**: Successful operations - **❌ Error**: Failed operations - **⚠️ Warning**: Operations with warnings - **📁 Info**: Additional information (like log paths) Examples: ``` ✅ Successfully booted simulator: iPhone 15 (ABC123) ✅ Build succeeded: MyApp ❌ Simulator not found: iPhone-16 ❌ Build failed ⚠️ Warnings (3): • Deprecated API usage • Unused variable 'x' 📁 Full logs saved to: /path/to/logs ``` ### Error Message Format 1. **Simple Errors**: Direct message with emoji ``` ❌ Simulator not found: iPhone-16 ❌ Unable to boot device ``` 2. **Complex Errors** (builds, tests): Structured format ``` ❌ Build failed: MyApp Platform: iOS Configuration: Debug ❌ Errors (3): • /path/file.swift:10: Cannot find type 'Foo' • /path/file.swift:20: Missing return statement ``` ### ErrorFormatter Usage The `ErrorFormatter` class provides consistent error formatting across all tools: ```typescript import { ErrorFormatter } from '../formatters/ErrorFormatter.js'; // In controller or presenter const message = ErrorFormatter.format(error); return `❌ ${message}`; ``` The ErrorFormatter: - Formats domain validation errors - Formats build issues - Cleans up common error prefixes - Provides fallback for unknown errors ## Implementation Guidelines ### Controllers Controllers should format results consistently: ```typescript private formatResult(result: DomainResult): string { switch (result.outcome) { case Outcome.Success: return `✅ Successfully completed: ${result.name}`; case Outcome.Failed: if (result.error instanceof SpecificError) { return `❌ Specific error: ${result.error.details}`; } return `❌ ${ErrorFormatter.format(result.error)}`; } } ``` ### Use Cases Use cases should NOT format error messages: ```typescript // ❌ BAD: Formatting in use case return Result.failed( `Simulator not found: ${deviceId}` // Don't format here! ); // ✅ GOOD: Return typed error return Result.failed( new SimulatorNotFoundError(deviceId) // Just the error object ); ``` ### Presenters For complex formatting (like build results), use a dedicated presenter: ```typescript export class BuildXcodePresenter { presentError(error: Error): MCPResponse { const message = ErrorFormatter.format(error); return { content: [{ type: 'text', text: `❌ ${message}` }] }; } } ``` ## Testing Error Handling ### Unit Tests Test that controllers format errors correctly: ```typescript it('should handle boot failure', async () => { // Arrange const error = new BootCommandFailedError('Device is locked'); const result = BootResult.failed('123', 'iPhone', error); // Act const response = controller.execute({ deviceId: 'iPhone' }); // Assert - Check for emoji and message expect(response.text).toBe('❌ Device is locked'); }); ``` ### Integration Tests Test behavior, not specific formatting: ```typescript it('should handle simulator not found', async () => { // Act const result = await controller.execute({ deviceId: 'NonExistent' }); // Assert - Test behavior: error message shown expect(result.content[0].text).toContain('❌'); expect(result.content[0].text).toContain('not found'); }); ``` ## Common Error Scenarios ### 1. Resource Not Found ```typescript export class ResourceNotFoundError extends DomainError { constructor(public readonly resourceId: string) { super(resourceId); } } // Presentation `❌ Resource not found: ${error.resourceId}` ``` ### 2. Command Execution Failed ```typescript export class CommandFailedError extends DomainError { constructor(public readonly stderr: string, public readonly exitCode?: number) { super(stderr); } } // Presentation `❌ Command failed: ${error.stderr}` ``` ### 3. Resource Busy/State Conflicts ```typescript export class SimulatorBusyError extends DomainError { constructor(public readonly currentState: string) { super(currentState); // Just store the state, no message } } // Presentation layer formats the message `❌ Cannot boot simulator: currently ${error.currentState.toLowerCase()}` ``` ### 4. Validation Failed Domain value objects handle their own validation with consistent error types: ```typescript // Domain value objects validate themselves export class AppPath { static create(path: unknown): AppPath { // Check for missing field if (path === undefined || path === null) { throw new AppPath.RequiredError(); // "App path is required" } // Check type if (typeof path !== 'string') { throw new AppPath.InvalidTypeError(path); // "App path must be a string" } // Check empty if (path.trim() === '') { throw new AppPath.EmptyError(path); // "App path cannot be empty" } // Check format if (!path.endsWith('.app')) { throw new AppPath.InvalidFormatError(path); // "App path must end with .app" } return new AppPath(path); } } ``` #### Validation Error Hierarchy Each value object follows this consistent validation order: 1. **Required**: `undefined`/`null` → "X is required" 2. **Type**: Wrong type → "X must be a {type}" 3. **Empty**: Empty string → "X cannot be empty" 4. **Format**: Invalid format → Specific format message ```typescript // Consistent error base classes export abstract class DomainRequiredError extends DomainError { constructor(fieldDisplayName: string) { super(`${fieldDisplayName} is required`); } } export abstract class DomainEmptyError extends DomainError { constructor(fieldDisplayName: string) { super(`${fieldDisplayName} cannot be empty`); } } export abstract class DomainInvalidTypeError extends DomainError { constructor(fieldDisplayName: string, expectedType: string) { super(`${fieldDisplayName} must be a ${expectedType}`); } } ``` ## Benefits 1. **Consistency**: Users see consistent error formatting across all tools 2. **Maintainability**: Error formatting logic is centralized 3. **Testability**: Domain logic doesn't depend on presentation 4. **Flexibility**: Easy to change formatting without touching business logic 5. **Type Safety**: TypeScript ensures error types are handled correctly

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/Stefan-Nitu/mcp-xcode'

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