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>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
"""Apple Reminders tools — uses compiled EventKit helper for speed."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from helpers import run_applescript, safe_applescript_string
|
||||
|
||||
HELPER_BIN = Path(__file__).parent.parent / "helpers" / "reminders_helper"
|
||||
|
||||
|
||||
def _run_helper(*args: str, timeout: int = 15) -> dict | list:
|
||||
"""Run the compiled EventKit reminders helper and return parsed JSON."""
|
||||
result = subprocess.run(
|
||||
[str(HELPER_BIN)] + list(args),
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
stderr = result.stderr.strip()
|
||||
try:
|
||||
err = json.loads(result.stdout)
|
||||
raise RuntimeError(err.get("error", stderr))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
raise RuntimeError(f"Reminders helper error: {stderr or result.stdout}")
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def list_lists() -> list[dict]:
|
||||
"""List all reminder lists (e.g., Reminders, Shopping, Work)."""
|
||||
return _run_helper("lists")
|
||||
|
||||
|
||||
def get_reminders(list_name: str | None = None, include_completed: bool = False) -> dict:
|
||||
"""Get reminders, optionally filtered by list name.
|
||||
|
||||
Args:
|
||||
list_name: Filter to a specific list. None = all lists.
|
||||
include_completed: Include completed reminders.
|
||||
"""
|
||||
args = ["get"]
|
||||
if list_name:
|
||||
args += ["--list", list_name]
|
||||
if not include_completed:
|
||||
pass # Default is incomplete only
|
||||
return _run_helper(*args, timeout=30)
|
||||
|
||||
|
||||
def search_reminders(query: str) -> dict:
|
||||
"""Search reminders by name across all lists."""
|
||||
return _run_helper("search", query, timeout=30)
|
||||
|
||||
|
||||
def get_overdue_reminders() -> dict:
|
||||
"""Get all overdue (past due) incomplete reminders."""
|
||||
return _run_helper("get", "--overdue")
|
||||
|
||||
|
||||
def get_due_today() -> dict:
|
||||
"""Get reminders due today."""
|
||||
return _run_helper("get", "--due-today")
|
||||
|
||||
|
||||
def get_due_this_week() -> dict:
|
||||
"""Get reminders due in the next 7 days."""
|
||||
return _run_helper("get", "--due-this-week")
|
||||
|
||||
|
||||
def create_reminder(name: str, list_name: str = "Reminders", due_date: str | None = None,
|
||||
body: str | None = None, priority: int = 0) -> dict:
|
||||
"""Create a new reminder."""
|
||||
safe_name = safe_applescript_string(name)
|
||||
safe_list = safe_applescript_string(list_name)
|
||||
|
||||
props = [f'name:"{safe_name}"']
|
||||
if body:
|
||||
props.append(f'body:"{safe_applescript_string(body)}"')
|
||||
if priority > 0:
|
||||
props.append(f'priority:{priority}')
|
||||
|
||||
props_str = ", ".join(props)
|
||||
|
||||
due_line = ""
|
||||
if due_date:
|
||||
due_line = f'''
|
||||
set due date of newReminder to date "{due_date}"'''
|
||||
|
||||
script = f'''
|
||||
tell application "Reminders"
|
||||
set theList to list "{safe_list}"
|
||||
set newReminder to make new reminder at end of theList with properties {{{props_str}}}
|
||||
{due_line}
|
||||
return id of newReminder
|
||||
end tell'''
|
||||
reminder_id = run_applescript(script)
|
||||
return {"id": reminder_id.strip(), "name": name, "list": list_name, "created": True}
|
||||
|
||||
|
||||
def complete_reminder(name: str, list_name: str | None = None) -> dict:
|
||||
"""Mark a reminder as completed by name."""
|
||||
safe_name = safe_applescript_string(name)
|
||||
if list_name:
|
||||
safe_list = safe_applescript_string(list_name)
|
||||
script = f'''
|
||||
tell application "Reminders"
|
||||
set theList to list "{safe_list}"
|
||||
set theReminders to (every reminder of theList whose name is "{safe_name}" and completed is false)
|
||||
if (count of theReminders) > 0 then
|
||||
set completed of item 1 of theReminders to true
|
||||
return "completed"
|
||||
else
|
||||
return "not_found"
|
||||
end if
|
||||
end tell'''
|
||||
else:
|
||||
script = f'''
|
||||
tell application "Reminders"
|
||||
repeat with L in lists
|
||||
set theReminders to (every reminder of L whose name is "{safe_name}" and completed is false)
|
||||
if (count of theReminders) > 0 then
|
||||
set completed of item 1 of theReminders to true
|
||||
return "completed"
|
||||
end if
|
||||
end repeat
|
||||
return "not_found"
|
||||
end tell'''
|
||||
result = run_applescript(script)
|
||||
if result == "completed":
|
||||
return {"name": name, "completed": True}
|
||||
raise RuntimeError(f"Reminder '{name}' not found or already completed")
|
||||
|
||||
|
||||
def delete_reminder(name: str, list_name: str | None = None) -> dict:
|
||||
"""Delete a reminder by name."""
|
||||
safe_name = safe_applescript_string(name)
|
||||
if list_name:
|
||||
safe_list = safe_applescript_string(list_name)
|
||||
script = f'''
|
||||
tell application "Reminders"
|
||||
set theList to list "{safe_list}"
|
||||
set theReminders to (every reminder of theList whose name is "{safe_name}")
|
||||
if (count of theReminders) > 0 then
|
||||
delete item 1 of theReminders
|
||||
return "deleted"
|
||||
else
|
||||
return "not_found"
|
||||
end if
|
||||
end tell'''
|
||||
else:
|
||||
script = f'''
|
||||
tell application "Reminders"
|
||||
repeat with L in lists
|
||||
set theReminders to (every reminder of L whose name is "{safe_name}")
|
||||
if (count of theReminders) > 0 then
|
||||
delete item 1 of theReminders
|
||||
return "deleted"
|
||||
end if
|
||||
end repeat
|
||||
return "not_found"
|
||||
end tell'''
|
||||
result = run_applescript(script)
|
||||
if result == "deleted":
|
||||
return {"name": name, "deleted": True}
|
||||
raise RuntimeError(f"Reminder '{name}' not found")
|
||||
Reference in New Issue
Block a user