Skip to main content
Glama

XC-MCP: XCode CLI wrapper

by conorluddy
push.ts11.4 kB
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { executeCommand } from '../../utils/command.js'; import { simulatorCache } from '../../state/simulator-cache.js'; interface SimctlPushToolArgs { udid: string; bundleId: string; payload: string; // LLM optimization: delivery tracking and test context testName?: string; expectedBehavior?: string; } /** * Send simulated push notifications to an app on a simulator * * Examples: * - Simple alert: udid: "device-123", bundleId: "com.example.MyApp", payload: '{"aps":{"alert":"Test notification"}}' * - With badge: udid: "device-123", bundleId: "com.example.MyApp", payload: '{"aps":{"alert":"Test","badge":1}}' * - Complex payload: udid: "device-123", bundleId: "com.example.MyApp", payload: '{"aps":{"alert":{"title":"Title","body":"Body"},"sound":"default"},"custom":"data"}' * - With test context: udid: "device-123", bundleId: "com.example.MyApp", payload: '...', testName: "PushNotification_DeepLinkTest", expectedBehavior: "App navigates to ProductDetail view" * * Payload must be valid JSON with APS dictionary * * LLM Optimization: * Include testName and expectedBehavior for structured test tracking. Enables agents to verify * push delivery and validate app behavior against expectations. * * **Full documentation:** See simctl/push.md for detailed parameters and examples */ export async function simctlPushTool(args: any) { const { udid, bundleId, payload, testName, expectedBehavior } = args as SimctlPushToolArgs; try { // Validate inputs if (!udid || udid.trim().length === 0) { throw new McpError(ErrorCode.InvalidRequest, 'UDID is required and cannot be empty'); } if (!bundleId || bundleId.trim().length === 0) { throw new McpError(ErrorCode.InvalidRequest, 'Bundle ID is required and cannot be empty'); } if (!bundleId.includes('.')) { throw new McpError( ErrorCode.InvalidRequest, 'Bundle ID should follow the format: com.company.appname' ); } if (!payload || payload.trim().length === 0) { throw new McpError(ErrorCode.InvalidRequest, 'Payload is required and cannot be empty'); } // Validate JSON payload let parsedPayload: any; try { parsedPayload = JSON.parse(payload); } catch { throw new McpError(ErrorCode.InvalidRequest, 'Payload must be valid JSON'); } // 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.` ); } // Write payload to temp file const tempFile = `/tmp/push_payload_${Date.now()}.json`; const fs = await import('fs/promises'); await fs.writeFile(tempFile, payload); try { // Execute push command const command = `xcrun simctl push "${udid}" "${bundleId}" "${tempFile}"`; console.error(`[simctl-push] Executing: ${command}`); const result = await executeCommand(command, { timeout: 15000, }); const success = result.code === 0; // Build guidance messages const guidanceMessages: (string | undefined)[] = []; if (success) { guidanceMessages.push( `✅ Push notification sent to "${bundleId}" on "${simulator.name}"`, testName ? `Test: ${testName}` : undefined, expectedBehavior ? `Expected: ${expectedBehavior}` : undefined, `Verify notification in app`, `Take screenshot to confirm visual delivery: simctl-io ${udid} screenshot`, `Send another notification: simctl-push ${udid} ${bundleId} '{"aps":{"alert":"Test"}}'` ); } else { guidanceMessages.push( `❌ Failed to send push: ${result.stderr || 'Unknown error'}`, testName ? `Test: ${testName}` : undefined, `App may not be running`, `Launch app first: simctl-launch ${udid} ${bundleId}`, `Verify app has push support` ); } // 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, bundleId, payload: parsedPayload, simulatorInfo: { name: simulator.name, udid: simulator.udid, state: simulator.state, isAvailable: simulator.isAvailable, }, // LLM optimization: delivery tracking and test context deliveryInfo: { sent: success, sentAt: new Date().toISOString(), // Note: For full delivery confirmation with app response, pair with simctl-io screenshot // to verify the notification was visually displayed in the app }, testContext: testName || expectedBehavior ? { testName: testName || undefined, expectedBehavior: expectedBehavior || undefined, actualBehavior: success ? 'Notification sent successfully' : 'Notification delivery failed', passed: success, } : undefined, command, output: result.stdout, error: result.stderr || undefined, exitCode: result.code, guidance: guidanceMessages.filter(Boolean), }; const responseText = JSON.stringify(responseData, null, 2); return { content: [ { type: 'text' as const, text: responseText, }, ], isError: !success, }; } finally { // Clean up temp file try { await fs.unlink(tempFile); } catch { // Ignore cleanup errors } } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `simctl-push failed: ${error instanceof Error ? error.message : String(error)}` ); } } export const SIMCTL_PUSH_DOCS = ` # simctl-push Send simulated push notifications to apps on simulators with test context tracking. ## What it does Sends push notifications with custom JSON payloads to apps, simulating remote notifications from APNS. Supports test tracking to verify push delivery and validate app behavior. ## Parameters - **udid** (string, required): Simulator UDID (from simctl-list) - **bundleId** (string, required): App bundle ID (e.g., com.example.MyApp) - **payload** (string, required): JSON payload with APS dictionary - **testName** (string, optional): Test name for tracking - **expectedBehavior** (string, optional): Expected app behavior description ## Payload Format Must be valid JSON with an "aps" dictionary: \`\`\`json { "aps": { "alert": "Notification text", "badge": 1, "sound": "default" }, "custom": "Additional data" } \`\`\` ## LLM Optimization The **testName** and **expectedBehavior** parameters enable structured test tracking. This allows AI agents to verify push notification delivery and validate that app behavior matches expectations (e.g., navigation, UI updates, data refresh). ## Returns JSON response with: - Push delivery status - Delivery information (sent timestamp) - Test context with expected vs actual behavior - Guidance for verifying notification handling ## Examples ### Simple alert notification \`\`\`typescript await simctlPushTool({ udid: 'device-123', bundleId: 'com.example.MyApp', payload: JSON.stringify({ aps: { alert: 'Test notification' } }) }) \`\`\` ### Notification with badge and sound \`\`\`typescript await simctlPushTool({ udid: 'device-123', bundleId: 'com.example.MyApp', payload: JSON.stringify({ aps: { alert: 'New message', badge: 5, sound: 'default' } }) }) \`\`\` ### Rich notification with custom data \`\`\`typescript await simctlPushTool({ udid: 'device-123', bundleId: 'com.example.MyApp', payload: JSON.stringify({ aps: { alert: { title: 'New Order', body: 'Order #1234 has been placed' }, badge: 1 }, orderId: '1234', action: 'view_order' }) }) \`\`\` ### Push with test context tracking \`\`\`typescript await simctlPushTool({ udid: 'device-123', bundleId: 'com.example.MyApp', payload: JSON.stringify({ aps: { alert: 'Product available' }, productId: '567' }), testName: 'PushNotification_DeepLinkTest', expectedBehavior: 'App navigates to ProductDetail view for product 567' }) \`\`\` ## Common Use Cases 1. **Notification delivery testing**: Verify app receives and displays notifications 2. **Deep link navigation**: Test notification taps navigate to correct screens 3. **Badge updates**: Verify badge count is updated correctly 4. **Custom data handling**: Test app processes custom payload data 5. **Background behavior**: Test app behavior when notification arrives in background ## Important Notes - **App must be running**: Launch app first or test background notification handling - **Payload validation**: JSON must be valid and include "aps" dictionary - **Immediate delivery**: Notification is delivered immediately (no delay) - **No user interaction**: Notification appears automatically without tapping - **Visual verification**: Use simctl-io screenshot to confirm notification display ## Error Handling - **Invalid JSON**: Error if payload is not valid JSON - **App not running**: May fail if app is not running (test background handling) - **Simulator not booted**: Indicates simulator must be booted first - **Invalid bundle ID**: Validates bundle ID format (must contain '.') ## Testing Workflow 1. **Launch app**: \`simctl-launch <udid> <bundleId>\` 2. **Send push**: \`simctl-push <udid> <bundleId> <payload>\` 3. **Take screenshot**: \`simctl-io <udid> screenshot\` to verify delivery 4. **Check navigation**: Verify app navigated to expected screen 5. **Validate data**: Confirm app processed custom payload data ## Test Context Tracking The testContext in the response includes: - testName: Identifier for this push notification test - expectedBehavior: What should happen when notification is received - actualBehavior: What actually happened (delivery success/failure) - passed: Whether test passed This enables agents to track push notification tests and verify expected behavior. ## Advanced Testing - **Multiple notifications**: Send sequential pushes to test badge accumulation - **Different payload types**: Test alert, sound-only, silent notifications - **Content extensions**: Test notification service extensions with custom content - **Action buttons**: Test notification actions and user responses - **Notification grouping**: Test thread-id for notification grouping `; export const SIMCTL_PUSH_DOCS_MINI = 'Send push notifications to simulator. Use rtfm({ toolName: "simctl-push" }) 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