Wann habe ich eigentlich Zeit? Ein MCP-Server für Apple Calendar
Mein Kalender weiß genau, was am Donnerstag ansteht. Was er mir nie beantwortet: Wann habe ich diese Woche eigentlich zwei Stunden am Stück? Das habe ich mir bisher selbst zusammengesucht — Woche aufklappen, Lücken abzählen, hoffen, dass ich keine übersehen habe.
Gebaut habe ich das für Claude Cowork. Dort übernimmt Claude mehrschrittige Aufgaben in meinen Dateien und Werkzeugen, auf Wunsch auch unbeaufsichtigt nach Zeitplan — nur an meinen Kalender kam es dabei nicht heran. @redpop/apple-calendar-mcp (öffnet in neuem Tab) schließt diese Lücke: Der Server sitzt zwischen dem KI-Client und dem Kalender auf dem Mac und spricht dort mit EventKit, Apples eigenem Kalender-Framework. Er läuft nur unter macOS, und das ist keine Bequemlichkeit: Für EventKit gibt es kein plattformübergreifendes Gegenstück.
Freie Zeit statt voller Termine
Die meisten Kalender-Integrationen beantworten, was im Kalender steht. find_free_time beantwortet, wo nichts steht. Es gibt die freien Lücken zurück statt der belegten Blöcke.
Klingt nach einer Kleinigkeit, macht aber den Unterschied zwischen „hier sind deine 14 Termine“ und „Donnerstag zwischen 9 und 11 Uhr hast du Zeit“. Ich frage: „Finde mir diese Woche zwei Stunden für konzentriertes Arbeiten“ — und bekomme Vorschläge statt einer Terminliste.
Damit daraus brauchbare Vorschläge werden, braucht es ein paar Annahmen. Ein Parameter namens activityDomain koppelt Tage und Uhrzeiten aneinander: work heißt Wochentage zwischen 9 und 18 Uhr, personal heißt jeder Tag, unrestricted heißt rund um die Uhr.
Ein früherer Entwurf hatte das getrennt — ein Schalter für „nur Wochentage“, ein anderer für die Uhrzeit. Damit ließ sich „nur Wochentage, aber auch drei Uhr nachts“ einstellen. Der Schalter ist wieder rausgeflogen.
Dazu kommt bufferMinutes, ein Puffer vor und nach jedem Termin. Eine Lücke, die direkt zwischen zwei Meetings klemmt, ist auf dem Papier frei und in der Praxis nutzlos.
Sechs Tools, eine Zeile Installation
Die Installation braucht weder Xcode noch eine Swift-Toolchain noch einen Clone. Das signierte Helper-Binary liegt fertig im npm-Paket:
claude mcp add apple-calendar -- npx -y @redpop/apple-calendar-mcpNeben der Suche nach freier Zeit kann der Server das Übliche: Kalender auflisten, Termine in einem Zeitraum lesen, anlegen, ändern, löschen. Wiederkehrende Termine kommen dabei als einzelne Vorkommen zurück, nicht als Serien-Master — ein Detail, das AppleScript-basierte Server regelmäßig falsch machen. Und wer eine Serie ändert, muss explizit sagen, ob nur dieser eine Termin gemeint ist oder alle folgenden. Einen Standardwert gibt es dafür bewusst nicht: „diesen Dienstag absagen“ und „nie wieder“ sind nicht dasselbe, und rückgängig machen lässt sich beides nicht.
Dass die Installation heute eine Zeile ist, verdanke ich der Konkurrenz. Bevor ich angefangen habe, habe ich mir angesehen, was es schon gibt, und das hat mein eigenes Konzept zusammengestrichen. Ein Hintergrunddienst, ein Unix-Socket, ein Installationsskript und ein geplantes Installationspaket haben sich alle als überflüssig herausgestellt. Aus fünf Befehlen plus Daemon wurde eine Zeile, bei 409 Zeilen weniger Code.
Was die anderen besser machen, will ich dabei nicht verschweigen. mcp-ical (öffnet in neuem Tab) hat mit 329 Sternen die mit Abstand größte Community und ist in Python geschrieben — wer dort mitarbeiten will, braucht kein Apple-Developer-Konto und muss nichts signieren. mcp-server-apple-events (öffnet in neuem Tab) hat das Berechtigungsproblem, um das es gleich geht, zuerst und unabhängig gelöst und deckt zusätzlich die Erinnerungen ab. Beides fehlt bei mir.
Warum der Dialog nach calendar-helper fragt
Und damit zu dem Teil, der mich am meisten Zeit gekostet hat.
macOS vergibt Kalenderrechte nicht an das Programm, das fragt, sondern an den responsible parent process — an die Anwendung, die letztlich für den Start verantwortlich ist. Das System schaut in deren Bundle nach, ob dort überhaupt eine Begründung für den Kalenderzugriff hinterlegt ist.
Ein KI-Client wie Claude Desktop hat so eine Begründung nicht. Also lehnt macOS alles ab, was er startet, und zwar lautlos. Kein Dialog, kein Eintrag in den Systemeinstellungen, kein Schalter zum Umlegen. Dasselbe Binary kam aus dem Terminal aufgerufen problemlos an alle sechs Kalender.
Andere Server lösen das mit einer Anleitung: Starte deinen KI-Client aus dem Terminal, dann erbt er dessen Rechte. Funktioniert, ist aber nichts, was man jemandem zumuten will.
Der Helper hier macht stattdessen etwas anderes. Beim Start, bevor er EventKit anfasst, wirft er die geerbte Verantwortung weg und startet sich selbst neu:
posix_spawnattr_setflags(&attributes, Int16(POSIX_SPAWN_SETEXEC))
responsibility_spawnattrs_setdisclaim(&attributes, 1)
posix_spawn(&pid, executable, nil, &attributes, &arguments, &environment)Danach ist er seine eigene Berechtigungs-Identität. macOS liest die Begründung aus seinem Bundle und fragt unter seinem eigenen Namen — deshalb steht im Dialog „calendar-helper“ und nicht der Name deines KI-Clients. Dieselbe System-Funktion nutzt übrigens auch Chromium, um seine Hilfsprozesse selbstverantwortlich zu machen.
Am meisten gelernt habe ich beim Nachmessen. Ein byte-identisches Binary an einem anderen Pfad löst einen komplett neuen Berechtigungsdialog aus und bekommt einen eigenen Eintrag in den Systemeinstellungen. Der Inhalt der Datei ist dagegen egal — dasselbe Binary habe ich nach der Freigabe zweimal neu gebaut, und es lief weiter.
Daraus folgt alles Weitere: Der Server kopiert den Helper an einen festen Pfad unter ~/Library/Application Support/ und startet ihn von dort. Läge er dort, wo npm ihn ablegt, würde jedes Update den Pfad ändern und einen neuen Dialog auslösen. So überlebt die einmal erteilte Freigabe jedes Update.
Ganz ohne Handarbeit geht es trotzdem nicht. Beim ersten Aufruf erscheint der Dialog, und danach muss man in den Systemeinstellungen noch von „Nur hinzufügen“ auf „Vollzugriff“ umstellen. Diesen einen Schritt kann kein Paketformat abnehmen.
Was kein Test gefunden hätte
Ein externes Code-Review hat einen Fehler gefunden, über den ich immer noch nachdenke. Beim Anlegen ganztägiger Termine landete das Enddatum auf Mitternacht — und EventKit liest Mitternacht als Ende des Vortags. Mehrtägige ganztägige Termine verloren also stillschweigend ihren letzten Tag.
Kein Test, kein Typecheck und kein CI-Lauf hätte das gefunden, weil keiner davon EventKit anfasst. Die Testabdeckung des Projekts liegt in der TypeScript-Logik, die ohne Kalender und ohne erteilte Berechtigung läuft. Der Swift-Teil hat keine automatisierten Tests — er bräuchte einen echten Kalender und eine echte Freigabe, und beides hat eine CI nicht.
Fazit
Angefangen hat das Projekt mit einer Frage an meinen Kalender. Der größere Teil der Arbeit steckte am Ende woanders: in einer Eigenheit von macOS, die kaum jemand dokumentiert, weil sie normalerweise auch niemanden stört. Was am Anfang wie ein Wochenendprojekt aussah, wurde vor allem eine Lehrstunde darüber, wie das System Rechte vergibt.
Der Code liegt auf GitHub (öffnet in neuem Tab), das Paket auf npm (öffnet in neuem Tab), die Lizenz ist MIT. Alles läuft lokal — es gibt in dem Projekt keinen einzigen Netzwerkaufruf.
Was ich mitgenommen habe:
- „Was steht drin“ ist nützlich, „wann ist frei“ beantwortet aber kaum jemand. Im Alltag brauche ich beides.
- Plattform-Eigenheiten kosten mehr Zeit als Features. Bei macOS-Berechtigungen half am Ende nur Nachmessen, nicht Nachlesen.
- Ein Blick auf die Konkurrenz vor dem ersten Commit hat mir mehr Code erspart als jedes Refactoring danach.
- Dort, wo Code auf ein System trifft, hört die Testautomatisierung auf. Das ist eine Grenze, keine Nachlässigkeit.
Teilen
Verwandte Artikel
Code-Reviews mit KI: Wie CodeRabbit in meinen Entwickler-Alltag passt
Code-Reviews als Solo-Entwickler? CodeRabbit gibt mir den zweiten Blick — im Terminal kostenlos, auf GitLab und GitHub mit Pro-Plan. Ein Erfahrungsbericht über KI-gestützte Code-Reviews im Alltag.
AgentKit: Warum ich meine Claude Code Tools als Plugin-Marketplace neu gebaut habe
Vom eigenen Shell-Toolkit zum offiziellen Plugin-System: Wie ich mein Claude Code Toolkit als Plugin-Marketplace neu aufgebaut habe — und warum sich der Umstieg gelohnt hat.