# 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. The `workflow-discovery` workflow is only auto-included when `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY=true` (or when explicitly listed).

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`
- `snapshot_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.
