Skip to content

Commit 5add48b

Browse files
cameroncookeclaude
andcommitted
fix(session-defaults): Keep runtime config immutable after init
Limit config-store persistence updates to session-default fields so non-session runtime\nsettings stay fixed for process lifetime after server startup.\n\nUpdate session profile tooling/manifests to match the new flow, including\nprofile activation behavior and clearer session-default tool descriptions. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 890c75f commit 5add48b

17 files changed

Lines changed: 381 additions & 51 deletions

docs/SESSION_DEFAULTS.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ You can also manually create the config file to essentially seed the defaults at
2828
## Namespaced profiles
2929
Session defaults support named profiles so one workspace can keep separate defaults for iOS/watchOS/macOS (or any custom profile names).
3030

31-
- Use `session_use_defaults_profile` to switch the active profile.
31+
- Use `session_use_defaults_profile` to switch the active profile (existing profiles only).
3232
- Existing tools (`session_set_defaults`, `session_show_defaults`, build/test tools) use the active profile automatically.
33+
- `session_set_defaults` can also accept `profile` to switch and set in one call; use `createIfNotExists: true` to create a new profile intentionally.
34+
- Profiles are strictly isolated: values are not inherited from global defaults or other profiles.
35+
- The unnamed global defaults profile exists for backwards compatibility and is the default active profile when no named profile is selected.
36+
- There is always an active profile context: either a named profile or `global`.
3337
- Use `global: true` to switch back to the unnamed global profile.
3438
- Set `persist: true` on `session_use_defaults_profile` to write `activeSessionDefaultsProfile` in `.xcodebuildmcp/config.yaml`.
3539

