Skip to content
Merged
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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ For clients that don't support MCP Sampling but still want to reduce context win
**Available Workflows:**
- `device` (14 tools) - iOS Device Development
- `simulator` (18 tools) - iOS Simulator Development
- `simulator-management` (7 tools) - Simulator Management
- `simulator-management` (8 tools) - Simulator Management
- `swift-package` (6 tools) - Swift Package Manager
- `project-discovery` (5 tools) - Project Discovery
- `macos` (11 tools) - macOS Development
Expand Down Expand Up @@ -371,4 +371,3 @@ See our documentation for development:
## Licence

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

11 changes: 6 additions & 5 deletions docs/TOOLS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# XcodeBuildMCP Tools Reference

XcodeBuildMCP provides 59 tools organized into 12 workflow groups for comprehensive Apple development workflows.
XcodeBuildMCP provides 60 tools organized into 12 workflow groups for comprehensive Apple development workflows.

## Workflow Groups

Expand Down Expand Up @@ -64,10 +64,11 @@ XcodeBuildMCP provides 59 tools organized into 12 workflow groups for comprehens
### Project Utilities (`utilities`)
**Purpose**: Essential project maintenance utilities for cleaning and managing existing projects. Provides clean operations for both .xcodeproj and .xcworkspace files. (1 tools)

