Skip to content

Commit a31f2b4

Browse files
committed
feat(xcodebuildmcp): add support for session-default persistance
Resolves issue #180
1 parent d0a4fff commit a31f2b4

21 files changed

Lines changed: 797 additions & 85 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ bundled/
108108
.mcpli
109109
.factory
110110
DerivedData
111+
.derivedData

docs/CONFIGURATION.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,51 @@ If you prefer the older, explicit style where each tool requires its own paramet
5656

5757
Leave this unset for the streamlined session-aware experience; enable it to force explicit parameters on each tool call.
5858

59+
## Project config (config.yaml)
60+
61+
You can provide deterministic session defaults for every AI coding session by creating a project config file at:
62+
63+
```
64+
<workspace-root>/.xcodebuildmcp/config.yaml
65+
```
66+
67+
Notes:
68+
- Put the file in your **workspace root** (where your Xcode project is located).
69+
- Agents can persist changes by calling `session_set_defaults` with `"persist": true` (see below).
70+
71+
### Schema
72+
73+
```yaml
74+
schemaVersion: 1
75+
sessionDefaults:
76+
projectPath: "./MyApp.xcodeproj" # xor workspacePath
77+
workspacePath: "./MyApp.xcworkspace" # xor projectPath
78+
scheme: "MyApp"
79+
configuration: "Debug"
80+
simulatorName: "iPhone 16" # xor simulatorId
81+
simulatorId: "<UUID>" # xor simulatorName
82+
deviceId: "<UUID>"
83+
useLatestOS: true
84+
arch: "arm64"
85+
suppressWarnings: false
86+
derivedDataPath: "./.derivedData"
87+
preferXcodebuild: false
88+
platform: "iOS"
89+
bundleId: "com.example.myapp"
90+
```
91+
92+
Behavior:
93+
- Relative paths in `projectPath`, `workspacePath`, and `derivedDataPath` resolve against the workspace root at load time.
94+
- If both `projectPath` and `workspacePath` are set, **workspacePath wins**.
95+
- If both `simulatorId` and `simulatorName` are set, **simulatorId wins**.
96+
97+
### Persisting defaults from an agent
98+
99+
By default when the agent calls `session_set_defaults`, defaults are only stored in memory for that session, to persist them to the config file, ask the agent to set the `persist` flag to `true`.
100+
101+
> [!IMPORTANT]
102+
> The write is **patch-only**: only keys provided in that call are written (plus any removals needed for mutual exclusivity).
103+
59104
## Sentry telemetry opt-out
60105

61106
If you do not wish to send error logs to Sentry, set `XCODEBUILDMCP_SENTRY_DISABLED=true`.

docs/SESSION_DEFAULTS.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Session Defaults
22

3-
By default, XcodeBuildMCP uses a session-aware mode. The client sets shared defaults once (simulator, device, project/workspace, scheme, etc.) and all tools reuse them. This reduces schema size and repeated payloads.
3+
By default, XcodeBuildMCP uses a session-aware mode. The client sets shared defaults once (simulator, device, project/workspace, scheme, etc.) and all tools reuse them. This reduces schema size and repeated payloads and ensure a more deterministic experience.
44

55
## How it works
6-
- Call `session_set_defaults` once at the start of a workflow.
6+
- Agent calls `session_set_defaults` once at the start of a workflow.
77
- Tools reuse those defaults automatically.
8-
- Use `session_show_defaults` to inspect current values.
9-
- Use `session_clear_defaults` to clear values when switching contexts.
8+
- Agent can call `session_show_defaults` to inspect current values.
9+
- Agent can call `session_clear_defaults` to clear values when switching contexts.
10+
- Defaults can also be seeded from `.xcodebuildmcp/config.yaml` at server startup.
1011

1112
See the session-management tools in [TOOLS.md](TOOLS.md).
1213

