From 01413746e66a42da25366286d0b58ab105fba3d3 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Thu, 7 Aug 2025 23:13:37 +0100 Subject: [PATCH 1/3] fix: improve dynamic tool discovery for not development tasks --- docs/TOOLS.md | 10 +- scripts/tools-cli.js | 110 +++++++++--------- src/mcp/tools/discovery/discover_tools.ts | 41 ++++--- .../__tests__/index.test.ts | 90 -------------- src/mcp/tools/simulator-environment/index.ts | 17 --- .../__tests__/index.test.ts | 52 +++++++++ .../reset_simulator_location.test.ts | 2 +- .../__tests__/set_sim_appearance.test.ts | 2 +- .../__tests__/set_simulator_location.test.ts | 0 .../__tests__/sim_statusbar.test.ts | 6 +- .../tools/simulator-management/boot_sim.ts | 2 + src/mcp/tools/simulator-management/index.ts | 16 +++ .../tools/simulator-management/list_sims.ts | 2 + .../tools/simulator-management/open_sim.ts | 2 + .../reset_simulator_location.ts | 8 +- .../set_sim_appearance.ts | 6 +- .../set_simulator_location.ts | 6 +- .../sim_statusbar.ts | 6 +- 18 files changed, 174 insertions(+), 204 deletions(-) delete mode 100644 src/mcp/tools/simulator-environment/__tests__/index.test.ts delete mode 100644 src/mcp/tools/simulator-environment/index.ts create mode 100644 src/mcp/tools/simulator-management/__tests__/index.test.ts rename src/mcp/tools/{simulator-environment => simulator-management}/__tests__/reset_simulator_location.test.ts (97%) rename src/mcp/tools/{simulator-environment => simulator-management}/__tests__/set_sim_appearance.test.ts (97%) rename src/mcp/tools/{simulator-environment => simulator-management}/__tests__/set_simulator_location.test.ts (100%) rename src/mcp/tools/{simulator-environment => simulator-management}/__tests__/sim_statusbar.test.ts (98%) create mode 100644 src/mcp/tools/simulator-management/boot_sim.ts create mode 100644 src/mcp/tools/simulator-management/index.ts create mode 100644 src/mcp/tools/simulator-management/list_sims.ts create mode 100644 src/mcp/tools/simulator-management/open_sim.ts rename src/mcp/tools/{simulator-environment => simulator-management}/reset_simulator_location.ts (95%) rename src/mcp/tools/{simulator-environment => simulator-management}/set_sim_appearance.ts (97%) rename src/mcp/tools/{simulator-environment => simulator-management}/set_simulator_location.ts (97%) rename src/mcp/tools/{simulator-environment => simulator-management}/sim_statusbar.ts (97%) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 9611031d..6b9b71db 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -161,13 +161,15 @@ XcodeBuildMCP uses a **workflow-based architecture** with tools organized into - `touch` - Perform touch down/up events at specific coordinates - `type_text` - Type text (supports US keyboard characters) -#### 11. Simulator Environment Configuration (`simulator-environment`) -**Purpose**: Simulator environment and configuration management (5 tools) -- `reset_network_condition` - Resets network conditions to default in the simulator +#### 11. Simulator Management (`simulator-management`) +**Purpose**: Manage simulators and their environment (7 tools) +- `boot_sim` - Boots an iOS simulator using its UUID +- `list_sims` - Lists available iOS simulators with their UUIDs +- `open_sim` - Opens the iOS Simulator app - `reset_simulator_location` - Resets the simulator's location to default -- `set_network_condition` - Simulates different network conditions in the simulator - `set_sim_appearance` - Sets the appearance mode (dark/light) of an iOS simulator - `set_simulator_location` - Sets a custom GPS location for the simulator +- `sim_statusbar` - Sets the data network indicator and status bar overrides in the iOS simulator #### 12. Logging & Monitoring (`logging`) **Purpose**: Log capture and monitoring across platforms (4 tools) diff --git a/scripts/tools-cli.js b/scripts/tools-cli.js index 19573445..2609ca54 100755 --- a/scripts/tools-cli.js +++ b/scripts/tools-cli.js @@ -204,14 +204,14 @@ if (command === 'help' || command === 'h') { */ async function executeReloaderoo(reloaderooArgs) { const buildPath = path.resolve(__dirname, '..', 'build', 'index.js'); - + if (!fs.existsSync(buildPath)) { throw new Error('Build not found. Please run "npm run build" first.'); } - + const tempFile = `/tmp/reloaderoo-output-${Date.now()}.json`; - const command = `npx reloaderoo@latest inspect ${reloaderooArgs.join(' ')} -- node "${buildPath}"`; - + const command = `npx -y reloaderoo@latest inspect ${reloaderooArgs.join(' ')} -- node "${buildPath}"`; + return new Promise((resolve, reject) => { const child = spawn('bash', ['-c', `${command} > "${tempFile}"`], { stdio: 'inherit' @@ -225,22 +225,22 @@ async function executeReloaderoo(reloaderooArgs) { } const content = fs.readFileSync(tempFile, 'utf8'); - + // Remove stderr log lines and find JSON const lines = content.split('\n'); const cleanLines = []; - + for (const line of lines) { if (line.match(/^\[\d{4}-\d{2}-\d{2}T/) || line.includes('[INFO]') || line.includes('[DEBUG]') || line.includes('[ERROR]')) { continue; } - + const trimmed = line.trim(); if (trimmed) { cleanLines.push(line); } } - + // Find JSON start let jsonStartIndex = -1; for (let i = 0; i < cleanLines.length; i++) { @@ -249,12 +249,12 @@ async function executeReloaderoo(reloaderooArgs) { break; } } - + if (jsonStartIndex === -1) { reject(new Error(`No JSON response found in output.\nOutput: ${content.substring(0, 500)}...`)); return; } - + const jsonText = cleanLines.slice(jsonStartIndex).join('\n'); const response = JSON.parse(jsonText); resolve(response); @@ -282,21 +282,21 @@ async function getRuntimeInfo() { try { const toolsResponse = await executeReloaderoo(['list-tools']); const resourcesResponse = await executeReloaderoo(['list-resources']); - + let tools = []; let toolCount = 0; - + if (toolsResponse.tools && Array.isArray(toolsResponse.tools)) { toolCount = toolsResponse.tools.length; - tools = toolsResponse.tools.map(tool => ({ + tools = toolsResponse.tools.map(tool => ({ name: tool.name, description: tool.description })); } - + let resources = []; let resourceCount = 0; - + if (resourcesResponse.resources && Array.isArray(resourcesResponse.resources)) { resourceCount = resourcesResponse.resources.length; resources = resourcesResponse.resources.map(resource => ({ @@ -305,7 +305,7 @@ async function getRuntimeInfo() { description: resource.title || resource.description || 'No description available' })); } - + return { tools, resources, @@ -328,11 +328,11 @@ function isReExportFile(filePath) { const lines = content.split('\n').map(line => line.trim()); const codeLines = lines.filter(line => { - return line.length > 0 && - !line.startsWith('//') && - !line.startsWith('/*') && - !line.startsWith('*') && - line !== '*/'; + return line.length > 0 && + !line.startsWith('//') && + !line.startsWith('/*') && + !line.startsWith('*') && + line !== '*/'; }); if (codeLines.length === 0) { @@ -352,7 +352,7 @@ function isReExportFile(filePath) { function getWorkflowDirectories() { const workflowDirs = []; const entries = fs.readdirSync(toolsDir, { withFileTypes: true }); - + for (const entry of entries) { if (entry.isDirectory()) { const indexPath = path.join(toolsDir, entry.name, 'index.ts'); @@ -361,7 +361,7 @@ function getWorkflowDirectories() { } } } - + return workflowDirs; } @@ -372,7 +372,7 @@ async function getStaticInfo() { try { // Get workflow directories const workflowDirs = getWorkflowDirectories(); - + // Find all tool files const files = await glob('**/*.ts', { cwd: toolsDir, @@ -387,23 +387,23 @@ async function getStaticInfo() { for (const file of files) { const toolName = path.basename(file, '.ts'); const workflowDir = path.basename(path.dirname(file)); - + if (!toolsByWorkflow.has(workflowDir)) { toolsByWorkflow.set(workflowDir, { canonical: [], reExports: [] }); } - + if (isReExportFile(file)) { - reExportFiles.push({ - name: toolName, - file, + reExportFiles.push({ + name: toolName, + file, workflowDir, relativePath: path.relative(projectRoot, file) }); toolsByWorkflow.get(workflowDir).reExports.push(toolName); } else { - canonicalTools.set(toolName, { + canonicalTools.set(toolName, { name: toolName, - file, + file, workflowDir, relativePath: path.relative(projectRoot, file) }); @@ -431,20 +431,20 @@ async function getStaticInfo() { function displaySummary(runtimeData, staticData) { console.log(`${colors.bright}${colors.blue}📊 XcodeBuildMCP Tools Summary${colors.reset}`); console.log('═'.repeat(60)); - + if (runtimeData) { console.log(`${colors.green}🚀 Runtime Analysis:${colors.reset}`); console.log(` Mode: ${runtimeData.dynamicMode ? 'Dynamic' : 'Static'}`); console.log(` Tools: ${runtimeData.toolCount}`); console.log(` Resources: ${runtimeData.resourceCount}`); console.log(` Total: ${runtimeData.toolCount + runtimeData.resourceCount}`); - + if (runtimeData.dynamicMode) { console.log(` ${colors.yellow}â„šī¸ Dynamic mode: Only enabled workflow tools shown${colors.reset}`); } console.log(); } - + if (staticData) { console.log(`${colors.cyan}📁 Static Analysis:${colors.reset}`); console.log(` Workflow directories: ${staticData.workflowDirs.length}`); @@ -460,16 +460,16 @@ function displaySummary(runtimeData, staticData) { */ function displayWorkflows(staticData) { if (!options.workflows || !staticData) return; - + console.log(`${colors.bright}📂 Workflow Directories:${colors.reset}`); console.log('─'.repeat(40)); - + for (const workflowDir of staticData.workflowDirs) { const workflow = staticData.toolsByWorkflow.get(workflowDir) || { canonical: [], reExports: [] }; const totalTools = workflow.canonical.length + workflow.reExports.length; - + console.log(`${colors.green}â€ĸ ${workflowDir}${colors.reset} (${totalTools} tools)`); - + if (options.verbose) { if (workflow.canonical.length > 0) { console.log(` ${colors.cyan}Canonical:${colors.reset} ${workflow.canonical.join(', ')}`); @@ -487,11 +487,11 @@ function displayWorkflows(staticData) { */ function displayTools(runtimeData, staticData) { if (!options.tools) return; - + if (runtimeData) { console.log(`${colors.bright}đŸ› ī¸ Runtime Tools (${runtimeData.toolCount}):${colors.reset}`); console.log('─'.repeat(40)); - + if (runtimeData.tools.length === 0) { console.log(' No tools available'); } else { @@ -506,11 +506,11 @@ function displayTools(runtimeData, staticData) { } console.log(); } - + if (staticData && options.static) { console.log(`${colors.bright}📁 Static Tools (${staticData.toolCount}):${colors.reset}`); console.log('─'.repeat(40)); - + if (staticData.tools.length === 0) { console.log(' No tools found'); } else { @@ -534,10 +534,10 @@ function displayTools(runtimeData, staticData) { */ function displayResources(runtimeData) { if (!options.resources || !runtimeData) return; - + console.log(`${colors.bright}📚 Resources (${runtimeData.resourceCount}):${colors.reset}`); console.log('─'.repeat(40)); - + if (runtimeData.resources.length === 0) { console.log(' No resources available'); } else { @@ -560,26 +560,26 @@ async function main() { try { let runtimeData = null; let staticData = null; - + // Gather data based on options if (options.runtime) { console.log(`${colors.cyan}🔍 Gathering runtime information...${colors.reset}`); runtimeData = await getRuntimeInfo(); } - + if (options.static) { console.log(`${colors.cyan}📁 Performing static analysis...${colors.reset}`); staticData = await getStaticInfo(); } - + // For default command or workflows option, always gather static data for workflow info if (options.workflows && !staticData) { console.log(`${colors.cyan}📁 Gathering workflow information...${colors.reset}`); staticData = await getStaticInfo(); } - + console.log(); // Blank line after gathering - + // Display based on command switch (command) { case 'count': @@ -587,14 +587,14 @@ async function main() { displaySummary(runtimeData, staticData); displayWorkflows(staticData); break; - + case 'list': case 'l': displaySummary(runtimeData, staticData); displayTools(runtimeData, staticData); displayResources(runtimeData); break; - + case 'static': case 's': if (!staticData) { @@ -603,7 +603,7 @@ async function main() { } displaySummary(null, staticData); displayWorkflows(staticData); - + if (options.verbose) { displayTools(null, staticData); console.log(`${colors.bright}🔄 Re-export Files (${staticData.reExportCount}):${colors.reset}`); @@ -614,16 +614,16 @@ async function main() { }); } break; - + default: // Default case (no command) - show runtime summary with workflows displaySummary(runtimeData, staticData); displayWorkflows(staticData); break; } - + console.log(`${colors.green}✅ Analysis complete!${colors.reset}`); - + } catch (error) { console.error(`${colors.red}❌ Error: ${error.message}${colors.reset}`); process.exit(1); diff --git a/src/mcp/tools/discovery/discover_tools.ts b/src/mcp/tools/discovery/discover_tools.ts index cd9ac0a4..c4cf2d69 100644 --- a/src/mcp/tools/discovery/discover_tools.ts +++ b/src/mcp/tools/discovery/discover_tools.ts @@ -83,7 +83,8 @@ const discoverToolsSchema = z.object({ .string() .describe( 'A detailed description of the development task you want to accomplish. ' + - "For example: 'I need to build my iOS app and run it on the iPhone 15 Pro simulator.'", + "For example: 'I need to build my iOS app and run it on the iPhone 16 simulator.' " + + 'If working with Xcode projects, explicitly state whether you are using a .xcworkspace (workspace) or a .xcodeproj (project).', ), additive: z .boolean() @@ -157,24 +158,28 @@ export async function discover_toolsLogic( The user wants to perform the following task: "${sanitizedTaskDescription}" -IMPORTANT: Each workflow represents a complete end-to-end development workflow. Choose ONLY ONE workflow that best matches the user's project type and target platform: - -**Project Type Selection Guide:** -- If working with .xcworkspace files (CocoaPods, SPM, multi-project): Choose *-workspace workflows -- If working with .xcodeproj files (single project): Choose *-project workflows -- If working with Swift Package Manager: Choose swift-package -- If only exploring/discovering projects: Choose project-discovery - -**Platform Selection Guide:** -- iOS development on simulators: Choose simulator-workspace or simulator-project -- iOS development on physical devices: Choose device-workspace or device-project -- macOS development: Choose macos-workspace or macos-project - -Available Workflows: +IMPORTANT: Select EXACTLY ONE workflow that best matches the user's task. In most cases, users are working with a project or workspace. Use this selection guide: + +Primary (project/workspace-based) workflows: +- iOS simulator with .xcworkspace: choose "simulator-workspace" +- iOS simulator with .xcodeproj: choose "simulator-project" +- iOS physical device with .xcworkspace: choose "device-workspace" +- iOS physical device with .xcodeproj: choose "device-project" +- macOS with .xcworkspace: choose "macos-workspace" +- macOS with .xcodeproj: choose "macos-project" +- Swift Package Manager (no Xcode project): choose "swift-package" + +Secondary (task-based, no project/workspace needed): +- Simulator management (boot, list, open, status bar, appearance, GPS/location): choose "simulator-management" +- Logging or log capture (simulator or device): choose "logging" +- UI automation/gestures/screenshots on a simulator app: choose "ui-testing" +- System/environment diagnostics or validation: choose "diagnostics" +- Create new iOS/macOS projects from templates: choose "project-scaffolding" + +All available workflows: ${workflowDescriptions} -Respond with ONLY a JSON array containing ONE workflow name that best matches the task (e.g., ["simulator-workspace"]). -Each workflow contains ALL tools needed for its complete development workflow - no need to combine workflows.`; +Respond with ONLY a JSON array containing ONE workflow name that best matches the task (e.g., ["simulator-workspace"]).`; // 4. Send sampling request with configurable parameters const llmConfig = getLLMConfig(); @@ -364,7 +369,7 @@ Each workflow contains ALL tools needed for its complete development workflow - export default { name: 'discover_tools', description: - 'Analyzes a natural language task description to enable a relevant set of Xcode and Apple development tools. For best results, specify the target platform (iOS, macOS, watchOS, tvOS, visionOS) and project type (.xcworkspace or .xcodeproj).', + 'Analyzes a natural language task description and enables the most relevant development workflow. Prioritizes project/workspace workflows (simulator/device/macOS) and also supports task-based workflows (simulator-management, logging, diagnostics) and Swift packages.', schema: discoverToolsSchema.shape, // MCP SDK compatibility handler: createTypedTool( discoverToolsSchema, diff --git a/src/mcp/tools/simulator-environment/__tests__/index.test.ts b/src/mcp/tools/simulator-environment/__tests__/index.test.ts deleted file mode 100644 index 8676f57a..00000000 --- a/src/mcp/tools/simulator-environment/__tests__/index.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Tests for simulator-environment workflow metadata - */ -import { describe, it, expect } from 'vitest'; -import { workflow } from '../index.js'; - -describe('simulator-environment workflow metadata', () => { - describe('Workflow Structure', () => { - it('should export workflow object with required properties', () => { - expect(workflow).toHaveProperty('name'); - expect(workflow).toHaveProperty('description'); - expect(workflow).toHaveProperty('platforms'); - expect(workflow).toHaveProperty('targets'); - expect(workflow).toHaveProperty('projectTypes'); - expect(workflow).toHaveProperty('capabilities'); - }); - - it('should have correct workflow name', () => { - expect(workflow.name).toBe('Simulator Environment Configuration'); - }); - - it('should have correct description', () => { - expect(workflow.description).toBe( - 'Tools for configuring iOS Simulator environment settings including appearance, location services, and network conditions. Perfect for testing apps under various environmental conditions.', - ); - }); - - it('should have correct platforms array', () => { - expect(workflow.platforms).toEqual(['iOS']); - }); - - it('should have correct targets array', () => { - expect(workflow.targets).toEqual(['simulator']); - }); - - it('should have correct projectTypes array', () => { - expect(workflow.projectTypes).toEqual(['project', 'workspace']); - }); - - it('should have correct capabilities array', () => { - expect(workflow.capabilities).toEqual([ - 'environment-config', - 'appearance', - 'location', - 'network-simulation', - ]); - }); - }); - - describe('Workflow Validation', () => { - it('should have valid string properties', () => { - expect(typeof workflow.name).toBe('string'); - expect(typeof workflow.description).toBe('string'); - expect(workflow.name.length).toBeGreaterThan(0); - expect(workflow.description.length).toBeGreaterThan(0); - }); - - it('should have valid array properties', () => { - expect(Array.isArray(workflow.platforms)).toBe(true); - expect(Array.isArray(workflow.targets)).toBe(true); - expect(Array.isArray(workflow.projectTypes)).toBe(true); - expect(Array.isArray(workflow.capabilities)).toBe(true); - - expect(workflow.platforms.length).toBeGreaterThan(0); - expect(workflow.targets.length).toBeGreaterThan(0); - expect(workflow.projectTypes.length).toBeGreaterThan(0); - expect(workflow.capabilities.length).toBeGreaterThan(0); - }); - - it('should contain expected platform values', () => { - expect(workflow.platforms).toContain('iOS'); - }); - - it('should contain expected target values', () => { - expect(workflow.targets).toContain('simulator'); - }); - - it('should contain expected project type values', () => { - expect(workflow.projectTypes).toContain('project'); - expect(workflow.projectTypes).toContain('workspace'); - }); - - it('should contain expected capability values', () => { - expect(workflow.capabilities).toContain('environment-config'); - expect(workflow.capabilities).toContain('appearance'); - expect(workflow.capabilities).toContain('location'); - expect(workflow.capabilities).toContain('network-simulation'); - }); - }); -}); diff --git a/src/mcp/tools/simulator-environment/index.ts b/src/mcp/tools/simulator-environment/index.ts deleted file mode 100644 index 2ddd5605..00000000 --- a/src/mcp/tools/simulator-environment/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Simulator Environment Configuration workflow - * - * Provides tools for configuring simulator environment settings like appearance, - * location, and network conditions. These tools are used less frequently than - * core build/test tools and are focused on environment setup for testing. - */ - -export const workflow = { - name: 'Simulator Environment Configuration', - description: - 'Tools for configuring iOS Simulator environment settings including appearance, location services, and network conditions. Perfect for testing apps under various environmental conditions.', - platforms: ['iOS'], - targets: ['simulator'], - projectTypes: ['project', 'workspace'], - capabilities: ['environment-config', 'appearance', 'location', 'network-simulation'], -}; diff --git a/src/mcp/tools/simulator-management/__tests__/index.test.ts b/src/mcp/tools/simulator-management/__tests__/index.test.ts new file mode 100644 index 00000000..bb507bb3 --- /dev/null +++ b/src/mcp/tools/simulator-management/__tests__/index.test.ts @@ -0,0 +1,52 @@ +/** + * Tests for simulator-environment workflow metadata + */ +import { describe, it, expect } from 'vitest'; +import { workflow } from '../index.js'; + +describe('simulator-environment workflow metadata', () => { + describe('Workflow Structure', () => { + it('should export workflow object with required properties', () => { + expect(workflow).toHaveProperty('name'); + expect(workflow).toHaveProperty('description'); + expect(workflow).toHaveProperty('platforms'); + expect(workflow).toHaveProperty('targets'); + expect(workflow).toHaveProperty('projectTypes'); + expect(workflow).toHaveProperty('capabilities'); + }); + + it('should have correct workflow name', () => { + expect(workflow.name).toBe('Simulator Management'); + }); + + 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.', + ); + }); + + it('should have correct platforms array', () => { + expect(workflow.platforms).toEqual(['iOS']); + }); + + it('should have correct targets array', () => { + expect(workflow.targets).toEqual(['simulator']); + }); + + it('should have correct projectTypes array', () => { + expect(workflow.projectTypes).toEqual(['project', 'workspace']); + }); + + it('should have correct capabilities array', () => { + expect(workflow.capabilities).toEqual([ + 'boot', + 'open', + 'list', + 'appearance', + 'location', + 'network', + 'statusbar', + ]); + }); + }); +}); diff --git a/src/mcp/tools/simulator-environment/__tests__/reset_simulator_location.test.ts b/src/mcp/tools/simulator-management/__tests__/reset_simulator_location.test.ts similarity index 97% rename from src/mcp/tools/simulator-environment/__tests__/reset_simulator_location.test.ts rename to src/mcp/tools/simulator-management/__tests__/reset_simulator_location.test.ts index 645f248d..97a5c773 100644 --- a/src/mcp/tools/simulator-environment/__tests__/reset_simulator_location.test.ts +++ b/src/mcp/tools/simulator-management/__tests__/reset_simulator_location.test.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import resetSimulatorLocationPlugin, { reset_simulator_locationLogic, } from '../reset_simulator_location.ts'; -import { createMockExecutor, createMockFileSystemExecutor } from '../../../../utils/command.js'; +import { createMockExecutor } from '../../../../utils/command.js'; describe('reset_simulator_location plugin', () => { describe('Export Field Validation (Literal)', () => { diff --git a/src/mcp/tools/simulator-environment/__tests__/set_sim_appearance.test.ts b/src/mcp/tools/simulator-management/__tests__/set_sim_appearance.test.ts similarity index 97% rename from src/mcp/tools/simulator-environment/__tests__/set_sim_appearance.test.ts rename to src/mcp/tools/simulator-management/__tests__/set_sim_appearance.test.ts index df58626e..89d61137 100644 --- a/src/mcp/tools/simulator-environment/__tests__/set_sim_appearance.test.ts +++ b/src/mcp/tools/simulator-management/__tests__/set_sim_appearance.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { z } from 'zod'; import setSimAppearancePlugin, { set_sim_appearanceLogic } from '../set_sim_appearance.ts'; -import { createMockExecutor, createMockFileSystemExecutor } from '../../../../utils/command.js'; +import { createMockExecutor } from '../../../../utils/command.js'; describe('set_sim_appearance plugin', () => { describe('Export Field Validation (Literal)', () => { diff --git a/src/mcp/tools/simulator-environment/__tests__/set_simulator_location.test.ts b/src/mcp/tools/simulator-management/__tests__/set_simulator_location.test.ts similarity index 100% rename from src/mcp/tools/simulator-environment/__tests__/set_simulator_location.test.ts rename to src/mcp/tools/simulator-management/__tests__/set_simulator_location.test.ts diff --git a/src/mcp/tools/simulator-environment/__tests__/sim_statusbar.test.ts b/src/mcp/tools/simulator-management/__tests__/sim_statusbar.test.ts similarity index 98% rename from src/mcp/tools/simulator-environment/__tests__/sim_statusbar.test.ts rename to src/mcp/tools/simulator-management/__tests__/sim_statusbar.test.ts index 754b9147..77ef7ced 100644 --- a/src/mcp/tools/simulator-environment/__tests__/sim_statusbar.test.ts +++ b/src/mcp/tools/simulator-management/__tests__/sim_statusbar.test.ts @@ -6,11 +6,7 @@ import { describe, it, expect } from 'vitest'; import { z } from 'zod'; -import { - createMockExecutor, - createMockFileSystemExecutor, - type CommandExecutor, -} from '../../../../utils/command.js'; +import { createMockExecutor, type CommandExecutor } from '../../../../utils/command.js'; import simStatusbar, { sim_statusbarLogic } from '../sim_statusbar.ts'; describe('sim_statusbar tool', () => { diff --git a/src/mcp/tools/simulator-management/boot_sim.ts b/src/mcp/tools/simulator-management/boot_sim.ts new file mode 100644 index 00000000..f5bced6c --- /dev/null +++ b/src/mcp/tools/simulator-management/boot_sim.ts @@ -0,0 +1,2 @@ +// Re-export from simulator-workspace to avoid duplication +export { default } from '../simulator-shared/boot_sim.ts'; diff --git a/src/mcp/tools/simulator-management/index.ts b/src/mcp/tools/simulator-management/index.ts new file mode 100644 index 00000000..6c538e4a --- /dev/null +++ b/src/mcp/tools/simulator-management/index.ts @@ -0,0 +1,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. + */ + +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.', + platforms: ['iOS'], + targets: ['simulator'], + projectTypes: ['project', 'workspace'], + capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar'], +}; diff --git a/src/mcp/tools/simulator-management/list_sims.ts b/src/mcp/tools/simulator-management/list_sims.ts new file mode 100644 index 00000000..f7923424 --- /dev/null +++ b/src/mcp/tools/simulator-management/list_sims.ts @@ -0,0 +1,2 @@ +// Re-export from simulator-shared to avoid duplication +export { default } from '../simulator-shared/list_sims.ts'; diff --git a/src/mcp/tools/simulator-management/open_sim.ts b/src/mcp/tools/simulator-management/open_sim.ts new file mode 100644 index 00000000..d5414a09 --- /dev/null +++ b/src/mcp/tools/simulator-management/open_sim.ts @@ -0,0 +1,2 @@ +// Re-export from simulator-shared to avoid duplication +export { default } from '../simulator-shared/open_sim.ts'; diff --git a/src/mcp/tools/simulator-environment/reset_simulator_location.ts b/src/mcp/tools/simulator-management/reset_simulator_location.ts similarity index 95% rename from src/mcp/tools/simulator-environment/reset_simulator_location.ts rename to src/mcp/tools/simulator-management/reset_simulator_location.ts index 7c871915..6113c697 100644 --- a/src/mcp/tools/simulator-environment/reset_simulator_location.ts +++ b/src/mcp/tools/simulator-management/reset_simulator_location.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import { ToolResponse } from '../../../types/common.js'; -import { log } from '../../../utils/index.js'; -import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js'; -import { createTypedTool } from '../../../utils/typed-tool-factory.js'; +import { ToolResponse } from '../../../types/common.ts'; +import { log } from '../../../utils/index.ts'; +import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.ts'; +import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const resetSimulatorLocationSchema = z.object({ diff --git a/src/mcp/tools/simulator-environment/set_sim_appearance.ts b/src/mcp/tools/simulator-management/set_sim_appearance.ts similarity index 97% rename from src/mcp/tools/simulator-environment/set_sim_appearance.ts rename to src/mcp/tools/simulator-management/set_sim_appearance.ts index 577ff358..4fae1ba5 100644 --- a/src/mcp/tools/simulator-environment/set_sim_appearance.ts +++ b/src/mcp/tools/simulator-management/set_sim_appearance.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import { ToolResponse } from '../../../types/common.js'; -import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js'; -import { createTypedTool } from '../../../utils/typed-tool-factory.js'; +import { ToolResponse } from '../../../types/common.ts'; +import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.ts'; +import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const setSimAppearanceSchema = z.object({ diff --git a/src/mcp/tools/simulator-environment/set_simulator_location.ts b/src/mcp/tools/simulator-management/set_simulator_location.ts similarity index 97% rename from src/mcp/tools/simulator-environment/set_simulator_location.ts rename to src/mcp/tools/simulator-management/set_simulator_location.ts index f006f1cc..f8f4e07e 100644 --- a/src/mcp/tools/simulator-environment/set_simulator_location.ts +++ b/src/mcp/tools/simulator-management/set_simulator_location.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import { ToolResponse } from '../../../types/common.js'; -import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js'; -import { createTypedTool } from '../../../utils/typed-tool-factory.js'; +import { ToolResponse } from '../../../types/common.ts'; +import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.ts'; +import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const setSimulatorLocationSchema = z.object({ diff --git a/src/mcp/tools/simulator-environment/sim_statusbar.ts b/src/mcp/tools/simulator-management/sim_statusbar.ts similarity index 97% rename from src/mcp/tools/simulator-environment/sim_statusbar.ts rename to src/mcp/tools/simulator-management/sim_statusbar.ts index 0a786845..aa7148c3 100644 --- a/src/mcp/tools/simulator-environment/sim_statusbar.ts +++ b/src/mcp/tools/simulator-management/sim_statusbar.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import { ToolResponse } from '../../../types/common.js'; -import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.js'; -import { createTypedTool } from '../../../utils/typed-tool-factory.js'; +import { ToolResponse } from '../../../types/common.ts'; +import { log, CommandExecutor, getDefaultCommandExecutor } from '../../../utils/index.ts'; +import { createTypedTool } from '../../../utils/typed-tool-factory.ts'; // Define schema as ZodObject const simStatusbarSchema = z.object({ From d478193d5550ef4365a5236523f6158b8ccb3092 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Thu, 7 Aug 2025 23:21:08 +0100 Subject: [PATCH 2/3] fix: test description --- src/mcp/tools/simulator-management/__tests__/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcp/tools/simulator-management/__tests__/index.test.ts b/src/mcp/tools/simulator-management/__tests__/index.test.ts index bb507bb3..1d7ec9e7 100644 --- a/src/mcp/tools/simulator-management/__tests__/index.test.ts +++ b/src/mcp/tools/simulator-management/__tests__/index.test.ts @@ -1,10 +1,10 @@ /** - * Tests for simulator-environment workflow metadata + * Tests for simulator-management workflow metadata */ import { describe, it, expect } from 'vitest'; import { workflow } from '../index.js'; -describe('simulator-environment workflow metadata', () => { +describe('simulator-management workflow metadata', () => { describe('Workflow Structure', () => { it('should export workflow object with required properties', () => { expect(workflow).toHaveProperty('name'); From ca635e96715e864d5ad50c7ebb14909122411252 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Thu, 7 Aug 2025 23:25:55 +0100 Subject: [PATCH 3/3] Fix failing tests --- .../__tests__/discover_tools.test.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/mcp/tools/discovery/__tests__/discover_tools.test.ts b/src/mcp/tools/discovery/__tests__/discover_tools.test.ts index d0d86b55..a1eb4885 100644 --- a/src/mcp/tools/discovery/__tests__/discover_tools.test.ts +++ b/src/mcp/tools/discovery/__tests__/discover_tools.test.ts @@ -116,7 +116,7 @@ describe('discover_tools', () => { it('should have correct description', () => { expect(discoverTools.description).toBe( - 'Analyzes a natural language task description to enable a relevant set of Xcode and Apple development tools. For best results, specify the target platform (iOS, macOS, watchOS, tvOS, visionOS) and project type (.xcworkspace or .xcodeproj).', + 'Analyzes a natural language task description and enables the most relevant development workflow. Prioritizes project/workspace workflows (simulator/device/macOS) and also supports task-based workflows (simulator-management, logging, diagnostics) and Swift packages.', ); }); @@ -656,9 +656,10 @@ describe('discover_tools', () => { const prompt = requestCall[0].messages[0].content.text; expect(prompt).toContain(taskDescription); - expect(prompt).toContain('Project Type Selection Guide'); - expect(prompt).toContain('Platform Selection Guide'); - expect(prompt).toContain('Available Workflows'); + expect(prompt).toContain('Select EXACTLY ONE workflow'); + expect(prompt).toContain('Primary (project/workspace-based) workflows:'); + expect(prompt).toContain('Secondary (task-based, no project/workspace needed):'); + expect(prompt).toContain('All available workflows:'); }); it('should provide clear selection guidelines in prompt', async () => { @@ -693,11 +694,11 @@ describe('discover_tools', () => { const requestCall = requestCalls[0]; const prompt = requestCall[0].messages[0].content.text; - expect(prompt).toContain('Choose ONLY ONE workflow'); - expect(prompt).toContain('If working with .xcworkspace files'); - expect(prompt).toContain('If working with .xcodeproj files'); - expect(prompt).toContain('iOS development on simulators'); - expect(prompt).toContain('macOS development'); + expect(prompt).toContain('Select EXACTLY ONE workflow'); + expect(prompt).toContain('.xcworkspace'); + expect(prompt).toContain('.xcodeproj'); + expect(prompt).toContain('simulator-management'); + expect(prompt).toContain('macOS'); expect(prompt).toContain('Respond with ONLY a JSON array'); }); });