Skip to content

Commit 7e35021

Browse files
author
Ismar Iljazovic
committed
feat: env var session defaults and setup --format mcp-json
- Add readEnvSessionDefaults() to parse all 15 session default env vars (XCODEBUILDMCP_WORKSPACE_PATH, XCODEBUILDMCP_SCHEME, etc.) - Wire env layer into resolveSessionDefaults() with precedence: tool overrides > config file > env vars - Add --format mcp-json flag to xcodebuildmcp setup: outputs a ready-to-paste MCP client config JSON block instead of writing config.yaml - Update CONFIGURATION.md: env vars documented as recommended method for MCP client integration, remove 'legacy' label, add layering section - Add tests for env var parsing and file-over-env precedence - Add test for --format mcp-json output Closes #267
1 parent 684f71e commit 7e35021

File tree

6 files changed

+460
-37
lines changed

6 files changed

+460
-37
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Added
6+
7+
- 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)).
8+
- 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)).
9+
- 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)).
10+
11+
### Changed
12+
13+
- 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)).
14+
315
## [2.2.1]
416

517
- Fix AXe bundling issue.

docs/CONFIGURATION.md

Lines changed: 166 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Configuration
22

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

55
## Contents
66

7+
- [Environment variables](#environment-variables)
78
- [Config file](#config-file)
9+
- [Configuration layering](#configuration-layering)
810
- [Session defaults](#session-defaults)
911
- [Workflow selection](#workflow-selection)
1012
- [Build settings](#build-settings)
@@ -13,13 +15,164 @@ XcodeBuildMCP reads configuration from a project config file. The config file is
1315
- [Templates](#templates)
1416
- [Telemetry](#telemetry)
1517
- [Quick reference](#quick-reference)
16-
- [Environment variables (legacy)](#environment-variables-legacy)
18+
19+
---
20+
21+
## Environment variables
22+
23+
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).
24+
25+
This approach works reliably across all MCP clients regardless of working directory, and avoids the need for filesystem-based config discovery.
26+
27+
### General settings
28+
29+
| Config option | Environment variable |
30+
|---------------|---------------------|
31+
| `enabledWorkflows` | `XCODEBUILDMCP_ENABLED_WORKFLOWS` (comma-separated) |
32+
| `experimentalWorkflowDiscovery` | `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY` |
33+
| `disableSessionDefaults` | `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` |
34+
| `disableXcodeAutoSync` | `XCODEBUILDMCP_DISABLE_XCODE_AUTO_SYNC` |
35+
| `incrementalBuildsEnabled` | `INCREMENTAL_BUILDS_ENABLED` |
36+
| `debug` | `XCODEBUILDMCP_DEBUG` |
37+
| `sentryDisabled` | `XCODEBUILDMCP_SENTRY_DISABLED` |
38+
| `debuggerBackend` | `XCODEBUILDMCP_DEBUGGER_BACKEND` |
39+
| `dapRequestTimeoutMs` | `XCODEBUILDMCP_DAP_REQUEST_TIMEOUT_MS` |
40+
| `dapLogEvents` | `XCODEBUILDMCP_DAP_LOG_EVENTS` |
41+
| `launchJsonWaitMs` | `XBMCP_LAUNCH_JSON_WAIT_MS` |
42+
| `uiDebuggerGuardMode` | `XCODEBUILDMCP_UI_DEBUGGER_GUARD_MODE` |
43+
| `axePath` | `XCODEBUILDMCP_AXE_PATH` |
44+
| `iosTemplatePath` | `XCODEBUILDMCP_IOS_TEMPLATE_PATH` |
45+
| `iosTemplateVersion` | `XCODEBUILD_MCP_IOS_TEMPLATE_VERSION` |
46+
| `macosTemplatePath` | `XCODEBUILDMCP_MACOS_TEMPLATE_PATH` |
47+
| `macosTemplateVersion` | `XCODEBUILD_MCP_MACOS_TEMPLATE_VERSION` |
48+
49+
### Session default settings
50+
51+
| Session default | Environment variable |
52+
|----------------|---------------------|
53+
| `workspacePath` | `XCODEBUILDMCP_WORKSPACE_PATH` |
54+
| `projectPath` | `XCODEBUILDMCP_PROJECT_PATH` |
55+
| `scheme` | `XCODEBUILDMCP_SCHEME` |
56+
| `configuration` | `XCODEBUILDMCP_CONFIGURATION` |
57+
| `simulatorName` | `XCODEBUILDMCP_SIMULATOR_NAME` |
58+
| `simulatorId` | `XCODEBUILDMCP_SIMULATOR_ID` |
59+
| `simulatorPlatform` | `XCODEBUILDMCP_SIMULATOR_PLATFORM` |
60+
| `deviceId` | `XCODEBUILDMCP_DEVICE_ID` |
61+
| `platform` | `XCODEBUILDMCP_PLATFORM` |
62+
| `useLatestOS` | `XCODEBUILDMCP_USE_LATEST_OS` |
63+
| `arch` | `XCODEBUILDMCP_ARCH` |
64+
| `suppressWarnings` | `XCODEBUILDMCP_SUPPRESS_WARNINGS` |
65+
| `derivedDataPath` | `XCODEBUILDMCP_DERIVED_DATA_PATH` |
66+
| `preferXcodebuild` | `XCODEBUILDMCP_PREFER_XCODEBUILD` |
67+
| `bundleId` | `XCODEBUILDMCP_BUNDLE_ID` |
68+
69+
### Example MCP configs
70+
71+
Use one of these as a starting point and fill in your workspace path and scheme.
72+
73+
**macOS app**
74+
75+
```json
76+
{
77+
"mcpServers": {
78+
"XcodeBuildMCP": {
79+
"command": "npx",
80+
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
81+
"env": {
82+
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "coverage,debugging,doctor,logging,macos,project-discovery,project-scaffolding,swift-package,ui-automation,utilities,xcode-ide",
83+
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
84+
"XCODEBUILDMCP_SCHEME": "MyApp",
85+
"XCODEBUILDMCP_PLATFORM": "macOS"
86+
}
87+
}
88+
}
89+
}
90+
```
91+
92+
> `macos` provides build/run/test/stop tools for macOS apps. No simulator workflow needed — macOS apps run natively.
93+
94+
---
95+
96+
**iOS app**
97+
98+
```json
99+
{
100+
"mcpServers": {
101+
"XcodeBuildMCP": {
102+
"command": "npx",
103+
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
104+
"env": {
105+
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "coverage,debugging,doctor,logging,project-discovery,project-scaffolding,simulator,swift-package,ui-automation,utilities,xcode-ide",
106+
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
107+
"XCODEBUILDMCP_SCHEME": "MyApp",
108+
"XCODEBUILDMCP_PLATFORM": "iOS Simulator",
109+
"XCODEBUILDMCP_SIMULATOR_NAME": "iPhone 16 Pro"
110+
}
111+
}
112+
}
113+
}
114+
```
115+
116+
> `simulator` provides build/run/test/install tools targeting iOS Simulator. Use `XCODEBUILDMCP_SIMULATOR_NAME` or `XCODEBUILDMCP_SIMULATOR_ID` to pin the target device.
117+
118+
---
119+
120+
**iOS + macOS (multi-platform or Catalyst)**
121+
122+
```json
123+
{
124+
"mcpServers": {
125+
"XcodeBuildMCP": {
126+
"command": "npx",
127+
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
128+
"env": {
129+
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "coverage,debugging,doctor,logging,macos,project-discovery,project-scaffolding,simulator,swift-package,ui-automation,utilities,xcode-ide",
130+
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
131+
"XCODEBUILDMCP_SCHEME": "MyApp"
132+
}
133+
}
134+
}
135+
}
136+
```
137+
138+
> Include both `simulator` and `macos` when the project supports multiple platforms. Omit `XCODEBUILDMCP_PLATFORM` to let the agent choose per-command.
139+
140+
---
141+
142+
**tvOS or watchOS app**
143+
144+
```json
145+
{
146+
"mcpServers": {
147+
"XcodeBuildMCP": {
148+
"command": "npx",
149+
"args": ["-y", "xcodebuildmcp@latest", "mcp"],
150+
"env": {
151+
"XCODEBUILDMCP_ENABLED_WORKFLOWS": "debugging,doctor,logging,project-discovery,simulator,swift-package,utilities,xcode-ide",
152+
"XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
153+
"XCODEBUILDMCP_SCHEME": "MyApp",
154+
"XCODEBUILDMCP_PLATFORM": "tvOS Simulator"
155+
}
156+
}
157+
}
158+
}
159+
```
160+
161+
> Replace `tvOS Simulator` with `watchOS Simulator` for watchOS. Coverage and UI automation are not available on these platforms.
162+
163+
---
164+
165+
You can also generate a config block interactively:
166+
167+
```bash
168+
xcodebuildmcp setup --format mcp-json
169+
```
17170

18171
---
19172

20173
## Config file
21174

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

24177
```
25178
<workspace-root>/.xcodebuildmcp/config.yaml
@@ -337,30 +490,19 @@ Notes:
337490

338491
---
339492

340-
## Environment variables (legacy)
493+
## Configuration layering
341494

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

344-
| Config option | Environment variable |
345-
|---------------|---------------------|
346-
| `enabledWorkflows` | `XCODEBUILDMCP_ENABLED_WORKFLOWS` (comma-separated) |
347-
| `experimentalWorkflowDiscovery` | `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY` |
348-
| `disableSessionDefaults` | `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` |
349-
| `incrementalBuildsEnabled` | `INCREMENTAL_BUILDS_ENABLED` |
350-
| `debug` | `XCODEBUILDMCP_DEBUG` |
351-
| `sentryDisabled` | `XCODEBUILDMCP_SENTRY_DISABLED` |
352-
| `debuggerBackend` | `XCODEBUILDMCP_DEBUGGER_BACKEND` |
353-
| `dapRequestTimeoutMs` | `XCODEBUILDMCP_DAP_REQUEST_TIMEOUT_MS` |
354-
| `dapLogEvents` | `XCODEBUILDMCP_DAP_LOG_EVENTS` |
355-
| `launchJsonWaitMs` | `XBMCP_LAUNCH_JSON_WAIT_MS` |
356-
| `uiDebuggerGuardMode` | `XCODEBUILDMCP_UI_DEBUGGER_GUARD_MODE` |
357-
| `axePath` | `XCODEBUILDMCP_AXE_PATH` |
358-
| `iosTemplatePath` | `XCODEBUILDMCP_IOS_TEMPLATE_PATH` |
359-
| `iosTemplateVersion` | `XCODEBUILD_MCP_IOS_TEMPLATE_VERSION` |
360-
| `macosTemplatePath` | `XCODEBUILDMCP_MACOS_TEMPLATE_PATH` |
361-
| `macosTemplateVersion` | `XCODEBUILD_MCP_MACOS_TEMPLATE_VERSION` |
497+
1. **`session_set_defaults` tool** (highest) — agent runtime overrides, set during a session
498+
2. **Config file** — project-local config (`config.yaml`), committed to repo
499+
3. **Environment variables** (lowest) — MCP client integration, set in `mcp_config.json`
500+
501+
This follows the same pattern as tools like `git config` (`--flag` > `--local` > `--global`). Each layer serves a different context:
362502

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

365507
---
366508

src/cli/commands/__tests__/setup.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,91 @@ describe('setup command', () => {
248248
).rejects.toThrow('Setup prerequisites failed');
249249
});
250250

251+
it('outputs MCP config JSON when format is mcp-json', async () => {
252+
const fs = createMockFileSystemExecutor({
253+
existsSync: () => false,
254+
stat: async () => ({ isDirectory: () => true, mtimeMs: 0 }),
255+
readdir: async (targetPath) => {
256+
if (targetPath === cwd) {
257+
return [
258+
{
259+
name: 'App.xcworkspace',
260+
isDirectory: () => true,
261+
isSymbolicLink: () => false,
262+
},
263+
];
264+
}
265+
266+
return [];
267+
},
268+
readFile: async () => '',
269+
writeFile: async () => {},
270+
});
271+
272+
const executor: CommandExecutor = async (command) => {
273+
if (command.includes('--json')) {
274+
return createMockCommandResponse({
275+
success: true,
276+
output: JSON.stringify({
277+
devices: {
278+
'iOS 17.0': [
279+
{
280+
name: 'iPhone 15',
281+
udid: 'SIM-1',
282+
state: 'Shutdown',
283+
isAvailable: true,
284+
},
285+
],
286+
},
287+
}),
288+
});
289+
}
290+
291+
if (command[0] === 'xcrun') {
292+
return createMockCommandResponse({
293+
success: true,
294+
output: `== Devices ==\n-- iOS 17.0 --\n iPhone 15 (SIM-1) (Shutdown)`,
295+
});
296+
}
297+
298+
return createMockCommandResponse({
299+
success: true,
300+
output: `Information about workspace "App":\n Schemes:\n App`,
301+
});
302+
};
303+
304+
const result = await runSetupWizard({
305+
cwd,
306+
fs,
307+
executor,
308+
prompter: createTestPrompter(),
309+
quietOutput: true,
310+
outputFormat: 'mcp-json',
311+
});
312+
313+
expect(result.configPath).toBeUndefined();
314+
expect(result.mcpConfigJson).toBeDefined();
315+
316+
const parsed = JSON.parse(result.mcpConfigJson!) as {
317+
mcpServers: {
318+
XcodeBuildMCP: {
319+
command: string;
320+
args: string[];
321+
env: Record<string, string>;
322+
};
323+
};
324+
};
325+
326+
const serverConfig = parsed.mcpServers.XcodeBuildMCP;
327+
expect(serverConfig.command).toBe('npx');
328+
expect(serverConfig.args).toEqual(['-y', 'xcodebuildmcp@latest', 'mcp']);
329+
expect(serverConfig.env.XCODEBUILDMCP_ENABLED_WORKFLOWS).toBeDefined();
330+
expect(serverConfig.env.XCODEBUILDMCP_WORKSPACE_PATH).toBe(path.join(cwd, 'App.xcworkspace'));
331+
expect(serverConfig.env.XCODEBUILDMCP_SCHEME).toBe('App');
332+
expect(serverConfig.env.XCODEBUILDMCP_SIMULATOR_ID).toBe('SIM-1');
333+
expect(serverConfig.env.XCODEBUILDMCP_SIMULATOR_NAME).toBe('iPhone 15');
334+
});
335+
251336
it('fails in non-interactive mode', async () => {
252337
Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
253338
Object.defineProperty(process.stdout, 'isTTY', { value: false, configurable: true });

0 commit comments

Comments
 (0)