- `clean` - Cleans build products for either a project or a workspace using xcodebuild. Provide exactly one of projectPath or workspacePath. Example: clean({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })
- `clean` - Cleans build products for either a project or a workspace using xcodebuild. Provide exactly one of projectPath or workspacePath. Platform defaults to iOS if not specified. Example: clean({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme', platform: 'iOS' })
### Simulator Management (`simulator-management`)
**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance. (4 tools)
**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (5 tools)

- `erase_sims` - Erases simulator content and settings. Provide exactly one of: simulatorUuid or all=true. Optional: shutdownFirst to shut down before erasing.
- `reset_sim_location` - Resets the simulator's location to default.
- `set_sim_appearance` - Sets the appearance mode (dark/light) of an iOS simulator.
- `set_sim_location` - Sets a custom GPS location for the simulator.
Expand Down Expand Up @@ -102,9 +103,9 @@ XcodeBuildMCP provides 59 tools organized into 12 workflow groups for comprehens

## Summary Statistics

- **Total Tools**: 59 canonical tools + 22 re-exports = 81 total
- **Total Tools**: 60 canonical tools + 22 re-exports = 82 total
- **Workflow Groups**: 12

---

*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2025-08-16*
*This documentation is automatically generated by `scripts/update-tools-docs.ts` using static analysis. Last updated: 2025-09-21*
116 changes: 116 additions & 0 deletions src/mcp/tools/simulator-management/__tests__/erase_sims.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import eraseSims, { erase_simsLogic } from '../erase_sims.ts';
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';

describe('erase_sims tool (UDID or ALL only)', () => {
describe('Export Field Validation (Literal)', () => {
it('should have correct name', () => {
expect(eraseSims.name).toBe('erase_sims');
});

it('should have correct description', () => {
expect(eraseSims.description).toContain('Provide exactly one of: simulatorUdid or all=true');
expect(eraseSims.description).toContain('shutdownFirst');
});

it('should have handler function', () => {
expect(typeof eraseSims.handler).toBe('function');
});

it('should validate schema fields (shape only)', () => {
const schema = z.object(eraseSims.schema);
// Valid
expect(
schema.safeParse({ simulatorUdid: '123e4567-e89b-12d3-a456-426614174000' }).success,
).toBe(true);
expect(schema.safeParse({ all: true }).success).toBe(true);
// Shape-level schema does not enforce selection rules; handler validation covers that.
});
});

describe('Single mode', () => {
it('erases a simulator successfully', async () => {
const mock = createMockExecutor({ success: true, output: 'OK' });
const res = await erase_simsLogic({ simulatorUdid: 'UD1' }, mock);
expect(res).toEqual({
content: [{ type: 'text', text: 'Successfully erased simulator UD1' }],
});
});

it('returns failure when erase fails', async () => {
const mock = createMockExecutor({ success: false, error: 'Booted device' });
const res = await erase_simsLogic({ simulatorUdid: 'UD1' }, mock);
expect(res).toEqual({
content: [{ type: 'text', text: 'Failed to erase simulator: Booted device' }],
});
});

it('adds tool hint when booted error occurs without shutdownFirst', async () => {
const bootedError =
'An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=405):\nUnable to erase contents and settings in current state: Booted\n';
const mock = createMockExecutor({ success: false, error: bootedError });
const res = await erase_simsLogic({ simulatorUdid: 'UD1' }, mock);
expect((res.content?.[1] as any).text).toContain('Tool hint');
expect((res.content?.[1] as any).text).toContain('shutdownFirst: true');
});

it('performs shutdown first when shutdownFirst=true', async () => {
const calls: any[] = [];
const exec = async (cmd: string[]) => {
calls.push(cmd);
return { success: true, output: 'OK', error: '', process: { pid: 1 } as any };
};
const res = await erase_simsLogic({ simulatorUdid: 'UD1', shutdownFirst: true }, exec as any);
expect(calls).toEqual([
['xcrun', 'simctl', 'shutdown', 'UD1'],
['xcrun', 'simctl', 'erase', 'UD1'],
]);
expect(res).toEqual({
content: [{ type: 'text', text: 'Successfully erased simulator UD1' }],
});
});
});

describe('All mode', () => {
it('erases all simulators successfully', async () => {
const exec = createMockExecutor({ success: true, output: 'OK' });
const res = await erase_simsLogic({ all: true }, exec);
expect(res).toEqual({
content: [{ type: 'text', text: 'Successfully erased all simulators' }],
});
});

it('returns failure when erase all fails', async () => {
const exec = createMockExecutor({ success: false, error: 'Denied' });
const res = await erase_simsLogic({ all: true }, exec);
expect(res).toEqual({
content: [{ type: 'text', text: 'Failed to erase all simulators: Denied' }],
});
});

it('performs shutdown all when shutdownFirst=true', async () => {
const calls: any[] = [];
const exec = async (cmd: string[]) => {
calls.push(cmd);
return { success: true, output: 'OK', error: '', process: { pid: 1 } as any };
};
const res = await erase_simsLogic({ all: true, shutdownFirst: true }, exec as any);
expect(calls).toEqual([
['xcrun', 'simctl', 'shutdown', 'all'],
['xcrun', 'simctl', 'erase', 'all'],
]);
expect(res).toEqual({
content: [{ type: 'text', text: 'Successfully erased all simulators' }],
});
});

it('adds tool hint on booted error without shutdownFirst (all mode)', async () => {
const bootedError = 'Unable to erase contents and settings in current state: Booted';
const exec = createMockExecutor({ success: false, error: bootedError });
const res = await erase_simsLogic({ all: true }, exec);
expect((res.content?.[1] as any).text).toContain('Tool hint');
expect((res.content?.[1] as any).text).toContain('shutdownFirst: true');
});
});
});
3 changes: 2 additions & 1 deletion src/mcp/tools/simulator-management/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('simulator-management workflow metadata', () => {

it('should have correct description', () => {
expect(workflow.description).toBe(
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance.',
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance.',
);
});

Expand All @@ -46,6 +46,7 @@ describe('simulator-management workflow metadata', () => {
'location',
'network',
'statusbar',
'erase',
]);
});
});
Expand Down
136 changes: 136 additions & 0 deletions src/mcp/tools/simulator-management/erase_sims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { z } from 'zod';
import { ToolResponse, type ToolResponseContent } from '../../../types/common.ts';
import { log } from '../../../utils/logging/index.ts';
import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';

