- Overview
- Core Architecture
- Design Principles
- Component Details
- Registration System
- Tool Naming Conventions & Glossary
- Testing Architecture
- Build and Deployment
- Extension Guidelines
- Performance Considerations
- Security Considerations
XcodeBuildMCP is a Model Context Protocol (MCP) server that exposes Xcode operations as tools for AI assistants. The architecture emphasizes modularity, type safety, and selective enablement to support diverse development workflows.
- Expose Xcode-related tools (build, test, deploy, diagnostics, UI automation) through MCP
- Run as a long-lived stdio-based server for LLM agents, CLIs, or editors
- Enable fine-grained, opt-in activation of individual tools or tool groups
- Support incremental builds via experimental xcodemake with xcodebuild fallback
-
Initialization
- The
xcodebuildmcpexecutable, as defined inpackage.json, points to the compiledbuild/index.jswhich executes the main logic fromsrc/index.ts. - Sentry initialized for error tracking (optional)
- Version information loaded from
package.json
- The
-
Server Creation
- MCP server created with stdio transport
- Plugin discovery system initialized
-
Plugin Discovery & Loading
loadPlugins()scanssrc/mcp/tools/directory automaticallyloadResources()scanssrc/mcp/resources/directory automatically- Each tool exports standardized interface (
name,description,schema,handler) - Tools are self-contained with no external dependencies
- Dynamic vs static mode determines loading behavior
-
Tool Registration
- Discovered tools automatically registered with server
- No manual registration or configuration required
- Environment variables can still control dynamic tool discovery
-
Request Handling
- MCP client calls tool → server routes to tool handler
- Zod validates parameters before execution
- Tool handler uses shared utilities (build, simctl, etc.)
- Returns standardized
ToolResponse
-
Response Streaming
- Server streams response back to client
- Consistent error handling with
isErrorflag
Tools are self-contained units that export a standardized interface. They don't know about the server implementation, ensuring loose coupling and high testability.
- Most utilities are stateless pure functions
- Stateful components (e.g., process tracking) isolated in specific tool modules
- Clear separation between computation and side effects
- Version from
package.jsondrives all version references - Tool directory structure is authoritative tool source
- Environment variables provide consistent configuration interface
- Experimental features behind environment flags
- Optional dependencies (Sentry, xcodemake) gracefully degrade
- Tool directory structure enables workflow-specific organization
- TypeScript strict mode enabled
- Zod schemas for runtime validation
- Generic type constraints ensure compile-time safety
Main server entry point responsible for:
- Sentry initialization (if enabled)
- xcodemake availability check
- Server creation and startup
- Process lifecycle management (SIGTERM, SIGINT)
- Error handling and logging
Standalone diagnostic tool for:
- Environment validation
- Dependency checking
- Configuration verification
- Troubleshooting assistance
MCP server wrapper providing:
- Server instance creation
- stdio transport configuration
- Request/response handling
- Error boundary implementation
Automatic plugin loading system:
- Scans
src/mcp/tools/directory structure using glob patterns - Dynamically imports plugin modules
- Validates plugin interface compliance
- Handles both default exports and named exports (for re-exports)
- Supports workflow group metadata via
index.jsfiles
Plugin type definitions:
PluginMetainterface for plugin structureWorkflowMetainterface for workflow metadataWorkflowGroupinterface for directory organization
Each plugin (src/mcp/tools/*/*.js) follows this standardized pattern:
// 1. Import dependencies and schemas
import { z } from 'zod';
import { log } from '../../src/utils/logger.js';
import { executeCommand } from '../../src/utils/command.js';
// 2. Define and export plugin
export default {
name: 'tool_name',
description: 'Tool description for AI agents',
// 3. Define parameter schema
schema: {
requiredParam: z.string().describe('Description for AI'),
optionalParam: z.string().optional().describe('Optional parameter')
},
// 4. Implement handler function
async handler(params) {
try {
// 5. Execute tool logic using shared utilities
const result = await executeCommand(['some', 'command']);
// 6. Return standardized response
return {
content: [{ type: 'text', text: result.output }],
isError: false
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
isError: true
};
}
}
};XcodeBuildMCP provides dual interfaces: traditional MCP tools and efficient MCP resources for supported clients. Resources are located in src/mcp/resources/ and are automatically discovered. For more details on creating resources, see the Plugin Development Guide.
src/mcp/resources/
├── simulators.ts # Simulator data resource
└── __tests__/ # Resource-specific tests
The system automatically detects client MCP capabilities:
// src/core/resources.ts
export function supportsResources(server?: unknown): boolean {
// Detects client capabilities via getClientCapabilities()
// Conservative fallback: assumes resource support
}Resources can reuse existing tool logic for consistency:
// src/mcp/resources/some_resource.ts
import { log, getDefaultCommandExecutor, CommandExecutor } from '../../utils/index.js';
import { getSomeResourceLogic } from '../tools/some-workflow/get_some_resource.js';
// Testable resource logic separated from MCP handler
export async function someResourceResourceLogic(
executor: CommandExecutor = getDefaultCommandExecutor(),
): Promise<{ contents: Array<{ text: string }> }> {
try {
log('info', 'Processing some resource request');
const result = await getSomeResourceLogic({}, executor);
if (result.isError) {
const errorText = result.content[0]?.text;
throw new Error(
typeof errorText === 'string' ? errorText : 'Failed to retrieve some resource data',
);
}
return {
contents: [
{
text:
typeof result.content[0]?.text === 'string'
? result.content[0].text
: 'No data for that resource is available',
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log('error', `Error in some_resource resource handler: ${errorMessage}`);
return {
contents: [
{
text: `Error retrieving resource data: ${errorMessage}`,
},
],
};
}
}
export default {
uri: 'xcodebuildmcp://some_resource',
name: 'some_resource',
description: 'Returns some resource information',
mimeType: 'text/plain',
async handler(_uri: URL): Promise<{ contents: Array<{ text: string }> }> {
return someResourceResourceLogic();
},
};XcodeBuildMCP supports two primary operating modes for tool registration, controlled by the XCODEBUILDMCP_DYNAMIC_TOOLS environment variable.
- Environment:
XCODEBUILDMCP_DYNAMIC_TOOLSisfalseor not set. - Behavior: All available tools are loaded and registered with the MCP server at startup.
- Use Case: This mode is ideal for environments where the full suite of tools is desired immediately, providing a comprehensive and predictable toolset for the AI assistant.
- Environment:
XCODEBUILDMCP_DYNAMIC_TOOLS=true - Behavior: At startup, only the
discover_toolstool is registered. This tool is designed to analyze a natural language task description from the user. - Workflow:
- The client sends a task description (e.g., "I want to build and test my iOS app") to the
discover_toolstool. - The tool uses the client's LLM via an MCP sampling request to determine the most relevant workflow group (e.g.,
simulator-workspace). - The server then dynamically loads and registers all tools from the selected workflow group.
- The client is notified of the newly available tools.
- The client sends a task description (e.g., "I want to build and test my iOS app") to the
- Use Case: This mode is beneficial for conserving the LLM's context window by only loading a relevant subset of tools, leading to more focused and efficient interactions.
Tools follow a consistent naming pattern to ensure predictability and clarity. Understanding this convention is crucial for both using and developing tools.
The standard naming convention for tools is:
{action}_{target}_{specifier}_{projectType}
- action: The primary verb describing the tool's function (e.g.,
build,test,get,list). - target: The main subject of the action (e.g.,
simfor simulator,devfor device,macfor macOS). - specifier: A variant that specifies how the target is identified (e.g.,
idfor UUID,namefor by-name). - projectType: The type of Xcode project the tool operates on (e.g.,
wsfor workspace,projfor project).
Not all parts are required for every tool. For example, swift_package_build has an action and a target, but no specifier or project type.
build_sim_id_ws: Build for a simulator identified by its ID (UUID) from a workspace.test_dev_proj: Test on a device from a project.get_mac_app_path_ws: Get the app path for a macOS application from a workspace.list_sims: List all simulators.
| Term/Abbreviation | Meaning | Description |
|---|---|---|
ws |
Workspace | Refers to an .xcworkspace file. Used for projects with multiple .xcodeproj files or dependencies managed by CocoaPods or SPM. |
proj |
Project | Refers to an .xcodeproj file. Used for single-project setups. |
sim |
Simulator | Refers to the iOS, watchOS, tvOS, or visionOS simulator. |
dev |
Device | Refers to a physical Apple device (iPhone, iPad, etc.). |
mac |
macOS | Refers to a native macOS application target. |
id |
Identifier | Refers to the unique identifier (UUID/UDID) of a simulator or device. |
name |
Name | Refers to the human-readable name of a simulator (e.g., "iPhone 15 Pro"). |
cap |
Capture | Used in logging tools, e.g., start_sim_log_cap. |
- Test Runner: Vitest 3.x
- Environment: Node.js
- Configuration:
vitest.config.ts - Test Pattern:
*.test.tsfiles alongside implementation
XcodeBuildMCP uses a strict Dependency Injection (DI) pattern for testing, which completely bans the use of traditional mocking libraries like Vitest's vi.mock or vi.fn. This ensures that tests are robust, maintainable, and verify the actual integration between components.
For detailed guidelines, see the Testing Guide.
Tests inject mock "executors" for external interactions like command-line execution or file system access. This allows for deterministic testing of tool logic without mocking the implementation itself.
import { describe, it, expect } from 'vitest';
import { toolNameLogic } from '../tool-file.js'; // Import the logic function
import { createMockExecutor } from '../../../utils/test-common.js';
describe('Tool Name', () => {
it('should execute successfully', async () => {
// 1. Create a mock executor to simulate command-line results
const mockExecutor = createMockExecutor({
success: true,
output: 'Command output'
});
// 2. Call the tool's logic function, injecting the mock executor
const result = await toolNameLogic({ param: 'value' }, mockExecutor);
// 3. Assert the final result
expect(result).toEqual({
content: [{ type: 'text', text: 'Expected output' }],
isError: false
});
});
});-
Version Generation
npm run build
- Reads version from
package.json - Generates
src/version.ts - Compiles TypeScript with tsup
- Reads version from
-
Build Configuration (
tsup.config.ts)- Entry points:
index.ts,diagnostic-cli.ts - Output format: ESM
- Target: Node 18+
- Source maps enabled
- Entry points:
-
Distribution Structure
build/ ├── index.js # Main server executable ├── diagnostic-cli.js # Diagnostic tool └── *.js.map # Source maps
- Name:
xcodebuildmcp - Executables:
xcodebuildmcp→ Main serverxcodebuildmcp-diagnostic→ Diagnostic tool
- Dependencies: Minimal runtime dependencies
- Platform: macOS only (due to Xcode requirement)
bundled/
├── axe # UI automation binary
└── Frameworks/ # Facebook device frameworks
├── FBControlCore.framework
├── FBDeviceControl.framework
└── FBSimulatorControl.framework
This project is designed to be extensible. For comprehensive instructions on creating new tools, workflow groups, and resources, please refer to the dedicated Plugin Development Guide.
The guide covers:
- The auto-discovery system architecture.
- The dependency injection pattern required for all new tools.
- How to organize tools into workflow groups.
- Testing guidelines and patterns.
- Lazy Loading: Tools only initialized when registered
- Selective Registration: Fewer tools = faster startup
- Minimal Dependencies: Fast module resolution
- Stateless Operations: Most tools complete quickly
- Process Management: Long-running processes tracked separately
- Incremental Builds: xcodemake provides significant speedup
- Parallel Execution: Tools can run concurrently
- Process Cleanup: Proper process termination handling
- Log Rotation: Captured logs have size limits
- Resource Disposal: Explicit cleanup in lifecycle hooks
- Use Tool Groups: Enable only needed workflows
- Enable Incremental Builds: Set
INCREMENTAL_BUILDS_ENABLED=true - Limit Log Capture: Use structured logging when possible
- Profile Performance: Use diagnostic tool for bottleneck identification
- All tool inputs validated with Zod schemas
- Command injection prevented via proper escaping
- Path traversal protection in file operations
- Tools run with user permissions
- No privilege escalation
- Sandboxed execution environment
- Sensitive information scrubbed from errors
- Stack traces limited to application code
- Sentry integration respects privacy settings