Skip to main content
Glama
diagnostic.ts13.9 kB
/** * Diagnostic Tool - Provides comprehensive information about the MCP server environment * * This module provides a diagnostic tool that reports on the server environment, * available dependencies, and configuration status. It's only registered when * the XCODEBUILDMCP_DEBUG environment variable is set. * * Responsibilities: * - Reporting on Node.js and system environment * - Checking for required dependencies (xcodebuild, axe, etc.) * - Reporting on environment variables that affect server behavior * - Providing detailed information for debugging and troubleshooting */ import { z } from 'zod'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ToolResponse } from '../types/common.js'; import { log } from '../utils/logger.js'; import { execSync } from 'child_process'; import { version } from '../version.js'; import { areAxeToolsAvailable } from '../utils/axe-setup.js'; import { isXcodemakeEnabled, isXcodemakeAvailable, doesMakefileExist } from '../utils/xcodemake.js'; import * as os from 'os'; import { ToolGroup, isSelectiveToolsEnabled, listEnabledGroups } from '../utils/tool-groups.js'; // Constants const LOG_PREFIX = '[Diagnostic]'; /** * Check if a binary is available in the PATH and attempt to get its version * @param binary The binary name to check * @returns Object with availability status and optional version string */ export function checkBinaryAvailability(binary: string): { available: boolean; version?: string } { // First check if the binary exists at all try { execSync(`which ${binary}`, { stdio: 'ignore' }); } catch { // Binary not found in PATH return { available: false }; } // Binary exists, now try to get version info if possible let version: string | undefined; // Define version commands for specific binaries const versionCommands: Record<string, string> = { axe: 'axe --version', mise: 'mise --version', }; // Try to get version using binary-specific commands if (binary in versionCommands) { try { const output = execSync(versionCommands[binary], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], }).trim(); if (output) { // For xcodebuild, include both version and build info if (binary === 'xcodebuild') { const lines = output.split('\n').slice(0, 2); version = lines.join(' - '); } else { version = output; } } } catch { // Command failed, continue to generic attempts } } // We only care about the specific binaries we've defined return { available: true, version: version || 'Available (version info not available)', }; } /** * Get information about the Xcode installation */ export function getXcodeInfo(): | { version: string; path: string; selectedXcode: string; xcrunVersion: string } | { error: string } { try { // Get Xcode version info const xcodebuildOutput = execSync('xcodebuild -version', { encoding: 'utf8' }).trim(); const version = xcodebuildOutput.split('\n').slice(0, 2).join(' - '); // Get Xcode selection info const path = execSync('xcode-select -p', { encoding: 'utf8' }).trim(); const selectedXcode = execSync('xcrun --find xcodebuild', { encoding: 'utf8' }).trim(); // Get xcrun version info const xcrunVersion = execSync('xcrun --version', { encoding: 'utf8' }).trim(); return { version, path, selectedXcode, xcrunVersion }; } catch (error) { return { error: error instanceof Error ? error.message : String(error) }; } } /** * Get information about the environment variables */ export function getEnvironmentVariables(): Record<string, string | undefined> { const relevantVars = [ 'XCODEBUILDMCP_DEBUG', 'INCREMENTAL_BUILDS_ENABLED', 'PATH', 'DEVELOPER_DIR', 'HOME', 'USER', 'TMPDIR', 'NODE_ENV', 'SENTRY_DISABLED', ]; const envVars: Record<string, string | undefined> = {}; // Add standard environment variables for (const varName of relevantVars) { envVars[varName] = process.env[varName]; } // Add all tool and group environment variables for debugging Object.keys(process.env).forEach((key) => { if ( key.startsWith('XCODEBUILDMCP_TOOL_') || key.startsWith('XCODEBUILDMCP_GROUP_') || key.startsWith('XCODEBUILDMCP_') ) { envVars[key] = process.env[key]; } }); return envVars; } /** * Get system information */ function getSystemInfo(): Record<string, string> { 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(), }; } /** * Get Node.js information */ function getNodeInfo(): Record<string, string> { 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(' '), }; } /** * Get information about tool groups and their status */ export function getToolGroupsInfo(): Record<string, unknown> { const selectiveMode = isSelectiveToolsEnabled(); const enabledGroups = listEnabledGroups(); const toolGroups: Record<string, { enabled: boolean; envVar: string }> = {}; // Add information about each tool group for (const group of Object.values(ToolGroup)) { const isEnabled = process.env[group] === 'true'; toolGroups[group] = { enabled: isEnabled, envVar: group, }; } return { selectiveMode, enabledGroups, groups: toolGroups, }; } /** * Get a list of individually enabled tools via environment variables */ function getIndividuallyEnabledTools(): string[] { return Object.keys(process.env) .filter((key) => key.startsWith('XCODEBUILDMCP_TOOL_') && process.env[key] === 'true') .map((key) => key.replace('XCODEBUILDMCP_TOOL_', '')); } /** * Run the diagnostic tool and return the results * @returns Promise resolving to ToolResponse with diagnostic information */ export async function runDiagnosticTool(): Promise<ToolResponse> { log('info', `${LOG_PREFIX}: Running diagnostic tool`); // Check for required binaries const requiredBinaries = ['axe', 'xcodemake', 'mise']; const binaryStatus: Record<string, { available: boolean; version?: string }> = {}; for (const binary of requiredBinaries) { binaryStatus[binary] = checkBinaryAvailability(binary); } // Get Xcode information const xcodeInfo = getXcodeInfo(); // Get environment variables const envVars = getEnvironmentVariables(); // Get system information const systemInfo = getSystemInfo(); // Get Node.js information const nodeInfo = getNodeInfo(); // Check for axe tools availability const axeAvailable = areAxeToolsAvailable(); // Get tool groups information const toolGroupsInfo = getToolGroupsInfo(); // Get individually enabled tools const individuallyEnabledTools = getIndividuallyEnabledTools(); // Check for xcodemake configuration const xcodemakeEnabled = isXcodemakeEnabled(); const xcodemakeAvailable = await isXcodemakeAvailable(); const makefileExists = doesMakefileExist('./'); // Compile the diagnostic information const diagnosticInfo = { serverVersion: version, timestamp: new Date().toISOString(), system: systemInfo, node: nodeInfo, xcode: xcodeInfo, dependencies: binaryStatus, environmentVariables: envVars, features: { axe: { available: axeAvailable, uiAutomationSupported: axeAvailable && binaryStatus['axe'].available, }, xcodemake: { enabled: xcodemakeEnabled, available: xcodemakeAvailable, makefileExists: makefileExists, }, mise: { running_under_mise: Boolean(process.env.XCODEBUILDMCP_RUNNING_UNDER_MISE), available: binaryStatus['mise'].available, }, }, toolGroups: toolGroupsInfo as { selectiveMode: boolean; enabledGroups: string[]; groups: Record<string, { enabled: boolean; envVar: string }>; }, individuallyEnabledTools, }; // Format the diagnostic information as a nicely formatted text response const formattedOutput = [ `# XcodeBuildMCP Diagnostic Report`, `\nGenerated: ${diagnosticInfo.timestamp}`, `Server Version: ${diagnosticInfo.serverVersion}`, `\n## System Information`, ...Object.entries(diagnosticInfo.system).map(([key, value]) => `- ${key}: ${value}`), `\n## Node.js Information`, ...Object.entries(diagnosticInfo.node).map(([key, value]) => `- ${key}: ${value}`), `\n## Xcode Information`, ...('error' in diagnosticInfo.xcode ? [`- Error: ${diagnosticInfo.xcode.error}`] : Object.entries(diagnosticInfo.xcode).map(([key, value]) => `- ${key}: ${value}`)), `\n## Dependencies`, ...Object.entries(diagnosticInfo.dependencies).map( ([binary, status]) => `- ${binary}: ${status.available ? `✅ ${status.version || 'Available'}` : '❌ Not found'}`, ), `\n## Environment Variables`, ...Object.entries(diagnosticInfo.environmentVariables) .filter(([key]) => key !== 'PATH' && key !== 'PYTHONPATH') // These are too long, handle separately .map(([key, value]) => `- ${key}: ${value || '(not set)'}`), `\n### PATH`, `\`\`\``, `${diagnosticInfo.environmentVariables.PATH || '(not set)'}`.split(':').join('\n'), `\`\`\``, `\n## Feature Status`, `\n### UI Automation (axe)`, `- Available: ${diagnosticInfo.features.axe.available ? '✅ Yes' : '❌ No'}`, `- UI Automation Supported: ${diagnosticInfo.features.axe.uiAutomationSupported ? '✅ Yes' : '❌ No'}`, `\n### Incremental Builds`, `- Enabled: ${diagnosticInfo.features.xcodemake.enabled ? '✅ Yes' : '❌ No'}`, `- Available: ${diagnosticInfo.features.xcodemake.available ? '✅ Yes' : '❌ No'}`, `- Makefile exists: ${diagnosticInfo.features.xcodemake.makefileExists ? '✅ Yes' : '❌ No'}`, `\n### Mise Integration`, `- Running under mise: ${diagnosticInfo.features.mise.running_under_mise ? '✅ Yes' : '❌ No'}`, `- Mise available: ${diagnosticInfo.features.mise.available ? '✅ Yes' : '❌ No'}`, `\n### Tool Groups Status`, ...(diagnosticInfo.toolGroups.selectiveMode ? Object.entries(diagnosticInfo.toolGroups.groups).map(([group, info]) => { // Extract the group name without the prefix for display purposes const displayName = group.replace('XCODEBUILDMCP_GROUP_', ''); return `- ${displayName}: ${info.enabled ? '✅ Enabled' : '❌ Disabled'} (Set with ${info.envVar}=true)`; }) : ['- All tool groups are enabled (selective mode is disabled).']), `\n### Individually Enabled Tools`, ...(diagnosticInfo.toolGroups.selectiveMode ? diagnosticInfo.individuallyEnabledTools.length > 0 ? diagnosticInfo.individuallyEnabledTools.map( (tool) => `- ${tool}: ✅ Enabled (via XCODEBUILDMCP_TOOL_${tool}=true)`, ) : ['- No tools are individually enabled via environment variables.'] : ['- All tools are enabled (selective mode is disabled).']), `\n## Tool Availability Summary`, `- Build Tools: ${!('error' in diagnosticInfo.xcode) ? '\u2705 Available' : '\u274c Not available'}`, `- UI Automation Tools: ${diagnosticInfo.features.axe.uiAutomationSupported ? '\u2705 Available' : '\u274c Not available'}`, `- Incremental Build Support: ${diagnosticInfo.features.xcodemake.available && diagnosticInfo.features.xcodemake.enabled ? '\u2705 Available & Enabled' : diagnosticInfo.features.xcodemake.available ? '\u2705 Available but Disabled' : '\u274c Not available'}`, `\n## Sentry`, `- Sentry enabled: ${diagnosticInfo.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`, `- To enable specific tool groups, set the appropriate environment variables (e.g., \`export XCODEBUILDMCP_GROUP_DISCOVERY=true\`)`, `- If you're having issues with environment variables, make sure to use the correct prefix:`, ` - Use \`XCODEBUILDMCP_GROUP_NAME=true\` to enable a tool group`, ` - Use \`XCODEBUILDMCP_TOOL_NAME=true\` to enable an individual tool`, ` - Common mistake: Using \`XCODEBUILDMCP_BUILD_IOS_SIM=true\` instead of \`XCODEBUILDMCP_GROUP_BUILD_IOS_SIM=true\``, ].join('\n'); return { content: [ { type: 'text', text: formattedOutput, }, ], }; } /** * Registers the diagnostic tool with the dispatcher. * This tool is only registered when the XCODEBUILDMCP_DEBUG environment variable is set. * @param server The McpServer instance. */ export function registerDiagnosticTool(server: McpServer): void { server.tool( 'diagnostic', 'Provides comprehensive information about the MCP server environment, available dependencies, and configuration status.', { enabled: z.boolean().optional().describe('Optional: dummy parameter to satisfy MCP protocol'), }, async (): Promise<ToolResponse> => { return runDiagnosticTool(); }, ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SampsonKY/XcodeBuildMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server