const eraseSimsBaseSchema = z.object({
simulatorUdid: z.string().uuid().optional().describe('UDID of the simulator to erase.'),
all: z.boolean().optional().describe('When true, erases all simulators.'),
shutdownFirst: z
.boolean()
.optional()
.describe('If true, shuts down the target (UDID or all) before erasing.'),
Comment on lines +10 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Spec mismatch: shutdownFirst included but PR objectives say extras are intentionally omitted.

Either (a) remove shutdownFirst and associated logic, or (b) keep it but update PR/docs/workflow to advertise the option. Current state diverges from stated scope.

Option A — remove the option (sample of necessary edits):

-  shutdownFirst: z
-    .boolean()
-    .optional()
-    .describe('If true, shuts down the target (UDID or all) before erasing.'),
-      if (params.shutdownFirst) {
-        try {
-          await executor(['xcrun', 'simctl', 'shutdown', udid], 'Shutdown Simulator', true, undefined);
-        } catch { /* ignore */ }
-      }
-      log('info', `Erasing ALL simulators${params.shutdownFirst ? ' (shutdownFirst=true)' : ''}`);
-      if (params.shutdownFirst) {
-        try {
-          await executor(['xcrun', 'simctl', 'shutdown', 'all'], 'Shutdown All Simulators', true, undefined);
-        } catch { /* ignore */ }
-      }
+      log('info', 'Erasing ALL simulators');
-        if (
-          /Unable to erase contents and settings.*Booted/i.test(errText) &&
-          !params.shutdownFirst
-        ) {
+        if (/Unable to erase contents and settings.*Booted/i.test(errText)) {
           content.push({
             type: 'text',
-            text: 'Tool hint: One or more simulators appear to be Booted. Re-run erase_sims with { all: true, shutdownFirst: true } to shut them down before erasing.',
+            text: 'Tool hint: One or more simulators appear to be Booted. Shut them down and retry (xcrun simctl shutdown <UDID>|all).',
           });
         }
-    'Erases simulator content and settings. Provide exactly one of: simulatorUdid or all=true. Optional: shutdownFirst to shut down before erasing.',
+    'Erases simulator content and settings. Provide exactly one of: simulatorUdid or all=true.',

If you prefer Option B, please update PR description/docs accordingly. Let me know and I’ll draft the doc changes.

Also applies to: 38-49, 80-93, 105-111, 131-131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
if command -v rg >/dev/null 2>&1; then
  SEARCH="rg -n --hidden --no-ignore -S"
else
  SEARCH="grep -RIn --binary-files=without-match"
fi

echo "=== Searching for 'shutdownFirst' across repo ==="
$SEARCH "shutdownFirst" || true

echo
echo "=== Showing src/mcp/tools/simulator-management/erase_sims.ts (first 300 lines) if it exists ==="
FILE="src/mcp/tools/simulator-management/erase_sims.ts"
if [ -f "$FILE" ]; then
  nl -ba "$FILE" | sed -n '1,300p'
else
  echo "File not found: $FILE"
fi

echo
echo "=== Searching for other relevant strings ==="
$SEARCH "Unable to erase contents and settings" || true
$SEARCH "Erases simulator content" || true
$SEARCH "shutdown" "$FILE" -n -S || true

Length of output: 3106


Spec mismatch — remove shutdownFirst or update PR/docs

shutdownFirst is present in code and tests, which contradicts the PR objective to omit extras. Either remove the option and all related logic/messages (and update tests), or keep it and update the PR description/docs/workflow to advertise the option.

Locations to fix:

  • src/mcp/tools/simulator-management/erase_sims.ts: lines 10 (schema), 35–38 (per-UDID log/shutdown), 61–69 (booted-error hint), 81–82 (erase ALL log/shutdown), 106–110 (all-mode booted hint), 131 (description).
  • src/mcp/tools/simulator-management/tests/erase_sims.test.ts: lines 14, 47, 53–56, 62, 90, 96, 106, 111.

Choose A (remove option + update tests) or B (keep option + update PR/docs/tests) and update accordingly.

🤖 Prompt for AI Agents
In src/mcp/tools/simulator-management/erase_sims.ts (lines 10, 35–38, 61–69,
81–82, 106–110, 131) and tests
src/mcp/tools/simulator-management/__tests__/erase_sims.test.ts (lines 14, 47,
53–56, 62, 90, 96, 106, 111): remove the shutdownFirst schema option and all
runtime handling/logging/conditional shutdown logic and hints that reference it
(delete the z.boolean() entry at line 10, remove any per-UDID and "erase ALL"
shutdown calls and the booted-error/booted hint branches, and update the
top-level description at line 131 to omit shutdownFirst). Then update tests to
stop setting or asserting shutdownFirst behavior and remove expectations about
shutdown-related logs or actions; adjust test inputs and assertions accordingly
so they reflect the simplified erase behavior without shutdownFirst.

});

const eraseSimsSchema = eraseSimsBaseSchema.refine(
(v) => {
const selectors = (v.simulatorUdid ? 1 : 0) + (v.all === true ? 1 : 0);
return selectors === 1;
},
{ message: 'Provide exactly one of: simulatorUdid or all=true.' },
);

type EraseSimsParams = z.infer<typeof eraseSimsSchema>;

export async function erase_simsLogic(
params: EraseSimsParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
try {
if (params.simulatorUdid) {
const udid = params.simulatorUdid;
log(
'info',
`Erasing simulator ${udid}${params.shutdownFirst ? ' (shutdownFirst=true)' : ''}`,
);

if (params.shutdownFirst) {
try {
await executor(
['xcrun', 'simctl', 'shutdown', udid],
'Shutdown Simulator',
true,
undefined,
);
} catch {
// ignore shutdown errors; proceed to erase attempt
}
}

const result = await executor(
['xcrun', 'simctl', 'erase', udid],
'Erase Simulator',
true,
undefined,
);
if (result.success) {
return { content: [{ type: 'text', text: `Successfully erased simulator ${udid}` }] };
}

// Add tool hint if simulator is booted and shutdownFirst was not requested
const errText = result.error ?? 'Unknown error';
if (/Unable to erase contents and settings.*Booted/i.test(errText) && !params.shutdownFirst) {
return {
content: [
{ type: 'text', text: `Failed to erase simulator: ${errText}` },
{
type: 'text',
text: `Tool hint: The simulator appears to be Booted. Re-run erase_sims with { simulatorUdid: '${udid}', shutdownFirst: true } to shut it down before erasing.`,
},
],
};
}

return {
content: [{ type: 'text', text: `Failed to erase simulator: ${errText}` }],
};
}

if (params.all === true) {
log('info', `Erasing ALL simulators${params.shutdownFirst ? ' (shutdownFirst=true)' : ''}`);
if (params.shutdownFirst) {
try {
await executor(
['xcrun', 'simctl', 'shutdown', 'all'],
'Shutdown All Simulators',
true,
undefined,
);
} catch {
// ignore and continue to erase
}
}

const result = await executor(
['xcrun', 'simctl', 'erase', 'all'],
'Erase All Simulators',
true,
undefined,
);
if (!result.success) {
const errText = result.error ?? 'Unknown error';
const content: ToolResponseContent[] = [
{ type: 'text', text: `Failed to erase all simulators: ${errText}` },
];
if (
/Unable to erase contents and settings.*Booted/i.test(errText) &&
!params.shutdownFirst
) {
content.push({
type: 'text',
text: 'Tool hint: One or more simulators appear to be Booted. Re-run erase_sims with { all: true, shutdownFirst: true } to shut them down before erasing.',
});
}
return { content };
}
return { content: [{ type: 'text', text: 'Successfully erased all simulators' }] };
}

return {
content: [{ type: 'text', text: 'Invalid parameters: provide simulatorUdid or all=true.' }],
};
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
log('error', `Error erasing simulators: ${message}`);
return { content: [{ type: 'text', text: `Failed to erase simulators: ${message}` }] };
}
}

export default {
name: 'erase_sims',
description:
'Erases simulator content and settings. Provide exactly one of: simulatorUdid or all=true. Optional: shutdownFirst to shut down before erasing.',
schema: eraseSimsBaseSchema.shape,
handler: createTypedTool(eraseSimsSchema, erase_simsLogic, getDefaultCommandExecutor),
};
7 changes: 4 additions & 3 deletions src/mcp/tools/simulator-management/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
* Simulator Management workflow
*
* Provides tools for working with simulators like booting and opening simulators, launching apps,
* listing sims, stopping apps and setting sim environment options like location, network, statusbar and appearance.
* listing sims, stopping apps, erasing simulator content and settings, and setting sim environment
* options like location, network, statusbar and appearance.
*/

export const workflow = {
name: 'Simulator Management',
description:
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance.',
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance.',
platforms: ['iOS'],
targets: ['simulator'],
projectTypes: ['project', 'workspace'],
capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar'],
capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar', 'erase'],
};
Loading