Skip to content

Commit fc872ca

Browse files
committed
Add iOS device deployment tools with JSON-based device discovery
Add complete physical device support with 5 new MCP tools for end-to-end iOS device workflows: • Device discovery and management via list_devices tool with JSON-based output • Native device building with build_ios_dev_proj/ws tools supporting incremental builds • App deployment via install_app_device and launch_app_device tools • Real-time console log capture with start/stop_device_log_cap session management • Physical device testing with test_ios_dev_proj/ws tools and detailed result parsing Key implementation details: - Standardized parameter naming (deviceId, bundleId, appPath) across all device tools - Added DEVICE_MANAGEMENT tool group for selective enablement parallel to simulator tools - Console output capture via xcrun devicectl with --terminate-existing flag for reliability - Proper error handling and session management for log capture workflows - Enhanced example iOS app with comprehensive startup and interaction logging Documentation and testing: - Updated README with device management features section and code signing prerequisites - Added code signing configuration guide for Xcode setup requirements - End-to-end tested complete device workflow: discover → build → install → launch → log capture → test - Verified console log capture works for both debugPrint and print statements
1 parent 6f86874 commit fc872ca

15 files changed

Lines changed: 1008 additions & 368 deletions

File tree

README.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ A Model Context Protocol (MCP) server that provides Xcode-related tools for inte
1414
- [Xcode project management](#xcode-project-management)
1515
- [Swift Package Manager](#swift-package-manager)
1616
- [Simulator management](#simulator-management)
17+
- [Device management](#device-management)
1718
- [App utilities](#app-utilities)
1819
- [Getting started](#getting-started)
1920
- [Prerequisites](#prerequisites)
@@ -24,6 +25,7 @@ A Model Context Protocol (MCP) server that provides Xcode-related tools for inte
2425
- [Alternative installation method using mise](#alternative-installation-method-using-mise)
2526
- [Installing via Smithery](#installing-via-smithery)
2627
- [Incremental build support](#incremental-build-support)
28+
- [Code Signing for Device Deployment](#code-signing-for-device-deployment)
2729
- [Troubleshooting](#troubleshooting)
2830
- [Diagnostic Tool](#diagnostic-tool)
2931
- [Using with npx](#using-with-npx)
@@ -75,15 +77,25 @@ The XcodeBuildMCP server provides the following tool capabilities:
7577
- **Clean Artifacts**: Remove build artifacts and derived data for fresh builds
7678

7779
### Simulator management
78-
- **Simulator Control**: List, boot, and open iOS simulators
79-
- **App Deployment**: Install and launch apps on iOS simulators
80+
- **Simulator Control**: List, boot, and open simulators
81+
- **App Deployment**: Install and launch apps on simulators
8082
- **Log Capture**: Capture run-time logs from a simulator
8183
- **UI Automation**: Interact with simulator UI elements (beta)
8284
- **Screenshot**: Capture screenshots from a simulator (beta)
8385

86+
### Device management
87+
- **Device Discovery**: List connected physical Apple devices (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro)
88+
- **App Deployment**: Build, install, and launch apps on physical Apple devices across all platforms
89+
- **Testing**: Run test suites on physical devices with detailed results and cross-platform support
90+
- **Log Capture**: Capture console output from apps running on physical Apple devices
91+
- **Wireless Connectivity**: Connect to physical devices over Wi-Fi
92+
8493
### App utilities
85-
- **Bundle ID Extraction**: Extract bundle identifiers from iOS and macOS app bundles
86-
- **App Launching**: Launch built applications on both simulators and macOS
94+
- **Bundle ID Extraction**: Extract bundle identifiers from app bundles across all Apple platforms
95+
- **App Launching**: Launch built applications on simulators, physical devices, and macOS
96+
-
97+
> [!IMPORTANT]
98+
> Please note that XcodeBuildMCP will request xcodebuild to skip macro validation. This is to avoid errors when building projects that use Swift Macros.
8799
88100
## Getting started
89101

@@ -93,7 +105,7 @@ The XcodeBuildMCP server provides the following tool capabilities:
93105
- Xcode 16.x or later
94106
- Node 18.x or later
95107
- AXe 1.0.0 or later (optional, required for UI automation)
96-
108+
97109
### UI Automation
98110

99111
For UI automation features (tap, swipe, type etc.), you'll need to install AXe:
@@ -132,6 +144,7 @@ Configure your MCP client (Windsurf, Cursor, Claude Desktop, Claude Code etc.) t
132144
}
133145
}
134146
```
147+
135148
#### Alternative installation method using mise
136149

137150
Alternatively, you can use XcodeBuildMCP without a specific installation of Node.js by using `mise` to install it:
@@ -173,9 +186,6 @@ To install XcodeBuildMCP Server for Claude Desktop automatically via [Smithery](
173186
npx -y @smithery/cli install @cameroncooke/XcodeBuildMCP --client claude
174187
```
175188

176-
> [!IMPORTANT]
177-
> Please note that XcodeBuildMCP will request xcodebuild to skip macro validation. This is to avoid errors when building projects that use Swift Macros.
178-
179189
## Incremental build support
180190

181191
XcodeBuildMCP includes experimental support for incremental builds. This feature is disabled by default and can be enabled by setting the `INCREMENTAL_BUILDS_ENABLED` environment variable to `true`:
@@ -203,6 +213,18 @@ Example MCP client configuration:
203213
> [!IMPORTANT]
204214
> Please note that incremental builds support is currently highly experimental and your mileage may vary. Please report any issues you encounter to the [issue tracker](https://github.com/cameroncooke/XcodeBuildMCP/issues).
205215
216+
## Code Signing for Device Deployment
217+
218+
For device deployment features to work, code signing must be properly configured in Xcode **before** using XcodeBuildMCP device tools:
219+
220+
1. Open your project in Xcode
221+
2. Select your project target
222+
3. Go to "Signing & Capabilities" tab
223+
4. Configure "Automatically manage signing" and select your development team
224+
5. Ensure a valid provisioning profile is selected
225+
226+
> **Note**: XcodeBuildMCP cannot configure code signing automatically. This initial setup must be done once in Xcode, after which the MCP device tools can build, install, and test apps on physical devices.
227+
206228
## Troubleshooting
207229

208230
If you encounter issues with XcodeBuildMCP, the diagnostic tool can help identify the problem by providing detailed information about your environment and dependencies.

example_projects/iOS_Calculator/CalculatorApp.xcodeproj/project.pbxproj

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 56;
6+
objectVersion = 71;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -28,7 +28,7 @@
2828
/* End PBXFileReference section */
2929

3030
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
31-
8BD71C0A2DEE41E000CEDD92 /* Exceptions for "Config" folder in "CalculatorApp" target */ = {
31+
8BD71C0A2DEE41E000CEDD92 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
3232
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
3333
membershipExceptions = (
3434
Debug.xcconfig,
@@ -41,24 +41,9 @@
4141
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
4242

4343
/* Begin PBXFileSystemSynchronizedRootGroup section */
44-
8B41F6472DEDD0D5001A66F9 /* CalculatorApp */ = {
45-
isa = PBXFileSystemSynchronizedRootGroup;
46-
path = CalculatorApp;
47-
sourceTree = "<group>";
48-
};
49-
8B9093512DF246C0008F026A /* CalculatorAppTests */ = {
50-
isa = PBXFileSystemSynchronizedRootGroup;
51-
path = CalculatorAppTests;
52-
sourceTree = "<group>";
53-
};
54-
8BD71C052DEE41D800CEDD92 /* Config */ = {
55-
isa = PBXFileSystemSynchronizedRootGroup;
56-
exceptions = (
57-
8BD71C0A2DEE41E000CEDD92 /* Exceptions for "Config" folder in "CalculatorApp" target */,
58-
);
59-
path = Config;
60-
sourceTree = "<group>";
61-
};
44+
8B41F6472DEDD0D5001A66F9 /* CalculatorApp */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CalculatorApp; sourceTree = "<group>"; };
45+
8B9093512DF246C0008F026A /* CalculatorAppTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CalculatorAppTests; sourceTree = "<group>"; };
46+
8BD71C052DEE41D800CEDD92 /* Config */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (8BD71C0A2DEE41E000CEDD92 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Config; sourceTree = "<group>"; };
6247
/* End PBXFileSystemSynchronizedRootGroup section */
6348

6449
/* Begin PBXFrameworksBuildPhase section */
@@ -180,6 +165,7 @@
180165
};
181166
};
182167
buildConfigurationList = 8B41F6402DEDD0D5001A66F9 /* Build configuration list for PBXProject "CalculatorApp" */;
168+
compatibilityVersion = "Xcode 14.0";
183169
developmentRegion = en;
184170
hasScannedForEncodings = 0;
185171
knownRegions = (
@@ -188,7 +174,6 @@
188174
);
189175
mainGroup = 8B41F63C2DEDD0D5001A66F9;
190176
minimizedProjectReferenceProxies = 1;
191-
preferredProjectObjectVersion = 56;
192177
productRefGroup = 8B41F6462DEDD0D5001A66F9 /* Products */;
193178
projectDirPath = "";
194179
projectRoot = "";
@@ -370,7 +355,9 @@
370355
buildSettings = {
371356
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
372357
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
358+
CODE_SIGN_IDENTITY = "Apple Development";
373359
CODE_SIGN_STYLE = Automatic;
360+
DEVELOPMENT_TEAM = BR6WD3M6ZD;
374361
ENABLE_PREVIEWS = YES;
375362
GENERATE_INFOPLIST_FILE = YES;
376363
INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_DISPLAY_NAME)";
@@ -382,6 +369,7 @@
382369
"@executable_path/Frameworks",
383370
);
384371
PRODUCT_NAME = CalculatorApp;
372+
PROVISIONING_PROFILE_SPECIFIER = "";
385373
SWIFT_EMIT_LOC_STRINGS = YES;
386374
SWIFT_VERSION = 5.0;
387375
};
@@ -394,7 +382,9 @@
394382
buildSettings = {
395383
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
396384
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
385+
CODE_SIGN_IDENTITY = "Apple Development";
397386
CODE_SIGN_STYLE = Automatic;
387+
DEVELOPMENT_TEAM = BR6WD3M6ZD;
398388
ENABLE_PREVIEWS = YES;
399389
GENERATE_INFOPLIST_FILE = YES;
400390
INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_DISPLAY_NAME)";
@@ -406,6 +396,7 @@
406396
"@executable_path/Frameworks",
407397
);
408398
PRODUCT_NAME = CalculatorApp;
399+
PROVISIONING_PROFILE_SPECIFIER = "";
409400
SWIFT_EMIT_LOC_STRINGS = YES;
410401
SWIFT_VERSION = 5.0;
411402
TARGETED_DEVICE_FAMILY = "1,2";
@@ -417,12 +408,15 @@
417408
isa = XCBuildConfiguration;
418409
buildSettings = {
419410
BUNDLE_LOADER = "$(TEST_HOST)";
411+
CODE_SIGN_IDENTITY = "Apple Development";
420412
CODE_SIGN_STYLE = Automatic;
421413
CURRENT_PROJECT_VERSION = 1;
414+
DEVELOPMENT_TEAM = BR6WD3M6ZD;
422415
GENERATE_INFOPLIST_FILE = YES;
423416
MARKETING_VERSION = 1.0;
424417
PRODUCT_BUNDLE_IDENTIFIER = com.mycompany.CalculatorAppTests;
425418
PRODUCT_NAME = "$(TARGET_NAME)";
419+
PROVISIONING_PROFILE_SPECIFIER = "";
426420
SWIFT_EMIT_LOC_STRINGS = NO;
427421
SWIFT_VERSION = 5.0;
428422
TARGETED_DEVICE_FAMILY = "1,2";
@@ -434,12 +428,15 @@
434428
isa = XCBuildConfiguration;
435429
buildSettings = {
436430
BUNDLE_LOADER = "$(TEST_HOST)";
431+
CODE_SIGN_IDENTITY = "Apple Development";
437432
CODE_SIGN_STYLE = Automatic;
438433
CURRENT_PROJECT_VERSION = 1;
434+
DEVELOPMENT_TEAM = BR6WD3M6ZD;
439435
GENERATE_INFOPLIST_FILE = YES;
440436
MARKETING_VERSION = 1.0;
441437
PRODUCT_BUNDLE_IDENTIFIER = com.mycompany.CalculatorAppTests;
442438
PRODUCT_NAME = "$(TARGET_NAME)";
439+
PROVISIONING_PROFILE_SPECIFIER = "";
443440
SWIFT_EMIT_LOC_STRINGS = NO;
444441
SWIFT_VERSION = 5.0;
445442
TARGETED_DEVICE_FAMILY = "1,2";

src/tools/app_path.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,29 @@ async function _handleGetAppPathLogic(params: {
148148
nextStepsText = `Next Steps:
149149
1. Get bundle ID: get_macos_bundle_id({ appPath: "${appPath}" })
150150
2. Launch the app: launch_macos_app({ appPath: "${appPath}" })`;
151-
} else if (params.platform === XcodePlatform.iOSSimulator) {
151+
} else if (isSimulatorPlatform) {
152152
nextStepsText = `Next Steps:
153-
1. Get bundle ID: get_ios_bundle_id({ appPath: "${appPath}" })
153+
1. Get bundle ID: get_app_bundle_id({ appPath: "${appPath}" })
154154
2. Boot simulator: boot_simulator({ simulatorUuid: "SIMULATOR_UUID" })
155155
3. Install app: install_app_in_simulator({ simulatorUuid: "SIMULATOR_UUID", appPath: "${appPath}" })
156156
4. Launch app: launch_app_in_simulator({ simulatorUuid: "SIMULATOR_UUID", bundleId: "BUNDLE_ID" })`;
157-
} else if (params.platform === XcodePlatform.iOS) {
157+
} else if (
158+
[
159+
XcodePlatform.iOS,
160+
XcodePlatform.watchOS,
161+
XcodePlatform.tvOS,
162+
XcodePlatform.visionOS,
163+
].includes(params.platform)
164+
) {
165+
nextStepsText = `Next Steps:
166+
1. Get bundle ID: get_app_bundle_id({ appPath: "${appPath}" })
167+
2. Install app on device: install_app_device({ deviceId: "DEVICE_UDID", appPath: "${appPath}" })
168+
3. Launch app on device: launch_app_device({ deviceId: "DEVICE_UDID", bundleId: "BUNDLE_ID" })`;
169+
} else {
170+
// For other platforms
158171
nextStepsText = `Next Steps:
159-
1. Get bundle ID: get_ios_bundle_id({ appPath: "${appPath}" })
160-
2. Use Xcode to install the app on your connected iOS device`;
172+
1. The app has been built for ${params.platform}
173+
2. Use platform-specific deployment tools to install and run the app`;
161174
}
162175

163176
return {
@@ -248,18 +261,22 @@ export function registerGetMacOSAppPathProjectTool(server: McpServer): void {
248261
}
249262

250263
/**
251-
* Registers the get_ios_dev_app_path_ws tool
264+
* Registers the get_device_app_path_ws tool
252265
*/
253-
export function registerGetiOSDeviceAppPathWorkspaceTool(server: McpServer): void {
254-
type Params = BaseWorkspaceParams & { configuration?: string };
266+
export function registerGetDeviceAppPathWorkspaceTool(server: McpServer): void {
267+
type Params = BaseWorkspaceParams & { configuration?: string; platform?: string };
255268
registerTool<Params>(
256269
server,
257-
'get_ios_dev_app_path_ws',
258-
"Gets the app bundle path for an iOS physical device application using a workspace. IMPORTANT: Requires workspacePath and scheme. Example: get_ios_dev_app_path_ws({ workspacePath: '/path/to/workspace', scheme: 'MyScheme' })",
270+
'get_device_app_path_ws',
271+
"Gets the app bundle path for a physical device application (iOS, watchOS, tvOS, visionOS) using a workspace. IMPORTANT: Requires workspacePath and scheme. Example: get_device_app_path_ws({ workspacePath: '/path/to/workspace', scheme: 'MyScheme' })",
259272
{
260273
workspacePath: workspacePathSchema,
261274
scheme: schemeSchema,
262275
configuration: configurationSchema,
276+
platform: z
277+
.enum(['iOS', 'watchOS', 'tvOS', 'visionOS'])
278+
.optional()
279+
.describe('Target platform (defaults to iOS)'),
263280
},
264281
async (params: Params) => {
265282
const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath);
@@ -268,29 +285,40 @@ export function registerGetiOSDeviceAppPathWorkspaceTool(server: McpServer): voi
268285
const schemeValidation = validateRequiredParam('scheme', params.scheme);
269286
if (!schemeValidation.isValid) return schemeValidation.errorResponse!;
270287

288+
const platformMap: Record<string, XcodePlatform> = {
289+
iOS: XcodePlatform.iOS,
290+
watchOS: XcodePlatform.watchOS,
291+
tvOS: XcodePlatform.tvOS,
292+
visionOS: XcodePlatform.visionOS,
293+
};
294+
271295
return _handleGetAppPathLogic({
272296
...params,
273297
configuration: params.configuration ?? 'Debug',
274-
platform: XcodePlatform.iOS,
298+
platform: platformMap[params.platform ?? 'iOS'],
275299
useLatestOS: true,
276300
});
277301
},
278302
);
279303
}
280304

281305
/**
282-
* Registers the get_ios_dev_app_path_proj tool
306+
* Registers the get_device_app_path_proj tool
283307
*/
284-
export function registerGetiOSDeviceAppPathProjectTool(server: McpServer): void {
285-
type Params = BaseProjectParams & { configuration?: string };
308+
export function registerGetDeviceAppPathProjectTool(server: McpServer): void {
309+
type Params = BaseProjectParams & { configuration?: string; platform?: string };
286310
registerTool<Params>(
287311
server,
288-
'get_ios_dev_app_path_proj',
289-
"Gets the app bundle path for an iOS physical device application using a project file. IMPORTANT: Requires projectPath and scheme. Example: get_ios_dev_app_path_proj({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
312+
'get_device_app_path_proj',
313+
"Gets the app bundle path for a physical device application (iOS, watchOS, tvOS, visionOS) using a project file. IMPORTANT: Requires projectPath and scheme. Example: get_device_app_path_proj({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme' })",
290314
{
291315
projectPath: projectPathSchema,
292316
scheme: schemeSchema,
293317
configuration: configurationSchema,
318+
platform: z
319+
.enum(['iOS', 'watchOS', 'tvOS', 'visionOS'])
320+
.optional()
321+
.describe('Target platform (defaults to iOS)'),
294322
},
295323
async (params: Params) => {
296324
const projectValidation = validateRequiredParam('projectPath', params.projectPath);
@@ -299,10 +327,17 @@ export function registerGetiOSDeviceAppPathProjectTool(server: McpServer): void
299327
const schemeValidation = validateRequiredParam('scheme', params.scheme);
300328
if (!schemeValidation.isValid) return schemeValidation.errorResponse!;
301329

330+
const platformMap: Record<string, XcodePlatform> = {
331+
iOS: XcodePlatform.iOS,
332+
watchOS: XcodePlatform.watchOS,
333+
tvOS: XcodePlatform.tvOS,
334+
visionOS: XcodePlatform.visionOS,
335+
};
336+
302337
return _handleGetAppPathLogic({
303338
...params,
304339
configuration: params.configuration ?? 'Debug',
305-
platform: XcodePlatform.iOS,
340+
platform: platformMap[params.platform ?? 'iOS'],
306341
useLatestOS: true,
307342
});
308343
},

0 commit comments

Comments
 (0)