"""Apple Find My tools — reads cached location data.""" import json import plistlib from pathlib import Path from datetime import datetime FINDMY_CACHE = Path.home() / "Library" / "Caches" / "com.apple.findmy.fmipcore" ITEMS_FILE = FINDMY_CACHE / "Items.data" DEVICES_FILE = FINDMY_CACHE / "Devices.data" def _load_cache_file(path: Path) -> list[dict]: """Load a Find My cache file (plist format).""" if not path.exists(): raise RuntimeError( f"Find My cache not found at {path}. " "Make sure the Find My app has been opened at least once." ) try: f = open(path, "rb") except PermissionError: raise RuntimeError( f"Permission denied reading {path}. " "Grant Full Disk Access to the terminal/app running the MCP server: " "System Settings > Privacy & Security > Full Disk Access." ) with f: try: data = plistlib.load(f) except Exception: # Some versions use JSON f.seek(0) try: data = json.load(f) except Exception: raise RuntimeError(f"Could not parse Find My cache at {path}") if isinstance(data, list): return data return [] def _format_location(item: dict) -> dict | None: """Extract location info from a Find My item/device.""" loc = item.get("location") if not loc: return None result = { "latitude": loc.get("latitude"), "longitude": loc.get("longitude"), "altitude": loc.get("altitude"), "horizontal_accuracy": loc.get("horizontalAccuracy"), "is_old": loc.get("isOld", False), "floor_level": loc.get("floorLevel"), } timestamp = loc.get("timeStamp") if timestamp: try: if isinstance(timestamp, (int, float)): # Apple epoch (2001-01-01) or Unix epoch if timestamp > 1e15: # nanoseconds timestamp = timestamp / 1e9 if timestamp < 1e9: # Apple epoch timestamp += 978307200 # seconds between 1970 and 2001 result["timestamp"] = datetime.fromtimestamp(timestamp).isoformat() else: result["timestamp"] = str(timestamp) except Exception: result["timestamp"] = str(timestamp) return result def list_devices() -> list[dict]: """List all Apple devices in Find My.""" try: devices = _load_cache_file(DEVICES_FILE) except RuntimeError as e: return [{"error": str(e)}] results = [] for dev in devices: location = _format_location(dev) results.append({ "name": dev.get("name", "Unknown"), "device_model": dev.get("deviceDisplayName", dev.get("deviceModel", "Unknown")), "battery_level": dev.get("batteryLevel"), "battery_status": dev.get("batteryStatus"), "location": location, "id": dev.get("baUUID", dev.get("id", "")), }) return results def get_device_location(device_name: str) -> dict: """Get location of a specific Apple device.""" try: devices = _load_cache_file(DEVICES_FILE) except RuntimeError as e: raise RuntimeError(str(e)) device_name_lower = device_name.lower() for dev in devices: name = dev.get("name", "") if device_name_lower in name.lower(): location = _format_location(dev) return { "name": name, "device_model": dev.get("deviceDisplayName", dev.get("deviceModel", "Unknown")), "battery_level": dev.get("batteryLevel"), "location": location, } raise RuntimeError(f"Device '{device_name}' not found in Find My") def list_items() -> list[dict]: """List all Find My items (AirTags, third-party trackers).""" try: items = _load_cache_file(ITEMS_FILE) except RuntimeError as e: return [{"error": str(e)}] results = [] for item in items: location = _format_location(item) results.append({ "name": item.get("name", "Unknown"), "product_type": item.get("productType", {}).get("type", "Unknown") if isinstance(item.get("productType"), dict) else item.get("productType", "Unknown"), "serial_number": item.get("serialNumber"), "battery_status": item.get("batteryStatus"), "location": location, "id": item.get("identifier", ""), }) return results