# XcodeBuildMCP Plugin Development Guide
This guide provides comprehensive instructions for creating new tools and workflow groups in XcodeBuildMCP using the filesystem-based auto-discovery system.
## Table of Contents
1. [Overview](#overview)
2. [Plugin Architecture](#plugin-architecture)
3. [Creating New Tools](#creating-new-tools)
4. [Creating New Workflow Groups](#creating-new-workflow-groups)
5. [Creating MCP Resources](#creating-mcp-resources)
6. [Auto-Discovery System](#auto-discovery-system)
7. [Testing Guidelines](#testing-guidelines)
8. [Development Workflow](#development-workflow)
9. [Best Practices](#best-practices)
## Overview
XcodeBuildMCP uses a **plugin-based architecture** with **filesystem-based auto-discovery**. Tools are automatically discovered and loaded without manual registration, and can be selectively enabled using `XCODEBUILDMCP_ENABLED_WORKFLOWS`.
### Key Features
- **Auto-Discovery**: Tools are automatically found by scanning `src/mcp/tools/` directory
- **Selective Workflow Loading**: Limit startup tool registration with `XCODEBUILDMCP_ENABLED_WORKFLOWS`
- **Dependency Injection**: All tools use testable patterns with mock-friendly executors
- **Workflow Organization**: Tools are grouped into end-to-end development workflows
## Plugin Architecture
### Directory Structure
```
src/mcp/tools/
├── simulator-workspace/ # iOS Simulator + Workspace tools
├── simulator-project/ # iOS Simulator + Project tools (re-exports)
├── simulator-shared/ # Shared simulator tools (canonical)
├── device-workspace/ # iOS Device + Workspace tools
├── device-project/ # iOS Device + Project tools (re-exports)
├── device-shared/ # Shared device tools (canonical)
├── macos-workspace/ # macOS + Workspace tools
├── macos-project/ # macOS + Project tools (re-exports)
├── macos-shared/ # Shared macOS tools (canonical)
├── swift-package/ # Swift Package Manager tools
├── ui-testing/ # UI automation tools
├── project-discovery/ # Project analysis tools
├── utilities/ # General utilities
├── doctor/ # System health check tools
└── logging/ # Log capture tools
```
### Plugin Tool Types
1. **Canonical Workflows**: Standalone workflow groups (e.g., `swift-package`, `ui-testing`) defined as folders in the `src/mcp/tools/` directory
2. **Shared Tools**: Common tools in `*-shared` directories (not exposed to clients)
3. **Re-exported Tools**: Share tools to other workflow groups by re-exporting them
## Creating New Tools
### 1. Tool File Structure
Every tool follows this standardized pattern:
```typescript
// src/mcp/tools/my-workflow/my_tool.ts
import { z } from 'zod';
import { ToolResponse } from '../../../types/common.js';
import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/command.js';
import { log, validateRequiredParam, createTextResponse, createErrorResponse } from '../../../utils/index.js';
// 1. Define parameters type for clarity
type MyToolParams = {
requiredParam: string;
optionalParam?: string;
};
// 2. Implement the core logic in a separate, testable function
export async function my_toolLogic(
params: MyToolParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
// 3. Validate required parameters
const requiredValidation = validateRequiredParam('requiredParam', params.requiredParam);
if (!requiredValidation.isValid) {
return requiredValidation.errorResponse;
}
log('info', `Executing my_tool with param: ${params.requiredParam}`);
try {
// 4. Build and execute the command using the injected executor
const command = ['my-command', '--param', params.requiredParam];
if (params.optionalParam) {
command.push('--optional', params.optionalParam);
}
const result = await executor(command, 'My Tool Operation');
if (!result.success) {
return createErrorResponse('My Tool operation failed', result.error);
}
return createTextResponse(`✅ Success: ${result.output}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `My Tool execution error: ${errorMessage}`);
return createErrorResponse('Tool execution failed', errorMessage);
}
}
// 5. Export the tool definition as the default export
export default {
name: 'my_tool',
description: 'A brief description of what my_tool does, with a usage example. e.g. my_tool({ requiredParam: "value" })',
schema: {
requiredParam: z.string().describe('Description of the required parameter.'),
optionalParam: z.string().optional().describe('Description of the optional parameter.'),
},
// The handler wraps the logic function with the default executor for production use
handler: async (args: Record<string, unknown>): Promise<ToolResponse> => {
return my_toolLogic(args as MyToolParams, getDefaultCommandExecutor());
},
};
```
### 2. Required Tool Plugin Properties
Every tool plugin **must** export a default object with these properties:
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Tool name (must match filename without extension) |
| `description` | `string` | Clear description with usage examples |
| `schema` | `Record<string, z.ZodTypeAny>` | Zod validation schema for parameters |
| `handler` | `function` | Async function: `(args) => Promise<ToolResponse>` |
### 3. Naming Conventions
Tools follow the pattern: `{action}_{target}_{specifier}_{projectType}`
**Examples:**
- `build_sim_id_ws` → Build + Simulator + ID + Workspace
- `build_sim_name_proj` → Build + Simulator + Name + Project
- `test_device_ws` → Test + Device + Workspace
- `swift_package_build` → Swift Package + Build
**Project Type Suffixes:**
- `_ws` → Works with `.xcworkspace` files
- `_proj` → Works with `.xcodeproj` files
- No suffix → Generic or canonical tools
### 4. Parameter Validation Patterns
Use utility functions for consistent validation:
```typescript
// Required parameter validation
const pathValidation = validateRequiredParam('workspacePath', params.workspacePath);
if (!pathValidation.isValid) return pathValidation.errorResponse;
// At-least-one parameter validation
const identifierValidation = validateAtLeastOneParam(
'simulatorId', params.simulatorId,
'simulatorName', params.simulatorName
);
if (!identifierValidation.isValid) return identifierValidation.errorResponse;
// File existence validation
const fileValidation = validateFileExists(params.workspacePath as string);
if (!fileValidation.isValid) return fileValidation.errorResponse;
```
### 5. Response Patterns
Use utility functions for consistent responses:
```typescript
// Success responses
return createTextResponse('✅ Operation succeeded');
return createTextResponse('Operation completed', false); // Not an error
// Error responses
return createErrorResponse('Operation failed', errorDetails);
return createErrorResponse('Validation failed', errorMessage, 'ValidationError');
// Complex responses
return {
content: [
{ type: 'text', text: '✅ Build succeeded' },
{ type: 'text', text: 'Next steps: Run install_app_sim...' }
],
isError: false
};
```
## Creating New Workflow Groups
### 1. Workflow Group Structure
Each workflow group requires:
1. **Directory**: Following naming convention
2. **Workflow Metadata**: `index.ts` file with workflow export
3. **Tool Files**: Individual tool implementations
4. **Tests**: Comprehensive test coverage
### 2. Directory Naming Convention
```
[platform]-[projectType]/ # e.g., simulator-workspace, device-project
[platform]-shared/ # e.g., simulator-shared, macos-shared
[workflow-name]/ # e.g., swift-package, ui-testing
```
### 3. Workflow Metadata (index.ts)
**Required for all workflow groups:**
```typescript
// Example: src/mcp/tools/simulator-workspace/index.ts
export const workflow = {
name: 'iOS Simulator Workspace Development',
description: 'Complete iOS development workflow for .xcworkspace files including build, test, deploy, and debug capabilities',
};
```
**Required Properties:**
- `name`: Human-readable workflow name
- `description`: Clear description of workflow purpose
### 4. Tool Organization Patterns
#### Canonical Workflow Groups
Self-contained workflows that don't re-export from other groups:
```
swift-package/
├── index.ts # Workflow metadata
├── swift_package_build.ts # Build tool
├── swift_package_test.ts # Test tool
├── swift_package_run.ts # Run tool
└── __tests__/ # Test directory
├── index.test.ts # Workflow tests
├── swift_package_build.test.ts
└── ...
```
#### Shared Workflow Groups
Provide canonical tools for re-export by project/workspace variants:
```
simulator-shared/
├── boot_sim.ts # Canonical simulator boot tool
├── install_app_sim.ts # Canonical app install tool
└── __tests__/ # Test directory
├── boot_sim.test.ts
└── ...
```
#### Project/Workspace Workflow Groups
Re-export shared tools and add variant-specific tools:
```
simulator-project/
├── index.ts # Workflow metadata
├── boot_sim.ts # Re-export: export { default } from '../simulator-shared/boot_sim.js';
├── build_sim_id_proj.ts # Project-specific build tool
└── __tests__/ # Test directory
├── index.test.ts # Workflow tests
├── re-exports.test.ts # Re-export validation
└── ...
```
### 5. Re-export Implementation
For project/workspace groups that share tools:
```typescript
// simulator-project/boot_sim.ts
export { default } from '../simulator-shared/boot_sim.js';
```
**Re-export Rules:**
1. Re-exports come from canonical `-shared` groups
2. No chained re-exports (re-exports from re-exports)
3. Each tool maintains project or workspace specificity
4. Implementation shared, interfaces remain unique
## Creating MCP Resources
MCP Resources provide efficient URI-based data access for clients that support the MCP resource specification
### 1. Resource Structure
Resources are located in `src/resources/` and follow this pattern:
```typescript
// src/resources/example.ts
import { log, getDefaultCommandExecutor, CommandExecutor } from '../../utils/index.js';
// Testable resource logic separated from MCP handler
export async function exampleResourceLogic(
executor: CommandExecutor,
): Promise<{ contents: Array<{ text: string }> }> {
try {
log('info', 'Processing example resource request');
// Use the executor to get data
const result = await executor(['some', 'command'], 'Example Resource Operation');
if (!result.success) {
throw new Error(result.error || 'Failed to get resource data');
}
return {
contents: [{ text: result.output || 'resource data' }]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error in example resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving resource data: ${errorMessage}`,
},
],
};
}
}
export default {
uri: 'xcodebuildmcp://example',
name: 'example',
description: 'Description of the resource data',
mimeType: 'text/plain',
async handler(_uri: URL): Promise<{ contents: Array<{ text: string }> }> {
return exampleResourceLogic(getDefaultCommandExecutor());
},
};
```
### 2. Resource Implementation Guidelines
**Reuse Existing Logic**: Resources that mirror tools should reuse existing tool logic for consistency:
```typescript
// src/mcp/resources/simulators.ts (simplified example)
import { list_simsLogic } from '../tools/simulator-shared/list_sims.js';
export default {
uri: 'xcodebuildmcp://simulators',
name: 'simulators'
description: 'Available iOS simulators with UUIDs and states',
mimeType: 'text/plain',
async handler(uri: URL): Promise<{ contents: Array<{ text: string }> }> {
const executor = getDefaultCommandExecutor();
const result = await list_simsLogic({}, executor);
return {
contents: [{ text: result.content[0].text }]
};
}
};
```
As not all clients support resources it important that resource content that would be ideally be served by resources be mirroed as a tool as well. This ensurew clients that don't support this capability continue to will still have access to that resource data via a simple tool call.
### 3. Resource Testing
Create tests in `src/mcp/resources/__tests__/`:
```typescript
// src/mcp/resources/__tests__/example.test.ts
import exampleResource, { exampleResourceLogic } from '../example.js';
import { createMockExecutor } from '../../utils/test-common.js';
describe('example resource', () => {
describe('Export Field Validation', () => {
it('should export correct uri', () => {
expect(exampleResource.uri).toBe('xcodebuildmcp://example');
});
it('should export correct description', () => {
expect(exampleResource.description).toBe('Description of the resource data');
});
it('should export correct mimeType', () => {
expect(exampleResource.mimeType).toBe('text/plain');
});
it('should export handler function', () => {
expect(typeof exampleResource.handler).toBe('function');
});
});
describe('Resource Logic Functionality', () => {
it('should return resource data successfully', async () => {
const mockExecutor = createMockExecutor({
success: true,
output: 'test data'
});
// Test the logic function directly, not the handler
const result = await exampleResourceLogic(mockExecutor);
expect(result.contents).toHaveLength(1);
expect(result.contents[0].text).toContain('expected data');
});
it('should handle command execution errors', async () => {
const mockExecutor = createMockExecutor({
success: false,
error: 'Command failed'
});
const result = await exampleResourceLogic(mockExecutor);
expect(result.contents[0].text).toContain('Error retrieving');
});
});
});
```
### 4. Auto-Discovery
Resources are automatically discovered and loaded by the build system. After creating a resource:
1. Run `npm run build` to regenerate resource loaders
2. The resource will be available at its URI for supported clients
## Auto-Discovery System
### How Auto-Discovery Works
1. **Filesystem Scan**: `loadPlugins()` scans `src/mcp/tools/` directory
2. **Workflow Loading**: Each subdirectory is treated as a potential workflow group
3. **Metadata Validation**: `index.ts` files provide workflow metadata
4. **Tool Discovery**: All `.ts` files (except tests and index) are loaded as tools
5. **Registration**: Tools are automatically registered with the MCP server
### Discovery Process
```typescript
// Simplified discovery flow
const plugins = await loadPlugins();
for (const plugin of plugins.values()) {
server.tool(plugin.name, plugin.description, plugin.schema, plugin.handler);
}
```
### Selective Workflow Loading
To limit which workflows are registered at startup, set `XCODEBUILDMCP_ENABLED_WORKFLOWS` to a comma-separated list of workflow directory names. The `session-management` workflow is always auto-included since other tools depend on it.
Example:
```bash
XCODEBUILDMCP_ENABLED_WORKFLOWS=simulator,device,project-discovery
```
`XCODEBUILDMCP_DEBUG=true` can still be used to increase logging verbosity.
## Testing Guidelines
### Test Organization
```
__tests__/
├── index.test.ts # Workflow metadata tests (canonical groups only)
├── re-exports.test.ts # Re-export validation (project/workspace groups)
└── tool_name.test.ts # Individual tool tests
```
### Dependency Injection Testing
**✅ CORRECT Pattern:**
```typescript
import { createMockExecutor } from '../../../utils/test-common.js';
describe('build_sim_name_ws', () => {
it('should build successfully', async () => {
const mockExecutor = createMockExecutor({
success: true,
output: 'BUILD SUCCEEDED'
});
const result = await build_sim_name_wsLogic(params, mockExecutor);
expect(result.isError).toBe(false);
});
});
```
**❌ FORBIDDEN Pattern (Vitest Mocking Banned):**
```typescript
// ❌ ALL VITEST MOCKING IS COMPLETELY BANNED
vi.mock('child_process');
const mockSpawn = vi.fn();
```
### Three-Dimensional Testing
Every tool test must cover:
1. **Input Validation**: Parameter schema validation and error cases
2. **Command Generation**: Verify correct CLI commands are built
3. **Output Processing**: Test response formatting and error handling
### Test Template
```typescript
import { describe, it, expect } from 'vitest';
import { createMockExecutor } from '../../../utils/test-common.js';
import tool, { toolNameLogic } from '../tool_name.js';
describe('tool_name', () => {
describe('Export Validation', () => {
it('should export correct name', () => {
expect(tool.name).toBe('tool_name');
});
it('should export correct description', () => {
expect(tool.description).toContain('Expected description');
});
it('should export handler function', () => {
expect(typeof tool.handler).toBe('function');
});
});
describe('Parameter Validation', () => {
it('should validate required parameters', async () => {
const mockExecutor = createMockExecutor({ success: true, output: '' });
const result = await toolNameLogic({}, mockExecutor);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Required parameter");
});
});
describe('Command Generation', () => {
it('should generate correct command', async () => {
const mockExecutor = createMockExecutor({ success: true, output: 'SUCCESS' });
await toolNameLogic({ param: 'value' }, mockExecutor);
expect(mockExecutor).toHaveBeenCalledWith(
expect.arrayContaining(['expected', 'command']),
expect.any(String),
expect.any(Boolean)
);
});
});
describe('Response Processing', () => {
it('should handle successful execution', async () => {
const mockExecutor = createMockExecutor({ success: true, output: 'SUCCESS' });
const result = await toolNameLogic({ param: 'value' }, mockExecutor);
expect(result.isError).toBe(false);
expect(result.content[0].text).toContain('✅');
});
it('should handle execution errors', async () => {
const mockExecutor = createMockExecutor({ success: false, error: 'Command failed' });
const result = await toolNameLogic({ param: 'value' }, mockExecutor);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Command failed');
});
});
});
```
## Development Workflow
### Adding a New Tool
1. **Choose Directory**: Select appropriate workflow group or create new one
2. **Create Tool File**: Follow naming convention and structure
3. **Implement Logic**: Use dependency injection pattern
4. **Define Schema**: Add comprehensive Zod validation
5. **Write Tests**: Cover all three dimensions
6. **Test Integration**: Build and verify auto-discovery
### Step-by-Step Tool Creation
```bash
# 1. Create tool file
touch src/mcp/tools/simulator-workspace/my_new_tool_ws.ts
# 2. Implement tool following patterns above
# 3. Create test file
touch src/mcp/tools/simulator-workspace/__tests__/my_new_tool_ws.test.ts
# 4. Build project
npm run build
# 5. Verify tool is discovered (should appear in tools list)
npm run inspect # Use MCP Inspector to verify
```
### Adding a New Workflow Group
1. **Create Directory**: Follow naming convention
2. **Add Workflow Metadata**: Create `index.ts` with workflow export
3. **Implement Tools**: Add tool files following patterns
4. **Create Tests**: Add comprehensive test coverage
5. **Verify Discovery**: Test auto-discovery and tool registration
### Step-by-Step Workflow Creation
```bash
# 1. Create workflow directory
mkdir src/mcp/tools/my-new-workflow
# 2. Create workflow metadata
cat > src/mcp/tools/my-new-workflow/index.ts << 'EOF'
export const workflow = {
name: 'My New Workflow',
description: 'Description of workflow capabilities',
};
EOF
# 3. Create tools directory and test directory
mkdir src/mcp/tools/my-new-workflow/__tests__
# 4. Implement tools following patterns
# 5. Build and verify
npm run build
npm run inspect
```
## Best Practices
### Tool Design
1. **Single Responsibility**: Each tool should have one clear purpose
2. **Descriptive Names**: Follow naming conventions for discoverability
3. **Clear Descriptions**: Include usage examples in tool descriptions
4. **Comprehensive Validation**: Validate all parameters with helpful error messages
5. **Consistent Responses**: Use utility functions for response formatting
### Error Handling
1. **Graceful Failures**: Always return ToolResponse, never throw from handlers
2. **Descriptive Errors**: Provide actionable error messages
3. **Error Types**: Use appropriate error types for different scenarios
4. **Logging**: Log important events and errors for debugging
### Testing
1. **Dependency Injection**: Always test with mock executors
2. **Complete Coverage**: Test all input, command, and output scenarios
3. **Literal Assertions**: Use exact string expectations to catch changes
4. **Fast Execution**: Tests should complete quickly without real system calls
### Workflow Organization
1. **End-to-End Workflows**: Groups should provide complete functionality
2. **Logical Grouping**: Group related tools together
3. **Clear Capabilities**: Document what each workflow can accomplish
4. **Consistent Patterns**: Follow established patterns for maintainability
### Workflow Metadata Considerations
1. **Workflow Completeness**: Each group should be self-sufficient
2. **Clear Descriptions**: Keep the `description` concise and user-focused
## Updating TOOLS.md Documentation
### Critical Documentation Maintenance
**Every time you add, change, move, edit, or delete a tool, you MUST review and update the [TOOLS.md](../TOOLS.md) file to reflect the current state of the codebase.**
### Documentation Update Process
#### 1. Use Tree CLI for Accurate Discovery
**Always use the `tree` command to get the actual filesystem representation of tools:**
```bash
# Get the definitive source of truth for all workflow groups and tools
tree src/mcp/tools/ -I "__tests__" -I "*.test.ts"
```
This command:
- Shows ALL workflow directories and their tools
- Excludes test files (`__tests__` directories and `*.test.ts` files)
- Provides the actual proof of what exists in the codebase
- Gives an accurate count of tools per workflow group
#### 2. Ignore Shared Groups in Documentation
When updating [TOOLS.md](../TOOLS.md):
- **Ignore `*-shared` directories** (e.g., `simulator-shared`, `device-shared`, `macos-shared`)
- These are implementation details, not user-facing workflow groups
- Only document the main workflow groups that users interact with
- The group count should exclude shared groups
#### 3. List Actual Tool Names
Instead of using generic descriptions like "Additional Tools: Simulator management, logging, UI testing tools":
**❌ Wrong:**
```markdown
- **Additional Tools**: Simulator management, logging, UI testing tools
```
**✅ Correct:**
```markdown
- `boot_sim`, `install_app_sim`, `launch_app_sim`, `list_sims`, `open_sim`
- `describe_ui`, `screenshot`, `start_sim_log_cap`, `stop_sim_log_cap`
```
#### 4. Systematic Documentation Update Steps
1. **Run the tree command** to get current filesystem state
2. **Identify all non-shared workflow directories**
3. **Count actual tool files** in each directory (exclude `index.ts` and test files)
4. **List all tool names** explicitly in the documentation
5. **Update tool counts** to reflect actual numbers
6. **Verify consistency** between filesystem and documentation
#### 5. Documentation Formatting Requirements
**Format: One Tool Per Bullet Point with Description**
Each tool must be listed individually with its actual description from the tool file:
```markdown
### 1. My Awesome Workflow (`my-awesome-workflow`)
**Purpose**: A short description of what this workflow is for. (2 tools)
- `my_tool_one` - Description for my_tool_one from its definition file.
- `my_tool_two` - Description for my_tool_two from its definition file.
```
**Description Sources:**
- Use the actual `description` field from each tool's TypeScript file
- Descriptions should be concise but informative for end users
- Include platform/context information (iOS, macOS, simulator, device, etc.)
- Mention required parameters when critical for usage
#### 6. Validation Checklist
After updating [TOOLS.md](../TOOLS.md):
- [ ] Tool counts match actual filesystem counts (from tree command)
- [ ] Each tool has its own bullet point (one tool per line)
- [ ] Each tool includes its actual description from the tool file
- [ ] No generic descriptions like "Additional Tools: X, Y, Z"
- [ ] Descriptions are user-friendly and informative
- [ ] Shared groups (`*-shared`) are not included in main workflow list
- [ ] Workflow group count reflects only user-facing groups (15 groups)
- [ ] Tree command output was used as source of truth
- [ ] Documentation is user-focused, not implementation-focused
- [ ] Tool names are in alphabetical order within each workflow group
### Why This Process Matters
1. **Accuracy**: Tree command provides definitive proof of current state
2. **Maintainability**: Systematic process prevents documentation drift
3. **User Experience**: Accurate documentation helps users understand available tools
4. **Development Confidence**: Developers can trust the documentation reflects reality
**Remember**: The filesystem is the source of truth. Documentation must always reflect the actual codebase structure, and the tree command is the most reliable way to ensure accuracy.