@@ -19,7 +20,14 @@ If you prefer explicit parameters on every tool call, set:
1920
}
2021
```
2122

22-
This restores the legacy schemas with per-call parameters while still honoring any defaults you choose to set.
23+
This restores the legacy schemas with per-call parameters while still honoring any defaults you choose to set. Though this is not recommended, it can be useful in certain scenarios where you are working on monorepos or multiple projects at once.
24+
25+
## Persisting defaults
26+
Session defaults can be persisted between sessions by asking your agent to set the defaults with the `persist` flag set to `true`. This will save the defaults into `.xcodebuildmcp/config.yaml` at the root of your project's workspace.
27+
28+
The persisted config is patch-only (only provided keys are written).
29+
30+
You can also manually create the config file to essentually seed the defaults at startup see [CONFIGURATION.md](CONFIGURATION.md) for more information.
2331

2432
## Related docs
2533
- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)

docs/TOOLS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens
6363

6464
- `clean` - Clean build products.
6565
### session-management (`session-management`)
66-
**Purpose**: Manage session defaults for projectPath/workspacePath, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS and arch. These defaults are required by many tools and must be set before attempting to call tools that would depend on these values. (3 tools)
66+
**Purpose**: Manage session defaults for project/workspace paths, scheme, configuration, simulatorName/simulatorId, deviceId, useLatestOS, arch, suppressWarnings, derivedDataPath, preferXcodebuild, platform, and bundleId. Defaults can be seeded from .xcodebuildmcp/config.yaml at startup. (3 tools)
6767

6868
- `session_clear_defaults` - Clear session defaults.
6969
- `session_set_defaults` - Set the session defaults, should be called at least once to set tool defaults.
@@ -126,4 +126,4 @@ XcodeBuildMCP provides 72 tools organized into 14 workflow groups for comprehens
126126

127127
---
128128

129-
*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2026-01-25*
129+
*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2026-01-27*

docs/dev/PROJECT_CONFIG_PLAN.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Project Config + Session Defaults Plan
2+
3+
## Goal
4+
Add a project-level config file at `.xcodebuildmcp/config.yaml` that:
5+
1. Seeds session defaults at server startup (no client call required).
6+
2. Allows `session-set-defaults` to persist provided defaults back to that config when a flag is set.
7+
8+
Scope is limited to **cwd-only** resolution, **patch-only persistence** (provided keys only), and **warn+ignore** on invalid config.
9+
10+
## Decisions (Confirmed)
11+
- Config location: **only** `process.cwd()/.xcodebuildmcp/config.yaml` (no find-up).
12+
- Persistence: **only** keys provided in the `session-set-defaults` call (plus necessary deletions for mutual exclusivity).
13+
- Invalid config: **warn and ignore**, continue startup.
14+
15+
## Config Format
16+
17+
Proposed YAML:
18+
19+
```yaml
20+
schemaVersion: 1
21+
sessionDefaults:
22+
projectPath: "./MyApp.xcodeproj"
23+
workspacePath: "./MyApp.xcworkspace"
24+
scheme: "MyApp"
25+
configuration: "Debug"
26+
simulatorName: "iPhone 16"
27+
simulatorId: "<UUID>"
28+
deviceId: "<UUID>"
29+
useLatestOS: true
30+
arch: "arm64"
31+
suppressWarnings: false
32+
derivedDataPath: "./.derivedData"
33+
preferXcodebuild: false
34+
platform: "iOS"
35+
bundleId: "com.example.myapp"
36+
```
37+
38+
Notes:
39+
- `schemaVersion` supports future evolution.
40+
- The config file is **not** exclusive to session defaults; future sections (e.g., `server`, `logging`, `discovery`) are expected.
41+
- Relative paths resolve against the workspace root (cwd).
42+
43+
## Precedence (Operational)
44+
We seed the in-memory session defaults from config at startup, so after boot it behaves like normal session defaults.
45+
Operationally, the only precedence that matters during tool calls is:
46+
1. Tool call args (existing behavior in `createSessionAwareTool`).
47+
2. In-memory session defaults (initially seeded from config; can be changed by `session-set-defaults`).
48+
49+
## Implementation Plan
50+
51+
### 1) New shared schema for session defaults
52+
**File:** `src/utils/session-defaults-schema.ts`
53+
- Define a Zod schema that mirrors `SessionDefaults`.
54+
- Used by both config loading and `session-set-defaults` to avoid drift.
55+
56+
### 2) New project config loader/writer
57+
**File:** `src/utils/project-config.ts`
58+
Responsibilities:
59+
- Resolve config path: `path.join(cwd, '.xcodebuildmcp', 'config.yaml')`.
60+
- Read YAML via `FileSystemExecutor`.
61+
- Parse and validate with Zod.
62+
- **Allow unknown top-level keys** (use `.passthrough()` in Zod) so non-session sections can exist without failing validation.
63+
- Normalize mutual exclusivity:
64+
- If both `projectPath` and `workspacePath` are set, keep `workspacePath`.
65+
- If both `simulatorId` and `simulatorName` are set, keep `simulatorId`.
66+
- Resolve relative paths for: `projectPath`, `workspacePath`, `derivedDataPath`.
67+
- Persist changes when requested:
68+
- Merge provided keys into `sessionDefaults`.
69+
- Remove keys that were cleared due to exclusivity.
70+
- Overwrite YAML file (comments not preserved).
71+
72+
Suggested API:
73+
```ts
74+
export type LoadProjectConfigOptions = {
75+
fs: FileSystemExecutor;
76+
cwd: string;
77+
};
78+
79+
export type LoadProjectConfigResult =
80+
| { found: false }
81+
| { found: true; path: string; config: ProjectConfig; notices: string[] };
82+
83+
export async function loadProjectConfig(
84+
options: LoadProjectConfigOptions,
85+
): Promise<LoadProjectConfigResult>;
86+
87+
export type PersistSessionDefaultsOptions = {
88+
fs: FileSystemExecutor;
89+
cwd: string;
90+
patch: Partial<SessionDefaults>;
91+
deleteKeys?: (keyof SessionDefaults)[];
92+
};
93+
94+
export async function persistSessionDefaultsToProjectConfig(
95+
options: PersistSessionDefaultsOptions,
96+
): Promise<{ path: string }>;
97+
```
98+
99+
### 3) Startup injection
100+
**File:** `src/server/bootstrap.ts`
101+
- Accept `fileSystemExecutor` and `cwd` in `BootstrapOptions` (default to `getDefaultFileSystemExecutor()` and `process.cwd()`).
102+
- Load project config at the top of `bootstrapServer()`.
103+
- On success: `sessionStore.setDefaults(normalizedDefaults)`.
104+
- On parse/validation error: log warning and continue.
105+
106+
### 4) Persist flag in `session-set-defaults`
107+
**File:** `src/mcp/tools/session-management/session_set_defaults.ts`
108+
- Extend schema with `persist?: boolean`.
109+
- Use `createTypedToolWithContext` to access `{ fs, cwd }`.
110+
- Apply defaults to `sessionStore` as usual.
111+
- If `persist === true`, call `persistSessionDefaultsToProjectConfig()` with:
112+
- `patch`: only keys provided in the tool call (excluding `persist`).
113+
- `deleteKeys`: keys removed due to exclusivity rules.
114+
- Add a notice in response: `Persisted defaults to <path>`.
115+
116+
### 5) Clear defaults key parity
117+
**File:** `src/mcp/tools/session-management/session_clear_defaults.ts`
118+
- Expand `keys` list to match full `SessionDefaults` surface.
119+
120+
### 6) Documentation updates
121+
- Update `docs/SESSION_DEFAULTS.md` to mention config auto-load + `persist` flag.
122+
- Update tool description in `src/mcp/tools/session-management/index.ts`.
123+
124+
### 7) Dependency
125+
- Add `yaml` package for parsing/serializing.
126+
127+
## Tests
128+
- This change **must** be built TDD (red → green): write failing tests first, then implement code until tests pass.
129+
- Add unit tests for `project-config` loader and persistence using `createMockFileSystemExecutor`.
130+
- Update `session_set_defaults.test.ts` to cover `persist` path and mutual exclusivity deletions.
131+
132+
## Risks / Notes
133+
- Overwriting YAML drops comments and custom formatting.
134+
- Explicitly **cwd-only** prevents automatic discovery from subdirectories.
135+
- Warn+ignore avoids startup failures but can hide misconfigurations; add clear log messaging.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
schemaVersion: 1
2+
sessionDefaults:
3+
workspacePath: /Volumes/Developer/XcodeBuildMCP/example_projects/iOS_Calculator/CalculatorApp.xcworkspace
4+
scheme: CalculatorApp
5+
configuration: Debug
6+
simulatorId: B38FE93D-578B-454B-BE9A-C6FA0CE5F096
7+
useLatestOS: true
8+
arch: arm64
9+
suppressWarnings: false
10+
derivedDataPath: /Volumes/Developer/XcodeBuildMCP/example_projects/iOS_Calculator/.derivedData
11+
preferXcodebuild: true
12+
platform: iOS
13+
bundleId: com.example.calculatorapp

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"@sentry/cli": "^2.43.1",
7474
"@sentry/node": "^10.5.0",
7575
"uuid": "^11.1.0",
76+
"yaml": "^2.4.5",
7677
"zod": "^4.0.0"
7778
},
7879
"devDependencies": {

scripts/analysis/tools-analysis.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ export async function getStaticToolAnalysis(): Promise<StaticAnalysisResult> {
297297
'**/index.ts',
298298
'**/*.test.ts',
299299
'**/lib/**',
300+
'**/shared/**',
300301
'**/*-processes.ts', // Process management utilities
301302
'**/*.deps.ts', // Dependency files
302303
'**/*-utils.ts', // Utility files

skills/xcodebuildmcp/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ If a capability is missing, assume your tool list may be hiding tools (search/pr
1313

1414
### Session defaults
1515

16-
Most tools require session defaults to be set before they can be used, be sure to set all required defaults before tools. You may need to call one or more discovery/list tools to obtain the values needed for certain defaults.
16+
Before you call any other tools, call `session_show_defaults` to show the current defaults, ensure you then fill in the apropiate missing defaults. You may need to call one or more discovery/list tools to obtain the values needed for certain defaults.
1717

1818
- `session_set_defaults`
1919
- Set the session defaults, should be called at least once to set tool defaults.

0 commit comments

Comments
 (0)