Skip to main content
Glama

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
NameRequiredDescriptionDefault
enabledNoOptional: dummy parameter to satisfy MCP protocol

Implementation Reference

  • 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;
    }
  • 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>;
  • 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),
    };
  • 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 };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. While it indicates this is an informational tool ('Provides comprehensive information'), it doesn't specify whether this operation has side effects, requires specific permissions, returns structured data, or has any rate limits. For a tool with no annotation coverage, this leaves significant behavioral questions unanswered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that efficiently communicates the tool's purpose without unnecessary words. It's front-loaded with the core functionality and provides specific details about what information is provided. Every word earns its place in this concise description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's informational nature and the absence of both annotations and an output schema, the description provides adequate but minimal context. It clearly states what information the tool provides but doesn't describe the format or structure of the returned information. For a diagnostic/information tool with no output schema, the description could benefit from more detail about the return format.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 1 parameter with 100% description coverage, and the schema description explains it's a 'dummy parameter to satisfy MCP protocol.' The tool description doesn't mention parameters at all, which is appropriate since the single parameter is essentially a technical requirement rather than functional. With 0 required parameters and high schema coverage, the description correctly focuses on the tool's purpose rather than parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Provides comprehensive information about the MCP server environment, available dependencies, and configuration status.' It uses specific verbs ('Provides comprehensive information') and identifies the resource ('MCP server environment, available dependencies, and configuration status'). However, it doesn't explicitly differentiate from sibling tools like 'discover_tools' or 'describe_ui' which might have overlapping functionality.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention any prerequisites, timing considerations, or comparisons to sibling tools like 'discover_tools' (which might list available tools) or 'describe_ui' (which might provide UI-related information). The usage context is implied but not explicitly stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

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/getsentry/XcodeBuildMCP'

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