che-ical-mcp
Servidor MCP nativo de Calendario y Recordatorios de macOS mediante Apple EventKit: eventos, recordatorios, recurrencia, activadores de ubicación, verificación de conflictos y operaciones por lotes en iCloud/Google/Exchange.
Documentación
che-ical-mcp
Dale a Claude control nativo de Calendario y Recordatorios de macOS. Un servidor MCP en Swift construido directamente sobre EventKit: 29 herramientas para eventos, recordatorios, etiquetas, operaciones por lotes, detección de conflictos y deshacer/rehacer. No solo eventos de calendario: también controla Recordatorios y tareas.
Instalación
Claude Code — registra este repositorio como marketplace y luego instala el plugin. El plugin incluye los comandos de barra /today, /week, /quick-event, /remind y un hook PreToolUse que verifica el día de la semana en cada escritura de evento:
claude plugin marketplace add PsychQuant/che-ical-mcp
claude plugin install che-ical-mcp@che-ical-mcp
Claude Desktop — descarga el .mcpb más reciente desde Releases y haz doble clic para instalarlo.
MCP independiente — el servidor de 29 herramientas por sí solo, sin extras del plugin:
mkdir -p ~/bin
curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP && chmod +x ~/bin/CheICalMCP
claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP
En el primer uso, macOS solicitará acceso a Calendario y Recordatorios — haz clic en Permitir. ¿Compilando desde el código fuente, actualizando en el lugar, o ejecutando bajo SSH / launchd / VS Code? Consulta Instalación para la guía completa.
¿Por qué che-ical-mcp?
| Característica | Otros MCP de Calendario | che-ical-mcp |
|---|---|---|
| Eventos de Calendario | Sí | Sí |
| Recordatorios/Tareas | No | Sí |
| Etiquetas # de Recordatorios | No | Sí (a nivel MCP) |
| Búsqueda Multi-palabra clave | No | Sí |
| Detección de Duplicados | No | Sí |
| Detección de Conflictos | No | Sí |
| Operaciones por Lotes | No | Sí |
| Zona Horaria Local | No | Sí |
| Desambiguación de Fuente | No | Sí |
| Crear Calendario | Algunos | Sí |
| Eliminar Calendario | Algunos | Sí |
| Recordatorios de Eventos | Algunos | Sí |
| Ubicación y URL | Algunos | Sí |
| Lenguaje | Python | Swift (Nativo) |
Las 29 Herramientas
Calendarios (4)
| Herramienta | Descripción |
|---|---|
list_calendars | Lista todos los calendarios y listas de recordatorios (incluye source_type) |
create_calendar | Crea un nuevo calendario |
delete_calendar | Elimina un calendario |
update_calendar | Renombra un calendario o cambia su color (v0.9.0) |
Eventos (4)
| Herramienta | Descripción |
|---|---|
list_events | Lista eventos con filtro/orden/límite (v1.0.0) |
create_event | Crea un evento (con recordatorios, ubicación, URL, zona horaria por evento, recurrencia con fechas excluidas) |
update_event | Actualiza un evento (incluyendo zona horaria, recurrencia, alcance para recurrentes) |
delete_event | Elimina un evento (con soporte de ocurrencias para recurrentes) |
Exclusiones de recurrencia (#182):
create_event(y cada elemento decreate_events_batch) aceptarecurrence.excluded_occurrence_datespara omitir fechas específicas en una serie recurrente al momento de la creación — aplicado con semántica de mejor esfuerzo todo-o-nada (cualquier fallo elimina toda la nueva serie mediante eliminación compensatoria; el fallo de reversión se informa, nunca es silencioso; la primera ocurrencia no puede excluirse), y unundoelimina toda la serie incluyendo exclusiones. Limitación conocida: en reintento idempotente, una serie recurrente duplicada con cada fecha solicitada ya ausente esskipped, pero las exclusiones adicionales ya presentes en la serie existente (más allá de las solicitadas) no se detectan.
Recordatorios (8)
| Herramienta | Descripción |
|---|---|
list_reminders | Lista recordatorios con filtro/orden/límite, extracción de etiquetas (v1.0.0) |
create_reminder | Crea un recordatorio con fecha de vencimiento, etiquetas (v1.3.0) |
update_reminder | Actualiza un recordatorio (incluyendo etiquetas, clear_due_date) (v1.3.0) |
complete_reminder | Marca como completado/incompleto |
delete_reminder | Elimina un recordatorio |
search_reminders | Busca recordatorios por palabra(s) clave o etiqueta (v1.3.0) |
list_reminder_tags | Lista todas las etiquetas únicas con conteos de uso (v1.3.0) |
cleanup_completed_reminders | Elimina todos los recordatorios completados en una sola llamada, vista previa dry_run por defecto (v1.7.2) |
Recordatorios recurrentes (#194): listar/buscar incluye has_recurrence, recurrence_rules público completo, y due consciente de precisión. La finalización añade operation (resultado de escritura) y next_occurrence (confirmado/desconocido/no_aplica). Usa operation.status en lugar del heredado is_completed: una finalización exitosa puede dejar la siguiente ocurrencia incompleta. La información desconocida del sucesor no debe desencadenar una segunda escritura. Un sucesor con ID diferente o no confirmable se informa como unknown, nunca como si la serie hubiera terminado. Deshacer una finalización recurrente está protegido por identidad (#204): una vez que el identificador ya no resuelve a la ocurrencia registrada, se niega explícitamente y descarta su entrada de historial en lugar de saturar la pila. Cambio importante (#205): completed debe ser un booleano JSON en complete_reminder / list_reminders / search_reminders — las cadenas y números se rechazan; omitir o null mantiene el significado anterior. Cambio importante (#207, no publicado): el mismo contrato ahora aplica a cada argumento booleano de herramienta (all_day, clear_*, include_completed, dry_run, delete_original). Consulta contrato de respuesta y limitaciones.
Características Avanzadas (10) ✨ Nuevo en v0.3.0+
| Herramienta | Descripción |
|---|---|
search_events | Busca eventos por palabra(s) clave con coincidencia AND/OR |
list_events_quick | Atajos rápidos: today, tomorrow, this_week, next_7_days, etc. |
create_events_batch | Crea múltiples eventos a la vez (con zona horaria por evento) |
check_conflicts | Verifica eventos superpuestos en un rango de tiempo |
copy_event | Copia un evento a otro calendario (con movimiento opcional) |
move_events_batch | Mueve múltiples eventos a otro calendario |
delete_events_batch | Elimina eventos por IDs o rango de fechas, con vista previa dry-run (v1.0.0) |
find_duplicate_events | Encuentra eventos duplicados entre calendarios (v0.5.0) |
create_reminders_batch | Crea múltiples recordatorios a la vez (v0.9.0) |
delete_reminders_batch | Elimina múltiples recordatorios a la vez (v0.9.0) |
Deshacer/Rehacer (3) ✨ Nuevo en v1.4.0
| Herramienta | Descripción |
|---|---|
undo | Deshace la operación más reciente de calendario/recordatorio |
redo | Rehace la última operación deshecha |
undo_history | Lista operaciones deshacibles con marcas de tiempo |
Instalación
Las rutas rápidas están en la parte superior de este README. Esta es la referencia completa: configuración manual, compilación desde el código fuente, casos límite de permisos, actualizaciones en el lugar y modo CLI.
Requisitos
- macOS 14.0+ (Sonoma o posterior — requerido desde v1.11.0 para la API completa de permisos TCC)
- Xcode Command Line Tools (solo si compilas desde el código fuente)
Claude Desktop
Un clic (recomendado): descarga el che-ical-mcp-<version>.mcpb más reciente desde Releases, haz doble clic y reinicia Claude Desktop.
Configuración manual: descarga el binario y luego apunta claude_desktop_config.json hacia él.
curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o /usr/local/bin/che-ical-mcp
chmod +x /usr/local/bin/che-ical-mcp
Edita ~/Library/Application Support/Claude/claude_desktop_config.json y luego reinicia Claude Desktop:
{
"mcpServers": {
"che-ical-mcp": {
"command": "/usr/local/bin/che-ical-mcp"
}
}
}
Claude Code — plugin (recomendado)
claude plugin marketplace add PsychQuant/che-ical-mcp
claude plugin install che-ical-mcp@che-ical-mcp
- Dentro de Claude Code, los equivalentes de barra
/plugin marketplace add PsychQuant/che-ical-mcpy/plugin install che-ical-mcp@che-ical-mcpfuncionan de la misma manera. - Añade el marketplace a través de su repositorio Git (
owner/repo), no una URLmarketplace.jsoncruda — elsourcedel plugin es una ruta relativa del mismo repositorio (./plugin) que solo se resuelve cuando se añade vía Git. - También incluido en el agregador
psychquant-claude-plugins(claude plugin install che-ical-mcp@psychquant-claude-plugins); ambos sirven el mismo binario versionado. - El wrapper descarga automáticamente el binario a
~/bin/CheICalMCPen el primer uso si aún no está allí.
Claude Code — MCP independiente
mkdir -p ~/bin
# If upgrading, remove the old binary first. On macOS 26 the kernel can kill a
# fresh binary that inherits a stale code-signature cache from the old inode —
# one a running MCP process may still be holding open.
rm -f ~/bin/CheICalMCP
curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP
chmod +x ~/bin/CheICalMCP
# --scope user: available in all projects · --transport stdio: local stdin/stdout
claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP
💡 Consejo: Mantén el binario en un directorio local como
~/bin/. Las carpetas sincronizadas en la nube (Dropbox, iCloud, OneDrive) pueden provocar tiempos de espera de conexión MCP cuando la sincronización toca el archivo.
Compilar desde el Código Fuente (opcional)
git clone https://github.com/PsychQuant/che-ical-mcp.git
cd che-ical-mcp
make release && make install
claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP
⚠️ Usuarios de Swift 6 / Xcode 18: No ejecutes
swift builddirectamente — el SDK MCP upstream tiene un error de concurrencia (swift-sdk#214). El Makefile lo detecta automáticamente y recurre al modo de lenguaje Swift 5.
Otorgar Permisos
En el primer uso, macOS solicitará acceso a Calendario y Recordatorios. Haz clic en Permitir para ambos.
⚠️ Nota de macOS Sequoia (15.x): El diálogo de permisos se atribuye a la aplicación principal que lanzó el servidor MCP, no al binario en sí. Esto significa:
Entorno Permiso Atribuido A Claude Desktop Claude Desktop.app ✅ (funciona automáticamente) Claude Code en Terminal.app Terminal.app ✅ (funciona automáticamente) Claude Code en VS Code VS Code ❌ (puede no mostrar el diálogo) Claude Code en iTerm2 iTerm2 ✅ (funciona automáticamente) Si el diálogo de permisos no aparece (común con VS Code), necesitas añadir
NSCalendarsFullAccessUsageDescriptional Info.plist de VS Code:# Añade la descripción de uso de calendario a VS Code /usr/libexec/PlistBuddy -c "Add :NSCalendarsFullAccessUsageDescription string 'VS Code needs calendar access for MCP extensions.'" \ "/Applications/Visual Studio Code.app/Contents/Info.plist" /usr/libexec/PlistBuddy -c "Add :NSRemindersFullAccessUsageDescription string 'VS Code needs reminders access for MCP extensions.'" \ "/Applications/Visual Studio Code.app/Contents/Info.plist" # Vuelve a firmar VS Code (requerido después de modificar Info.plist) codesign -s - -f --deep "/Applications/Visual Studio Code.app" # Reinicia VS Code, luego aparecerá el diálogo de permisosNota: Esta modificación se sobrescribirá cuando VS Code se actualice. Deberás volver a aplicarla después de cada actualización de VS Code.
Actualizar una instalación existente
El wrapper del plugin descarga automáticamente en instalaciones nuevas pero no reemplaza un binario existente. Para actualizar en el lugar:
~/bin/CheICalMCP --self-update
Esto consulta GitHub Releases para la última etiqueta, descarga el nuevo binario y reemplaza atómicamente el actual. Si se está ejecutando como servidor MCP, reinicia tu host MCP (Claude Desktop / Claude Code) después para recoger la nueva versión. Respaldo manual: rm -f ~/bin/CheICalMCP && curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP && chmod +x ~/bin/CheICalMCP.
Modo CLI (sin servidor MCP)
Las 29 herramientas se pueden invocar directamente desde la línea de comandos, sin necesidad de servidor MCP:
# Flag-based: --key value pairs
CheICalMCP --cli list_events --start_date 2026-03-29 --end_date 2026-03-30
# JSON via stdin
echo '{"tool":"list_calendars","arguments":{}}' | CheICalMCP --cli
# From Claude Code via shell
claude -p "Run: ~/bin/CheICalMCP --cli list_events_quick --range today"
Útil para trabajos launchd, scripts de shell, pipelines de CI y agentes que prefieren un subproceso sobre el protocolo MCP. Los permisos TCC siguen aplicándose — ejecuta CheICalMCP --setup primero si es necesario.
Características de v1.0.0
Análisis Flexible de Fechas
Todos los parámetros de fecha ahora aceptan 4 formatos:
| Formato | Ejemplo | Interpretación |
|---|---|---|
| ISO8601 completo | "2026-02-06T14:00:00+08:00" | Fecha y hora exactas (desplazamiento preservado) |
| Sin zona horaria | "2026-02-06T14:00:00" | Usa el timezone del evento si se proporciona, de lo contrario la zona horaria del sistema |
| Solo fecha | "2026-02-06" | Medianoche en el timezone del evento o zona horaria del sistema |
| Solo hora | "14:00" | Hoy a esa hora |
Zona Horaria por Evento (v1.5.0)
Establece la zona horaria de visualización para eventos individuales — esencial para itinerarios de viaje con múltiples zonas horarias.
"Create a flight departure at 09:14 Berlin time"
→ create_event(title: "Flight LH123", start_time: "2026-04-08T09:14:00", timezone: "Europe/Berlin", ...)
"Update the hotel check-in to Dubai time"
→ update_event(event_id: "...", timezone: "Asia/Dubai")
"Remove the custom timezone from an event"
→ update_event(event_id: "...", clear_timezone: true)
- El parámetro
timezoneacepta identificadores IANA (p. ej.,Europe/Berlin,America/New_York,Asia/Taipei) - Cuando se proporciona
timezone, las fechas/horas ingenuas (sin desplazamiento) se interpretan en esa zona horaria - La salida del evento incluye la zona horaria propia del evento en el campo
timezoney formateastart_date_local/end_date_localen consecuencia - Disponible en
create_event,update_eventycreate_events_batch - Deshacer/rehacer conserva la zona horaria de cada evento
Asistentes y Organizador (Solo Lectura)
Las respuestas de eventos incluyen información de participantes cuando está disponible. Estos campos son de solo lectura debido a limitaciones de EventKit: no se pueden establecer ni modificar a través del MCP.
Disponible en: list_events, search_events, list_events_quick, check_conflicts
attendees (matriz, opcional) — Presente cuando el evento tiene participantes. Cada objeto de asistente contiene:
| Campo | Tipo | Descripción |
|---|---|---|
name | cadena o nulo | Nombre para mostrar, nulo si no está en la libreta de direcciones |
email | cadena | Dirección de correo electrónico extraída de la URL del participante |
role | cadena | Uno de: unknown, required, optional, chair, non_participant |
status | cadena | Uno de: unknown, pending, accepted, declined, tentative, delegated, completed, in_process |
type | cadena | Uno de: unknown, person, room, resource, group |
is_current_user | booleano | Si este participante es el usuario actual |
organizer (objeto, opcional) — Presente cuando el evento tiene un organizador. Contiene:
| Campo | Tipo | Descripción |
|---|---|---|
name | cadena o nulo | Nombre para mostrar |
email | cadena | Dirección de correo electrónico |
is_current_user | booleano | Si el organizador es el usuario actual |
Nota: Ambos campos se omiten cuando el evento no tiene participantes ni organizador (p. ej., eventos de calendario local creados sin invitados).
Coincidencia Difusa de Calendarios
Los nombres de calendarios ahora se comparan sin distinguir mayúsculas y minúsculas. Si no se encuentra, el mensaje de error enumera todos los calendarios disponibles.
Herramientas Mejoradas de Listado/Eliminación
list_events:filter(todos/pasados/futuros/todo_el_día),sort(asc/desc),limitlist_reminders:filter(todos/incompletos/completados/vencidos),sort(fecha_vencimiento/fecha_creación/prioridad/título),limitdelete_events_batch: modo de rango de fechas (before_date/after_date) + vista previa dedry_run
Cambio Importante:
list_eventsylist_remindersahora devuelven{events/reminders: [...], metadata: {...}}en lugar de una matriz simple.
Ejemplos de Uso
Gestión de Calendarios
"List all my calendars"
"What's on my schedule next week?"
"Create a meeting tomorrow at 2 PM titled 'Team Sync'"
"Add a dentist appointment on Friday at 10 AM with location '123 Main St'"
"Delete the meeting called 'Cancelled Meeting'"
Gestión de Recordatorios
"List my incomplete reminders"
"Show all reminders in my Shopping list"
"Add a reminder: Buy milk"
"Create a reminder to call mom tomorrow at 5 PM"
"Mark 'Buy milk' as completed"
"Delete the reminder about groceries"
Gestión de Recordatorios (v1.5.0)
"Remove the due date from 'Buy groceries'"
→ update_reminder(reminder_id: "...", clear_due_date: true)
Funciones Avanzadas (v0.3.0+)
"Search for events containing 'meeting'"
"Search for events with both 'project' AND 'review'"
"What do I have today?"
"Show me this week's schedule"
"Are there any conflicts if I schedule a meeting from 2-3 PM?"
"Create 3 weekly team meetings for the next 3 weeks"
"Copy the dentist appointment to my Work calendar"
"Move all events from 'Old Calendar' to 'New Calendar'"
"Delete all the cancelled events"
"Find duplicate events between 'IDOL' and 'Idol' calendars"
Mejoras de DX (v1.0.0)
"Show my next 5 upcoming events"
→ list_events(start_date: "2026-02-06", end_date: "2026-12-31", filter: "future", sort: "asc", limit: 5)
"Show my overdue reminders"
→ list_reminders(filter: "overdue")
"Preview which events would be deleted from 'Old Calendar' before 2025"
→ delete_events_batch(calendar_name: "Old Calendar", before_date: "2025-01-01", dry_run: true)
"Create an event at 2 PM" (no need for full ISO8601!)
→ create_event(start_time: "14:00", end_time: "15:00", ...)
Fuentes de Calendario Compatibles
Funciona con cualquier calendario sincronizado con la app Calendario de macOS:
- Calendario de iCloud
- Calendario de Google
- Microsoft Outlook/Exchange
- Calendarios CalDAV
- Calendarios locales
Desambiguación de Calendarios con el Mismo Nombre (v0.6.0+)
Si tienes calendarios con el mismo nombre de diferentes fuentes (p. ej., "Trabajo" tanto en iCloud como en Google), usa el parámetro calendar_source:
"Create an event in my iCloud Work calendar"
→ create_event(calendar_name: "Work", calendar_source: "iCloud", ...)
"Show events from my Google Work calendar"
→ list_events(calendar_name: "Work", calendar_source: "Google", ...)
Si se detecta ambigüedad, el mensaje de error enumerará todas las fuentes disponibles.
Solución de Problemas
| Problema | Solución |
|---|---|
| Servidor desconectado | Reconstruir con make release && make install |
| Permiso denegado | Otorgar acceso a Calendario/Recordatorios en Configuración del Sistema > Privacidad y Seguridad |
| El diálogo de permisos nunca aparece | Ver Otorgar Permisos para la solución en macOS Sequoia |
| Permiso denegado por SSH | Ver Acceso SSH a continuación |
| Permiso denegado bajo launchd | Ver launchd / Automatización a continuación |
| Un servicio denegado mientras todos los diagnósticos reportan verde | Ver Denegación permanente silenciosa después de la actualización a continuación |
| Calendario/Recordatorios se rompen de nuevo después de cada actualización de Claude Code | Ver Las actualizaciones de Claude Code rotan la concesión del lado del host a continuación |
| Calendario no encontrado | Asegúrate de que el calendario sea visible en la app Calendario de macOS |
| Recordatorios no se sincronizan | Verifica la sincronización de iCloud en Configuración del Sistema |
Denegación permanente silenciosa después de la actualización (#154)
Si un servicio (típicamente Calendario) devuelve access denied mientras el otro funciona, y --print-tcc-path y Configuración del Sistema reportan el permiso como otorgado, probablemente estás enfrentando la firma #154: una fila TCC creada por una compilación anterior a v1.7.1 (firmada ad-hoc) está anclada a los hashes de código de esa compilación antigua. El binario actualizado con Developer ID nunca puede coincidir, y en macOS 26.5+ el sistema operativo solo permite el re-prompt de curación cuando el binario lleva el entitlement com.apple.security.personal-information.* correspondiente.
A partir de v1.14.0+, el banner de inicio muestra esto directamente — una línea [drift] TCC.db <service> entry pins a code requirement this binary no longer satisfies (#155) — cuando la verificación del framework de Security puede confirmar el desajuste de csreq. Antes de eso, todos los diagnósticos de la API de estado (incluido el banner) reportaban verde, que es exactamente lo que hacía que esta clase fuera silenciosa. Si encuentras la denegación a través de la instalación de .mcpb de Claude Desktop, el mensaje de denegación ahora nombra el bloqueador real y las rutas funcionales en lugar del callejón sin salida --setup (#158).
Solución: actualiza a v1.11.0 o posterior (el binario ahora incluye ambos entitlements), reinicia la app host (Cmd+Q completo para Claude Desktop) y aprueba el diálogo de permisos que aparece en el primer acceso a Calendario/Recordatorios. Aprobar reescribe la fila TCC anclada al requisito de Developer ID, por lo que sobrevive a todas las actualizaciones futuras. Si accidentalmente deniegas el diálogo, vuelve a habilitar el interruptor correspondiente en Configuración del Sistema → Privacidad y Seguridad → Calendarios o Recordatorios.
⚠️ Fe de erratas para la solución de la era #108:
tccutil reset Calendar com.checheng.CheICalMCPno funciona para un binario desnudo (sin paquete) — falla conOSStatus error -10814porque el binario no tiene registro de LaunchServices. Y no ejecutes untccutil reset Calendardesnudo (sin un ID de paquete): borra las concesiones de Calendario para todas las apps de la máquina y, en un binario sin entitlements, deja a CheICalMCP permanentemente incapaz de volver a solicitar permisos.
Las actualizaciones de Claude Code rotan la concesión del lado del host (#170)
Bajo una instalación nativa de Claude Code, el ejecutable real reside en una ruta versionada (~/.local/share/claude/versions/<version>; ~/.local/bin/claude es solo un enlace simbólico), y TCC de macOS ancla la concesión de Calendario/Recordatorios del lado del host a esa ruta. Cada actualización automática de Claude Code rota la ruta e invalida silenciosamente la concesión — el síntoma clásico es "funcionaba ayer, roto justo después de una actualización", con Configuración del Sistema acumulando entradas obsoletas con números de versión desnudos (2.1.202, 2.1.203, …).
Solución: dispara cualquier llamada de herramienta de calendario desde Claude Code para que macOS vuelva a solicitar (o re-cree la entrada), luego activa la entrada más nueva con número de versión en Configuración del Sistema → Privacidad y Seguridad → Calendarios / Recordatorios. Lista de verificación completa: la habilidad troubleshoot-tcc (/che-ical-mcp:check-tcc). La causa raíz es ascendente (seguida en #170 — Claude Code necesitaría una identidad TCC estable); este repositorio solo puede detectarla y documentarla.
Acceso SSH
TCC de macOS (Transparencia, Consentimiento y Control) otorga permisos de privacidad por aplicación. Las sesiones SSH se ejecutan bajo sshd, que es un contexto de seguridad diferente — por lo que los permisos otorgados a Terminal o Claude Code localmente no se transfieren a SSH.
Solución A — Ejecutar localmente primero (recomendado):
- Ejecuta
CheICalMCPuna vez en la Mac de destino localmente (no por SSH) - Otorga acceso a Calendario y Recordatorios cuando aparezca el diálogo TCC
- Las sesiones SSH deberían entonces heredar la concesión para el binario
CheICalMCP
Solución B — Otorgar Acceso Total al Disco a sshd:
- Abre Configuración del Sistema → Privacidad y Seguridad → Acceso Total al Disco
- Haz clic en +, presiona ⌘⇧G, escribe
/usr/sbin/sshdy agrégalo - Reinicia la sesión SSH
⚠️ La Solución B otorga a
sshdacceso amplio a archivos — úsala solo en máquinas que controlas completamente.
launchd / Automatización
Cuando ejecutas CheICalMCP desde launchd, cron u otra automatización no interactiva, TCC de macOS no puede mostrar diálogos de permisos. Usa --setup para pre-otorgar permisos:
# Step 1: Run once from Terminal (triggers TCC permission dialog)
CheICalMCP --setup
# Step 2: Grant Calendar & Reminders access in the dialog that appears
# Step 3: The binary now has permission — launchd jobs can use it
Detección: CheICalMCP detecta automáticamente sesiones no interactivas (falta la variable de entorno
TERMo hijo directo de launchd) y proporciona mensajes de error específicos con instrucciones de--setup. Esto funciona incluso para cadenas de lanzamiento indirectas (launchd → Claude Code → CheICalMCP).
--setupen sesiones no interactivas (#143): si ejecutas--setupdesde una sesión no interactiva (sinTERM/ hijo directo de launchd) y el permiso aún no está determinado,--setupahora omite la solicitud y sale con código distinto de cero en lugar de colgarse — un diálogo TCC no puede aparecer allí, así que imprime instrucciones de concesión manual en lugar de bloquear. Ejecuta--setupdesde una Terminal real para activar el diálogo. (Un binario ya autorizado aún reporta éxito incluso si se vuelve a ejecutar de forma no interactiva.)Nota: Si
--setupotorga permiso pero el MCP aún falla bajo launchd, TCC puede haber asociado el permiso con el proceso padre. En ese caso, agrega manualmente CheICalMCP en Configuración del Sistema → Privacidad y Seguridad → Calendario/Recordatorios.
Detalles Técnicos
- Versión Actual: v1.15.0
- Framework: MCP Swift SDK v0.12.0
- API de Calendario: EventKit (framework nativo de macOS)
- Transporte: stdio
- Plataforma: macOS 14.0+ (Sonoma y posteriores — elevado desde 13.0 en el clúster posterior a 1.10 según #119)
- Herramientas: 29 herramientas para calendarios, eventos, recordatorios, etiquetas, deshacer/rehacer, limpieza y operaciones avanzadas
Historial de Versiones
| Versión | Cambios |
|---|---|
| v1.18.0 | Deshacer/rehacer completo para recordatorios, discard_id explícito, booleanos estrictos en todas partes (#196/#197/#198/#199/#202/#203/#206/#207/#208/#209/#211/#212/#214/#215/#216): deshacer escribe de vuelta el completion_date registrado y rehacer restaura el instante guardado en lugar de reinferir el estado; la finalización repetida conserva el instante original; undo(discard_id) elimina explícitamente un registro de cabecera bloqueado mediante su ID undo_history estable (los objetivos no encontrados ya no bloquean la pila para recordatorios eliminados o eventos muertos); los movimientos de eventos registran la ocurrencia de origen para que deshacer restaure en el calendario original; event_recurrence_rules / reminder_recurrence_rules distinguen los dos formatos (los alias heredados se conservan); cada anotación de herramienta tiene una política probada explícita, con complete_reminder, las herramientas de actualización y movimiento opcional ahora son destructiveHint: true. ROMPIENTE: cada argumento booleano de herramienta es un booleano JSON estricto (las cadenas/números se rechazan con <key> must be a boolean). Internos: lista de recordatorios/búsqueda/filtro/orden/límite dentro del actor antes de la instantánea, list_reminder_tags en la costura de la instantánea, escaneo de etiquetas lineal (sin retroceso exponencial), resultados de escritura de recordatorios Sendable inmutables, métodos de finalización/historial recurrente en una extensión de actor dedicada. |
| v1.17.0 | Recurrencia de recordatorios en lectura, resultados de finalización explícitos, deshacer protegido por identidad, completed booleano estricto (#194/#204/#205): list_reminders / search_reminders exponen has_recurrence, recurrence_rules completo (incl. frequency_raw_value) y un objeto due; complete_reminder separa el resultado de escritura (operation) del objeto guardado (observed) e informa el sucesor como next_occurrence — observado una vez, sincrónicamente, después de guardar (iCloud en el dispositivo: el mismo ID avanza en el lugar, la ocurrencia finalizada se archiva bajo un nuevo ID), mensaje en el reloj de pared del propio recordatorio. Deshacer/rehacer de una finalización recurrente está protegido por identidad: una vez que el ID ya no se resuelve a la ocurrencia registrada, se niega explícitamente y descarta la entrada para que las operaciones más antiguas sigan siendo deshacibles; no encontrado permanece transitorio (#191). ROMPIENTE: completed debe ser un booleano JSON en las tres herramientas de recordatorios (las cadenas/números se rechazan antes de cualquier lectura o escritura; omitir/null conservan su significado; --cli JSON null → omitido). Tres rondas de verificación 6-AI en el PR #195 más rondas en #200 / #201; dos sondas stdio en el dispositivo. 582 pruebas. |
| v1.16.1 | Correcciones de seguridad de tipos + verificación en el dispositivo (#184/#190/#191): recurrence no objeto ahora se rechaza (antes se descartaba silenciosamente); el emparejamiento all_day + timezone se rechaza (antes eliminaba silenciosamente la bandera de día completo y anulaba las exclusiones a través de la línea de fecha); deshacer de una eliminación de serie recurrente reconstruye reglas a partir de instantáneas de valor (corrige EKCADErrorDomain 1010) y un deshacer/rehacer fallido ya no consume la entrada. 529 pruebas. |
| v1.16.0 | Exclusiones de recurrencia + completitud de deshacer + habilidad de archivo de eventos (#182/#185/#180): excluded_occurrence_dates en create_event/batch (creación-eliminación de dos pasadas, eliminación compensatoria, primera ocurrencia no excluible); las eliminaciones por lotes/series ahora registran entradas de deshacer (una unidad .batch); deshacer de una creación recurrente ahora elimina toda la serie; nueva habilidad archive-event — archivo de origen a evento con seguimiento de correcciones y configuración de proyecto .claude/.ical/. 514 pruebas. |
| v1.15.0 | Contexto de ejecución --print-tcc-path + señal de banner de host versionado. El diagnóstico TCC ahora imprime su cadena de procesos padre (self → … → launchd) con una advertencia de dependencia de contexto — el estado de autorización sigue el contexto del proceso responsable (#168), por lo que saber bajo qué host se ejecutó la consulta es fundamental (#169). Pulido de diagnósticos de cadena padre: marcadores visibles de truncamiento/ciclo, enlace comm vacío, ps -ww, informes de fallos de decodificación/salida, precisión de NOTA (#173). Nueva señal de detector de deriva: "host Claude Code versionado + EventKit no concedido" — explica la rotura de actualización #170 proactivamente al inicio, suprimiendo la pista contradictoria --setup en ese escenario (#175). Los tres verificados por conjuntos de modelos cruzados 6-AI; una brecha de escape de terminal CWE-150 encontrada por verificación se corrigió antes de la fusión. La deriva de versión de la versión del plugin v1.14.2 se alineó en los cinco sitios de versión (#172). 490 pruebas. |
| v1.14.2 | Lanzamiento de capa de documentación/habilidad — modelo de autorización TCC de dos capas (#168): la habilidad troubleshoot-tcc, /check-tcc, mcpb/README.md y plugin/CLAUDE.md ahora documentan la capa TCC de la aplicación host (proceso responsable), las entradas de número de versión simple de Configuración del Sistema (2.1.202 = binario versionado de Claude Code) y el procedimiento de verificación de alternar y observar. Binario byte-idéntico a v1.14.1 en el momento del lanzamiento (solo shell de plugin; los sitios de versión de código fuente alineados más tarde en #172). |
| v1.14.1 | Corrección de metadatos — consistencia del conteo de herramientas. server.json description decía "24 herramientas" y PROMOTION.md decía "20 herramientas"; el servidor realmente expone 29 herramientas (coincidiendo con mcpb/manifest.json long_description y la guardia de paridad de herramientas ManifestParityTests). Corregido el server.json orientado al registro, docs/COMPETITIVE_ANALYSIS.md y PROMOTION.md a 29. Sin cambios de código o superficie de herramientas — funcionalmente idéntico a v1.14.0; este lanzamiento existe únicamente para publicar metadatos de registro corregidos (las versiones de registro son inmutables). |
| v1.14.0 | Corrección de caída de inyección de herramientas de Claude Desktop (#166): un & literal en mcpb/manifest.json display_name hizo que Desktop 1.18286.0 descartara silenciosamente todo el servidor de 29 herramientas de cada conversación (Claude Code no afectado); cambiado & → and, confirmado por intervención de variable única en la instalación fallida + una guardia de regresión ManifestParityTests. También alineado serverInfo.name al id de manifiesto kebab (higiene; refutado empíricamente como la causa). Lote hermano #154: señal de deriva TCC de discrepancia csreq (#155, autocomprobación SecCodeCheckValidity para la clase de denegación silenciosa), mensaje de denegación .mcpb ya no termina en callejón sin salida en --setup para la firma ya .denied (#158), insignia macOS 13.0 → 14.0 (#157), swift-nio 2.96 → 2.101 (#159). 454 pruebas. |
| v1.13.0 | SwiftUI SetupWindow (#164): --setup interactivo presenta una ventana de estado en vivo (botones de Concesión por entidad + ruta de binario resuelta) dentro del NSApplication de primer plano #163. Corrección de denegación de Calendario en Desktop (#165): isNonInteractive se disparó erróneamente en TERM == nil para servidores generados por aplicaciones GUI → falló rápido antes de requestFullAccess, por lo que el diálogo de primera concesión nunca apareció a través de Claude Desktop; ahora usa una señal de sesión GUI CGSession. 429 pruebas. |
| v1.12.0 | --setup de primer plano (#163): --setup interactivo ahora se ejecuta dentro de un NSApplication de primer plano para que el modal TCC de Calendario de EventKit realmente se presente (antes se denegaba silenciosamente desde un contexto CLI asíncrono simple). Los mensajes de denegación + banner de inicio muestran la ruta del binario resuelto + un comando "<path>" --setup copiable para el binario .mcpb enterrado. |
| v1.11.1 | Validación de rango de tiempo create_event (#160): simétrica con update_event — rechaza eventos temporizados invertidos / de duración cero mediante una guardia compartida validateTimeRange. 405 pruebas. |
| v1.11.0 | Re-prompt de curación TCC desbloqueado (#154): Entitlements.plist incluye personal-information.calendars + .reminders — las instalaciones de larga duración anteriores a v1.7.1 podían sufrir denegación permanente silenciosa de Calendario en macOS 26.5 (fila TCC fijada a cdhashes antiguos, re-prompt de curación bloqueado por política porque el binario no incluía entitlements, todos los diagnósticos reportando verde); puerta de lanzamiento de binario firmado verifica ambas claves. Endurecimiento de EventKit no interactivo (#131 / #143 / #144 + #146–#150). ROMPIENTE: piso de implementación elevado a macOS 14.0 (#119). 401 pruebas. |
| v1.10.0 | Detector de deriva TCC + banner de inicio (#122): banner stderr de un solo disparo al inicio del servidor MCP con versión/ruta/PID + señales de deriva (discrepancia de ruta TCC.db por servicio, procesos obsoletos); exclusión voluntaria mediante CHE_ICAL_MCP_NO_BANNER=1. Corrección de interbloqueo de tubería en ayudantes de subprocesos; defensa de inyección stderr CWE-117 en todos los valores de banner interpolados. |
| v1.9.0 | Refactorización de puerta de acceso TCC (#108 Fase 2, cierra #109): eliminado el anti-patrón de caché has*Access de duración de proceso; EKEventStore.authorizationStatus(for:) por llamada mediante nueva costura AuthorizationGate + AuthorizationStatusSource (patrón Apple TN3153) — los cambios de estado aparecen inmediatamente en lugar de fallo silencioso de concesión obsoleta. Añade bandera de diagnóstico --print-tcc-path. |
| v1.8.1 | Documentación: guía de configuración de permisos TCC post-instalación / actualización mcpb/README.md (#108 Fase 1). |
| v1.8.0 | Ola de consistencia de formato de cable + parámetros de forma de respuesta (grupo #101 — 5 problemas cerrados en 3 días, todo Refs #N IDD + verificación de conjunto 6-AI). Parámetros de forma de respuesta de listado de eventos (#47 / #101): detail_level (summary/standard), lista de permitidos fields, display_timezone (IANA estricto), limit (límite 10000) — ajuste de verbosidad LLM. Unificación de envoltura (#102 / #107, formato de cable rompiente): list_events.metadata.returned + list_reminders.metadata.returned eliminados; las 5 envolturas de lista/búsqueda usan <entity>_count de nivel superior con semántica previa al límite; search_reminders.result_count → reminder_count; search_reminders gana parámetro limit (espejo search_events). Los clientes MCP que leen metadata.returned o result_count deben actualizar. Endurecimiento de validadores (#101 F1–F3): requireOptionalInt usa Int(exactly:) cerrando la trampa DoS Int.max; los validadores detail_level / display_timezone distinguen ausente vs. no cadena (sin coerción silenciosa). Detección de deriva anclada en tiempo de ejecución (#103, fortaleciendo #101 M3): prueba de divergencia formatEventDict ↔ validEventFields ahora mediante costura EventFormattingSource + FakeFormattableEvent. Reclasificación de CHANGELOG (#106): renombramientos de formato de cable movidos de Fixed a Changed (Keep a Changelog 1.1.0). Corrección de tubería de lanzamiento: la verificación de defensa previa al empaquetado ahora deriva el ID de Equipo del certificado DEVELOPER_ID (antes comparaba hash SHA contra cadena Authority= legible por humanos). |
| v1.7.2 | Ola de endurecimiento + características (30+ commits sobre v1.7.1, todo Refs #N IDD con verificación 6-AI). --self-update (#49) + verificación binaria SHA-256 (#98): ruta de actualización de instalación existente con garantía criptográfica contra lanzamientos corruptos. make install-signed (#50): flujo TCC de desarrollador mantenedor en macOS 26 — fallo rápido en ausencia de ID de Desarrollador + verificación forzada de codesign. Flujo de trabajo de pruebas CI (#51): swift build + swift test en tiempo de PR en macos-latest. Grupo de endurecimiento de sanitizadores: cobertura completa C0+DEL escapeForStderr (#73), sanitizeForInterpolation para interpolación de títulos executeUndo/executeRedo (#74), stderr de CLIRunner delegado a writeFailureLog para exención de rama confiable (#80), límite DoS de 1024 caracteres writeFailureLog (#86), documento de contrato controlado solo por autor CLIError.invalidJSON (#85), seguridad de hilos FileHandle.standardError.write + PIPE_BUF=512 de macOS documentado (#70 / #94). Pulido de distribución: fragmentos de instalación de caché de codesign obsoleta obtienen preámbulo rm -f (#90 paridad zh-TW para #62). Pulido posterior a v1.7.1 (#46 #57 #58 #60): paridad de interpolación de errores de rehacer, renumeración de pasos build-mcpb.sh, documentación Entitlements.plist, nota de cwd Makefile release-signed:. Herramienta cleanup_completed_reminders (#21): limpieza de una sola llamada de todos los recordatorios completados, predeterminado dry_run=true. |
| v1.7.1 | Endurecimiento de seguridad (#20 #26): validación de entrada (límites de longitud + lista de permitidos de esquema URL) en todos los puntos de entrada de eventos/recordatorios, envoltura de inyección de prompts en respuestas de lectura MCP, validación de límites de análisis para days_of_week / days_of_month / alarms_minutes_offsets (lanza en lugar de descartar silenciosamente valores inválidos), puesta al día Info.plist, 42 nuevas pruebas de regresión. |
| v1.7.0 | Información de asistente y organizador (#17): array attendees de solo lectura y objeto organizer en respuestas de eventos. Método compartido formatEventDict refactorizado. |
| v1.6.0 | Indicador --setup (#13): preautoriza permisos de TCC para launchd/automatización. Detección de sesión no interactiva (TERM + ppid). Mensajes de error combinados de SSH+launchd. Modo --cli (#14): invoca las 28 herramientas directamente desde la línea de comandos sin servidor MCP. Modos basados en indicador (--key value) y stdin JSON. Inferencia inteligente de tipos para parámetros bool/int/double/array. MCP Swift SDK 0.12.0 (compatibilidad Swift 6.3). |
| v1.5.0 | Zona horaria por evento (#12): parámetro timezone en create_event/update_event/create_events_batch, la salida del evento usa la zona horaria propia del evento, fechas/horas ingenuas analizadas en la zona horaria del evento. Borrar fecha de vencimiento (#9): clear_due_date en update_reminder. Validación de día de semana (#5): create_event/update_event validan el día de semana start_time contra days_of_week. Deshacer/rehacer (#8): 3 nuevas herramientas (undo, redo, undo_history). Correcciones de eventos recurrentes (#7): eliminación/actualización a nivel de ocurrencia con occurrence_date. Compilación Swift 6 (#11): README actualizado para el flujo de trabajo make release |
| v1.4.0 | Fiabilidad LLM: corrige el rango de búsqueda predeterminado (±2 años en lugar de distantPast/Future), metadatos searched_range en la respuesta de search_events, sugerencias similar_events en create_events_batch, consejos LLM en descripciones de herramientas |
| v1.3.1 | Corrección de documentación: aclara que las etiquetas son a nivel de MCP (no etiquetas nativas de Reminders.app); Apple no proporciona API pública para etiquetas nativas |
| v1.3.0 | Etiquetas de recordatorios (nivel MCP): texto #hashtag almacenado en notas para create_reminder/update_reminder/create_reminders_batch, filtrado basado en etiquetas en search_reminders, nueva herramienta list_reminder_tags; MCP SDK 0.11.0. Nota: las etiquetas se pueden buscar vía MCP pero no aparecen como etiquetas nativas de Reminders.app (Apple no proporciona API pública para esto) |
| v1.2.0 | Escrituras idempotentes: create_event, create_events_batch, create_reminder, create_reminders_batch, create_calendar ahora verifican antes de escribir para evitar duplicados al reintentar; las respuestas incluyen el recuento de skipped |
| v1.1.0 | Recurrencia + Ubicación: eventos/recordatorios recurrentes (diario/semanal/mensual/anual), ubicaciones estructuradas con coordenadas, disparadores de recordatorios basados en ubicación (entrada/salida de geocerca), salida de recurrencia enriquecida |
| v1.0.0 | Mejoras de DX: análisis flexible de fechas (4 formatos), coincidencia difusa de calendarios, filtro/orden/límite de list_events/list_reminders, modo de rango de fechas y ejecución en seco de delete_events_batch |
| v0.9.0 | 4 nuevas herramientas (20→24): update_calendar, search_reminders, create_reminders_batch, delete_reminders_batch |
| v0.8.2 | Soporte de semana i18n: parámetro week_starts_on para list_events_quick (lunes/domingo/sábado/sistema) |
| v0.8.1 | Corrección: error de validación de tiempo de update_event, preservación de duración al mover eventos |
| v0.8.0 | RUPTURA: calendar_name ahora es obligatorio para operaciones de creación (sin valores predeterminados implícitos) |
| v0.7.0 | Anotaciones de herramientas para el Directorio de Conectores de Anthropic, mecanismo de actualización automática, descripciones de herramientas por lotes mejoradas |
| v0.6.0 | Desambiguación de fuentes: parámetro calendar_source para calendarios con el mismo nombre |
| v0.5.0 | Eliminación por lotes, detección de duplicados, búsqueda de múltiples palabras clave, errores de permisos mejorados, PRIVACY.md |
| v0.4.0 | Copiar/mover eventos: copy_event, move_events_batch |
| v0.3.0 | Funciones avanzadas: búsqueda, rango rápido, creación por lotes, verificación de conflictos, visualización de zona horaria |
| v0.2.0 | Reescritura en Swift con soporte completo de Reminders |
| v0.1.x | Versión en Python (obsoleta) |
Contribución
¡Las contribuciones son bienvenidas! No dudes en enviar un Pull Request.
Proceso de publicación (para mantenedores)
Los números de versión viven en tres lugares con semánticas diferentes:
| Archivo | Rol | Cuándo actualizar |
|---|---|---|
Sources/CheICalMCP/Version.swift — AppVersion.current | Fuente de verdad; aparece en --version, help y MCP serverInfo.version | Cada publicación |
Sources/CheICalMCP/Info.plist — CFBundleVersion | Versión del paquete macOS | Cada publicación; debe coincidir con AppVersion.current |
mcpb/manifest.json — version | Manifiesto del paquete de Claude Desktop enviado dentro de .mcpb | Cada publicación; debe coincidir con AppVersion.current |
server.json — version + packages[].identifier + fileSha256 | Instantánea de envío al Registro MCP | Solo al reenviar un nuevo .mcpb al Registro MCP (cadencia independiente) |
scripts/build-mcpb.sh garantiza que los primeros tres coincidan; fallará la compilación si alguno se desvía. server.json está intencionalmente desacoplado porque actualizarlo requiere reconstruir .mcpb, un nuevo SHA256 y un reenvío — pasos que no ocurren en cada publicación de código fuente.
Firma y Notarización (requerido para macOS 26+)
A partir de v1.7.1, los binarios de publicación están firmados con un certificado Developer ID Application y notarizados a través de notarytool de Apple. Esto es requerido en macOS 26 — los binarios firmados ad-hoc no pueden activar los diálogos de permisos TCC de Calendar / Reminders allí.
Requisitos previos (configuración única):
- Inscripción en el Programa de Desarrolladores de Apple.
- Certificado Developer ID Application instalado en el llavero de inicio de sesión.
- Verifica con:
security find-identity -p codesigning -v(debe mostrarDeveloper ID Application: <Your Name> (<TeamID>)). - Tu Team ID es propio — encuéntralo en https://developer.apple.com/account → Detalles de Membresía. (El
6W377FS7BSdel mantenedor mostrado en cualquier parte de este repositorio es solo de referencia.)
- Verifica con:
- Perfil de llavero
notarytool(cualquier nombre;che-ical-mcpes el predeterminado que busca el script de compilación).- Crea interactivamente (recomendado — mantiene la contraseña fuera del historial del shell):
xcrun notarytool store-credentials che-ical-mcp --apple-id <your-apple-id> --team-id <your-team-id> # notarytool will prompt for the app-specific password - Contraseña específica de la aplicación: genera en https://account.apple.com → Inicio de Sesión y Seguridad → Contraseñas Específicas de la Aplicación. Usa una contraseña de un solo propósito (por ejemplo, nombrada
che-ical-mcp); revoca y regenera si se filtra. Nunca la pases mediante--passworden la línea de comandos — termina en~/.zsh_history.
- Crea interactivamente (recomendado — mantiene la contraseña fuera del historial del shell):
- Exporta tu identidad para el script de compilación:
Persiste estos enexport DEVELOPER_ID='Developer ID Application: <Your Name> (<TeamID>)' export NOTARY_PROFILE='che-ical-mcp' # match what you set up in step 3~/.zshrco un.envrclocal del proyecto (gitignored). El script intencionalmente no tiene valores predeterminados para estos, para que un fork nuevo no falle con errores que se refieran a la identidad del mantenedor.
Flujo por publicación:
make release-signed # builds universal binary → signs + notarizes → packages .mcpb
gh release create vX.Y.Z mcpb/server/CheICalMCP mcpb/server/CheICalMCP.sha256 mcpb/che-ical-mcp-X.Y.Z.mcpb mcpb/che-ical-mcp-X.Y.Z.mcpb.sha256 --notes "..."
make release-signed ejecuta scripts/build-mcpb.sh, que después de crear el binario universal llama a scripts/sign-and-notarize.sh. El script de firma realiza verificaciones previas (certificado + perfil de notarytool) y falla rápidamente con mensajes amigables si falta algo. La notarización típicamente toma 1–15 minutos (notarytool submit --wait bloquea hasta que Apple termine).
Verificación después de la compilación (ejecuta los tres para confirmar de extremo a extremo):
# 1. Signature properties (cert + hardened runtime + team ID)
codesign -dv --verbose=2 mcpb/server/CheICalMCP
# Expected:
# Authority=Developer ID Application: <Your Name> (<TeamID>)
# TeamIdentifier=<TeamID>
# flags=0x10000(runtime)
# Signature size in the few thousand bytes range (varies by cert chain)
# 2. Signature integrity
codesign --verify --deep --strict --verbose=2 mcpb/server/CheICalMCP
# Expected: exit 0, no warnings
# 3. Notarization end-to-end (this is the real "Gatekeeper would accept" gate)
spctl -a -vvv -t install mcpb/server/CheICalMCP
# Expected: <binary>: accepted; source=Notarized Developer ID
#
# Note on flag choice (verified empirically on macOS 26.4.1, 2026-05-04):
# -t execute → rejected "code is valid but does not seem to be an app"
# (Apple's "execute" type expects a .app bundle structure,
# not raw Mach-O CLI binaries)
# -t install → accepted; source=Notarized Developer ID ← use this
# -t open → rejected "Insufficient Context"
#
# Apple's Code Signing Guide describes -t execute as the assessment type for
# "applications and tools", but on macOS 26 raw Mach-O binaries fall through
# the .app bundle check. -t install is the documented assessment type for
# software being installed (which describes how a CLI binary lands in ~/bin),
# and is the type that returns the actual notarization verdict in practice.
# Re-test if Apple changes this behavior in a future macOS update.
Iteración de desarrollo local sin latencia de firma:
SKIP_CODESIGN=1 ./scripts/build-mcpb.sh # ad-hoc signed; do NOT ship the result
make install # installs ad-hoc to ~/bin (dev only)
El script build-mcpb.sh también omite automáticamente la firma cuando DEVELOPER_ID no está configurado O el certificado no está en tu llavero — para que contribuyentes / CI / forks puedan compilar un .mcpb sin firmar funcional para pruebas sin configurar manualmente SKIP_CODESIGN. (Verás una advertencia clara de "Omitiendo codesign" cuando esto ocurra.)
Entorno de identidad de firma:
| Variable de entorno | Predeterminado | Requerido para |
|---|---|---|
DEVELOPER_ID | (sin configurar — omite firma automáticamente) | Publicación firmada |
NOTARY_PROFILE | (sin configurar — falla rápido en sign-and-notarize.sh) | Publicación firmada |
ENTITLEMENTS | Sources/CheICalMCP/Entitlements.plist | Archivo de entitlements personalizado |
SKIP_CODESIGN | (sin configurar) | Forzar omisión de firma incluso con certificado presente (configurar a 1 o true) |
REQUIRE_CODESIGN | (sin configurar) | Fallar rápido si faltan requisitos de firma (configurar a 1 por make release-signed — la ruta de publicación canónica no debe producir silenciosamente artefactos sin firmar; no configurar al ejecutar ./scripts/build-mcpb.sh directamente para compilaciones de desarrollo amigables con forks) |
Limitación conocida — sin stapling: stapler staple no admite binarios Mach-O sin procesar (solo paquetes .app / .pkg / .dmg). Después de la notarización, Gatekeeper verificará el binario en línea en el primer lanzamiento en lugar de leer un ticket con stapling. Los usuarios finales detrás de redes aisladas pueden ver advertencias de "no se puede verificar el desarrollador"; un lanzamiento con red lo resuelve (Apple almacena en caché el veredicto). Mitigación: xcrun stapler staple en un futuro envoltorio .pkg si es necesario.
Solución de problemas:
- ¿Notarización rechazada?
xcrun notarytool log <submission-id> --keychain-profile $NOTARY_PROFILEmuestra la razón de Apple. El script de firma imprime el ID de envío en cada ejecución. - ¿
codesignse queja de identidad faltante?security find-identity -p codesigning -vpara confirmar que el certificado está presente y es válido;xcrun notarytool history --keychain-profile $NOTARY_PROFILEpara confirmar que el perfil funciona. - ¿Certificado expirado? Reemite en https://developer.apple.com/account/resources/certificates, instala, reexporta
DEVELOPER_ID. - Advertencia de seguridad: no desbloquees el llavero de firma en máquinas compartidas o no confiables. El artefacto de firma de certificado + clave privada es crítico para la cadena de suministro.
Licencia
Licencia MIT - consulta LICENSE para más detalles.
Autor
Creado por Che Cheng (@kiki830621)
Si encuentras esto útil, ¡considera darle una estrella!
Nombres de lectura de recurrencia (#198, no publicado): usa event_recurrence_rules para eventos y reminder_recurrence_rules para recordatorios. Ambos son arreglos de reglas, pero los selectores faltantes y la representación de fechas de fin difieren. El alias heredado recurrence_rules se conserva. Los clientes que rechazan campos desconocidos necesitan actualizaciones de decodificador. Consulta la comparación de formatos.