xcode-mcp-server 1.3.10__tar.gz → 1.3.11b2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/PKG-INFO +1 -1
- xcode_mcp_server-1.3.11b2/__PUBLIC_REPO +0 -0
- xcode_mcp_server-1.3.11b2/test_projects/templates/BrokenApp/BrokenApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- {xcode_mcp_server-1.3.10/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp.xcodeproj → xcode_mcp_server-1.3.11b2/test_projects/templates/BrokenApp/BrokenApp.xcodeproj}/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +1 -1
- xcode_mcp_server-1.3.11b2/tests/test_build_log_parser.py +140 -0
- xcode_mcp_server-1.3.11b2/tests/test_manifest_wait.py +229 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/__init__.py +1 -1
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/cli.py +7 -3
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/config_manager.py +50 -36
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/security.py +76 -26
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/build_project.py +115 -109
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/clean_project.py +16 -28
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/debug_list_notification_history.py +26 -10
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_active_run_destination.py +91 -40
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_build_errors.py +1 -1
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_directory_listing.py +3 -19
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_directory_tree.py +36 -23
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_latest_test_results.py +3 -2
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_project_schemes.py +11 -24
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_xcode_projects.py +44 -11
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/list_mac_app_windows.py +14 -15
- xcode_mcp_server-1.3.11b2/xcode_mcp_server/tools/list_project_tests.py +91 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/list_run_destinations.py +19 -13
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/list_running_mac_apps.py +8 -8
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/run_project_tests.py +101 -128
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/run_project_unmonitored.py +9 -59
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/run_project_until_terminated.py +15 -64
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/run_project_with_user_interaction.py +100 -101
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/set_run_destination.py +3 -16
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/take_app_screenshot.py +2 -11
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/take_simulator_screenshot.py +4 -15
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/take_window_screenshot.py +2 -11
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/take_xcode_screenshot.py +3 -12
- xcode_mcp_server-1.3.11b2/xcode_mcp_server/utils/applescript.py +322 -0
- xcode_mcp_server-1.3.11b2/xcode_mcp_server/utils/build_log_parser.py +589 -0
- xcode_mcp_server-1.3.11b2/xcode_mcp_server/utils/paths.py +14 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/utils/screenshot.py +45 -14
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/utils/xcresult.py +150 -98
- xcode_mcp_server-1.3.10/fools-errand.md +0 -80
- xcode_mcp_server-1.3.10/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/fromXcode/macosEmptyCommandLineApp/macosEmptyCommandLineApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/fromXcode/macosEmptyCommandLineApp/macosEmptyCommandLineApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- xcode_mcp_server-1.3.10/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- xcode_mcp_server-1.3.10/test_projects/templates/BrokenApp/BrokenApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/templates/BrokenApp/BrokenApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- xcode_mcp_server-1.3.10/test_projects/templates/ConsoleApp/ConsoleApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/templates/ConsoleApp/ConsoleApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- xcode_mcp_server-1.3.10/test_projects/templates/SimpleApp/SimpleApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/templates/SimpleApp/SimpleApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- xcode_mcp_server-1.3.10/test_projects/templates/SwiftUITestApp/SwiftUITestApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/templates/SwiftUITestApp/SwiftUITestApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- xcode_mcp_server-1.3.10/test_projects/templates/iOSTestApp/iOSTestApp.xcodeproj/project.xcworkspace/xcuserdata/andrew.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- xcode_mcp_server-1.3.10/test_projects/templates/iOSTestApp/iOSTestApp.xcodeproj/xcuserdata/andrew.xcuserdatad/xcschemes/xcschememanagement.plist +0 -19
- xcode_mcp_server-1.3.10/xcode_mcp_server/tools/list_project_tests.py +0 -84
- xcode_mcp_server-1.3.10/xcode_mcp_server/utils/applescript.py +0 -179
- xcode_mcp_server-1.3.10/xcode_mcp_server/utils/build_log_parser.py +0 -324
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/.github/workflows/claude-code.yml +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/.github/workflows/claude-self-review.yml +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/.gitignore +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/CLAUDE.md +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/LICENSE +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/README.md +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/TODO.md +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/_venv-setup.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/deploy-beta.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/deploy.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/dev.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/info.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/pyproject.toml +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/requirements.txt +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/run_local_for_claude.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/cleanup_tests.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/create_projects.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/create_real_projects.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/create_spm_projects.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/create_xcode_projects.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/run_all_tests.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/setup_test_projects.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/scripts/setup_tests.sh +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp/Assets.xcassets/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp/ContentView.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp/iosEmptySwiftUIAppApp.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/iosEmptySwiftUIApp/iosEmptySwiftUIApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptyCommandLineApp/macosEmptyCommandLineApp/main.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptyCommandLineApp/macosEmptyCommandLineApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptyCommandLineApp/macosEmptyCommandLineApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp/Assets.xcassets/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp/ContentView.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp/macosEmptySwiftUIAppApp.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/fromXcode/macosEmptySwiftUIApp/macosEmptySwiftUIApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/BrokenApp/BrokenApp/main.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/BrokenApp/BrokenApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/BrokenApp/BrokenApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/ConsoleApp/ConsoleApp/main.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/ConsoleApp/ConsoleApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/ConsoleApp/ConsoleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SimpleApp/SimpleApp/main.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SimpleApp/SimpleApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SimpleApp/SimpleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp/Assets.xcassets/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp/ContentView.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp/macosEmptySwiftUIAppApp.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/SwiftUITestApp/SwiftUITestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp/Assets.xcassets/Contents.json +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp/ContentView.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp/iosEmptySwiftUIAppApp.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp.xcodeproj/project.pbxproj +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/test_projects/templates/iOSTestApp/iOSTestApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/debug_build.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/debug_test.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/test_basic.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/test_build.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/test_fixed.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/test_minimal.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/tests/test_runner.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/__main__.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/config_ui.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/exceptions.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/server.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/__init__.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/create_project.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_build_results.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/get_runtime_output.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/list_booted_simulators.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/stop_project.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/tools/version.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/utils/__init__.py +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/utils/decode_active_destination.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/utils/decode_xcode_recents.swift +0 -0
- {xcode_mcp_server-1.3.10 → xcode_mcp_server-1.3.11b2}/xcode_mcp_server/utils/project_templates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xcode-mcp-server
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.11b2
|
|
4
4
|
Summary: Drew's MCP server for Xcode integration
|
|
5
5
|
Project-URL: Homepage, https://github.com/drewster99/xcode-mcp-server
|
|
6
6
|
Project-URL: Repository, https://github.com/drewster99/xcode-mcp-server
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Regression tests for xcactivitylog parsing.
|
|
4
|
+
|
|
5
|
+
Two coverage gaps caused persistent stale warnings in build_project output:
|
|
6
|
+
|
|
7
|
+
1. The path regex required [a-zA-Z0-9_/.-] only, silently dropping any project
|
|
8
|
+
path containing a space (very common: "/Users/x/My Project/..."). Files
|
|
9
|
+
missing from compiled_files can never be cleared from the stale-warning set,
|
|
10
|
+
so old warnings linger across incremental builds until the user does a clean.
|
|
11
|
+
|
|
12
|
+
2. Only SwiftCompile lines and .swift warnings/errors were matched. Mixed-language
|
|
13
|
+
projects (ObjC/C/C++/Metal) were invisible to the parser, so their warnings
|
|
14
|
+
never appeared in aggregation and recompiles never cleared anything.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import gzip
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import unittest
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
25
|
+
|
|
26
|
+
from xcode_mcp_server.utils.build_log_parser import parse_xcactivitylog
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _write_gzipped_log(path: str, text: str) -> None:
|
|
30
|
+
"""xcactivitylog files are gzipped — write a fake one."""
|
|
31
|
+
with gzip.open(path, 'wb') as f:
|
|
32
|
+
f.write(text.encode('utf-8'))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PathsWithSpacesTests(unittest.TestCase):
|
|
36
|
+
def test_swift_file_with_space_in_path_is_tracked_as_compiled(self):
|
|
37
|
+
"""A path with a space must be recognized in SwiftCompile lines."""
|
|
38
|
+
log_text = (
|
|
39
|
+
"Some preamble\r"
|
|
40
|
+
"SwiftCompile normal arm64 /Users/test/My Project/Sources/Foo.swift "
|
|
41
|
+
"(in target 'App' from project 'App')\r"
|
|
42
|
+
"more output\r"
|
|
43
|
+
)
|
|
44
|
+
with tempfile.NamedTemporaryFile(suffix='.xcactivitylog', delete=False) as tmp:
|
|
45
|
+
tmp_path = tmp.name
|
|
46
|
+
try:
|
|
47
|
+
_write_gzipped_log(tmp_path, log_text)
|
|
48
|
+
warnings, compiled = parse_xcactivitylog(tmp_path)
|
|
49
|
+
self.assertIn('/Users/test/My Project/Sources/Foo.swift', compiled,
|
|
50
|
+
f"compiled files were: {compiled}")
|
|
51
|
+
finally:
|
|
52
|
+
os.unlink(tmp_path)
|
|
53
|
+
|
|
54
|
+
def test_warning_on_path_with_space_is_extracted(self):
|
|
55
|
+
log_text = (
|
|
56
|
+
"/Users/test/My Project/Sources/Foo.swift:42:5: warning: "
|
|
57
|
+
"variable 'x' was never used\r"
|
|
58
|
+
)
|
|
59
|
+
with tempfile.NamedTemporaryFile(suffix='.xcactivitylog', delete=False) as tmp:
|
|
60
|
+
tmp_path = tmp.name
|
|
61
|
+
try:
|
|
62
|
+
_write_gzipped_log(tmp_path, log_text)
|
|
63
|
+
warnings, _ = parse_xcactivitylog(tmp_path)
|
|
64
|
+
files = [w['file'] for w in warnings]
|
|
65
|
+
self.assertIn('/Users/test/My Project/Sources/Foo.swift', files,
|
|
66
|
+
f"warnings were: {warnings}")
|
|
67
|
+
finally:
|
|
68
|
+
os.unlink(tmp_path)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MixedLanguageTests(unittest.TestCase):
|
|
72
|
+
def test_objc_compilec_line_tracks_source_file(self):
|
|
73
|
+
"""CompileC <obj> <source> — we want the source path tracked."""
|
|
74
|
+
log_text = (
|
|
75
|
+
"CompileC /tmp/Build/Foo.o /Users/test/MyProj/Foo.m "
|
|
76
|
+
"normal arm64 objective-c\r"
|
|
77
|
+
)
|
|
78
|
+
with tempfile.NamedTemporaryFile(suffix='.xcactivitylog', delete=False) as tmp:
|
|
79
|
+
tmp_path = tmp.name
|
|
80
|
+
try:
|
|
81
|
+
_write_gzipped_log(tmp_path, log_text)
|
|
82
|
+
_, compiled = parse_xcactivitylog(tmp_path)
|
|
83
|
+
self.assertIn('/Users/test/MyProj/Foo.m', compiled,
|
|
84
|
+
f"compiled files were: {compiled}")
|
|
85
|
+
finally:
|
|
86
|
+
os.unlink(tmp_path)
|
|
87
|
+
|
|
88
|
+
def test_objc_warning_is_extracted(self):
|
|
89
|
+
log_text = (
|
|
90
|
+
"/Users/test/MyProj/Foo.m:10:3: warning: "
|
|
91
|
+
"'someAPI' is deprecated\r"
|
|
92
|
+
)
|
|
93
|
+
with tempfile.NamedTemporaryFile(suffix='.xcactivitylog', delete=False) as tmp:
|
|
94
|
+
tmp_path = tmp.name
|
|
95
|
+
try:
|
|
96
|
+
_write_gzipped_log(tmp_path, log_text)
|
|
97
|
+
warnings, _ = parse_xcactivitylog(tmp_path)
|
|
98
|
+
self.assertTrue(
|
|
99
|
+
any(w['file'] == '/Users/test/MyProj/Foo.m' for w in warnings),
|
|
100
|
+
f"warnings were: {warnings}"
|
|
101
|
+
)
|
|
102
|
+
finally:
|
|
103
|
+
os.unlink(tmp_path)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ExistingBehaviorPreservedTests(unittest.TestCase):
|
|
107
|
+
def test_plain_swift_path_still_works(self):
|
|
108
|
+
log_text = (
|
|
109
|
+
"SwiftCompile normal arm64 /Users/test/Proj/Sources/Foo.swift "
|
|
110
|
+
"(in target 'App' from project 'App')\r"
|
|
111
|
+
"/Users/test/Proj/Sources/Foo.swift:99:1: warning: shadows\r"
|
|
112
|
+
)
|
|
113
|
+
with tempfile.NamedTemporaryFile(suffix='.xcactivitylog', delete=False) as tmp:
|
|
114
|
+
tmp_path = tmp.name
|
|
115
|
+
try:
|
|
116
|
+
_write_gzipped_log(tmp_path, log_text)
|
|
117
|
+
warnings, compiled = parse_xcactivitylog(tmp_path)
|
|
118
|
+
self.assertIn('/Users/test/Proj/Sources/Foo.swift', compiled)
|
|
119
|
+
self.assertEqual(len(warnings), 1)
|
|
120
|
+
self.assertEqual(warnings[0]['line'], 99)
|
|
121
|
+
self.assertEqual(warnings[0]['column'], 1)
|
|
122
|
+
self.assertEqual(warnings[0]['type'], 'warning')
|
|
123
|
+
finally:
|
|
124
|
+
os.unlink(tmp_path)
|
|
125
|
+
|
|
126
|
+
def test_error_extracted_with_type_error(self):
|
|
127
|
+
log_text = "/Users/test/Proj/Sources/Foo.swift:5:1: error: bad thing\r"
|
|
128
|
+
with tempfile.NamedTemporaryFile(suffix='.xcactivitylog', delete=False) as tmp:
|
|
129
|
+
tmp_path = tmp.name
|
|
130
|
+
try:
|
|
131
|
+
_write_gzipped_log(tmp_path, log_text)
|
|
132
|
+
warnings, _ = parse_xcactivitylog(tmp_path)
|
|
133
|
+
self.assertEqual(len(warnings), 1)
|
|
134
|
+
self.assertEqual(warnings[0]['type'], 'error')
|
|
135
|
+
finally:
|
|
136
|
+
os.unlink(tmp_path)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == '__main__':
|
|
140
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Regression tests for the LogStoreManifest UUID-diff wait used to fix stale
|
|
4
|
+
build warnings.
|
|
5
|
+
|
|
6
|
+
Before this fix, build_project read the manifest immediately after AppleScript
|
|
7
|
+
returned, before Xcode had necessarily written the current build's xcactivitylog
|
|
8
|
+
entry. Aggregation then operated on stale data and surfaced warnings the user
|
|
9
|
+
had already fixed.
|
|
10
|
+
|
|
11
|
+
These tests exercise the deterministic alternative: snapshot the manifest's
|
|
12
|
+
build UUIDs before kicking off the build, then poll until a new build UUID
|
|
13
|
+
appears whose timeStartedRecording is at or after our start time.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
import plistlib
|
|
20
|
+
import tempfile
|
|
21
|
+
import threading
|
|
22
|
+
import unittest
|
|
23
|
+
import uuid
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
27
|
+
|
|
28
|
+
from xcode_mcp_server.utils.build_log_parser import (
|
|
29
|
+
CF_EPOCH_OFFSET,
|
|
30
|
+
snapshot_build_uuids,
|
|
31
|
+
wait_for_new_build_uuid,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _make_entry(title: str, time_started_cf: float, file_uuid: str = None) -> dict:
|
|
36
|
+
"""Build a single LogStoreManifest-style log entry."""
|
|
37
|
+
if file_uuid is None:
|
|
38
|
+
file_uuid = str(uuid.uuid4()).upper()
|
|
39
|
+
return {
|
|
40
|
+
'className': 'IDEActivityLogSection',
|
|
41
|
+
'documentTypeString': '<nil>',
|
|
42
|
+
'domainType': 'Xcode.IDEActivityLogDomainType.BuildLog',
|
|
43
|
+
'fileName': f'{file_uuid}.xcactivitylog',
|
|
44
|
+
'hasPrimaryLog': True,
|
|
45
|
+
'primaryObservable': {
|
|
46
|
+
'highLevelStatus': 'S',
|
|
47
|
+
'totalNumberOfAnalyzerIssues': 0,
|
|
48
|
+
'totalNumberOfErrors': 0,
|
|
49
|
+
'totalNumberOfTestFailures': 0,
|
|
50
|
+
'totalNumberOfWarnings': 0,
|
|
51
|
+
},
|
|
52
|
+
'schemeIdentifier-containerName': 'Test project',
|
|
53
|
+
'schemeIdentifier-schemeName': 'TestScheme',
|
|
54
|
+
'schemeIdentifier-sharedScheme': 1,
|
|
55
|
+
'signature': title,
|
|
56
|
+
'timeStartedRecording': time_started_cf,
|
|
57
|
+
'timeStoppedRecording': time_started_cf + 1.0,
|
|
58
|
+
'title': title,
|
|
59
|
+
'uniqueIdentifier': file_uuid,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _write_manifest(path: str, entries: list) -> None:
|
|
64
|
+
plist = {
|
|
65
|
+
'logFormatVersion': 11,
|
|
66
|
+
'logs': {e['uniqueIdentifier']: e for e in entries},
|
|
67
|
+
}
|
|
68
|
+
with open(path, 'wb') as f:
|
|
69
|
+
plistlib.dump(plist, f)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _now_cf() -> float:
|
|
73
|
+
"""Current time as CFAbsoluteTime (seconds since 2001-01-01 UTC)."""
|
|
74
|
+
return time.time() - CF_EPOCH_OFFSET
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class SnapshotTests(unittest.TestCase):
|
|
78
|
+
def test_snapshot_returns_all_uuids_regardless_of_title(self):
|
|
79
|
+
"""The snapshot is just the existing UUIDs — title filtering happens later."""
|
|
80
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
81
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
82
|
+
build_uuid = str(uuid.uuid4()).upper()
|
|
83
|
+
clean_uuid = str(uuid.uuid4()).upper()
|
|
84
|
+
_write_manifest(manifest, [
|
|
85
|
+
_make_entry('Build TestScheme', _now_cf() - 60, build_uuid),
|
|
86
|
+
_make_entry('Clean TestScheme', _now_cf() - 30, clean_uuid),
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
snap = snapshot_build_uuids(manifest)
|
|
90
|
+
|
|
91
|
+
self.assertIn(build_uuid, snap)
|
|
92
|
+
self.assertIn(clean_uuid, snap)
|
|
93
|
+
|
|
94
|
+
def test_snapshot_missing_manifest_returns_empty_set(self):
|
|
95
|
+
"""First-ever build has no manifest yet — must not crash."""
|
|
96
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
97
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
98
|
+
snap = snapshot_build_uuids(manifest)
|
|
99
|
+
self.assertEqual(snap, set())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class WaitForNewBuildUUIDTests(unittest.TestCase):
|
|
103
|
+
def test_returns_immediately_when_new_build_already_present(self):
|
|
104
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
105
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
106
|
+
existing_uuid = str(uuid.uuid4()).upper()
|
|
107
|
+
_write_manifest(manifest, [
|
|
108
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
109
|
+
])
|
|
110
|
+
before = snapshot_build_uuids(manifest)
|
|
111
|
+
|
|
112
|
+
start_unix = time.time()
|
|
113
|
+
new_uuid = str(uuid.uuid4()).upper()
|
|
114
|
+
_write_manifest(manifest, [
|
|
115
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
116
|
+
_make_entry('Build TestScheme', _now_cf() + 0.1, new_uuid),
|
|
117
|
+
])
|
|
118
|
+
|
|
119
|
+
found = wait_for_new_build_uuid(manifest, before, start_unix, timeout_seconds=2.0)
|
|
120
|
+
self.assertEqual(found, new_uuid)
|
|
121
|
+
|
|
122
|
+
def test_returns_new_uuid_added_mid_poll(self):
|
|
123
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
124
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
125
|
+
existing_uuid = str(uuid.uuid4()).upper()
|
|
126
|
+
_write_manifest(manifest, [
|
|
127
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
128
|
+
])
|
|
129
|
+
before = snapshot_build_uuids(manifest)
|
|
130
|
+
start_unix = time.time()
|
|
131
|
+
new_uuid = str(uuid.uuid4()).upper()
|
|
132
|
+
|
|
133
|
+
def append_after_delay():
|
|
134
|
+
time.sleep(0.6)
|
|
135
|
+
_write_manifest(manifest, [
|
|
136
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
137
|
+
_make_entry('Build TestScheme', _now_cf(), new_uuid),
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
t = threading.Thread(target=append_after_delay, daemon=True)
|
|
141
|
+
t.start()
|
|
142
|
+
|
|
143
|
+
found = wait_for_new_build_uuid(manifest, before, start_unix, timeout_seconds=5.0)
|
|
144
|
+
t.join(timeout=2.0)
|
|
145
|
+
self.assertEqual(found, new_uuid)
|
|
146
|
+
|
|
147
|
+
def test_timeout_returns_none(self):
|
|
148
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
149
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
150
|
+
existing_uuid = str(uuid.uuid4()).upper()
|
|
151
|
+
_write_manifest(manifest, [
|
|
152
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
153
|
+
])
|
|
154
|
+
before = snapshot_build_uuids(manifest)
|
|
155
|
+
start_unix = time.time()
|
|
156
|
+
|
|
157
|
+
found = wait_for_new_build_uuid(manifest, before, start_unix, timeout_seconds=1.0)
|
|
158
|
+
self.assertIsNone(found)
|
|
159
|
+
|
|
160
|
+
def test_clean_entries_are_ignored(self):
|
|
161
|
+
"""A Clean operation creates a manifest entry too; it must not be mistaken for our build."""
|
|
162
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
163
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
164
|
+
existing_uuid = str(uuid.uuid4()).upper()
|
|
165
|
+
_write_manifest(manifest, [
|
|
166
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
167
|
+
])
|
|
168
|
+
before = snapshot_build_uuids(manifest)
|
|
169
|
+
start_unix = time.time()
|
|
170
|
+
clean_uuid = str(uuid.uuid4()).upper()
|
|
171
|
+
_write_manifest(manifest, [
|
|
172
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
173
|
+
_make_entry('Clean TestScheme', _now_cf(), clean_uuid),
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
found = wait_for_new_build_uuid(manifest, before, start_unix, timeout_seconds=1.0)
|
|
177
|
+
self.assertIsNone(found, "Clean entry must not be returned as 'our' build")
|
|
178
|
+
|
|
179
|
+
def test_pre_existing_build_with_old_timestamp_is_rejected(self):
|
|
180
|
+
"""
|
|
181
|
+
If a user-triggered build finished before our snapshot (but somehow wasn't
|
|
182
|
+
in the snapshot — e.g. they hit Cmd-B between our snapshot and our wait),
|
|
183
|
+
its older timestamp must exclude it from being treated as ours.
|
|
184
|
+
"""
|
|
185
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
186
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
187
|
+
old_present_uuid = str(uuid.uuid4()).upper()
|
|
188
|
+
_write_manifest(manifest, [
|
|
189
|
+
_make_entry('Build TestScheme', _now_cf() - 60, old_present_uuid),
|
|
190
|
+
])
|
|
191
|
+
before = snapshot_build_uuids(manifest)
|
|
192
|
+
|
|
193
|
+
start_unix = time.time()
|
|
194
|
+
# A build entry that started well before our start time, but isn't
|
|
195
|
+
# in `before` — this represents an entry we missed.
|
|
196
|
+
stray_uuid = str(uuid.uuid4()).upper()
|
|
197
|
+
_write_manifest(manifest, [
|
|
198
|
+
_make_entry('Build TestScheme', _now_cf() - 60, old_present_uuid),
|
|
199
|
+
_make_entry('Build TestScheme', _now_cf() - 30, stray_uuid),
|
|
200
|
+
])
|
|
201
|
+
|
|
202
|
+
found = wait_for_new_build_uuid(manifest, before, start_unix, timeout_seconds=1.0)
|
|
203
|
+
self.assertIsNone(found)
|
|
204
|
+
|
|
205
|
+
def test_accepts_only_build_with_timestamp_at_or_after_start(self):
|
|
206
|
+
"""Stale stray entry (older) ignored; our entry (newer) accepted."""
|
|
207
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
208
|
+
manifest = os.path.join(tmp, 'LogStoreManifest.plist')
|
|
209
|
+
existing_uuid = str(uuid.uuid4()).upper()
|
|
210
|
+
_write_manifest(manifest, [
|
|
211
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
212
|
+
])
|
|
213
|
+
before = snapshot_build_uuids(manifest)
|
|
214
|
+
start_unix = time.time()
|
|
215
|
+
|
|
216
|
+
stray_uuid = str(uuid.uuid4()).upper()
|
|
217
|
+
our_uuid = str(uuid.uuid4()).upper()
|
|
218
|
+
_write_manifest(manifest, [
|
|
219
|
+
_make_entry('Build TestScheme', _now_cf() - 60, existing_uuid),
|
|
220
|
+
_make_entry('Build TestScheme', _now_cf() - 30, stray_uuid),
|
|
221
|
+
_make_entry('Build TestScheme', _now_cf() + 0.1, our_uuid),
|
|
222
|
+
])
|
|
223
|
+
|
|
224
|
+
found = wait_for_new_build_uuid(manifest, before, start_unix, timeout_seconds=2.0)
|
|
225
|
+
self.assertEqual(found, our_uuid)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == '__main__':
|
|
229
|
+
unittest.main(verbosity=2)
|
|
@@ -10,7 +10,7 @@ from xcode_mcp_server import __version__
|
|
|
10
10
|
from xcode_mcp_server.server import mcp
|
|
11
11
|
from xcode_mcp_server.security import get_allowed_folders, set_allowed_folders
|
|
12
12
|
from xcode_mcp_server.utils.applescript import set_notifications_enabled
|
|
13
|
-
from xcode_mcp_server.utils.xcresult import set_build_warnings_enabled
|
|
13
|
+
from xcode_mcp_server.utils.xcresult import set_build_warnings_enabled, freeze_build_warnings_settings
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def initialize_server():
|
|
@@ -95,8 +95,8 @@ All specified folders must:
|
|
|
95
95
|
subprocess.run(['osascript', '-e',
|
|
96
96
|
'display alert "Drew\'s Xcode MCP Server Error" message "No valid allowed folders found. Check your configuration."'],
|
|
97
97
|
capture_output=True)
|
|
98
|
-
except:
|
|
99
|
-
pass
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
100
|
|
|
101
101
|
sys.exit(1)
|
|
102
102
|
|
|
@@ -127,5 +127,9 @@ All specified folders must:
|
|
|
127
127
|
subtitle="✅ Server started"
|
|
128
128
|
)
|
|
129
129
|
|
|
130
|
+
# Lock startup-only globals so any later mutation surfaces as a
|
|
131
|
+
# RuntimeError instead of silently racing concurrent tool readers.
|
|
132
|
+
freeze_build_warnings_settings()
|
|
133
|
+
|
|
130
134
|
# Run the server
|
|
131
135
|
mcp.run()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import os
|
|
3
|
-
import sys
|
|
4
3
|
import json
|
|
5
4
|
import hashlib
|
|
6
5
|
import inspect
|
|
6
|
+
import tempfile
|
|
7
|
+
import threading
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from typing import Optional, Any, Dict, List, Set
|
|
@@ -36,20 +37,32 @@ class ConfigManager:
|
|
|
36
37
|
"""
|
|
37
38
|
|
|
38
39
|
_instance = None
|
|
40
|
+
_instance_lock = threading.Lock()
|
|
39
41
|
_config_dir = Path.home() / ".xcode-mcp-server"
|
|
40
42
|
|
|
41
43
|
def __new__(cls):
|
|
44
|
+
# Double-checked locking: cheap fast path once the singleton exists,
|
|
45
|
+
# serialized construction-and-init otherwise. FastMCP dispatches sync
|
|
46
|
+
# tools onto a threadpool, so two threads can race on first
|
|
47
|
+
# construction. All one-time setup — including `_tool_registry` — is
|
|
48
|
+
# done here under the lock so a second concurrent constructor call
|
|
49
|
+
# can't run __init__ a second time and clobber the first thread's
|
|
50
|
+
# registrations.
|
|
42
51
|
if cls._instance is None:
|
|
43
|
-
cls.
|
|
44
|
-
|
|
52
|
+
with cls._instance_lock:
|
|
53
|
+
if cls._instance is None:
|
|
54
|
+
instance = super().__new__(cls)
|
|
55
|
+
instance._tool_registry = {}
|
|
56
|
+
instance._ensure_config_dir_exists()
|
|
57
|
+
instance._initialized = True
|
|
58
|
+
cls._instance = instance
|
|
45
59
|
return cls._instance
|
|
46
60
|
|
|
47
61
|
def __init__(self):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
self._initialized = True
|
|
62
|
+
# All one-time setup happens in __new__ under the singleton lock; a
|
|
63
|
+
# no-op __init__ keeps construction atomic against repeated
|
|
64
|
+
# `ConfigManager()` calls from concurrent threadpool tools.
|
|
65
|
+
return
|
|
53
66
|
|
|
54
67
|
# === INTERNAL ===
|
|
55
68
|
|
|
@@ -79,10 +92,35 @@ class ConfigManager:
|
|
|
79
92
|
return None
|
|
80
93
|
|
|
81
94
|
def _save_config_file(self, file_path: Path, data: dict):
|
|
82
|
-
"""Write config JSON file
|
|
95
|
+
"""Write config JSON file atomically.
|
|
96
|
+
|
|
97
|
+
Writes to a sibling temp file then `os.replace`s into place so a crash
|
|
98
|
+
or concurrent write can't leave the destination truncated or partially
|
|
99
|
+
written. `os.replace` is atomic on POSIX when source and destination
|
|
100
|
+
are on the same filesystem, which is guaranteed here because the temp
|
|
101
|
+
file is created in `file_path.parent`.
|
|
102
|
+
"""
|
|
83
103
|
self._ensure_config_dir_exists()
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
file_path = Path(file_path)
|
|
105
|
+
tmp = tempfile.NamedTemporaryFile(
|
|
106
|
+
mode='w',
|
|
107
|
+
dir=file_path.parent,
|
|
108
|
+
prefix=file_path.name + '.',
|
|
109
|
+
suffix='.tmp',
|
|
110
|
+
delete=False,
|
|
111
|
+
)
|
|
112
|
+
try:
|
|
113
|
+
json.dump(data, tmp, indent=2)
|
|
114
|
+
tmp.flush()
|
|
115
|
+
os.fsync(tmp.fileno())
|
|
116
|
+
tmp.close()
|
|
117
|
+
os.replace(tmp.name, file_path)
|
|
118
|
+
except Exception:
|
|
119
|
+
try:
|
|
120
|
+
os.unlink(tmp.name)
|
|
121
|
+
except OSError:
|
|
122
|
+
pass
|
|
123
|
+
raise
|
|
86
124
|
|
|
87
125
|
def _create_empty_config(self, level_type: str, name: str, path: str) -> dict:
|
|
88
126
|
"""Create an empty config structure"""
|
|
@@ -419,39 +457,15 @@ def apply_config(func):
|
|
|
419
457
|
from xcode_mcp_server.exceptions import XCodeMCPError
|
|
420
458
|
raise XCodeMCPError(f"{func.__name__} is disabled in configuration")
|
|
421
459
|
|
|
422
|
-
# Apply parameter overrides
|
|
423
|
-
overridden_params = []
|
|
460
|
+
# Apply parameter overrides
|
|
424
461
|
for param_name in sig.parameters:
|
|
425
462
|
override = config.get_parameter_override(func.__name__, param_name, project_path)
|
|
426
463
|
if override is not None:
|
|
427
|
-
overridden_params.append(f"{param_name}={override}")
|
|
428
464
|
bound.arguments[param_name] = override
|
|
429
465
|
|
|
430
466
|
# Check if notification should be shown
|
|
431
467
|
should_notify = config.should_show_notification(func.__name__, project_path)
|
|
432
468
|
|
|
433
|
-
# DEBUG: Show configuration info in alert
|
|
434
|
-
import subprocess
|
|
435
|
-
debug_lines = [
|
|
436
|
-
"[apply_config DEBUG]",
|
|
437
|
-
f"Function: {func.__name__}",
|
|
438
|
-
f"Project: {project_path or 'None'}",
|
|
439
|
-
f"Show notification: {should_notify}",
|
|
440
|
-
f"Overridden params: {', '.join(overridden_params) if overridden_params else 'None'}"
|
|
441
|
-
]
|
|
442
|
-
debug_msg = "\\n".join(debug_lines)
|
|
443
|
-
|
|
444
|
-
# Print to stderr
|
|
445
|
-
print(debug_msg.replace("\\n", "\n"), file=sys.stderr)
|
|
446
|
-
|
|
447
|
-
# Show in AppleScript alert (temporary debugging)
|
|
448
|
-
# try:
|
|
449
|
-
# alert_script = f'display alert "apply_config Debug" message "{debug_msg}"'
|
|
450
|
-
# # No timeout - let the user dismiss it when ready
|
|
451
|
-
# subprocess.run(['osascript', '-e', alert_script], capture_output=True)
|
|
452
|
-
# except:
|
|
453
|
-
# pass # Ignore alert errors
|
|
454
|
-
|
|
455
469
|
# Set tool context for this execution
|
|
456
470
|
context = {
|
|
457
471
|
'tool_name': func.__name__,
|