doctor
Analyze and diagnose the MCP server environment, check dependencies, and verify configuration status with XcodeBuildMCP's diagnostic tool.
Instructions
Provides comprehensive information about the MCP server environment, available dependencies, and configuration status.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| enabled | No | Optional: dummy parameter to satisfy MCP protocol |
Implementation Reference
- src/mcp/tools/doctor/doctor.ts:30-252 (handler)Core handler function executing the doctor tool logic: performs system health checks, gathers Xcode info, binary status, environment details, feature flags, plugin info, and formats a comprehensive diagnostic report.export async function runDoctor( params: DoctorParams, deps: DoctorDependencies, showAsciiLogo = false, ): Promise<ToolResponse> { const prevSilence = process.env.XCODEBUILDMCP_SILENCE_LOGS; process.env.XCODEBUILDMCP_SILENCE_LOGS = 'true'; log('info', `${LOG_PREFIX}: Running doctor tool`); const requiredBinaries = ['axe', 'xcodemake', 'mise']; const binaryStatus: Record<string, { available: boolean; version?: string }> = {}; for (const binary of requiredBinaries) { binaryStatus[binary] = await deps.binaryChecker.checkBinaryAvailability(binary); } const xcodeInfo = await deps.xcode.getXcodeInfo(); const envVars = deps.env.getEnvironmentVariables(); const systemInfo = deps.env.getSystemInfo(); const nodeInfo = deps.env.getNodeInfo(); const axeAvailable = deps.features.areAxeToolsAvailable(); const pluginSystemInfo = await deps.plugins.getPluginSystemInfo(); const runtimeInfo = await deps.runtime.getRuntimeToolInfo(); const xcodemakeEnabled = deps.features.isXcodemakeEnabled(); const xcodemakeAvailable = await deps.features.isXcodemakeAvailable(); const makefileExists = deps.features.doesMakefileExist('./'); const doctorInfo = { serverVersion: version, timestamp: new Date().toISOString(), system: systemInfo, node: nodeInfo, xcode: xcodeInfo, dependencies: binaryStatus, environmentVariables: envVars, features: { axe: { available: axeAvailable, uiAutomationSupported: axeAvailable, }, xcodemake: { enabled: xcodemakeEnabled, available: xcodemakeAvailable, makefileExists: makefileExists, }, mise: { running_under_mise: Boolean(process.env.XCODEBUILDMCP_RUNNING_UNDER_MISE), available: binaryStatus['mise'].available, }, }, pluginSystem: pluginSystemInfo, } as const; // Custom ASCII banner (multiline) const asciiLogo = ` ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗ ██╗ ██╗██╗██╗ ██████╗ ███╗ ███╗ ██████╗██████╗ ╚██╗██╔╝██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██║ ██║██║██║ ██╔══██╗████╗ ████║██╔════╝██╔══██╗ ╚███╔╝ ██║ ██║ ██║██║ ██║█████╗ ██████╔╝██║ ██║██║██║ ██║ ██║██╔████╔██║██║ ██████╔╝ ██╔██╗ ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██╗██║ ██║██║██║ ██║ ██║██║╚██╔╝██║██║ ██╔═══╝ ██╔╝ ██╗╚██████╗╚██████╔╝██████╔╝███████╗██████╔╝╚██████╔╝██║███████╗██████╔╝██║ ╚═╝ ██║╚██████╗██║ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ██████╗ ██████╗ ██████╗████████╗ ██████╗ ██████╗ ██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗ ██║ ██║██║ ██║██║ ██║ ██║ ██║██████╔╝ ██║ ██║██║ ██║██║ ██║ ██║ ██║██╔══██╗ ██████╔╝╚██████╔╝╚██████╗ ██║ ╚██████╔╝██║ ██║ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ `; const RESET = '\x1b[0m'; // 256-color: orangey-pink foreground and lighter shade for outlines const FOREGROUND = '\x1b[38;5;209m'; const SHADOW = '\x1b[38;5;217m'; function colorizeAsciiArt(ascii: string): string { const lines = ascii.split('\n'); const coloredLines: string[] = []; const shadowChars = new Set([ '╔', '╗', '╝', '╚', '═', '║', '╦', '╩', '╠', '╣', '╬', '┌', '┐', '└', '┘', '│', '─', ]); for (const line of lines) { let colored = ''; for (const ch of line) { if (ch === '█') { colored += `${FOREGROUND}${ch}${RESET}`; } else if (shadowChars.has(ch)) { colored += `${SHADOW}${ch}${RESET}`; } else { colored += ch; } } coloredLines.push(colored + RESET); } return coloredLines.join('\n'); } const outputLines = []; // Only show ASCII logo when explicitly requested (CLI usage) if (showAsciiLogo) { outputLines.push(colorizeAsciiArt(asciiLogo)); } outputLines.push( 'XcodeBuildMCP Doctor', `\nGenerated: ${doctorInfo.timestamp}`, `Server Version: ${doctorInfo.serverVersion}`, ); const formattedOutput = [ ...outputLines, `\n## System Information`, ...Object.entries(doctorInfo.system).map(([key, value]) => `- ${key}: ${value}`), `\n## Node.js Information`, ...Object.entries(doctorInfo.node).map(([key, value]) => `- ${key}: ${value}`), `\n## Xcode Information`, ...('error' in doctorInfo.xcode ? [`- Error: ${doctorInfo.xcode.error}`] : Object.entries(doctorInfo.xcode).map(([key, value]) => `- ${key}: ${value}`)), `\n## Dependencies`, ...Object.entries(doctorInfo.dependencies).map( ([binary, status]) => `- ${binary}: ${status.available ? `✅ ${status.version ?? 'Available'}` : '❌ Not found'}`, ), `\n## Environment Variables`, ...Object.entries(doctorInfo.environmentVariables) .filter(([key]) => key !== 'PATH' && key !== 'PYTHONPATH') // These are too long, handle separately .map(([key, value]) => `- ${key}: ${value ?? '(not set)'}`), `\n### PATH`, `\`\`\``, `${doctorInfo.environmentVariables.PATH ?? '(not set)'}`.split(':').join('\n'), `\`\`\``, `\n## Feature Status`, `\n### UI Automation (axe)`, `- Available: ${doctorInfo.features.axe.available ? '✅ Yes' : '❌ No'}`, `- UI Automation Supported: ${doctorInfo.features.axe.uiAutomationSupported ? '✅ Yes' : '❌ No'}`, `\n### Incremental Builds`, `- Enabled: ${doctorInfo.features.xcodemake.enabled ? '✅ Yes' : '❌ No'}`, `- Available: ${doctorInfo.features.xcodemake.available ? '✅ Yes' : '❌ No'}`, `- Makefile exists: ${doctorInfo.features.xcodemake.makefileExists ? '✅ Yes' : '❌ No'}`, `\n### Mise Integration`, `- Running under mise: ${doctorInfo.features.mise.running_under_mise ? '✅ Yes' : '❌ No'}`, `- Mise available: ${doctorInfo.features.mise.available ? '✅ Yes' : '❌ No'}`, `\n### Available Tools`, `- Total Plugins: ${'totalPlugins' in doctorInfo.pluginSystem ? doctorInfo.pluginSystem.totalPlugins : 0}`, `- Plugin Directories: ${'pluginDirectories' in doctorInfo.pluginSystem ? doctorInfo.pluginSystem.pluginDirectories : 0}`, ...('pluginsByDirectory' in doctorInfo.pluginSystem && doctorInfo.pluginSystem.pluginDirectories > 0 ? Object.entries(doctorInfo.pluginSystem.pluginsByDirectory).map( ([dir, tools]) => `- ${dir}: ${Array.isArray(tools) ? tools.length : 0} tools`, ) : ['- Plugin directory grouping unavailable in this build']), `\n### Runtime Tool Registration`, `- Mode: ${runtimeInfo.mode}`, `- Enabled Workflows: ${runtimeInfo.enabledWorkflows.length}`, `- Registered Tools: ${runtimeInfo.totalRegistered}`, ...(runtimeInfo.enabledWorkflows.length > 0 ? [`- Workflows: ${runtimeInfo.enabledWorkflows.join(', ')}`] : []), `\n## Tool Availability Summary`, `- Build Tools: ${!('error' in doctorInfo.xcode) ? '\u2705 Available' : '\u274c Not available'}`, `- UI Automation Tools: ${doctorInfo.features.axe.uiAutomationSupported ? '\u2705 Available' : '\u274c Not available'}`, `- Incremental Build Support: ${doctorInfo.features.xcodemake.available && doctorInfo.features.xcodemake.enabled ? '\u2705 Available & Enabled' : doctorInfo.features.xcodemake.available ? '\u2705 Available but Disabled' : '\u274c Not available'}`, `\n## Sentry`, `- Sentry enabled: ${doctorInfo.environmentVariables.SENTRY_DISABLED !== 'true' ? '✅ Yes' : '❌ No'}`, `\n## Troubleshooting Tips`, `- If UI automation tools are not available, install axe: \`brew tap cameroncooke/axe && brew install axe\``, `- If incremental build support is not available, you can download the tool from https://github.com/cameroncooke/xcodemake. Make sure it's executable and available in your PATH`, `- To enable xcodemake, set environment variable: \`export INCREMENTAL_BUILDS_ENABLED=1\``, `- For mise integration, follow instructions in the README.md file`, ...(process.env.XCODEBUILDMCP_DYNAMIC_TOOLS === 'true' ? [ `- Dynamic mode is enabled. Use 'discover_tools' to enable workflows relevant to your task`, ] : []), ].join('\n'); const result: ToolResponse = { content: [ { type: 'text', text: formattedOutput, }, ], }; // Restore previous silence flag if (prevSilence === undefined) { delete process.env.XCODEBUILDMCP_SILENCE_LOGS; } else { process.env.XCODEBUILDMCP_SILENCE_LOGS = prevSilence; } return result; }
- src/mcp/tools/doctor/doctor.ts:19-25 (schema)Zod schema defining optional input parameters for the doctor tool.// Define schema as ZodObject const doctorSchema = z.object({ enabled: z.boolean().optional().describe('Optional: dummy parameter to satisfy MCP protocol'), }); // Use z.infer for type safety type DoctorParams = z.infer<typeof doctorSchema>;
- src/mcp/tools/doctor/doctor.ts:271-277 (registration)MCP tool registration object defining name, description, schema, and wrapped handler function.export default { name: 'doctor', description: 'Provides comprehensive information about the MCP server environment, available dependencies, and configuration status.', schema: doctorSchema.shape, // MCP SDK compatibility handler: createTypedTool(doctorSchema, doctorMcpHandler, getDefaultCommandExecutor), };
- src/mcp/tools/doctor/index.ts:1-13 (registration)Workflow definition registering the doctor tool and related diagnostic capabilities.export const workflow = { name: 'System Doctor', description: 'Debug tools and system doctor for troubleshooting XcodeBuildMCP server, development environment, and tool availability.', platforms: ['system'], capabilities: [ 'doctor', 'server-diagnostics', 'troubleshooting', 'system-analysis', 'environment-validation', ], };
- Helper function creating dependency injection container for all doctor tool services (binary checks, Xcode info, system env, plugins, runtime info, features).export function createDoctorDependencies(executor: CommandExecutor): DoctorDependencies { const binaryChecker: BinaryChecker = { async checkBinaryAvailability(binary: string) { // If bundled axe is available, reflect that in dependencies even if not on PATH if (binary === 'axe' && areAxeToolsAvailable()) { return { available: true, version: 'Bundled' }; } try { const which = await executor(['which', binary], 'Check Binary Availability'); if (!which.success) { return { available: false }; } } catch { return { available: false }; } let version: string | undefined; const versionCommands: Record<string, string> = { axe: 'axe --version', mise: 'mise --version', }; if (binary in versionCommands) { try { const res = await executor(versionCommands[binary]!.split(' '), 'Get Binary Version'); if (res.success && res.output) { version = res.output.trim(); } } catch { // ignore } } return { available: true, version: version ?? 'Available (version info not available)' }; }, }; const xcode: XcodeInfoProvider = { async getXcodeInfo() { try { const xcodebuild = await executor(['xcodebuild', '-version'], 'Get Xcode Version'); if (!xcodebuild.success) throw new Error('xcodebuild command failed'); const version = xcodebuild.output.trim().split('\n').slice(0, 2).join(' - '); const pathRes = await executor(['xcode-select', '-p'], 'Get Xcode Path'); if (!pathRes.success) throw new Error('xcode-select command failed'); const path = pathRes.output.trim(); const selected = await executor(['xcrun', '--find', 'xcodebuild'], 'Find Xcodebuild'); if (!selected.success) throw new Error('xcrun --find command failed'); const selectedXcode = selected.output.trim(); const xcrun = await executor(['xcrun', '--version'], 'Get Xcrun Version'); if (!xcrun.success) throw new Error('xcrun --version command failed'); const xcrunVersion = xcrun.output.trim(); return { version, path, selectedXcode, xcrunVersion }; } catch (error) { return { error: error instanceof Error ? error.message : String(error) }; } }, }; const env: EnvironmentInfoProvider = { getEnvironmentVariables() { const relevantVars = [ 'INCREMENTAL_BUILDS_ENABLED', 'PATH', 'DEVELOPER_DIR', 'HOME', 'USER', 'TMPDIR', 'NODE_ENV', 'SENTRY_DISABLED', ]; const envVars: Record<string, string | undefined> = {}; for (const varName of relevantVars) { envVars[varName] = process.env[varName]; } Object.keys(process.env).forEach((key) => { if (key.startsWith('XCODEBUILDMCP_')) { envVars[key] = process.env[key]; } }); return envVars; }, getSystemInfo() { return { platform: os.platform(), release: os.release(), arch: os.arch(), cpus: `${os.cpus().length} x ${os.cpus()[0]?.model ?? 'Unknown'}`, memory: `${Math.round(os.totalmem() / (1024 * 1024 * 1024))} GB`, hostname: os.hostname(), username: os.userInfo().username, homedir: os.homedir(), tmpdir: os.tmpdir(), }; }, getNodeInfo() { return { version: process.version, execPath: process.execPath, pid: process.pid.toString(), ppid: process.ppid.toString(), platform: process.platform, arch: process.arch, cwd: process.cwd(), argv: process.argv.join(' '), }; }, }; const plugins: PluginInfoProvider = { async getPluginSystemInfo() { try { const workflows = await loadWorkflowGroups(); const pluginsByDirectory: Record<string, string[]> = {}; let totalPlugins = 0; for (const [dirName, wf] of workflows.entries()) { const toolNames = wf.tools.map((t) => t.name).filter(Boolean) as string[]; totalPlugins += toolNames.length; pluginsByDirectory[dirName] = toolNames; } return { totalPlugins, pluginDirectories: workflows.size, pluginsByDirectory, systemMode: 'plugin-based', }; } catch (error) { return { error: `Failed to load plugins: ${error instanceof Error ? error.message : 'Unknown error'}`, systemMode: 'error', }; } }, }; const runtime: RuntimeInfoProvider = { async getRuntimeToolInfo() { const dynamic = process.env.XCODEBUILDMCP_DYNAMIC_TOOLS === 'true'; if (dynamic) { const enabledWf = getEnabledWorkflows(); const enabledTools = getTrackedToolNames(); return { mode: 'dynamic', enabledWorkflows: enabledWf, enabledTools, totalRegistered: enabledTools.length, }; } // Static mode: all tools are registered const workflows = await loadWorkflowGroups(); const enabledWorkflows = Array.from(workflows.keys()); const plugins = await loadPlugins(); const enabledTools = Array.from(plugins.keys()); return { mode: 'static', enabledWorkflows, enabledTools, totalRegistered: enabledTools.length, }; }, }; const features: FeatureDetector = { areAxeToolsAvailable, isXcodemakeEnabled, isXcodemakeAvailable, doesMakefileExist, }; return { binaryChecker, xcode, env, plugins, runtime, features }; }