Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
openurl.ts7.84 kB
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { executeCommand } from '../../utils/command.js'; import { simulatorCache } from '../../state/simulator-cache.js'; interface SimctlOpenUrlToolArgs { udid: string; url: string; } /** * Open a URL in a simulator (deep linking support) * * Examples: * - Open web URL: udid: "device-123", url: "https://example.com" * - Open deep link: udid: "device-123", url: "myapp://open?id=123" * - Open mailto: udid: "device-123", url: "mailto:test@example.com" * - Open tel: udid: "device-123", url: "tel:+1234567890" * * Supports: * - HTTP/HTTPS URLs * - Custom scheme deep links (myapp://) * - Special URLs (mailto:, tel:, sms:, etc.) * * **Full documentation:** See simctl/openurl.md for detailed parameters and examples */ export async function simctlOpenUrlTool(args: any) { const { udid, url } = args as SimctlOpenUrlToolArgs; try { // Validate inputs if (!udid || udid.trim().length === 0) { throw new McpError(ErrorCode.InvalidRequest, 'UDID is required and cannot be empty'); } if (!url || url.trim().length === 0) { throw new McpError(ErrorCode.InvalidRequest, 'URL is required and cannot be empty'); } // Basic URL validation const urlRegex = /^([a-z][a-z0-9+.-]*:)?\/\/.+|^[a-z][a-z0-9+.-]*:.*$/i; if (!urlRegex.test(url)) { throw new McpError( ErrorCode.InvalidRequest, 'URL must be a valid format (e.g., https://example.com or myapp://deeplink)' ); } // Validate simulator exists const simulator = await simulatorCache.findSimulatorByUdid(udid); if (!simulator) { throw new McpError( ErrorCode.InvalidRequest, `Simulator with UDID "${udid}" not found. Use simctl-list to see available simulators.` ); } // Execute openurl command const command = `xcrun simctl openurl "${udid}" "${url}"`; console.error(`[simctl-openurl] Executing: ${command}`); const result = await executeCommand(command, { timeout: 15000, }); const success = result.code === 0; // Extract scheme from URL for response const schemeMatch = url.match(/^([a-z][a-z0-9+.-]*?):/i); const scheme = schemeMatch ? schemeMatch[1] : 'http'; // Build guidance messages const guidanceMessages: string[] = []; if (success) { guidanceMessages.push( `✅ URL opened on "${simulator.name}"`, `URL: ${url}`, `Scheme: ${scheme}`, `View open URLs in Safari: simctl-launch ${udid} com.apple.mobilesafari`, `Test deep links with different parameters` ); } else { guidanceMessages.push( `❌ Failed to open URL: ${result.stderr || 'Unknown error'}`, simulator.state !== 'Booted' ? `Simulator is not booted. Boot it first: simctl-boot ${udid}` : scheme !== 'http' && scheme !== 'https' ? `No handler registered for scheme "${scheme}". Install an app that handles this scheme.` : `Verify URL format: ${url}`, `Check simulator health: simctl-health-check` ); } // Add warnings for simulator state regardless of success if (simulator.state !== 'Booted') { guidanceMessages.push( `⚠️ Warning: Simulator is in ${simulator.state} state. Boot the simulator for optimal functionality: simctl-boot ${udid}` ); } if (simulator.isAvailable === false) { guidanceMessages.push( `⚠️ Warning: Simulator is marked as unavailable. This may cause issues with operations.` ); } const responseData = { success, udid, url, scheme, simulatorInfo: { name: simulator.name, udid: simulator.udid, state: simulator.state, isAvailable: simulator.isAvailable, }, command, output: result.stdout, error: result.stderr || undefined, exitCode: result.code, guidance: guidanceMessages, }; const responseText = JSON.stringify(responseData, null, 2); return { content: [ { type: 'text' as const, text: responseText, }, ], isError: !success, }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `simctl-openurl failed: ${error instanceof Error ? error.message : String(error)}` ); } } export const SIMCTL_OPENURL_DOCS = ` # simctl-openurl Open URLs in a simulator, including web URLs, deep links, and special URL schemes. ## What it does Opens a URL in the simulator, which can be a web URL (http/https), custom app deep link (myapp://), or special URL scheme (mailto:, tel:, sms:). The system will route the URL to the appropriate app handler. ## Parameters - **udid** (string, required): Simulator UDID (from simctl-list) - **url** (string, required): URL to open (e.g., https://example.com or myapp://deeplink?id=123) ## Supported URL Schemes - **HTTP/HTTPS**: Web URLs (opens in Safari) - **Custom schemes**: Deep links to your app (myapp://, yourapp://) - **mailto**: Email composition (opens Mail app) - **tel**: Phone dialer (opens Phone app on iPhone) - **sms**: SMS composition (opens Messages app) - **facetime**: FaceTime calls - **maps**: Apple Maps URLs ## Returns JSON response with: - URL open status - Detected URL scheme - Guidance for testing URL handling and deep links ## Examples ### Open web URL \`\`\`typescript await simctlOpenUrlTool({ udid: 'device-123', url: 'https://example.com' }) \`\`\` ### Open deep link with parameters \`\`\`typescript await simctlOpenUrlTool({ udid: 'device-123', url: 'myapp://open?id=123&action=view' }) \`\`\` ### Open mailto link \`\`\`typescript await simctlOpenUrlTool({ udid: 'device-123', url: 'mailto:test@example.com?subject=Hello' }) \`\`\` ### Open tel link \`\`\`typescript await simctlOpenUrlTool({ udid: 'device-123', url: 'tel:+1234567890' }) \`\`\` ## Common Use Cases 1. **Deep link testing**: Verify app handles custom URL schemes correctly 2. **Universal links**: Test https:// URLs that open your app 3. **Navigation testing**: Confirm deep links navigate to correct screens 4. **Parameter parsing**: Verify URL parameters are parsed correctly 5. **Fallback handling**: Test behavior when no handler is registered ## Important Notes - **Simulator must be booted**: URLs can only be opened on running simulators - **Handler registration**: Custom schemes require an app that handles them - **URL encoding**: Ensure URL parameters are properly encoded - **Timing**: Consider launching app first if testing immediate URL handling ## Error Handling - **No handler registered**: Error if no app handles the URL scheme - **Simulator not booted**: Indicates simulator must be booted first - **Invalid URL format**: Validates URL has proper scheme and format - **Simulator not found**: Validates simulator exists in cache ## Deep Link Testing Workflow 1. **Install app**: \`simctl-install <udid> /path/to/App.app\` 2. **Launch app**: \`simctl-launch <udid> <bundleId>\` 3. **Open deep link**: \`simctl-openurl <udid> myapp://route?param=value\` 4. **Take screenshot**: \`simctl-io <udid> screenshot\` to verify navigation 5. **Check logs**: Monitor console for URL handling logs ## Testing Strategies - **Parameter variations**: Test different query parameters - **Invalid URLs**: Verify error handling for malformed URLs - **Background handling**: Test URLs when app is backgrounded - **Fresh launch**: Test URLs when app is not running - **State preservation**: Verify app state is maintained after URL handling `; export const SIMCTL_OPENURL_DOCS_MINI = 'Open URLs and deep links on simulator. Use rtfm({ toolName: "simctl-openurl" }) for docs.';

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/conorluddy/xc-mcp'

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