@@ -59,6 +63,11 @@ Switch targets later in the same session:
5963
}}
6064
```
6165

66+
Isolation example:
67+
- Global profile has `workspacePath: /repo/MyApp.xcworkspace`
68+
- Active profile is `watch` with only `scheme` set
69+
- Result: `watch` does not see global `workspacePath` until you set it on `watch` or switch back to `global`
70+
6271
## Related docs
6372
- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
6473
- Tools reference: [TOOLS.md](TOOLS.md)

docs/TOOLS-CLI.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,4 @@ XcodeBuildMCP provides 73 canonical tools organized into 13 workflow groups.
189189

190190
---
191191

192-
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-17T16:38:52.535Z UTC*
192+
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-17T21:23:33.036Z UTC*

docs/TOOLS.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,10 @@ This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP prov
128128
### Session Management (`session-management`)
129129
**Purpose**: Manage session defaults for project/workspace paths, scheme, configuration, simulator/device settings. (5 tools)
130130

131-
- `session_clear_defaults` - Clear session defaults.
132-
- `session_set_defaults` - Set the session defaults, should be called at least once to set tool defaults.
133-
- `session_show_defaults` - Show session defaults.
134-
- `session_use_defaults_profile` - Select the active session defaults profile for multi-target workflows.
131+
- `session_clear_defaults` - Clear session defaults for the active profile or a specified profile.
132+
- `session_set_defaults` - Set session defaults for the active profile, or for a specified profile and make it active.
133+
- `session_show_defaults` - Show the current active defaults.
134+
- `session_use_defaults_profile` - Switch the active session defaults profile.
135135
- `sync_xcode_defaults` - Sync session defaults (scheme, simulator) from Xcode's current IDE selection.
136136

137137

@@ -205,4 +205,4 @@ This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP prov
205205

206206
---
207207

208-
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-17T16:38:52.535Z UTC*
208+
*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-02-17T21:23:33.036Z UTC*

manifests/tools/session_clear_defaults.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module: mcp/tools/session-management/session_clear_defaults
33
names:
44
mcp: session_clear_defaults
55
cli: clear-defaults
6-
description: Clear session defaults.
6+
description: Clear session defaults for the active profile or a specified profile.
77
annotations:
88
title: Clear Session Defaults
99
destructiveHint: true

manifests/tools/session_set_defaults.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module: mcp/tools/session-management/session_set_defaults
33
names:
44
mcp: session_set_defaults
55
cli: set-defaults
6-
description: Set the session defaults, should be called at least once to set tool defaults.
6+
description: Set session defaults for the active profile, or for a specified profile and make it active.
77
annotations:
88
title: Set Session Defaults
99
destructiveHint: true

manifests/tools/session_show_defaults.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module: mcp/tools/session-management/session_show_defaults
33
names:
44
mcp: session_show_defaults
55
cli: show-defaults
6-
description: Show session defaults.
6+
description: Show the current active defaults.
77
annotations:
88
title: Show Session Defaults
99
readOnlyHint: true

manifests/tools/session_use_defaults_profile.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module: mcp/tools/session-management/session_use_defaults_profile
33
names:
44
mcp: session_use_defaults_profile
55
cli: use-defaults-profile
6-
description: Select the active session defaults profile for multi-target workflows.
6+
description: Switch the active session defaults profile.
77
annotations:
88
title: Use Session Defaults Profile
9-
readOnlyHint: true
9+
readOnlyHint: false

skills/xcodebuildmcp/SKILL.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ If a capability is missing, assume your tool list may be hiding tools (search/pr
2020

2121
### Session defaults
2222

23-
Before you call any other tools, you **must** call `session_show_defaults` to show the current defaults, ensure you then fill in the appropriate missing defaults. You may need to call one or more discovery/list tools to obtain the values needed.
23+
Before you call any other tools, you **must** call `session_show_defaults` to show the current defaults, then fill in any missing defaults. You may need discovery/list tools first to obtain valid values.
2424

25-
- `session_set_defaults`
26-
- Set the session defaults, should be called at least once to set tool defaults.
2725
- `session_show_defaults`
28-
- Show session defaults.
26+
- Show the current active defaults (including the active profile name).
27+
- `session_set_defaults`
28+
- Set defaults for the current active profile, or set defaults for a specific profile via `profile`.
29+
- `session_use_defaults_profile`
30+
- Switch the active defaults profile.
2931
- `session_clear_defaults`
30-
- Clear session defaults.
32+
- Clear defaults (current active profile by default, or a specific profile when provided).
3133

3234
### Project discovery
3335

src/mcp/tools/session-management/__tests__/session_set_defaults.test.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,58 @@ describe('session-set-defaults tool', () => {
258258
expect(parsed.sessionDefaults?.simulatorName).toBeUndefined();
259259
});
260260

261-
it('should persist into the active named profile when selected', async () => {
261+
it('sets defaults on existing named profile and activates it', async () => {
262+
sessionStore.setActiveProfile('ios');
263+
sessionStore.setDefaults({ scheme: 'OldIOS' });
264+
sessionStore.setActiveProfile(null);
265+
266+
const result = await sessionSetDefaultsLogic(
267+
{
268+
profile: 'ios',
269+
scheme: 'NewIOS',
270+
simulatorName: 'iPhone 16',
271+
},
272+
createContext(),
273+
);
274+
275+
expect(result.isError).toBe(false);
276+
expect(result.content[0].text).toContain('Activated profile "ios".');
277+
expect(sessionStore.getActiveProfile()).toBe('ios');
278+
expect(sessionStore.getAll().scheme).toBe('NewIOS');
279+
expect(sessionStore.getAll().simulatorName).toBe('iPhone 16');
280+
});
281+
282+
it('returns error when profile does not exist and createIfNotExists is false', async () => {
283+
const result = await sessionSetDefaultsLogic(
284+
{
285+
profile: 'missing',
286+
scheme: 'NewIOS',
287+
},
288+
createContext(),
289+
);
290+
291+
expect(result.isError).toBe(true);
292+
expect(result.content[0].text).toContain('Profile "missing" does not exist');
293+
expect(result.content[0].text).toContain('createIfNotExists=true');
294+
});
295+
296+
it('creates profile when createIfNotExists is true and activates it', async () => {
297+
const result = await sessionSetDefaultsLogic(
298+
{
299+
profile: 'ios',
300+
createIfNotExists: true,
301+
scheme: 'NewIOS',
302+
},
303+
createContext(),
304+
);
305+
306+
expect(result.isError).toBe(false);
307+
expect(result.content[0].text).toContain('Created and activated profile "ios".');
308+
expect(sessionStore.getActiveProfile()).toBe('ios');
309+
expect(sessionStore.getAll().scheme).toBe('NewIOS');
310+
});
311+
312+
it('persists defaults and active profile when profile is provided', async () => {
262313
const yaml = [
263314
'schemaVersion: 1',
264315
'sessionDefaultsProfiles:',
@@ -268,37 +319,43 @@ describe('session-set-defaults tool', () => {
268319
].join('\n');
269320

270321
const writes: { path: string; content: string }[] = [];
322+
let persistedYaml = yaml;
271323
const fs = createMockFileSystemExecutor({
272324
existsSync: (targetPath: string) => targetPath === configPath,
273325
readFile: async (targetPath: string) => {
274326
if (targetPath !== configPath) {
275327
throw new Error(`Unexpected readFile path: ${targetPath}`);
276328
}
277-
return yaml;
329+
return persistedYaml;
278330
},
279331
writeFile: async (targetPath: string, content: string) => {
280332
writes.push({ path: targetPath, content });
333+
persistedYaml = content;
281334
},
282335
});
283336

284337
await initConfigStore({ cwd, fs });
285338
sessionStore.setActiveProfile('ios');
339+
sessionStore.setActiveProfile(null);
286340

287341
await sessionSetDefaultsLogic(
288342
{
343+
profile: 'ios',
289344
scheme: 'NewIOS',
290345
simulatorName: 'iPhone 16',
291346
persist: true,
292347
},
293348
createContext(),
294349
);
295350

296-
expect(writes.length).toBe(1);
297-
const parsed = parseYaml(writes[0].content) as {
351+
expect(writes.length).toBe(2);
352+
const parsed = parseYaml(writes[writes.length - 1].content) as {
298353
sessionDefaultsProfiles?: Record<string, Record<string, unknown>>;
354+
activeSessionDefaultsProfile?: string;
299355
};
300356
expect(parsed.sessionDefaultsProfiles?.ios?.scheme).toBe('NewIOS');
301357
expect(parsed.sessionDefaultsProfiles?.ios?.simulatorName).toBe('iPhone 16');
358+
expect(parsed.activeSessionDefaultsProfile).toBe('ios');
302359
});
303360

304361
it('should not persist when persist is true but no defaults were provided', async () => {

src/mcp/tools/session-management/__tests__/session_use_defaults_profile.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ describe('session-use-defaults-profile tool', () => {
2525
expect(typeof schema).toBe('object');
2626
});
2727

28-
it('activates a named profile', async () => {
28+
it('activates an existing named profile', async () => {
29+
sessionStore.setActiveProfile('ios');
30+
sessionStore.setActiveProfile(null);
31+
2932
const result = await sessionUseDefaultsProfileLogic({ profile: 'ios' });
3033
expect(result.isError).toBe(false);
3134
expect(sessionStore.getActiveProfile()).toBe('ios');
@@ -45,12 +48,18 @@ describe('session-use-defaults-profile tool', () => {
4548
expect(result.content[0].text).toContain('either global=true or profile');
4649
});
4750

48-
it('returns error when profile is missing and create=false', async () => {
49-
const result = await sessionUseDefaultsProfileLogic({ profile: 'macos', create: false });
51+
it('returns error when profile does not exist', async () => {
52+
const result = await sessionUseDefaultsProfileLogic({ profile: 'macos' });
5053
expect(result.isError).toBe(true);
5154
expect(result.content[0].text).toContain('does not exist');
5255
});
5356

57+
it('returns error when profile name is blank after trimming', async () => {
58+
const result = await sessionUseDefaultsProfileLogic({ profile: ' ' });
59+
expect(result.isError).toBe(true);
60+
expect(result.content[0].text).toContain('Profile name cannot be empty');
61+
});
62+
5463
it('returns status for empty args', async () => {
5564
const result = await sessionUseDefaultsProfileLogic({});
5665
expect(result.isError).toBe(false);
@@ -68,6 +77,9 @@ describe('session-use-defaults-profile tool', () => {
6877
});
6978
await initConfigStore({ cwd, fs });
7079

80+
sessionStore.setActiveProfile('ios');
81+
sessionStore.setActiveProfile(null);
82+
7183
const result = await sessionUseDefaultsProfileLogic({ profile: 'ios', persist: true });
7284
expect(result.isError).toBe(false);
7385
expect(result.content[0].text).toContain('Persisted active profile selection');

0 commit comments

Comments
 (0)