Skip to main content
Glama
build_ios_simulator.ts28.2 kB
/** * iOS Simulator Build Tools - Tools for building and running iOS applications in simulators * * This module provides specialized tools for building and running iOS applications in simulators * using xcodebuild. It supports both workspace and project-based builds with simulator targeting * by name or UUID. * * Responsibilities: * - Building iOS applications for simulators from project files and workspaces * - Running iOS applications in simulators after building * - Supporting simulator targeting by name or UUID * - Handling build configuration and derived data paths */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { log } from '../utils/logger.js'; import { XcodePlatform } from '../utils/xcode.js'; import { executeCommand } from '../utils/command.js'; import { validateRequiredParam, createTextResponse } from '../utils/validation.js'; import { ToolResponse } from '../types/common.js'; import { executeXcodeBuildCommand } from '../utils/build-utils.js'; import { registerTool, workspacePathSchema, projectPathSchema, schemeSchema, configurationSchema, derivedDataPathSchema, extraArgsSchema, simulatorNameSchema, simulatorIdSchema, useLatestOSSchema, preferXcodebuildSchema, } from './common.js'; import { execSync } from 'child_process'; // --- Private Helper Functions --- /** * Internal logic for building iOS Simulator apps. */ async function _handleIOSSimulatorBuildLogic(params: { workspacePath?: string; projectPath?: string; scheme: string; configuration: string; simulatorName?: string; simulatorId?: string; useLatestOS: boolean; derivedDataPath?: string; extraArgs?: string[]; preferXcodebuild?: boolean; }): Promise<ToolResponse> { log('info', `Starting iOS Simulator build for scheme ${params.scheme} (internal)`); return executeXcodeBuildCommand( { ...params, }, { platform: XcodePlatform.iOSSimulator, simulatorName: params.simulatorName, simulatorId: params.simulatorId, useLatestOS: params.useLatestOS, logPrefix: 'iOS Simulator Build', }, params.preferXcodebuild, 'build', ); } /** * Internal logic for building and running iOS Simulator apps. */ async function _handleIOSSimulatorBuildAndRunLogic(params: { workspacePath?: string; projectPath?: string; scheme: string; configuration: string; simulatorName?: string; simulatorId?: string; useLatestOS: boolean; derivedDataPath?: string; extraArgs?: string[]; preferXcodebuild?: boolean; }): Promise<ToolResponse> { log('info', `Starting iOS Simulator build and run for scheme ${params.scheme} (internal)`); try { // --- Build Step --- const buildResult = await _handleIOSSimulatorBuildLogic(params); if (buildResult.isError) { return buildResult; // Return the build error } // --- Get App Path Step --- // Create the command array for xcodebuild with -showBuildSettings option const command = ['xcodebuild', '-showBuildSettings']; // Add the workspace or project if (params.workspacePath) { command.push('-workspace', params.workspacePath); } else if (params.projectPath) { command.push('-project', params.projectPath); } // Add the scheme and configuration command.push('-scheme', params.scheme); command.push('-configuration', params.configuration); // Handle destination for simulator let destinationString = ''; if (params.simulatorId) { destinationString = `platform=iOS Simulator,id=${params.simulatorId}`; } else if (params.simulatorName) { destinationString = `platform=iOS Simulator,name=${params.simulatorName}${params.useLatestOS ? ',OS=latest' : ''}`; } else { return createTextResponse( 'Either simulatorId or simulatorName must be provided for iOS simulator build', true, ); } command.push('-destination', destinationString); // Add derived data path if provided if (params.derivedDataPath) { command.push('-derivedDataPath', params.derivedDataPath); } // Add extra args if provided if (params.extraArgs && params.extraArgs.length > 0) { command.push(...params.extraArgs); } // Execute the command directly const result = await executeCommand(command, 'Get App Path'); // If there was an error with the command execution, return it if (!result.success) { return createTextResponse( `Build succeeded, but failed to get app path: ${result.error || 'Unknown error'}`, true, ); } // Parse the output to extract the app path const buildSettingsOutput = result.output; // Extract CODESIGNING_FOLDER_PATH from build settings to get app path const appPathMatch = buildSettingsOutput.match(/CODESIGNING_FOLDER_PATH = (.+\.app)/); if (!appPathMatch || !appPathMatch[1]) { return createTextResponse( `Build succeeded, but could not find app path in build settings.`, true, ); } const appBundlePath = appPathMatch[1].trim(); log('info', `App bundle path for run: ${appBundlePath}`); // --- Find/Boot Simulator Step --- let simulatorUuid = params.simulatorId; if (!simulatorUuid && params.simulatorName) { try { log('info', `Finding simulator UUID for name: ${params.simulatorName}`); const simulatorsOutput = execSync('xcrun simctl list devices available --json').toString(); const simulatorsJson = JSON.parse(simulatorsOutput); let foundSimulator = null; // Find the simulator in the available devices list for (const runtime in simulatorsJson.devices) { const devices = simulatorsJson.devices[runtime]; for (const device of devices) { if (device.name === params.simulatorName && device.isAvailable) { foundSimulator = device; break; } } if (foundSimulator) break; } if (foundSimulator) { simulatorUuid = foundSimulator.udid; log('info', `Found simulator for run: ${foundSimulator.name} (${simulatorUuid})`); } else { return createTextResponse( `Build succeeded, but could not find an available simulator named '${params.simulatorName}'. Use list_simulators({}) to check available devices.`, true, ); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createTextResponse( `Build succeeded, but error finding simulator: ${errorMessage}`, true, ); } } if (!simulatorUuid) { return createTextResponse( 'Build succeeded, but no simulator specified and failed to find a suitable one.', true, ); } // Ensure simulator is booted try { log('info', `Checking simulator state for UUID: ${simulatorUuid}`); const simulatorStateOutput = execSync('xcrun simctl list devices').toString(); const simulatorLine = simulatorStateOutput .split('\n') .find((line) => line.includes(simulatorUuid)); const isBooted = simulatorLine ? simulatorLine.includes('(Booted)') : false; if (!simulatorLine) { return createTextResponse( `Build succeeded, but could not find simulator with UUID: ${simulatorUuid}`, true, ); } if (!isBooted) { log('info', `Booting simulator ${simulatorUuid}`); execSync(`xcrun simctl boot "${simulatorUuid}"`); // Wait a moment for the simulator to fully boot await new Promise((resolve) => setTimeout(resolve, 2000)); } else { log('info', `Simulator ${simulatorUuid} is already booted`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('error', `Error checking/booting simulator: ${errorMessage}`); return createTextResponse( `Build succeeded, but error checking/booting simulator: ${errorMessage}`, true, ); } // --- Open Simulator UI Step --- try { log('info', 'Opening Simulator app'); execSync('open -a Simulator'); // Give the Simulator app time to open await new Promise((resolve) => setTimeout(resolve, 2000)); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('warning', `Warning: Could not open Simulator app: ${errorMessage}`); // Don't fail the whole operation for this } // --- Install App Step --- try { log('info', `Installing app at path: ${appBundlePath} to simulator: ${simulatorUuid}`); execSync(`xcrun simctl install "${simulatorUuid}" "${appBundlePath}"`); // Wait a moment for installation to complete await new Promise((resolve) => setTimeout(resolve, 1000)); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('error', `Error installing app: ${errorMessage}`); return createTextResponse( `Build succeeded, but error installing app on simulator: ${errorMessage}`, true, ); } // --- Get Bundle ID Step --- let bundleId; try { log('info', `Extracting bundle ID from app: ${appBundlePath}`); // Try PlistBuddy first (more reliable) try { bundleId = execSync( `/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${appBundlePath}/Info.plist"`, ) .toString() .trim(); } catch (plistError: unknown) { // Fallback to defaults if PlistBuddy fails const errorMessage = plistError instanceof Error ? plistError.message : String(plistError); log('warning', `PlistBuddy failed, trying defaults: ${errorMessage}`); bundleId = execSync(`defaults read "${appBundlePath}/Info" CFBundleIdentifier`) .toString() .trim(); } if (!bundleId) { throw new Error('Could not extract bundle ID from Info.plist'); } log('info', `Bundle ID for run: ${bundleId}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('error', `Error getting bundle ID: ${errorMessage}`); return createTextResponse( `Build and install succeeded, but error getting bundle ID: ${errorMessage}`, true, ); } // --- Launch App Step --- try { log('info', `Launching app with bundle ID: ${bundleId} on simulator: ${simulatorUuid}`); execSync(`xcrun simctl launch "${simulatorUuid}" "${bundleId}"`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('error', `Error launching app: ${errorMessage}`); return createTextResponse( `Build and install succeeded, but error launching app on simulator: ${errorMessage}`, true, ); } // --- Success --- log('info', '✅ iOS simulator build & run succeeded.'); const target = params.simulatorId ? `simulator UUID ${params.simulatorId}` : `simulator name '${params.simulatorName}'`; return { content: [ { type: 'text', text: `✅ iOS simulator build and run succeeded for scheme ${params.scheme} targeting ${target}. The app (${bundleId}) is now running in the iOS Simulator. If you don't see the simulator window, it may be hidden behind other windows. The Simulator app should be open. Next Steps: - Option 1: Capture structured logs only (app continues running): start_simulator_log_capture({ simulatorUuid: '${simulatorUuid}', bundleId: '${bundleId}' }) - Option 2: Capture both console and structured logs (app will restart): start_simulator_log_capture({ simulatorUuid: '${simulatorUuid}', bundleId: '${bundleId}', captureConsole: true }) - Option 3: Launch app with logs in one step (for a fresh start): launch_app_with_logs_in_simulator({ simulatorUuid: '${simulatorUuid}', bundleId: '${bundleId}' }) When done with any option, use: stop_sim_log_cap({ logSessionId: 'SESSION_ID' })`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); log('error', `Error in iOS Simulator build and run: ${errorMessage}`); return createTextResponse(`Error in iOS Simulator build and run: ${errorMessage}`, true); } } // --- Public Tool Definitions --- /** * Registers the iOS Simulator build by name workspace tool */ export function registerIOSSimulatorBuildByNameWorkspaceTool(server: McpServer): void { type Params = { workspacePath: string; scheme: string; simulatorName: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_ios_sim_name_ws', "Builds an iOS app from a workspace for a specific simulator by name. IMPORTANT: Requires workspacePath, scheme, and simulatorName. Example: build_ios_sim_name_ws({ workspacePath: '/path/to/MyProject.xcworkspace', scheme: 'MyScheme', simulatorName: 'iPhone 16' })", { workspacePath: workspacePathSchema, scheme: schemeSchema, simulatorName: simulatorNameSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath); if (!workspaceValidation.isValid) return workspaceValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorNameValidation = validateRequiredParam('simulatorName', params.simulatorName); if (!simulatorNameValidation.isValid) return simulatorNameValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build by name project tool */ export function registerIOSSimulatorBuildByNameProjectTool(server: McpServer): void { type Params = { projectPath: string; scheme: string; simulatorName: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_ios_sim_name_proj', "Builds an iOS app from a project file for a specific simulator by name. IMPORTANT: Requires projectPath, scheme, and simulatorName. Example: build_ios_sim_name_proj({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme', simulatorName: 'iPhone 16' })", { projectPath: projectPathSchema, scheme: schemeSchema, simulatorName: simulatorNameSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const projectValidation = validateRequiredParam('projectPath', params.projectPath); if (!projectValidation.isValid) return projectValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorNameValidation = validateRequiredParam('simulatorName', params.simulatorName); if (!simulatorNameValidation.isValid) return simulatorNameValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build by ID workspace tool */ export function registerIOSSimulatorBuildByIdWorkspaceTool(server: McpServer): void { type Params = { workspacePath: string; scheme: string; simulatorId: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_ios_sim_id_ws', "Builds an iOS app from a workspace for a specific simulator by UUID. IMPORTANT: Requires workspacePath, scheme, and simulatorId. Example: build_ios_sim_id_ws({ workspacePath: '/path/to/MyProject.xcworkspace', scheme: 'MyScheme', simulatorId: 'SIMULATOR_UUID' })", { workspacePath: workspacePathSchema, scheme: schemeSchema, simulatorId: simulatorIdSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath); if (!workspaceValidation.isValid) return workspaceValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorIdValidation = validateRequiredParam('simulatorId', params.simulatorId); if (!simulatorIdValidation.isValid) return simulatorIdValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, // May be ignored by xcodebuild preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build by ID project tool */ export function registerIOSSimulatorBuildByIdProjectTool(server: McpServer): void { type Params = { projectPath: string; scheme: string; simulatorId: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_ios_sim_id_proj', "Builds an iOS app from a project file for a specific simulator by UUID. IMPORTANT: Requires projectPath, scheme, and simulatorId. Example: build_ios_sim_id_proj({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme', simulatorId: 'SIMULATOR_UUID' })", { projectPath: projectPathSchema, scheme: schemeSchema, simulatorId: simulatorIdSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const projectValidation = validateRequiredParam('projectPath', params.projectPath); if (!projectValidation.isValid) return projectValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorIdValidation = validateRequiredParam('simulatorId', params.simulatorId); if (!simulatorIdValidation.isValid) return simulatorIdValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, // May be ignored by xcodebuild preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build and run by name workspace tool */ export function registerIOSSimulatorBuildAndRunByNameWorkspaceTool(server: McpServer): void { type Params = { workspacePath: string; scheme: string; simulatorName: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_run_ios_sim_name_ws', "Builds and runs an iOS app from a workspace on a simulator specified by name. IMPORTANT: Requires workspacePath, scheme, and simulatorName. Example: build_run_ios_sim_name_ws({ workspacePath: '/path/to/workspace', scheme: 'MyScheme', simulatorName: 'iPhone 16' })", { workspacePath: workspacePathSchema, scheme: schemeSchema, simulatorName: simulatorNameSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath); if (!workspaceValidation.isValid) return workspaceValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorNameValidation = validateRequiredParam('simulatorName', params.simulatorName); if (!simulatorNameValidation.isValid) return simulatorNameValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildAndRunLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build and run by name project tool */ export function registerIOSSimulatorBuildAndRunByNameProjectTool(server: McpServer): void { type Params = { projectPath: string; scheme: string; simulatorName: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_run_ios_sim_name_proj', "Builds and runs an iOS app from a project file on a simulator specified by name. IMPORTANT: Requires projectPath, scheme, and simulatorName. Example: build_run_ios_sim_name_proj({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme', simulatorName: 'iPhone 16' })", { projectPath: projectPathSchema, scheme: schemeSchema, simulatorName: simulatorNameSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const projectValidation = validateRequiredParam('projectPath', params.projectPath); if (!projectValidation.isValid) return projectValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorNameValidation = validateRequiredParam('simulatorName', params.simulatorName); if (!simulatorNameValidation.isValid) return simulatorNameValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildAndRunLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build and run by ID workspace tool */ export function registerIOSSimulatorBuildAndRunByIdWorkspaceTool(server: McpServer): void { type Params = { workspacePath: string; scheme: string; simulatorId: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_run_ios_sim_id_ws', "Builds and runs an iOS app from a workspace on a simulator specified by UUID. IMPORTANT: Requires workspacePath, scheme, and simulatorId. Example: build_run_ios_sim_id_ws({ workspacePath: '/path/to/workspace', scheme: 'MyScheme', simulatorId: 'SIMULATOR_UUID' })", { workspacePath: workspacePathSchema, scheme: schemeSchema, simulatorId: simulatorIdSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const workspaceValidation = validateRequiredParam('workspacePath', params.workspacePath); if (!workspaceValidation.isValid) return workspaceValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorIdValidation = validateRequiredParam('simulatorId', params.simulatorId); if (!simulatorIdValidation.isValid) return simulatorIdValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildAndRunLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, // May be ignored preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); } /** * Registers the iOS Simulator build and run by ID project tool */ export function registerIOSSimulatorBuildAndRunByIdProjectTool(server: McpServer): void { type Params = { projectPath: string; scheme: string; simulatorId: string; configuration?: string; derivedDataPath?: string; extraArgs?: string[]; useLatestOS?: boolean; preferXcodebuild?: boolean; }; registerTool<Params>( server, 'build_run_ios_sim_id_proj', "Builds and runs an iOS app from a project file on a simulator specified by UUID. IMPORTANT: Requires projectPath, scheme, and simulatorId. Example: build_run_ios_sim_id_proj({ projectPath: '/path/to/project.xcodeproj', scheme: 'MyScheme', simulatorId: 'SIMULATOR_UUID' })", { projectPath: projectPathSchema, scheme: schemeSchema, simulatorId: simulatorIdSchema, configuration: configurationSchema, derivedDataPath: derivedDataPathSchema, extraArgs: extraArgsSchema, useLatestOS: useLatestOSSchema, preferXcodebuild: preferXcodebuildSchema, }, async (params: Params) => { // Validate required parameters const projectValidation = validateRequiredParam('projectPath', params.projectPath); if (!projectValidation.isValid) return projectValidation.errorResponse!; const schemeValidation = validateRequiredParam('scheme', params.scheme); if (!schemeValidation.isValid) return schemeValidation.errorResponse!; const simulatorIdValidation = validateRequiredParam('simulatorId', params.simulatorId); if (!simulatorIdValidation.isValid) return simulatorIdValidation.errorResponse!; // Provide defaults return _handleIOSSimulatorBuildAndRunLogic({ ...params, configuration: params.configuration ?? 'Debug', useLatestOS: params.useLatestOS ?? true, // May be ignored preferXcodebuild: params.preferXcodebuild ?? false, }); }, ); }

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