Skip to main content
Glama

Peekaboo MCP

by steipete
tool-formatter-architecture.mdβ€’9.86 kB
# Tool Formatter Architecture ## Overview The Peekaboo tool formatter system provides a type-safe, modular architecture for formatting tool execution output across both the CLI and Mac app. This document describes the architecture, components, and how to extend the system. ## Architecture Components ### Core Components (PeekabooCore) The shared formatting infrastructure lives in `PeekabooCore/Sources/PeekabooCore/ToolFormatting/`: #### 1. PeekabooToolType Enum ```swift public enum PeekabooToolType: String, CaseIterable, Sendable { case see = "see" case screenshot = "screenshot" case click = "click" // ... all 50+ tools } ``` **Properties:** - `displayName`: Human-readable name ("Launch Application" vs "launch_app") - `icon`: Emoji icon for visual representation - `category`: Tool categorization (vision, ui, app, etc.) - `isCommunicationTool`: Whether output should be suppressed #### 2. ToolResultExtractor Unified utility for extracting values from tool results with automatic unwrapping: ```swift // Extract with automatic type handling let count = ToolResultExtractor.int("count", from: result) let app = ToolResultExtractor.string("app", from: result) let windows = ToolResultExtractor.array("windows", from: result) ``` Handles both direct values and wrapped values: - Direct: `{"count": 5}` - Wrapped: `{"count": {"type": "number", "value": 5}}` #### 3. FormattingUtilities Common formatting helpers used across formatters: ```swift // Format keyboard shortcuts: "cmd+shift+a" β†’ "βŒ˜β‡§A" FormattingUtilities.formatKeyboardShortcut("cmd+shift+a") // Truncate long text FormattingUtilities.truncate(longText, maxLength: 50) // Format file sizes FormattingUtilities.formatFileSize(1024000) // "1 MB" // Format durations FormattingUtilities.formatDetailedDuration(1.5) // "1.5s" ``` ### CLI Components Located in `Apps/CLI/Sources/peekaboo/Commands/AI/ToolFormatting/`: #### ToolFormatter Protocol ```swift public protocol ToolFormatter { var toolType: ToolType { get } func formatStarting(arguments: [String: Any]) -> String func formatCompleted(result: [String: Any], duration: TimeInterval) -> String func formatError(error: String, result: [String: Any]) -> String func formatCompactSummary(arguments: [String: Any]) -> String func formatResultSummary(result: [String: Any]) -> String func formatForTitle(arguments: [String: Any]) -> String } ``` #### BaseToolFormatter Base implementation providing default formatting behavior that specific formatters can override. #### Specialized Formatters - `VisionToolFormatter`: Screenshots, screen capture, window capture - `ApplicationToolFormatter`: App launching, listing, window management - `UIAutomationToolFormatter`: Click, type, scroll, hotkeys - `ElementToolFormatter`: Finding and listing UI elements - `MenuDialogToolFormatter`: Menu and dialog interactions - `SystemToolFormatter`: Shell commands, waiting - `WindowToolFormatter`: Window focus, resize, spaces - `DockToolFormatter`: Dock operations - `CommunicationToolFormatter`: Internal communication tools #### Detailed Formatters (Default) The default formatters provide comprehensive result formatting: - `DetailedVisionToolFormatter`: Includes element counts, performance metrics, file sizes - `DetailedApplicationToolFormatter`: Includes memory usage, app states, process info - `DetailedUIAutomationToolFormatter`: Rich UI interaction details with validation - `DetailedMenuSystemToolFormatter`: Comprehensive menu, dialog, and system tool output #### ToolFormatterRegistry Singleton registry managing all formatters: ```swift let formatter = ToolFormatterRegistry.shared.formatter(for: .launchApp) let summary = formatter.formatResultSummary(result: resultDict) ``` ### Mac App Components Located in `Apps/Mac/Peekaboo/Features/Main/ToolFormatters/`: #### MacToolFormatterProtocol ```swift protocol MacToolFormatterProtocol { var handledTools: Set<String> { get } func formatSummary(toolName: String, arguments: [String: Any]) -> String? func formatResult(toolName: String, result: [String: Any]) -> String? } ``` #### Mac-Specific Formatters Similar structure to CLI but adapted for SwiftUI: - `VisionToolFormatter` - `ApplicationToolFormatter` - `UIAutomationToolFormatter` - `SystemToolFormatter` - `ElementToolFormatter` - `MenuToolFormatter` #### MacToolFormatterRegistry Central registry for Mac app formatters. ## Output Modes The formatter system supports multiple output modes: ### Minimal Mode Plain text, no colors, CI-friendly: ``` list_apps OK β†’ 29 apps running ``` ### Default Mode Rich formatting with detailed output (formerly "Enhanced Mode"): ``` πŸ“± Listing applications... βœ… β†’ 29 apps running [15 active, 14 background] (1.2s) ``` ### Verbose Mode Full JSON debug information with detailed arguments and results. ## Adding a New Tool ### 1. Add to PeekabooToolType ```swift // In PeekabooCore/Sources/PeekabooCore/ToolFormatting/PeekabooToolType.swift case myNewTool = "my_new_tool" // Add to displayName case .myNewTool: return "My New Tool" // Add to icon case .myNewTool: return "πŸ†•" // Add to category case .myNewTool: return .system ``` ### 2. Create or Update Formatter ```swift // In appropriate formatter file class SystemToolFormatter: BaseToolFormatter { override func formatCompactSummary(arguments: [String: Any]) -> String { switch toolType { case .myNewTool: return "doing something" // ... } } override func formatResultSummary(result: [String: Any]) -> String { switch toolType { case .myNewTool: let count = ToolResultExtractor.int("count", from: result) ?? 0 return "β†’ processed \(count) items" // ... } } } ``` ### 3. Register in ToolFormatterRegistry ```swift // In ToolFormatterRegistry.init() ToolType.myNewTool: SystemToolFormatter(toolType: .myNewTool) ``` ## Best Practices ### 1. Use ToolResultExtractor Always use `ToolResultExtractor` instead of direct casting to handle wrapped values: ```swift // ❌ Bad let count = result["count"] as? Int // βœ… Good let count = ToolResultExtractor.int("count", from: result) ``` ### 2. Provide Progressive Detail Format output based on available information: ```swift override func formatResultSummary(result: [String: Any]) -> String { var parts: [String] = [] // Always provide basic info parts.append("β†’ completed") // Add details if available if let count = ToolResultExtractor.int("count", from: result) { parts.append("\(count) items") } if let duration = ToolResultExtractor.double("duration", from: result) { parts.append(String(format: "%.1fs", duration)) } return parts.joined(separator: " ") } ``` ### 3. Handle Errors Gracefully Provide helpful error messages with suggestions: ```swift override func formatError(error: String, result: [String: Any]) -> String { if error.contains("not found") { return "βœ— \(error) - Try checking if the app is installed" } return "βœ— \(error)" } ``` ### 4. Keep Summaries Concise Compact summaries should be brief but informative: ```swift // ❌ Too verbose return "Launching the application named \(appName) with bundle identifier \(bundleId)" // βœ… Concise return appName ``` ### 5. Use Consistent Icons Follow the icon conventions: - πŸ‘ Vision/Screenshots - πŸ–± Clicking/Mouse - ⌨️ Typing/Keyboard - πŸ“± Applications - πŸͺŸ Windows - πŸ“‹ Menus - πŸ’» System/Shell - βœ… Success/Completion - ❌ Errors ## Testing Formatters ### Unit Testing ```swift func testLaunchAppFormatter() { let formatter = ApplicationToolFormatter(toolType: .launchApp) let args = ["app": "Safari"] let summary = formatter.formatCompactSummary(arguments: args) XCTAssertEqual(summary, "Safari") let result = ["success": true, "app": "Safari", "pid": 12345] let resultSummary = formatter.formatResultSummary(result: result) XCTAssertEqual(resultSummary, "β†’ Launched Safari (PID: 12345)") } ``` ### Integration Testing Test with actual tool execution: ```bash # Test formatter output polter peekaboo agent "list all apps" --verbose # Check different output modes polter peekaboo agent "take a screenshot" --simple # Minimal mode polter peekaboo agent "click on Safari" # Default detailed mode ``` ## Migration Guide ### Migrating from String-Based Formatting Old approach: ```swift switch toolName { case "launch_app": if let app = args["app"] as? String { print("Launching \(app)") } // ... many more cases } ``` New approach: ```swift let formatter = ToolFormatterRegistry.shared.formatter(for: .launchApp) let summary = formatter.formatCompactSummary(arguments: args) print(summary) ``` ### Sharing Formatters Between CLI and Mac App 1. Move common logic to PeekabooCore: ```swift // In PeekabooCore/ToolFormatting/FormattingUtilities.swift public static func formatAppLaunch(_ app: String, pid: Int?) -> String { var result = "Launched \(app)" if let pid = pid { result += " (PID: \(pid))" } return result } ``` 2. Use in both CLI and Mac formatters: ```swift // CLI formatter return FormattingUtilities.formatAppLaunch(app, pid: pid) // Mac formatter return FormattingUtilities.formatAppLaunch(app, pid: pid) ``` ## Performance Considerations - Formatters are lightweight and stateless - Registry uses lazy initialization - ToolResultExtractor caches unwrapped values - Enhanced formatters only process available data ## Future Enhancements - [ ] Localization support for display names - [ ] Custom format templates - [ ] Streaming formatter for real-time updates - [ ] Format caching for repeated operations - [ ] Plugin system for custom formatters

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/steipete/Peekaboo'

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