Skip to main content
Glama
devyhan
by devyhan
index.ts32 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { exec, spawn } from "child_process"; import { promisify } from "util"; import { executeCommand, findDeviceIdentifier, getBundleIdentifier, buildAndInstallApp, launchAppOnDevice, startDeviceLogStream, findDeviceInfo, getAllDevices, findXcodeInstallations, getDeviceCtlPath } from './device-runner.js'; // 명령어 실행을 위한 promisify const execPromise = promisify(exec); // 내부 유틸리티: 명령어 실행 함수 (LLM에 노출되지 않음) - 이전 버전 호환성 유지 async function _executeCommand(command: string, workingDir?: string, timeout: number = 60000) { return executeCommand(command, workingDir, timeout); } /** * Shell 명령어를 실행하는 MCP 서버 */ async function main() { const server = new McpServer({ name: "xcode-mcp", version: "0.4.0", description: "MCP Server for executing shell commands, particularly useful for Xcode-related operations" }); console.error("xcode-mcp 서버 초기화..."); // 2. Xcode 프로젝트 정보 도구 server.tool( "xcode-project-info", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로") }, async ({ projectPath }) => { try { console.error(`Xcode 프로젝트 정보 확인: ${projectPath}`); // xcodebuild -list 명령어로 프로젝트 정보 가져오기 const command = `xcodebuild -list -json -project "${projectPath}"`; try { const { stdout } = await executeCommand(command); // JSON 파싱 시도 try { const projectInfo = JSON.parse(stdout); return { content: [{ type: "text", text: `Xcode 프로젝트 정보:\n${JSON.stringify(projectInfo, null, 2)}` }] }; } catch (parseError) { // JSON 파싱 실패 시 원본 출력 반환 return { content: [{ type: "text", text: `Xcode 프로젝트 정보:\n${stdout}` }] }; } } catch (error: any) { throw error; } } catch (error: any) { console.error(`Xcode 프로젝트 정보 오류: ${error.message}`); return { content: [{ type: "text", text: `Xcode 프로젝트 정보를 가져오는 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 3. Xcode 빌드 도구 (개선된 버전) server.tool( "xcode-build", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로"), scheme: z.string().describe("빌드할 스킴"), configuration: z.string().optional().describe("빌드 구성 (예: Debug, Release)"), destination: z.string().optional().describe("빌드 대상 (예: 'platform=iOS Simulator,name=iPhone 14')"), extraArgs: z.array(z.string()).optional().describe("추가 xcodebuild 인자들"), outputDir: z.string().optional().describe("빌드 결과물 저장 경로 (SYMROOT)"), clean: z.boolean().optional().describe("빌드 전 clean 실행 여부") }, async ({ projectPath, scheme, configuration, destination, extraArgs = [], outputDir, clean = false }) => { try { console.error(`Xcode 프로젝트 빌드: ${projectPath}, Scheme: ${scheme}`); let command = `xcodebuild`; // 워크스페이스인지 프로젝트인지 확인 if (projectPath.endsWith(".xcworkspace")) { command += ` -workspace "${projectPath}"`; } else { command += ` -project "${projectPath}"`; } command += ` -scheme "${scheme}"`; if (clean) { command += ` clean`; } command += ` build`; // 명시적으로 build 액션 지정 if (configuration) { command += ` -configuration "${configuration}"`; } if (destination) { command += ` -destination "${destination}"`; } if (outputDir) { command += ` SYMROOT="${outputDir}"`; } // 추가 인자 추가 if (extraArgs.length > 0) { command += " " + extraArgs.join(" "); } console.error(`실행할 빌드 명령어: ${command}`); // 빌드 명령어 실행 try { const { stdout, stderr } = await executeCommand(command); let resultText = "빌드 결과:\n"; if (stdout) resultText += `${stdout}\n`; if (stderr) resultText += `STDERR:\n${stderr}\n`; return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`Xcode 빌드 오류: ${error.message}`); return { content: [{ type: "text", text: `Xcode 빌드 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 4. 프로젝트 스킴 및 타겟 목록 조회 도구 server.tool( "xcode-list-schemes", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로") }, async ({ projectPath }) => { try { console.error(`Xcode 스킴 목록 조회: ${projectPath}`); // 워크스페이스인지 프로젝트인지 확인 let command; if (projectPath.endsWith(".xcworkspace")) { command = `xcodebuild -list -workspace "${projectPath}"`; } else { command = `xcodebuild -list -project "${projectPath}"`; } try { const { stdout, stderr } = await executeCommand(command); let resultText = "Xcode 스킴 및 타겟 목록:\n"; if (stdout) resultText += `${stdout}\n`; if (stderr) resultText += `${stderr}\n`; return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`스킴 목록 조회 오류: ${error.message}`); return { content: [{ type: "text", text: `스킴 목록을 조회하는 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 5. Xcode 테스트 실행 도구 server.tool( "xcode-test", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로"), scheme: z.string().describe("테스트할 스킴"), destination: z.string().describe("테스트 대상 (예: 'platform=iOS Simulator,name=iPhone 14')"), testPlan: z.string().optional().describe("사용할 테스트 플랜 이름"), onlyTesting: z.array(z.string()).optional().describe("실행할 특정 테스트 식별자들 (예: ['ModuleTests/ClassTests/testMethod'])"), skipTesting: z.array(z.string()).optional().describe("건너뛸 테스트 식별자들"), resultBundlePath: z.string().optional().describe("테스트 결과 번들 저장 경로"), buildForTesting: z.boolean().optional().describe("테스트용 빌드만 수행할지 여부"), testWithoutBuilding: z.boolean().optional().describe("빌드 없이 테스트만 수행할지 여부") }, async ({ projectPath, scheme, destination, testPlan, onlyTesting = [], skipTesting = [], resultBundlePath, buildForTesting = false, testWithoutBuilding = false }) => { try { console.error(`Xcode 테스트 실행: ${projectPath}, Scheme: ${scheme}`); let command = `xcodebuild`; // 워크스페이스인지 프로젝트인지 확인 if (projectPath.endsWith(".xcworkspace")) { command += ` -workspace "${projectPath}"`; } else { command += ` -project "${projectPath}"`; } command += ` -scheme "${scheme}"`; command += ` -destination "${destination}"`; // 테스트 모드 설정 if (buildForTesting) { command += ` build-for-testing`; } else if (testWithoutBuilding) { command += ` test-without-building`; } else { command += ` test`; // 기본 모드: 빌드 후 테스트 } // 테스트 플랜 설정 if (testPlan) { command += ` -testPlan "${testPlan}"`; } // 특정 테스트만 실행 if (onlyTesting.length > 0) { for (const test of onlyTesting) { command += ` -only-testing:"${test}"`; } } // 특정 테스트 건너뛰기 if (skipTesting.length > 0) { for (const test of skipTesting) { command += ` -skip-testing:"${test}"`; } } // 결과 번들 경로 설정 if (resultBundlePath) { command += ` -resultBundlePath "${resultBundlePath}"`; } console.error(`실행할 테스트 명령어: ${command}`); // 테스트 명령어 실행 try { const { stdout, stderr } = await executeCommand(command); let resultText = "테스트 결과:\n"; if (stdout) resultText += `${stdout}\n`; if (stderr) resultText += `STDERR:\n${stderr}\n`; return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`Xcode 테스트 오류: ${error.message}`); return { content: [{ type: "text", text: `Xcode 테스트 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 6. 앱 아카이브 및 익스포트 도구 server.tool( "xcode-archive", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로"), scheme: z.string().describe("아카이브할 스킴"), configuration: z.string().optional().describe("빌드 구성 (예: Release)"), archivePath: z.string().describe("아카이브 파일(.xcarchive) 저장 경로"), exportPath: z.string().optional().describe("익스포트 경로 (IPA 파일 등)"), exportOptionsPlist: z.string().optional().describe("익스포트 옵션 plist 파일 경로") }, async ({ projectPath, scheme, configuration = "Release", archivePath, exportPath, exportOptionsPlist }) => { try { console.error(`Xcode 아카이브 생성: ${projectPath}, Scheme: ${scheme}`); let archiveCommand = `xcodebuild`; // 워크스페이스인지 프로젝트인지 확인 if (projectPath.endsWith(".xcworkspace")) { archiveCommand += ` -workspace "${projectPath}"`; } else { archiveCommand += ` -project "${projectPath}"`; } archiveCommand += ` -scheme "${scheme}" -configuration "${configuration}" archive -archivePath "${archivePath}"`; console.error(`실행할 아카이브 명령어: ${archiveCommand}`); // 아카이브 명령어 실행 try { const { stdout: archiveStdout, stderr: archiveStderr } = await executeCommand(archiveCommand); let resultText = "아카이브 결과:\n"; if (archiveStdout) resultText += `${archiveStdout}\n`; if (archiveStderr) resultText += `STDERR:\n${archiveStderr}\n`; // 익스포트 실행 (옵션이 제공된 경우) if (exportPath && exportOptionsPlist) { console.error(`Xcode 아카이브 익스포트: ${archivePath} -> ${exportPath}`); let exportCommand = `xcodebuild -exportArchive -archivePath "${archivePath}" -exportPath "${exportPath}" -exportOptionsPlist "${exportOptionsPlist}"`; console.error(`실행할 익스포트 명령어: ${exportCommand}`); // 익스포트 명령어 실행 const { stdout: exportStdout, stderr: exportStderr } = await executeCommand(exportCommand); resultText += "\n익스포트 결과:\n"; if (exportStdout) resultText += `${exportStdout}\n`; if (exportStderr) resultText += `STDERR:\n${exportStderr}\n`; } return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`Xcode 아카이브/익스포트 오류: ${error.message}`); return { content: [{ type: "text", text: `Xcode 아카이브/익스포트 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 7. Xcode 서명 및 프로비저닝 프로파일 관리 도구 server.tool( "xcode-codesign-info", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로"), target: z.string().optional().describe("특정 타겟 이름 (선택사항)") }, async ({ projectPath, target }) => { try { console.error(`Xcode 코드 서명 정보 조회: ${projectPath}`); // 먼저 프로비저닝 프로파일 목록 조회 const profilesCommand = `security find-identity -v -p codesigning`; try { const { stdout: profilesStdout, stderr: profilesStderr } = await executeCommand(profilesCommand); let resultText = "코드 서명 인증서 목록:\n"; if (profilesStdout) resultText += `${profilesStdout}\n`; if (profilesStderr) resultText += `${profilesStderr}\n`; // 프로젝트 빌드 설정에서 서명 관련 정보 추출 let buildSettingsCommand = `xcodebuild`; // 워크스페이스인지 프로젝트인지 확인 if (projectPath.endsWith(".xcworkspace")) { buildSettingsCommand += ` -workspace "${projectPath}"`; } else { buildSettingsCommand += ` -project "${projectPath}"`; } // 타겟이 지정된 경우 추가 if (target) { buildSettingsCommand += ` -target "${target}"`; } buildSettingsCommand += ` -showBuildSettings | grep -E 'CODE_SIGN|PROVISIONING_PROFILE|DEVELOPMENT_TEAM'`; try { const { stdout: settingsStdout, stderr: settingsStderr } = await executeCommand(buildSettingsCommand); resultText += "\n프로젝트 코드 서명 설정:\n"; if (settingsStdout) resultText += `${settingsStdout}\n`; if (settingsStderr) resultText += `${settingsStderr}\n`; } catch (settingsError: any) { // grep 결과가 없어도 오류가 발생할 수 있으므로 무시 resultText += "\n프로젝트 코드 서명 설정을 찾을 수 없습니다.\n"; } // 프로비저닝 프로파일 목록 (~/Library/MobileDevice/Provisioning Profiles/) try { const profileListCommand = `ls -la ~/Library/MobileDevice/Provisioning\\ Profiles/ 2>/dev/null || echo "프로비저닝 프로파일 디렉토리를 찾을 수 없습니다."`; const { stdout: profileListStdout } = await executeCommand(profileListCommand); resultText += "\n설치된 프로비저닝 프로파일:\n"; resultText += `${profileListStdout}\n`; } catch (profileError) { resultText += "\n프로비저닝 프로파일 정보를 가져올 수 없습니다.\n"; } return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`코드 서명 정보 조회 오류: ${error.message}`); return { content: [{ type: "text", text: `코드 서명 정보를 조회하는 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 8. Swift Package 관리 도구 server.tool( "swift-package-manager", { command: z.enum(["init", "update", "resolve", "reset", "clean"]).describe("SPM 명령어 (init, update, resolve, reset, clean)"), packageDir: z.string().describe("Swift Package 디렉토리 경로"), extraArgs: z.array(z.string()).optional().describe("추가 SPM 인자들") }, async ({ command, packageDir, extraArgs = [] }) => { try { console.error(`Swift Package Manager 명령 실행: ${command} in ${packageDir}`); let spmCommand = `cd "${packageDir}" && swift package ${command}`; // 추가 인자 추가 if (extraArgs.length > 0) { spmCommand += " " + extraArgs.join(" "); } console.error(`실행할 SPM 명령어: ${spmCommand}`); // 명령어 실행 try { const { stdout, stderr } = await executeCommand(spmCommand); let resultText = "Swift Package Manager 결과:\n"; if (stdout) resultText += `${stdout}\n`; if (stderr) resultText += `${stderr}\n`; return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`Swift Package Manager 오류: ${error.message}`); return { content: [{ type: "text", text: `Swift Package Manager 명령 실행 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 9. SimCtl (시뮬레이터 관리) 도구 server.tool( "simctl-manager", { command: z.enum(["list", "create", "boot", "shutdown", "erase", "install", "launch", "delete"]).describe("SimCtl 명령어"), extraArgs: z.array(z.string()).optional().describe("추가 simctl 인자들") }, async ({ command, extraArgs = [] }) => { try { console.error(`SimCtl 명령 실행: ${command}`); let simctlCommand = `xcrun simctl ${command}`; // 추가 인자 추가 if (extraArgs.length > 0) { simctlCommand += " " + extraArgs.join(" "); } console.error(`실행할 SimCtl 명령어: ${simctlCommand}`); // 명령어 실행 try { const { stdout, stderr } = await executeCommand(simctlCommand); let resultText = "SimCtl 결과:\n"; if (stdout) resultText += `${stdout}\n`; if (stderr) resultText += `${stderr}\n`; return { content: [{ type: "text", text: resultText }] }; } catch (error: any) { throw error; } } catch (error: any) { console.error(`SimCtl 오류: ${error.message}`); return { content: [{ type: "text", text: `SimCtl 명령 실행 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 10. 실제 기기에서 앱 실행 도구 server.tool( "run-on-device", { projectPath: z.string().describe("Xcode 프로젝트 또는 워크스페이스 경로"), scheme: z.string().describe("빌드 및 실행할 스킴"), device: z.string().describe("기기 식별자 또는 이름 (한글 이름 지원)"), configuration: z.string().optional().describe("빌드 구성 (Debug/Release)"), streamLogs: z.boolean().optional().describe("앱 실행 후 로그 스트리밍 여부"), startStopped: z.boolean().optional().describe("디버거 연결을 위한 일시 중지 상태로 시작"), environmentVars: z.string().optional().describe("앱에 전달할 환경 변수 (key1=value1,key2=value2 형식)"), xcodePath: z.string().optional().describe("Xcode 애플리케이션 경로"), listDevices: z.boolean().optional().describe("실행 전 감지된 모든 디바이스 목록 표시"), skipBuild: z.boolean().optional().describe("이미 설치된 앱을 재실행할 때 빌드 및 설치 건너뛰기"), extraLaunchArgs: z.array(z.string()).optional().describe("devicectl launch 명령어에 전달할 추가 인자"), directBundleId: z.string().optional().describe("직접 지정할 번들 ID (프로젝트에서 추출하지 않음)") }, async ({ projectPath, scheme, device, configuration = "Debug", streamLogs = false, startStopped = false, environmentVars = "", xcodePath, listDevices = false, skipBuild = false, extraLaunchArgs = [], directBundleId }) => { try { console.error(`실제 기기에서 앱 실행 준비: ${projectPath}, 스킴: ${scheme}, 기기: ${device}`); // 0. 디바이스 목록 표시 (요청된 경우) if (listDevices) { // 캩0싱 무시하고 강제로 최신 디바이스 목록 가져오기 const allDevices = await getAllDevices(true); let deviceListText = "감지된 디바이스 목록:\n"; allDevices.forEach(d => { deviceListText += `- ${d.name}\n`; if (d.xcodeId) deviceListText += ` Xcode ID: ${d.xcodeId}\n`; if (d.deviceCtlId) deviceListText += ` DeviceCtl ID: ${d.deviceCtlId}\n`; deviceListText += ` 상태: ${d.isAvailable ? '사용 가능' : '사용 불가'}\n`; if (d.model) deviceListText += ` 모델: ${d.model}\n`; if (d.osVersion) deviceListText += ` OS 버전: ${d.osVersion}\n`; deviceListText += "\n"; }); return { content: [{ type: "text", text: deviceListText }] }; } // 1. 자동으로 Xcode 경로 찾기 (xcodePathw가 지정되지 않은 경우) if (!xcodePath) { const installations = await findXcodeInstallations(); if (installations.length > 0) { xcodePath = installations[0].path; console.error(`자동 감지된 Xcode 경로 사용: ${xcodePath}`); } } // 2. 디바이스 정보 찾기 const deviceInfo = await findDeviceInfo(device); console.error(`디바이스 정보: ${JSON.stringify(deviceInfo)}`); if (!deviceInfo.xcodeId) { return { content: [{ type: "text", text: `디바이스 '${deviceInfo.name}'의 Xcode 식별자(UDID)를 찾을 수 없습니다. 디바이스가 올바르게 연결되어 있는지 확인하세요.` }], isError: true }; } if (!deviceInfo.deviceCtlId) { return { content: [{ type: "text", text: `디바이스 '${deviceInfo.name}'의 CoreDevice 식별자(UUID)를 찾을 수 없습니다. 디바이스가 올바르게 연결되어 있는지 확인하세요.` }], isError: true }; } const xcodeDeviceId = deviceInfo.xcodeId; const deviceCtlId = deviceInfo.deviceCtlId; console.error(`Xcode 식별자: ${xcodeDeviceId}`); console.error(`DeviceCtl 식별자: ${deviceCtlId}`); // 기기 추가 정보 표시 if (deviceInfo.model) { console.error(`기기 모델: ${deviceInfo.model}`); } if (deviceInfo.osVersion) { console.error(`기기 OS 버전: ${deviceInfo.osVersion}`); } // 3. 번들 ID 가져오기 let bundleId: string; if (directBundleId) { console.error(`사용자 지정 번들 ID 사용: ${directBundleId}`); bundleId = directBundleId; } else { console.error(`번들 ID 조회 중...`); bundleId = await getBundleIdentifier(projectPath, scheme); console.error(`번들 ID: ${bundleId}`); } // 4. devicectl 경로 찾기 const deviceCtlPath = await getDeviceCtlPath(xcodePath); console.error(`devicectl 경로: ${deviceCtlPath}`); // 5. 앱 출력 경로 추정 (skipBuild = false만 사용) let appPath: string | undefined; if (!skipBuild && !directBundleId) { try { // 빌드 서적으로부터 앱 경로 추정 const derivedDataCmd = `xcodebuild -project "${projectPath}" -scheme "${scheme}" -showBuildSettings | grep CONFIGURATION_BUILD_DIR`; const { stdout: derivedDataOutput } = await executeCommand(derivedDataCmd); const match = derivedDataOutput.match(/CONFIGURATION_BUILD_DIR = (.*)/); if (match && match[1]) { const buildDir = match[1].trim(); appPath = `${buildDir}/${scheme}.app`; console.error(`추정된 앱 경로: ${appPath}`); } } catch (pathError) { console.error(`앱 경로 추정 실패, 자동 재설치 기능을 사용할 수 없습니다: ${pathError}`); } } // 6. 빌드 및 설치 (선택적) if (!skipBuild && !directBundleId) { console.error(`앱 빌드 및 설치 시작...`); await buildAndInstallApp(projectPath, scheme, xcodeDeviceId, configuration); console.error(`앱 빌드 및 설치 완료`); } else { console.error(`빌드 및 설치 과정 건너뛰기`); } // 7. 환경 변수 파싱 let envVars: Record<string, string> = {}; if (environmentVars) { environmentVars.split(',').forEach(pair => { const [key, value] = pair.split('='); if (key && value) { envVars[key] = value; } }); } // 8. 앱 실행 - 개선된 launchAppOnDevice 함수 사용 console.error(`앱 실행 시작...`); try { const launchResult = await launchAppOnDevice( deviceCtlId, bundleId, xcodePath, envVars, startStopped, extraLaunchArgs, appPath // 자동 재설치를 위한 앱 경로 (필요한 경우) ); let resultText = `앱 실행 결과:\n${launchResult}\n`; // 9. 로그 스트리밍 (옵션이 활성화된 경우) if (streamLogs) { resultText += "\n로그 스트리밍이 시작되었습니다. 로그는 터미널에서 확인할 수 있습니다.\n"; // 비동기적으로 로그 스트리밍 시작 startDeviceLogStream(deviceCtlId, bundleId, xcodePath); } return { content: [{ type: "text", text: resultText }] }; } catch (launchError: any) { // 실행 오류인 경우, 메시지 체크 if (launchError.message.includes('is not installed') && appPath) { try { // 앱이 설치되지 않은 경우 수동 설치 시도 console.error(`앱이 설치되지 않았습니다. 수동 설치 시도 중...`); // devicectl을 사용해 수동 설치 const installCmd = `"${deviceCtlPath}" device install app --device ${deviceCtlId} "${appPath}"`; console.error(`실행할 설치 명령어: ${installCmd}`); const { stdout: installOutput } = await executeCommand(installCmd); console.error(`설치 결과: ${installOutput}`); // 설치 후 다시 실행 시도 let retryLaunchCmd = `"${deviceCtlPath}" device process launch --device ${deviceCtlId}`; // 환경 변수 추가 if (Object.keys(envVars).length > 0) { const envString = Object.entries(envVars) .map(([key, value]) => `${key}=${value}`) .join(","); retryLaunchCmd += ` --environment-variables "${envString}"`; } // 중단 모드로 시작 (디버깅용) if (startStopped) { retryLaunchCmd += " --start-stopped"; } // 추가 인자 적용 if (extraLaunchArgs.length > 0) { retryLaunchCmd += " " + extraLaunchArgs.join(" "); } // 번들 ID 추가 retryLaunchCmd += ` ${bundleId}`; console.error(`재시도 명령어: ${retryLaunchCmd}`); const { stdout: retryOutput } = await executeCommand(retryLaunchCmd); let resultText = `앱 설치 및 실행 결과:\n설치: ${installOutput}\n실행: ${retryOutput}\n`; // 로그 스트리밍 (옵션이 활성화된 경우) if (streamLogs) { resultText += "\n로그 스트리밍이 시작되었습니다. 로그는 터미널에서 확인할 수 있습니다.\n"; startDeviceLogStream(deviceCtlId, bundleId, xcodePath); } return { content: [{ type: "text", text: resultText }] }; } catch (installError: any) { console.error(`수동 설치 실패: ${installError.message}`); return { content: [{ type: "text", text: `앱이 설치되지 않았고, 수동 설치도 실패했습니다:\n${installError.message}\n${installError.stderr || ''}` }], isError: true }; } } else { // 다른 오류인 경우 return { content: [{ type: "text", text: `실제 기기에서 앱 실행 중 오류가 발생했습니다:\n${launchError.message}\n${launchError.stderr || ''}` }], isError: true }; } } } catch (error: any) { console.error(`실제 기기에서 앱 실행 오류: ${error.message}`); return { content: [{ type: "text", text: `실제 기기에서 앱 실행 중 오류가 발생했습니다:\n${error.message}\n${error.stderr || ''}` }], isError: true }; } } ); // 서버 시작 console.error("xcode-mcp 서버 시작 중..."); const transport = new StdioServerTransport(); await server.connect(transport); console.error("xcode-mcp 서버가 시작되었습니다."); } // 메인 실행 main().catch(error => { console.error("서버 실행 중 오류 발생:", error); process.exit(1); });

Implementation Reference

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/devyhan/xcode-mcp'

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