Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [Unreleased]

### Added

- Added environment variable support for all session defaults (e.g., `XCODEBUILDMCP_WORKSPACE_PATH`, `XCODEBUILDMCP_SCHEME`, `XCODEBUILDMCP_PLATFORM`), enabling full configuration via the MCP client `env` field without requiring a config file ([#268](https://github.com/getsentry/XcodeBuildMCP/pull/268) by [@detailobsessed](https://github.com/detailobsessed)).
- Added `--format mcp-json` flag to `xcodebuildmcp setup` that outputs a ready-to-paste MCP client config JSON block instead of writing `config.yaml` ([#268](https://github.com/getsentry/XcodeBuildMCP/pull/268) by [@detailobsessed](https://github.com/detailobsessed)).
- Added copy-pastable MCP config examples for macOS, iOS, multi-platform, tvOS, and watchOS projects to [docs/CONFIGURATION.md](docs/CONFIGURATION.md) ([#268](https://github.com/getsentry/XcodeBuildMCP/pull/268) by [@detailobsessed](https://github.com/detailobsessed)).

### Changed

- Environment variables are now documented as the recommended configuration method for MCP client integration, replacing the previous "legacy" designation. See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) ([#268](https://github.com/getsentry/XcodeBuildMCP/pull/268) by [@detailobsessed](https://github.com/detailobsessed)).

## [2.2.1]

- Fix AXe bundling issue.
Expand Down
190 changes: 166 additions & 24 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Configuration

XcodeBuildMCP reads configuration from a project config file. The config file is optional but provides deterministic, repo-scoped behavior for every session.
XcodeBuildMCP reads configuration from environment variables and/or a project config file. Both are optional but provide deterministic behavior for every session.

## Contents

- [Environment variables](#environment-variables)
- [Config file](#config-file)
- [Configuration layering](#configuration-layering)
- [Session defaults](#session-defaults)
- [Workflow selection](#workflow-selection)
- [Build settings](#build-settings)
Expand All @@ -13,13 +15,164 @@ XcodeBuildMCP reads configuration from a project config file. The config file is
- [Templates](#templates)
- [Telemetry](#telemetry)
- [Quick reference](#quick-reference)
- [Environment variables (legacy)](#environment-variables-legacy)

---

## Environment variables

Environment variables are the recommended configuration method for MCP client integration. Set them in the `env` field of your MCP client config (e.g., `mcp_config.json` for Windsurf, `.vscode/mcp.json` for VS Code, `claude_desktop_config.json` for Claude Desktop).

This approach works reliably across all MCP clients regardless of working directory, and avoids the need for filesystem-based config discovery.

### General settings

| Config option | Environment variable |
|---------------|---------------------|
| `enabledWorkflows` | `XCODEBUILDMCP_ENABLED_WORKFLOWS` (comma-separated) |
| `experimentalWorkflowDiscovery` | `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY` |
| `disableSessionDefaults` | `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` |
| `disableXcodeAutoSync` | `XCODEBUILDMCP_DISABLE_XCODE_AUTO_SYNC` |
| `incrementalBuildsEnabled` | `INCREMENTAL_BUILDS_ENABLED` |
| `debug` | `XCODEBUILDMCP_DEBUG` |
| `sentryDisabled` | `XCODEBUILDMCP_SENTRY_DISABLED` |
| `debuggerBackend` | `XCODEBUILDMCP_DEBUGGER_BACKEND` |
| `dapRequestTimeoutMs` | `XCODEBUILDMCP_DAP_REQUEST_TIMEOUT_MS` |
| `dapLogEvents` | `XCODEBUILDMCP_DAP_LOG_EVENTS` |
| `launchJsonWaitMs` | `XBMCP_LAUNCH_JSON_WAIT_MS` |
| `uiDebuggerGuardMode` | `XCODEBUILDMCP_UI_DEBUGGER_GUARD_MODE` |
| `axePath` | `XCODEBUILDMCP_AXE_PATH` |
| `iosTemplatePath` | `XCODEBUILDMCP_IOS_TEMPLATE_PATH` |
| `iosTemplateVersion` | `XCODEBUILD_MCP_IOS_TEMPLATE_VERSION` |
| `macosTemplatePath` | `XCODEBUILDMCP_MACOS_TEMPLATE_PATH` |
| `macosTemplateVersion` | `XCODEBUILD_MCP_MACOS_TEMPLATE_VERSION` |

### Session default settings

| Session default | Environment variable |
|----------------|---------------------|
| `workspacePath` | `XCODEBUILDMCP_WORKSPACE_PATH` |
| `projectPath` | `XCODEBUILDMCP_PROJECT_PATH` |
| `scheme` | `XCODEBUILDMCP_SCHEME` |
| `configuration` | `XCODEBUILDMCP_CONFIGURATION` |
| `simulatorName` | `XCODEBUILDMCP_SIMULATOR_NAME` |
| `simulatorId` | `XCODEBUILDMCP_SIMULATOR_ID` |
| `simulatorPlatform` | `XCODEBUILDMCP_SIMULATOR_PLATFORM` |
| `deviceId` | `XCODEBUILDMCP_DEVICE_ID` |
| `platform` | `XCODEBUILDMCP_PLATFORM` |
| `useLatestOS` | `XCODEBUILDMCP_USE_LATEST_OS` |
| `arch` | `XCODEBUILDMCP_ARCH` |
| `suppressWarnings` | `XCODEBUILDMCP_SUPPRESS_WARNINGS` |
| `derivedDataPath` | `XCODEBUILDMCP_DERIVED_DATA_PATH` |
| `preferXcodebuild` | `XCODEBUILDMCP_PREFER_XCODEBUILD` |
| `bundleId` | `XCODEBUILDMCP_BUNDLE_ID` |

### Example MCP configs

Use one of these as a starting point and fill in your workspace path and scheme.

**macOS app**

```json
{
"mcpServers": {
"XcodeBuildMCP": {
"command": "npx",
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
"env": {
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "coverage,debugging,doctor,logging,macos,project-discovery,project-scaffolding,swift-package,ui-automation,utilities,xcode-ide",
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
"XCODEBUILDMCP_SCHEME": "MyApp",
"XCODEBUILDMCP_PLATFORM": "macOS"
}
}
}
}
```

> `macos` provides build/run/test/stop tools for macOS apps. No simulator workflow needed — macOS apps run natively.

---

**iOS app**

```json
{
"mcpServers": {
"XcodeBuildMCP": {
"command": "npx",
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
"env": {
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "coverage,debugging,doctor,logging,project-discovery,project-scaffolding,simulator,swift-package,ui-automation,utilities,xcode-ide",
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
"XCODEBUILDMCP_SCHEME": "MyApp",
"XCODEBUILDMCP_PLATFORM": "iOS Simulator",
"XCODEBUILDMCP_SIMULATOR_NAME": "iPhone 16 Pro"
}
}
}
}
```

> `simulator` provides build/run/test/install tools targeting iOS Simulator. Use `XCODEBUILDMCP_SIMULATOR_NAME` or `XCODEBUILDMCP_SIMULATOR_ID` to pin the target device.

---

**iOS + macOS (multi-platform or Catalyst)**

```json
{
"mcpServers": {
"XcodeBuildMCP": {
"command": "npx",
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
"env": {
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "coverage,debugging,doctor,logging,macos,project-discovery,project-scaffolding,simulator,swift-package,ui-automation,utilities,xcode-ide",
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
"XCODEBUILDMCP_SCHEME": "MyApp"
}
}
}
}
```

> Include both `simulator` and `macos` when the project supports multiple platforms. Omit `XCODEBUILDMCP_PLATFORM` to let the agent choose per-command.

---

**tvOS or watchOS app**

```json
{
"mcpServers": {
"XcodeBuildMCP": {
"command": "npx",
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
"env": {
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "debugging,doctor,logging,project-discovery,simulator,swift-package,utilities,xcode-ide",
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
"XCODEBUILDMCP_SCHEME": "MyApp",
"XCODEBUILDMCP_PLATFORM": "tvOS Simulator"
}
}
}
}
```

> Replace `tvOS Simulator` with `watchOS Simulator` for watchOS. Coverage and UI automation are not available on these platforms.

---

You can also generate a config block interactively:

```bash
xcodebuildmcp setup --format mcp-json
```

---

## Config file

Create a config file at your workspace root:
The config file provides repo-scoped, version-controllable configuration. Create it at your workspace root:

```
<workspace-root>/.xcodebuildmcp/config.yaml
Expand Down Expand Up @@ -337,30 +490,19 @@ Notes:

---

## Environment variables (legacy)
## Configuration layering

Environment variables are supported for backwards compatibility but the config file is preferred.
When multiple configuration sources are present, they are merged with clear precedence:

| Config option | Environment variable |
|---------------|---------------------|
| `enabledWorkflows` | `XCODEBUILDMCP_ENABLED_WORKFLOWS` (comma-separated) |
| `experimentalWorkflowDiscovery` | `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY` |
| `disableSessionDefaults` | `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` |
| `incrementalBuildsEnabled` | `INCREMENTAL_BUILDS_ENABLED` |
| `debug` | `XCODEBUILDMCP_DEBUG` |
| `sentryDisabled` | `XCODEBUILDMCP_SENTRY_DISABLED` |
| `debuggerBackend` | `XCODEBUILDMCP_DEBUGGER_BACKEND` |
| `dapRequestTimeoutMs` | `XCODEBUILDMCP_DAP_REQUEST_TIMEOUT_MS` |
| `dapLogEvents` | `XCODEBUILDMCP_DAP_LOG_EVENTS` |
| `launchJsonWaitMs` | `XBMCP_LAUNCH_JSON_WAIT_MS` |
| `uiDebuggerGuardMode` | `XCODEBUILDMCP_UI_DEBUGGER_GUARD_MODE` |
| `axePath` | `XCODEBUILDMCP_AXE_PATH` |
| `iosTemplatePath` | `XCODEBUILDMCP_IOS_TEMPLATE_PATH` |
| `iosTemplateVersion` | `XCODEBUILD_MCP_IOS_TEMPLATE_VERSION` |
| `macosTemplatePath` | `XCODEBUILDMCP_MACOS_TEMPLATE_PATH` |
| `macosTemplateVersion` | `XCODEBUILD_MCP_MACOS_TEMPLATE_VERSION` |
1. **`session_set_defaults` tool** (highest) — agent runtime overrides, set during a session
2. **Config file** — project-local config (`config.yaml`), committed to repo
3. **Environment variables** (lowest) — MCP client integration, set in `mcp_config.json`

This follows the same pattern as tools like `git config` (`--flag` > `--local` > `--global`). Each layer serves a different context:

Config file takes precedence over environment variables when both are set.
- **Env vars** are the portable MCP client integration path — they work regardless of working directory and are supported by every MCP client.
- **Config file** is for repo-scoped, version-controlled settings and interactive CLI usage.
- **Tool calls** are for agent-driven runtime adjustments.

---

Expand Down
85 changes: 85 additions & 0 deletions src/cli/commands/__tests__/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,91 @@ describe('setup command', () => {
).rejects.toThrow('Setup prerequisites failed');
});

it('outputs MCP config JSON when format is mcp-json', async () => {
const fs = createMockFileSystemExecutor({
existsSync: () => false,
stat: async () => ({ isDirectory: () => true, mtimeMs: 0 }),
readdir: async (targetPath) => {
if (targetPath === cwd) {
return [
{
name: 'App.xcworkspace',
isDirectory: () => true,
isSymbolicLink: () => false,
},
];
}

return [];
},
readFile: async () => '',
writeFile: async () => {},
});

const executor: CommandExecutor = async (command) => {
if (command.includes('--json')) {
return createMockCommandResponse({
success: true,
output: JSON.stringify({
devices: {
'iOS 17.0': [
{
name: 'iPhone 15',
udid: 'SIM-1',
state: 'Shutdown',
isAvailable: true,
},
],
},
}),
});
}

if (command[0] === 'xcrun') {
return createMockCommandResponse({
success: true,
output: `== Devices ==\n-- iOS 17.0 --\n iPhone 15 (SIM-1) (Shutdown)`,
});
}

return createMockCommandResponse({
success: true,
output: `Information about workspace "App":\n Schemes:\n App`,
});
};

const result = await runSetupWizard({
cwd,
fs,
executor,
prompter: createTestPrompter(),
quietOutput: true,
outputFormat: 'mcp-json',
});

expect(result.configPath).toBeUndefined();
expect(result.mcpConfigJson).toBeDefined();

const parsed = JSON.parse(result.mcpConfigJson!) as {
mcpServers: {
XcodeBuildMCP: {
command: string;
args: string[];
env: Record<string, string>;
};
};
};

const serverConfig = parsed.mcpServers.XcodeBuildMCP;
expect(serverConfig.command).toBe('npx');
expect(serverConfig.args).toEqual(['-y', 'xcodebuildmcp@latest', 'mcp']);
expect(serverConfig.env.XCODEBUILDMCP_ENABLED_WORKFLOWS).toBeDefined();
expect(serverConfig.env.XCODEBUILDMCP_WORKSPACE_PATH).toBe(path.join(cwd, 'App.xcworkspace'));
expect(serverConfig.env.XCODEBUILDMCP_SCHEME).toBe('App');
expect(serverConfig.env.XCODEBUILDMCP_SIMULATOR_ID).toBe('SIM-1');
expect(serverConfig.env.XCODEBUILDMCP_SIMULATOR_NAME).toBe('iPhone 15');
});

it('fails in non-interactive mode', async () => {
Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
Object.defineProperty(process.stdout, 'isTTY', { value: false, configurable: true });
Expand Down
Loading