From f6bec4aeaaed2ac8c9ca750cfd49ff1ae331ec83 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:09:25 -0800 Subject: [PATCH 01/12] Add script for release automation --- Scripts/release.sh | 223 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100755 Scripts/release.sh diff --git a/Scripts/release.sh b/Scripts/release.sh new file mode 100755 index 0000000..7ae363c --- /dev/null +++ b/Scripts/release.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_NAME="${APP_NAME:-iMCP}" +APP_BUNDLE="${APP_BUNDLE:-${APP_NAME}.app}" +KEYCHAIN_PROFILE="${KEYCHAIN_PROFILE:-}" +VERSION="${VERSION:-}" +BUILD_NUMBER="${BUILD_NUMBER:-}" +SCHEME="${SCHEME:-iMCP}" +CONFIGURATION="${CONFIGURATION:-Release}" +DESTINATION="${DESTINATION:-platform=macOS}" + +NOTARY_ZIP="${APP_BUNDLE}.zip" +RELEASE_ZIP="${APP_NAME}.zip" + +print_usage() { + cat <<'EOF' +Usage: Scripts/release.sh [command] + +Commands: + all Build check, bump, package, notarize, staple, commit/tag, release, upload (default) + check Quick release build check + package Bump version and create the release zip from the app bundle + notarize Submit the app bundle for notarization + staple Staple the notarization ticket to the app bundle + commit Commit version bump and create release tag + release Create a GitHub release (no assets) + upload Upload the release asset to GitHub + help Show this help + +Environment: + APP_NAME App name (default: iMCP) + APP_BUNDLE App bundle path (default: ${APP_NAME}.app) + KEYCHAIN_PROFILE Required for notarize + VERSION Required for bumping, commit, release, and upload + BUILD_NUMBER Optional; used when bumping build number + SCHEME Xcode scheme for build check (default: iMCP) + CONFIGURATION Build configuration for build check (default: Release) + DESTINATION Build destination for build check (default: platform=macOS) +EOF +} + +require_app_bundle() { + if [[ ! -d "${APP_BUNDLE}" ]]; then + echo "Missing app bundle: ${APP_BUNDLE}" >&2 + exit 1 + fi +} + +require_keychain_profile() { + if [[ -z "${KEYCHAIN_PROFILE}" ]]; then + echo "Missing keychain profile. Set KEYCHAIN_PROFILE." >&2 + exit 1 + fi +} + +require_version() { + if [[ -z "${VERSION}" ]]; then + echo "VERSION is required for releases." >&2 + exit 1 + fi +} + +require_clean_tree() { + if ! git diff --quiet || ! git diff --cached --quiet; then + echo "Working tree is dirty. Commit or stash changes first." >&2 + exit 1 + fi +} + +cleanup() { + rm -f "${NOTARY_ZIP}" +} + +trap cleanup EXIT + +bump_version() { + require_version + echo "Bumping version to ${VERSION}" + agvtool new-marketing-version "${VERSION}" + + if [[ -n "${BUILD_NUMBER}" ]]; then + echo "Setting build number to ${BUILD_NUMBER}" + agvtool new-version -all "${BUILD_NUMBER}" + else + echo "Bumping build number" + agvtool next-version -all + fi +} + +build_zip() { + local source_bundle="$1" + local output_zip="$2" + echo "Creating zip: ${output_zip}" + ditto -c -k --keepParent "${source_bundle}" "${output_zip}" +} + +build_check() { + echo "Checking release build (scheme: ${SCHEME}, configuration: ${CONFIGURATION})" + xcodebuild -quiet -scheme "${SCHEME}" -configuration "${CONFIGURATION}" -destination "${DESTINATION}" build +} + +notarize() { + require_app_bundle + require_keychain_profile + echo "Zipping for notarization: ${NOTARY_ZIP}" + ditto -c -k --keepParent "${APP_BUNDLE}" "${NOTARY_ZIP}" + echo "Submitting to notarization" + xcrun notarytool submit "${NOTARY_ZIP}" --wait --keychain-profile="${KEYCHAIN_PROFILE}" +} + +staple() { + require_app_bundle + echo "Stapling notarization ticket" + xcrun stapler staple "${APP_BUNDLE}" +} + +package_release() { + require_app_bundle + bump_version + build_zip "${APP_BUNDLE}" "${RELEASE_ZIP}" + echo "Done: ${RELEASE_ZIP}" +} + +validate_staple() { + require_app_bundle + echo "Validating stapled ticket" + xcrun stapler validate "${APP_BUNDLE}" +} + +commit_and_tag() { + require_version + if [[ ! -f "${RELEASE_ZIP}" ]]; then + echo "Missing release asset: ${RELEASE_ZIP}" >&2 + exit 1 + fi + validate_staple + if git rev-parse --verify "refs/tags/${VERSION}" >/dev/null 2>&1; then + echo "Tag already exists: ${VERSION}" >&2 + exit 1 + fi + echo "Committing version bump" + git add -A + if git diff --cached --quiet; then + echo "No changes to commit." + else + git commit -m "Release ${VERSION}" + fi + echo "Tagging release ${VERSION}" + git tag -a "${VERSION}" -m "Release ${VERSION}" +} + +create_release() { + require_version + echo "Creating GitHub release ${VERSION}" + gh release create "${VERSION}" --generate-notes +} + +upload_asset() { + require_version + if [[ ! -f "${RELEASE_ZIP}" ]]; then + echo "Missing release asset: ${RELEASE_ZIP}" >&2 + exit 1 + fi + echo "Uploading release asset ${RELEASE_ZIP}" + gh release upload "${VERSION}" "${RELEASE_ZIP}" --clobber + gh release view --web "${VERSION}" +} + +all() { + build_check + require_clean_tree + package_release + notarize + staple + commit_and_tag + create_release + upload_asset +} + +release() { + create_release +} + +upload() { + upload_asset +} + +COMMAND="${1:-all}" +case "${COMMAND}" in + all) + all + ;; + check) + build_check + ;; + package) + package_release + ;; + notarize) + notarize + ;; + staple) + staple + ;; + commit) + commit_and_tag + ;; + release) + release + ;; + upload) + upload + ;; + help|-h|--help) + print_usage + ;; + *) + echo "Unknown command: ${COMMAND}" >&2 + print_usage >&2 + exit 1 + ;; +esac From 1ecbb30dbb4757324af8c6d0396fa02f9f65db7c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:22:04 -0800 Subject: [PATCH 02/12] Update product bundle identifier --- iMCP.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index 4d877ed..f237b09 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -437,7 +437,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 15.1; MARKETING_VERSION = 1.3.0; - PRODUCT_BUNDLE_IDENTIFIER = com.loopwork.iMCP; + PRODUCT_BUNDLE_IDENTIFIER = co.dododo.iMCP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = auto; @@ -503,7 +503,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 15.1; MARKETING_VERSION = 1.3.0; - PRODUCT_BUNDLE_IDENTIFIER = com.loopwork.iMCP; + PRODUCT_BUNDLE_IDENTIFIER = co.dododo.iMCP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "iMCP Developer Build Provision Profile"; From 50382013cd93aa7ee5faef101691ef94662af0d6 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:22:30 -0800 Subject: [PATCH 03/12] Move entitlement usage descriptions into project configuration --- App/Info.plist | 4 ---- iMCP.xcodeproj/project.pbxproj | 28 ++++++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/App/Info.plist b/App/Info.plist index 8a6c508..57abec8 100644 --- a/App/Info.plist +++ b/App/Info.plist @@ -8,10 +8,6 @@ _mcp._tcp - NSCalendarsFullAccessUsageDescription - ${PRODUCT_NAME} needs access to provide event information to the MCP server. - NSRemindersFullAccessUsageDescription - ${PRODUCT_NAME} needs access to provide reminders information to the MCP server. SUEnableInstallerLauncherService SUFeedURL diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index f237b09..ad9fda7 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -415,13 +415,15 @@ INFOPLIST_KEY_CFBundleDisplayName = iMCP; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_LSUIElement = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "${PRODUCT_NAME} needs access to the camera to take pictures for the MCP server."; - INFOPLIST_KEY_NSContactsUsageDescription = "${PRODUCT_NAME} needs access to provide contact information to the MCP server."; + INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide event information to the MCP server."; + INFOPLIST_KEY_NSCameraUsageDescription = "iMCP needs access to the camera to take pictures for the MCP server."; + INFOPLIST_KEY_NSContactsUsageDescription = "iMCP needs access to provide contact information to the MCP server."; INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Loopwork Limited. All rights reserved."; - INFOPLIST_KEY_NSLocalNetworkUsageDescription = "${PRODUCT_NAME} uses the local network to connect to the MCP server."; - INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "${PRODUCT_NAME} needs access to provide location information to the MCP server."; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "${PRODUCT_NAME} needs access to provide location information to the MCP server."; - INFOPLIST_KEY_NSMicrophoneUsageDescription = "${PRODUCT_NAME} needs access to the microphone to record audio for the MCP server."; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "iMCP uses the local network to connect to the MCP server."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "iMCP needs access to the microphone to record audio for the MCP server."; + INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide reminders information to the MCP server."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -481,13 +483,15 @@ INFOPLIST_KEY_CFBundleDisplayName = iMCP; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_LSUIElement = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "${PRODUCT_NAME} needs access to the camera to take pictures for the MCP server."; - INFOPLIST_KEY_NSContactsUsageDescription = "${PRODUCT_NAME} needs access to provide contact information to the MCP server."; + INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide event information to the MCP server."; + INFOPLIST_KEY_NSCameraUsageDescription = "iMCP needs access to the camera to take pictures for the MCP server."; + INFOPLIST_KEY_NSContactsUsageDescription = "iMCP needs access to provide contact information to the MCP server."; INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Loopwork Limited. All rights reserved."; - INFOPLIST_KEY_NSLocalNetworkUsageDescription = "${PRODUCT_NAME} uses the local network to connect to the MCP server."; - INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "${PRODUCT_NAME} needs access to provide location information to the MCP server."; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "${PRODUCT_NAME} needs access to provide location information to the MCP server."; - INFOPLIST_KEY_NSMicrophoneUsageDescription = "${PRODUCT_NAME} needs access to the microphone to record audio for the MCP server."; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "iMCP uses the local network to connect to the MCP server."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "iMCP needs access to the microphone to record audio for the MCP server."; + INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide reminders information to the MCP server."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; From d774acb6b82974d08f1dd38969151833cb553a6e Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:24:27 -0800 Subject: [PATCH 04/12] Subdivide bump and package steps --- Scripts/release.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Scripts/release.sh b/Scripts/release.sh index 7ae363c..11623ed 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -20,7 +20,8 @@ Usage: Scripts/release.sh [command] Commands: all Build check, bump, package, notarize, staple, commit/tag, release, upload (default) check Quick release build check - package Bump version and create the release zip from the app bundle + bump Bump version/build numbers + package Create the release zip from the app bundle notarize Submit the app bundle for notarization staple Staple the notarization ticket to the app bundle commit Commit version bump and create release tag @@ -117,7 +118,6 @@ staple() { package_release() { require_app_bundle - bump_version build_zip "${APP_BUNDLE}" "${RELEASE_ZIP}" echo "Done: ${RELEASE_ZIP}" } @@ -170,6 +170,7 @@ upload_asset() { all() { build_check require_clean_tree + bump_version package_release notarize staple @@ -194,6 +195,9 @@ case "${COMMAND}" in check) build_check ;; + bump) + bump_version + ;; package) package_release ;; From fc1f555414f00dfd0d8801eaf756ed7d765eede9 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:24:40 -0800 Subject: [PATCH 05/12] Configure automatic codesigning for dododo team --- iMCP.xcodeproj/project.pbxproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index ad9fda7..9788ea1 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -462,7 +462,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 8; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TTY35UM57S; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; @@ -510,7 +510,6 @@ PRODUCT_BUNDLE_IDENTIFIER = co.dododo.iMCP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "iMCP Developer Build Provision Profile"; SDKROOT = auto; SUPPORTED_PLATFORMS = macosx; SUPPORTS_MACCATALYST = NO; From f47145e571614a750ae1927d88d2e1b021bfd379 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:29:14 -0800 Subject: [PATCH 06/12] Fix bump to set marketing version --- Scripts/release.sh | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/Scripts/release.sh b/Scripts/release.sh index 11623ed..2327b36 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -9,6 +9,7 @@ BUILD_NUMBER="${BUILD_NUMBER:-}" SCHEME="${SCHEME:-iMCP}" CONFIGURATION="${CONFIGURATION:-Release}" DESTINATION="${DESTINATION:-platform=macOS}" +PROJECT_FILE="${PROJECT_FILE:-${APP_NAME}.xcodeproj/project.pbxproj}" NOTARY_ZIP="${APP_BUNDLE}.zip" RELEASE_ZIP="${APP_NAME}.zip" @@ -38,6 +39,7 @@ Environment: SCHEME Xcode scheme for build check (default: iMCP) CONFIGURATION Build configuration for build check (default: Release) DESTINATION Build destination for build check (default: platform=macOS) + PROJECT_FILE Xcode project file (default: ${APP_NAME}.xcodeproj/project.pbxproj) EOF } @@ -77,16 +79,36 @@ trap cleanup EXIT bump_version() { require_version - echo "Bumping version to ${VERSION}" - agvtool new-marketing-version "${VERSION}" - - if [[ -n "${BUILD_NUMBER}" ]]; then - echo "Setting build number to ${BUILD_NUMBER}" - agvtool new-version -all "${BUILD_NUMBER}" - else - echo "Bumping build number" - agvtool next-version -all + if [[ ! -f "${PROJECT_FILE}" ]]; then + echo "Missing project file: ${PROJECT_FILE}" >&2 + exit 1 fi + local resolved_build_number="${BUILD_NUMBER}" + if [[ -z "${resolved_build_number}" ]]; then + resolved_build_number="0" + while IFS= read -r line; do + if [[ "${line}" =~ CURRENT_PROJECT_VERSION\ =\ ([0-9]+)\; ]]; then + resolved_build_number="${BASH_REMATCH[1]}" + break + fi + done < "${PROJECT_FILE}" + resolved_build_number="$((resolved_build_number + 1))" + fi + + echo "Setting MARKETING_VERSION to ${VERSION}" + echo "Setting CURRENT_PROJECT_VERSION to ${resolved_build_number}" + local tmp_file + tmp_file="$(mktemp)" + while IFS= read -r line; do + if [[ "${line}" == *"MARKETING_VERSION ="* ]]; then + printf '%s\n' "${line%%MARKETING_VERSION = *}MARKETING_VERSION = ${VERSION};" >> "${tmp_file}" + elif [[ "${line}" == *"CURRENT_PROJECT_VERSION ="* ]]; then + printf '%s\n' "${line%%CURRENT_PROJECT_VERSION = *}CURRENT_PROJECT_VERSION = ${resolved_build_number};" >> "${tmp_file}" + else + printf '%s\n' "${line}" >> "${tmp_file}" + fi + done < "${PROJECT_FILE}" + mv "${tmp_file}" "${PROJECT_FILE}" } build_zip() { From 3f452fa91d8c8cdceb54719d075c35368848adcf Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:30:54 -0800 Subject: [PATCH 07/12] Fix package step --- Scripts/release.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Scripts/release.sh b/Scripts/release.sh index 2327b36..51dca03 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -43,7 +43,27 @@ Environment: EOF } +resolve_app_bundle() { + if [[ -d "${APP_BUNDLE}" ]]; then + return 0 + fi + + local built_products_dir="" + local full_product_name="" + + built_products_dir="$(xcodebuild -quiet -scheme "${SCHEME}" -configuration "${CONFIGURATION}" -destination "${DESTINATION}" -showBuildSettings | awk -F ' = ' '/BUILT_PRODUCTS_DIR/ {print $2; exit}')" + full_product_name="$(xcodebuild -quiet -scheme "${SCHEME}" -configuration "${CONFIGURATION}" -destination "${DESTINATION}" -showBuildSettings | awk -F ' = ' '/FULL_PRODUCT_NAME/ {print $2; exit}')" + + if [[ -n "${built_products_dir}" && -n "${full_product_name}" ]]; then + local candidate="${built_products_dir}/${full_product_name}" + if [[ -d "${candidate}" ]]; then + APP_BUNDLE="${candidate}" + fi + fi +} + require_app_bundle() { + resolve_app_bundle if [[ ! -d "${APP_BUNDLE}" ]]; then echo "Missing app bundle: ${APP_BUNDLE}" >&2 exit 1 @@ -121,6 +141,7 @@ build_zip() { build_check() { echo "Checking release build (scheme: ${SCHEME}, configuration: ${CONFIGURATION})" xcodebuild -quiet -scheme "${SCHEME}" -configuration "${CONFIGURATION}" -destination "${DESTINATION}" build + resolve_app_bundle } notarize() { From 622a445fabb54dcba214601c520b19df43eaf6bc Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Thu, 29 Jan 2026 06:33:47 -0800 Subject: [PATCH 08/12] Put intermediate artifacts into (git ignored) /dist directory --- .gitignore | 1 + Scripts/release.sh | 45 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index fda4de3..c84988d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ playground.xcworkspace # .swiftpm .build/ +dist/ # CocoaPods # diff --git a/Scripts/release.sh b/Scripts/release.sh index 51dca03..93c2830 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -10,9 +10,10 @@ SCHEME="${SCHEME:-iMCP}" CONFIGURATION="${CONFIGURATION:-Release}" DESTINATION="${DESTINATION:-platform=macOS}" PROJECT_FILE="${PROJECT_FILE:-${APP_NAME}.xcodeproj/project.pbxproj}" +DIST_DIR="${DIST_DIR:-dist}" -NOTARY_ZIP="${APP_BUNDLE}.zip" -RELEASE_ZIP="${APP_NAME}.zip" +# Derived artifact names for notarization/release steps. +NOTARY_ZIP="${DIST_DIR}/${APP_NAME}-notarize.zip" print_usage() { cat <<'EOF' @@ -40,9 +41,11 @@ Environment: CONFIGURATION Build configuration for build check (default: Release) DESTINATION Build destination for build check (default: platform=macOS) PROJECT_FILE Xcode project file (default: ${APP_NAME}.xcodeproj/project.pbxproj) + DIST_DIR Output directory for artifacts (default: dist) EOF } +# If APP_BUNDLE isn't explicit, derive the built app path from Xcode settings. resolve_app_bundle() { if [[ -d "${APP_BUNDLE}" ]]; then return 0 @@ -91,6 +94,15 @@ require_clean_tree() { fi } +ensure_dist_dir() { + mkdir -p "${DIST_DIR}" +} + +release_zip() { + require_version + printf '%s/%s-%s.zip' "${DIST_DIR}" "${APP_NAME}" "${VERSION}" +} + cleanup() { rm -f "${NOTARY_ZIP}" } @@ -105,6 +117,7 @@ bump_version() { fi local resolved_build_number="${BUILD_NUMBER}" if [[ -z "${resolved_build_number}" ]]; then + # Find the current build number and increment it if not provided. resolved_build_number="0" while IFS= read -r line; do if [[ "${line}" =~ CURRENT_PROJECT_VERSION\ =\ ([0-9]+)\; ]]; then @@ -117,6 +130,7 @@ bump_version() { echo "Setting MARKETING_VERSION to ${VERSION}" echo "Setting CURRENT_PROJECT_VERSION to ${resolved_build_number}" + # Replace both version fields in the project file without agvtool. local tmp_file tmp_file="$(mktemp)" while IFS= read -r line; do @@ -134,6 +148,7 @@ bump_version() { build_zip() { local source_bundle="$1" local output_zip="$2" + ensure_dist_dir echo "Creating zip: ${output_zip}" ditto -c -k --keepParent "${source_bundle}" "${output_zip}" } @@ -147,6 +162,7 @@ build_check() { notarize() { require_app_bundle require_keychain_profile + ensure_dist_dir echo "Zipping for notarization: ${NOTARY_ZIP}" ditto -c -k --keepParent "${APP_BUNDLE}" "${NOTARY_ZIP}" echo "Submitting to notarization" @@ -161,8 +177,11 @@ staple() { package_release() { require_app_bundle - build_zip "${APP_BUNDLE}" "${RELEASE_ZIP}" - echo "Done: ${RELEASE_ZIP}" + local release_zip_path + release_zip_path="$(release_zip)" + build_zip "${APP_BUNDLE}" "${release_zip_path}" + shasum -a 256 "${release_zip_path}" > "${release_zip_path}.sha256" + echo "Done: ${release_zip_path}" } validate_staple() { @@ -173,10 +192,13 @@ validate_staple() { commit_and_tag() { require_version - if [[ ! -f "${RELEASE_ZIP}" ]]; then - echo "Missing release asset: ${RELEASE_ZIP}" >&2 + local release_zip_path + release_zip_path="$(release_zip)" + if [[ ! -f "${release_zip_path}" ]]; then + echo "Missing release asset: ${release_zip_path}" >&2 exit 1 fi + # Ensure the stapled build exists before tagging a release. validate_staple if git rev-parse --verify "refs/tags/${VERSION}" >/dev/null 2>&1; then echo "Tag already exists: ${VERSION}" >&2 @@ -201,16 +223,19 @@ create_release() { upload_asset() { require_version - if [[ ! -f "${RELEASE_ZIP}" ]]; then - echo "Missing release asset: ${RELEASE_ZIP}" >&2 + local release_zip_path + release_zip_path="$(release_zip)" + if [[ ! -f "${release_zip_path}" ]]; then + echo "Missing release asset: ${release_zip_path}" >&2 exit 1 fi - echo "Uploading release asset ${RELEASE_ZIP}" - gh release upload "${VERSION}" "${RELEASE_ZIP}" --clobber + echo "Uploading release asset ${release_zip_path}" + gh release upload "${VERSION}" "${release_zip_path}" --clobber gh release view --web "${VERSION}" } all() { + # Full release flow with strict gating at each step. build_check require_clean_tree bump_version From 6c0b22825c4c7af0978b522c6ddcc14b2cae922e Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 30 Jan 2026 03:08:22 -0800 Subject: [PATCH 09/12] Update .gitignore --- .gitignore | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index c84988d..bf7f363 100644 --- a/.gitignore +++ b/.gitignore @@ -30,34 +30,6 @@ playground.xcworkspace # .swiftpm .build/ -dist/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output \ No newline at end of file +# Release artifacts +dist/ From f7328fc71a502002e5e9bfb2e16fec220907bab3 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 30 Jan 2026 03:25:26 -0800 Subject: [PATCH 10/12] Get notarization working by temporarily disabling WeatherKit entitlement in Release mode --- App/App.Release.entitlements | 23 ++++++++++ Scripts/release.sh | 76 +++++++++++++++++++++++++++++++++- iMCP.xcodeproj/project.pbxproj | 10 ++--- 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 App/App.Release.entitlements diff --git a/App/App.Release.entitlements b/App/App.Release.entitlements new file mode 100644 index 0000000..b8565f2 --- /dev/null +++ b/App/App.Release.entitlements @@ -0,0 +1,23 @@ + + + + + com.apple.security.inherit + + com.apple.security.personal-information.health + + com.apple.security.temporary-exception.apple-events + + com.apple.Terminal + + com.apple.security.temporary-exception.files.absolute-path.read-write + + /Users/*/Library/Messages/ + + com.apple.security.temporary-exception.mach-lookup.global-name + + $(PRODUCT_BUNDLE_IDENTIFIER)-spks + $(PRODUCT_BUNDLE_IDENTIFIER)-spki + + + \ No newline at end of file diff --git a/Scripts/release.sh b/Scripts/release.sh index 93c2830..f255c18 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -11,6 +11,11 @@ CONFIGURATION="${CONFIGURATION:-Release}" DESTINATION="${DESTINATION:-platform=macOS}" PROJECT_FILE="${PROJECT_FILE:-${APP_NAME}.xcodeproj/project.pbxproj}" DIST_DIR="${DIST_DIR:-dist}" +ARCHIVE_PATH="${ARCHIVE_PATH:-${DIST_DIR}/${APP_NAME}.xcarchive}" +EXPORT_DIR="${EXPORT_DIR:-${DIST_DIR}/export}" +EXPORT_OPTIONS_PLIST="${EXPORT_OPTIONS_PLIST:-${DIST_DIR}/export-options.plist}" +TEAM_ID="${TEAM_ID:-}" +SIGNING_CERTIFICATE="${SIGNING_CERTIFICATE:-Developer ID Application}" # Derived artifact names for notarization/release steps. NOTARY_ZIP="${DIST_DIR}/${APP_NAME}-notarize.zip" @@ -20,9 +25,11 @@ print_usage() { Usage: Scripts/release.sh [command] Commands: - all Build check, bump, package, notarize, staple, commit/tag, release, upload (default) + all Build check, bump, archive, export, package, notarize, staple, commit/tag, release, upload (default) check Quick release build check bump Bump version/build numbers + archive Create an Xcode archive for direct distribution + export Export a Developer ID signed app from the archive package Create the release zip from the app bundle notarize Submit the app bundle for notarization staple Staple the notarization ticket to the app bundle @@ -42,11 +49,17 @@ Environment: DESTINATION Build destination for build check (default: platform=macOS) PROJECT_FILE Xcode project file (default: ${APP_NAME}.xcodeproj/project.pbxproj) DIST_DIR Output directory for artifacts (default: dist) + ARCHIVE_PATH Archive path (default: dist/${APP_NAME}.xcarchive) + EXPORT_DIR Export path for the signed app (default: dist/export) + EXPORT_OPTIONS_PLIST Export options plist path (default: dist/export-options.plist) + TEAM_ID Team ID for Developer ID signing (optional) + SIGNING_CERTIFICATE Signing certificate (default: Developer ID Application) EOF } # If APP_BUNDLE isn't explicit, derive the built app path from Xcode settings. resolve_app_bundle() { + resolve_exported_app || true if [[ -d "${APP_BUNDLE}" ]]; then return 0 fi @@ -98,6 +111,17 @@ ensure_dist_dir() { mkdir -p "${DIST_DIR}" } +resolve_exported_app() { + local candidate + for candidate in "${EXPORT_DIR}"/*.app "${EXPORT_DIR}"/Applications/*.app "${EXPORT_DIR}"/Products/Applications/*.app; do + if [[ -d "${candidate}" ]]; then + APP_BUNDLE="${candidate}" + return 0 + fi + done + return 1 +} + release_zip() { require_version printf '%s/%s-%s.zip' "${DIST_DIR}" "${APP_NAME}" "${VERSION}" @@ -159,6 +183,48 @@ build_check() { resolve_app_bundle } +archive_app() { + ensure_dist_dir + echo "Archiving app to ${ARCHIVE_PATH}" + xcodebuild -quiet -scheme "${SCHEME}" -configuration "${CONFIGURATION}" -destination "generic/platform=macOS" archive -archivePath "${ARCHIVE_PATH}" +} + +write_export_options() { + ensure_dist_dir + local tmp_file + tmp_file="$(mktemp)" + cat > "${tmp_file}" < + + + + method + developer-id + signingStyle + manual + signingCertificate + ${SIGNING_CERTIFICATE} +EOF + if [[ -n "${TEAM_ID}" ]]; then + cat >> "${tmp_file}" <teamID + ${TEAM_ID} +EOF + fi + cat >> "${tmp_file}" <<'EOF' + + +EOF + mv "${tmp_file}" "${EXPORT_OPTIONS_PLIST}" +} + +export_app() { + write_export_options + echo "Exporting Developer ID app to ${EXPORT_DIR}" + xcodebuild -quiet -exportArchive -archivePath "${ARCHIVE_PATH}" -exportPath "${EXPORT_DIR}" -exportOptionsPlist "${EXPORT_OPTIONS_PLIST}" + resolve_app_bundle +} + notarize() { require_app_bundle require_keychain_profile @@ -239,6 +305,8 @@ all() { build_check require_clean_tree bump_version + archive_app + export_app package_release notarize staple @@ -266,6 +334,12 @@ case "${COMMAND}" in bump) bump_version ;; + archive) + archive_app + ;; + export) + export_app + ;; package) package_release ;; diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index 9788ea1..ec05608 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -396,7 +396,7 @@ CODE_SIGN_IDENTITY = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_APP_SANDBOX = YES; @@ -438,7 +438,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 15.1; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; PRODUCT_BUNDLE_IDENTIFIER = co.dododo.iMCP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -457,10 +457,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; AUTOMATION_APPLE_EVENTS = YES; - CODE_SIGN_ENTITLEMENTS = App/App.entitlements; + CODE_SIGN_ENTITLEMENTS = App/App.Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = TTY35UM57S; ENABLE_APP_SANDBOX = YES; @@ -506,7 +506,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 15.1; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; PRODUCT_BUNDLE_IDENTIFIER = co.dododo.iMCP; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From f1d72449d281242d08f3934c45254be029c03cce Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 30 Jan 2026 03:53:39 -0800 Subject: [PATCH 11/12] Remove and revert Release entitlements change --- App/App.Release.entitlements | 23 -------- Scripts/release.sh | 102 +++++++++++++++++++++++++++++++++ iMCP.xcodeproj/project.pbxproj | 2 +- 3 files changed, 103 insertions(+), 24 deletions(-) delete mode 100644 App/App.Release.entitlements diff --git a/App/App.Release.entitlements b/App/App.Release.entitlements deleted file mode 100644 index b8565f2..0000000 --- a/App/App.Release.entitlements +++ /dev/null @@ -1,23 +0,0 @@ - - - - - com.apple.security.inherit - - com.apple.security.personal-information.health - - com.apple.security.temporary-exception.apple-events - - com.apple.Terminal - - com.apple.security.temporary-exception.files.absolute-path.read-write - - /Users/*/Library/Messages/ - - com.apple.security.temporary-exception.mach-lookup.global-name - - $(PRODUCT_BUNDLE_IDENTIFIER)-spks - $(PRODUCT_BUNDLE_IDENTIFIER)-spki - - - \ No newline at end of file diff --git a/Scripts/release.sh b/Scripts/release.sh index f255c18..07f56a1 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -16,6 +16,9 @@ EXPORT_DIR="${EXPORT_DIR:-${DIST_DIR}/export}" EXPORT_OPTIONS_PLIST="${EXPORT_OPTIONS_PLIST:-${DIST_DIR}/export-options.plist}" TEAM_ID="${TEAM_ID:-}" SIGNING_CERTIFICATE="${SIGNING_CERTIFICATE:-Developer ID Application}" +BUNDLE_ID="${BUNDLE_ID:-}" +PROVISIONING_PROFILE_NAME="${PROVISIONING_PROFILE_NAME:-}" +PROVISIONING_PROFILE_UUID="${PROVISIONING_PROFILE_UUID:-}" # Derived artifact names for notarization/release steps. NOTARY_ZIP="${DIST_DIR}/${APP_NAME}-notarize.zip" @@ -30,6 +33,7 @@ Commands: bump Bump version/build numbers archive Create an Xcode archive for direct distribution export Export a Developer ID signed app from the archive + profiles List installed provisioning profiles package Create the release zip from the app bundle notarize Submit the app bundle for notarization staple Staple the notarization ticket to the app bundle @@ -54,6 +58,9 @@ Environment: EXPORT_OPTIONS_PLIST Export options plist path (default: dist/export-options.plist) TEAM_ID Team ID for Developer ID signing (optional) SIGNING_CERTIFICATE Signing certificate (default: Developer ID Application) + BUNDLE_ID Bundle identifier for export profiles (optional) + PROVISIONING_PROFILE_NAME Provisioning profile name for export (optional) + PROVISIONING_PROFILE_UUID Provisioning profile UUID for export (optional) EOF } @@ -111,6 +118,79 @@ ensure_dist_dir() { mkdir -p "${DIST_DIR}" } +resolve_bundle_id() { + if [[ -n "${BUNDLE_ID}" ]]; then + return 0 + fi + if [[ ! -f "${PROJECT_FILE}" ]]; then + return 1 + fi + while IFS= read -r line; do + if [[ "${line}" == *"PRODUCT_BUNDLE_IDENTIFIER ="* && "${line}" != *"imcp-server"* ]]; then + BUNDLE_ID="${line#*PRODUCT_BUNDLE_IDENTIFIER = }" + BUNDLE_ID="${BUNDLE_ID%;}" + return 0 + fi + done < "${PROJECT_FILE}" + return 1 +} + +list_profiles() { + local profiles_dir + local found_dir="0" + local profile tmp_plist name uuid team weatherkit app_id + local profiles_dirs=( + "${HOME}/Library/MobileDevice/Provisioning Profiles" + "${HOME}/Library/Developer/Xcode/UserData/Provisioning Profiles" + ) + resolve_bundle_id || true + + for profiles_dir in "${profiles_dirs[@]}"; do + if [[ ! -d "${profiles_dir}" ]]; then + continue + fi + found_dir="1" + for profile in "${profiles_dir}"/*.mobileprovision "${profiles_dir}"/*.provisionprofile; do + if [[ ! -f "${profile}" ]]; then + continue + fi + tmp_plist="$(mktemp)" + if ! security cms -D -i "${profile}" > "${tmp_plist}" 2>/dev/null; then + rm -f "${tmp_plist}" + continue + fi + name="$("/usr/libexec/PlistBuddy" -c "Print Name" "${tmp_plist}" 2>/dev/null || true)" + uuid="$("/usr/libexec/PlistBuddy" -c "Print UUID" "${tmp_plist}" 2>/dev/null || true)" + team="$("/usr/libexec/PlistBuddy" -c "Print TeamIdentifier:0" "${tmp_plist}" 2>/dev/null || true)" + weatherkit="$("/usr/libexec/PlistBuddy" -c "Print Entitlements:com.apple.developer.weatherkit" "${tmp_plist}" 2>/dev/null || true)" + app_id="$("/usr/libexec/PlistBuddy" -c "Print Entitlements:com.apple.application-identifier" "${tmp_plist}" 2>/dev/null || true)" + rm -f "${tmp_plist}" + if [[ -n "${BUNDLE_ID}" && -n "${app_id}" ]]; then + if [[ "${app_id}" != *".${BUNDLE_ID}" && "${app_id}" != "${BUNDLE_ID}" ]]; then + continue + fi + fi + printf '%s\n' "Name: ${name}" + printf '%s\n' "UUID: ${uuid}" + printf '%s\n' "Team: ${team}" + if [[ -n "${app_id}" ]]; then + printf '%s\n' "App ID: ${app_id}" + fi + if [[ -n "${weatherkit}" ]]; then + printf '%s\n' "WeatherKit: ${weatherkit}" + fi + printf '%s\n\n' "File: ${profile}" + done + done + + if [[ "${found_dir}" != "1" ]]; then + echo "No provisioning profiles directory found at expected locations:" >&2 + echo " ${profiles_dirs[0]}" >&2 + echo " ${profiles_dirs[1]}" >&2 + exit 1 + fi +} + resolve_exported_app() { local candidate for candidate in "${EXPORT_DIR}"/*.app "${EXPORT_DIR}"/Applications/*.app "${EXPORT_DIR}"/Products/Applications/*.app; do @@ -205,6 +285,25 @@ write_export_options() { signingCertificate ${SIGNING_CERTIFICATE} EOF + local profile_value="" + if [[ -n "${PROVISIONING_PROFILE_UUID}" ]]; then + profile_value="${PROVISIONING_PROFILE_UUID}" + elif [[ -n "${PROVISIONING_PROFILE_NAME}" ]]; then + profile_value="${PROVISIONING_PROFILE_NAME}" + fi + if [[ -n "${profile_value}" ]]; then + if ! resolve_bundle_id; then + echo "BUNDLE_ID is required when using provisioning profiles." >&2 + exit 1 + fi + cat >> "${tmp_file}" <provisioningProfiles + + ${BUNDLE_ID} + ${profile_value} + +EOF + fi if [[ -n "${TEAM_ID}" ]]; then cat >> "${tmp_file}" <teamID @@ -340,6 +439,9 @@ case "${COMMAND}" in export) export_app ;; + profiles) + list_profiles + ;; package) package_release ;; diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index ec05608..5a021f4 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -457,7 +457,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; AUTOMATION_APPLE_EVENTS = YES; - CODE_SIGN_ENTITLEMENTS = App/App.Release.entitlements; + CODE_SIGN_ENTITLEMENTS = App/App.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 9; From 3fa77aa4e7ca20f7d1089ad1715058e708a713fc Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 30 Jan 2026 04:24:12 -0800 Subject: [PATCH 12/12] Update Copyright --- iMCP.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/iMCP.xcodeproj/project.pbxproj b/iMCP.xcodeproj/project.pbxproj index 5a021f4..56b8a8e 100644 --- a/iMCP.xcodeproj/project.pbxproj +++ b/iMCP.xcodeproj/project.pbxproj @@ -415,15 +415,15 @@ INFOPLIST_KEY_CFBundleDisplayName = iMCP; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_LSUIElement = YES; - INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide event information to the MCP server."; + INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "iMCP needs access to provide event information to the MCP server."; INFOPLIST_KEY_NSCameraUsageDescription = "iMCP needs access to the camera to take pictures for the MCP server."; INFOPLIST_KEY_NSContactsUsageDescription = "iMCP needs access to provide contact information to the MCP server."; - INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Loopwork Limited. All rights reserved."; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2026 dododo, LLC. All rights reserved."; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "iMCP uses the local network to connect to the MCP server."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "iMCP needs access to the microphone to record audio for the MCP server."; - INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide reminders information to the MCP server."; + INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "iMCP needs access to provide reminders information to the MCP server."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -483,15 +483,15 @@ INFOPLIST_KEY_CFBundleDisplayName = iMCP; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_LSUIElement = YES; - INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide event information to the MCP server."; + INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "iMCP needs access to provide event information to the MCP server."; INFOPLIST_KEY_NSCameraUsageDescription = "iMCP needs access to the camera to take pictures for the MCP server."; INFOPLIST_KEY_NSContactsUsageDescription = "iMCP needs access to provide contact information to the MCP server."; - INFOPLIST_KEY_NSHumanReadableCopyright = "© 2025 Loopwork Limited. All rights reserved."; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2026 dododo, LLC. All rights reserved."; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "iMCP uses the local network to connect to the MCP server."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "iMCP needs access to provide location information to the MCP server."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "iMCP needs access to the microphone to record audio for the MCP server."; - INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "${PRODUCT_NAME} needs access to provide reminders information to the MCP server."; + INFOPLIST_KEY_NSRemindersFullAccessUsageDescription = "iMCP needs access to provide reminders information to the MCP server."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;