Files
apple-mcp/helpers.py
T
Eric Jungbauer 7e619d0454 Initial commit — Apple Apps MCP server with native macOS config app
Python MCP server (FastMCP) providing Claude Code/CoWork access to Apple
Reminders, Calendar, Mail, Contacts, Find My, and Maps via AppleScript.
Includes a native SwiftUI config/installer app and a compiled EventKit
helper for fast reminder queries on large databases (8,000+ items).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:32:24 -06:00

96 lines
2.7 KiB
Python

"""Shared utilities for Apple MCP server."""
import json
import subprocess
from datetime import datetime, date
from pathlib import Path
from typing import Any
CONFIG_PATH = Path(__file__).parent / "config.json"
def load_config() -> dict:
"""Load the app permissions config."""
with open(CONFIG_PATH) as f:
return json.load(f)
def is_app_enabled(app_name: str) -> bool:
"""Check if an app is enabled in config."""
cfg = load_config()
app = cfg.get("apps", {}).get(app_name, {})
return app.get("enabled", False)
def is_write_allowed(app_name: str) -> bool:
"""Check if write access is allowed for an app."""
cfg = load_config()
app = cfg.get("apps", {}).get(app_name, {})
return app.get("enabled", False) and app.get("mode") == "read-write"
def run_applescript(script: str, timeout: int = 30) -> str:
"""Run an AppleScript and return stdout.
Raises RuntimeError on failure.
"""
result = subprocess.run(
["osascript", "-e", script],
capture_output=True,
text=True,
timeout=timeout,
)
if result.returncode != 0:
raise RuntimeError(f"AppleScript error: {result.stderr.strip()}")
return result.stdout.strip()
def run_jxa(script: str, timeout: int = 30) -> str:
"""Run a JXA (JavaScript for Automation) script and return stdout."""
result = subprocess.run(
["osascript", "-l", "JavaScript", "-e", script],
capture_output=True,
text=True,
timeout=timeout,
)
if result.returncode != 0:
raise RuntimeError(f"JXA error: {result.stderr.strip()}")
return result.stdout.strip()
def parse_applescript_date(date_str: str) -> str | None:
"""Try to parse a macOS AppleScript date string into ISO format."""
if not date_str or date_str == "missing value":
return None
formats = [
"%A, %B %d, %Y at %I:%M:%S %p",
"%A, %B %d, %Y at %H:%M:%S",
"%m/%d/%Y %I:%M:%S %p",
"%m/%d/%Y %H:%M:%S",
"%Y-%m-%d %H:%M:%S %z",
]
for fmt in formats:
try:
dt = datetime.strptime(date_str, fmt)
return dt.isoformat()
except ValueError:
continue
return date_str
def format_date_for_applescript(iso_date: str) -> str:
"""Convert ISO date string to AppleScript date format."""
dt = datetime.fromisoformat(iso_date)
return dt.strftime("%m/%d/%Y %I:%M:%S %p")
def today_str() -> str:
"""Return today's date as YYYY-MM-DD."""
return date.today().isoformat()
def safe_applescript_string(s: str) -> str:
"""Escape a string for safe embedding in AppleScript."""
return s.replace("\\", "\\\\").replace('"', '\\"')