loom-plan
Provides tools for reading and managing Google Calendar events, including upcoming and overdue events, agenda queries, free-slot calculation, search, event creation/update/deletion, recurring event scope handling, and marking events as done via title conventions.
Provides tools for reading and managing Google Tasks, including open and completed tasks, due dates, task lists, search, task creation/update/deletion, and marking tasks as done or cancelled via title conventions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@loom-plan what's on my plate today — anything overdue?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
loom-plan
Serveur MCP qui donne à Claude Desktop une vue unifiée et actionnable de mon Google Agenda et de mes Google Tasks. Les retards d'abord, puis la journée, les créneaux libres et les tâches ouvertes.
Google reste l'unique référentiel. Le serveur ne stocke rien en dehors du token OAuth et d'un cache de lecture de trente secondes. Claude ne voit jamais un objet Google brut : événements et tâches arrivent dans un seul format, en heure locale, déjà triés dans l'ordre où je veux les lire.
Ce que ça fait
Quand je demande à Claude « qu'est-ce que j'ai aujourd'hui ? », il appelle le tool next et reçoit en un seul aller-retour tout ce qui est en retard, les événements de la journée, les trous entre eux et les tâches ouvertes, datées ou non. Il me présente ça en trois tableaux : En retard, Aujourd'hui, À faire.
Quand je dis « c'est fait », il marque l'événement ou la tâche. Quand je dis « ajoute une tâche pour vendredi », il la crée dans Google Tasks. Il ne fait jamais d'écriture de sa propre initiative : la règle figure dans la description de chaque tool d'écriture.
Quelques principes qui structurent tout le reste. Une chose passée et non traitée continue de remonter tant qu'elle n'est pas marquée faite ou annulée ; le passage du temps ne vaut pas traitement. Une ambiguïté est refusée avec un message clair plutôt que résolue par défaut : modifier une occurrence d'une série récurrente sans dire si on vise l'occurrence ou la série, c'est une erreur, pas une supposition. Une tâche ne bloque pas de temps, elle se fait dans les heures libres. Et le serveur fournit des données déjà ordonnées, la mise en forme finale reste à Claude.
Related MCP server: Schedule MCP Server
Installation
Il faut Python 3.12 ou plus, uv, et un compte Google.
git clone <ce dépôt> ~/dev/loom-plan
cd ~/dev/loom-plan
uv syncCôté Google Cloud
Une fois, dans la console Google Cloud. Créer un projet, activer les API Google Calendar et Google Tasks. Dans Google Auth Platform, configurer l'écran de consentement en type Externe, ajouter les trois scopes ci-dessous dans Accès aux données, puis, dans Audience, publier l'application. Ce dernier point compte : en statut Test, Google fait expirer le refresh token au bout de sept jours et il faut se réauthentifier chaque semaine. En production sans validation, l'écran « application non vérifiée » s'affiche une fois au consentement et le token devient permanent.
https://www.googleapis.com/auth/calendar.events.owned
https://www.googleapis.com/auth/calendar.calendarlist.readonly
https://www.googleapis.com/auth/taskscalendar.events.owned ne donne accès qu'aux agendas dont je suis propriétaire. Les abonnements en lecture seule (jours fériés, numéros de semaine) sont ignorés, et le token ne pourrait pas y écrire de toute façon.
Créer ensuite un client OAuth de type Application de bureau, télécharger son JSON et le placer à la racine du dépôt sous le nom client_secret.json. Ce fichier et token.json sont dans le .gitignore.
Configuration
Le serveur lit loom-plan.toml, dont le chemin est donné par la variable LOOM_PLAN_CONFIG ou par l'option --config, sinon dans le répertoire courant. Les chemins relatifs qu'il contient sont résolus par rapport au fichier lui-même.
[auth]
client_secret = "client_secret.json"
token = "token.json"
[general]
timezone = "Europe/Paris"
epoch = 2026-09-13
primary_calendar = "lamard.denis@gmail.com"
default_task_list = "Liste de lamard.denis"
notes_max_chars = 500
cache_ttl_seconds = 30
[work]
start = 09:00:00
end = 18:00:00
days = ["mon", "tue", "wed", "thu", "fri"]
[status]
done_prefix = "[fait]"
cancelled_prefix = "[annulé]"
tracked_prefix = "[suivi]"L'epoch est la date de mise en service. Rien d'antérieur n'est considéré comme en retard, sinon tout l'historique de l'agenda remonterait au premier appel. Elle ne filtre que les retards : demander l'agenda de mars reste possible.
Premier lancement
uv run loom-plan auth # ouvre le navigateur pour le consentement Google
uv run loom-plan check # liste les agendas et les listes Tasks : les trois scopes sont validés d'un coup
uv run loom-plan nextDans Claude Desktop
Dans claude_desktop_config.json, au même niveau que les autres serveurs :
"loom-plan": {
"command": "/home/denis/dev/loom-plan/.venv/bin/loom-plan-mcp",
"args": ["--config", "/home/denis/dev/loom-plan/loom-plan.toml"],
"env": {}
}Claude Desktop lance deux instances du serveur (chat et Cowork). Elles partagent token.json ; le rafraîchissement du token est protégé par un verrou fichier, et une instance relit le fichier avant de rafraîchir au cas où l'autre l'aurait déjà fait. Une lecture qui échoue sur un token expiré est retentée une fois.
Les tools
Lecture libre.
Tool | Rôle |
| L'état de la journée en un appel : retards, événements, créneaux libres, tâches. Fenêtre par défaut jusqu'à la fin des horaires de travail. |
| Tout ce qui est passé et toujours ouvert, du plus ancien au plus récent. |
| Événements et tâches datées sur une fenêtre de 31 jours maximum. |
| Les tâches, y compris sans date. |
| Recherche texte sur titres et notes, 90 jours en arrière et en avant par défaut. |
| Créneaux libres calculés à partir des événements seuls, sur les jours ouvrés. |
| Agendas et listes disponibles. |
Écriture, uniquement sur demande explicite.
Tool | Rôle |
| Crée un événement, éventuellement récurrent (règle RRULE). |
| Crée une tâche. Une liste inexistante est refusée, jamais créée. |
| Modifie un événement. |
| Modifie une tâche, y compris pour la déplacer de liste. |
|
|
| Suppression définitive, réservée aux erreurs de saisie. Pour « ne plus faire », |
Chaque item porte un id qui encode son conteneur, evt_<agenda>/<id> ou task_<liste>/<id>, pour que les écritures sachent où frapper sans état côté serveur.
Les conventions de titre
Google Tasks distingue « à faire » de « terminé » mais pas « terminé » d'« abandonné », et Google Calendar n'a aucune notion de « fait ». Trois préfixes en tête de titre comblent ça. Ils sont retirés à la lecture et traduits en statut ; le serveur les réécrit lui-même à l'écriture.
Préfixe | Où | Sens |
| événement | L'événement a été traité. Posé par |
| tâche | La tâche est terminée côté Google mais c'était un abandon. Posé par |
| série récurrente | Chaque occurrence est une chose à faire, pas un rendez-vous qui se produit tout seul. Une occurrence passée sans |
Sans [suivi], les occurrences récurrentes sont exclues des retards : un point hebdo passé a eu lieu, personne ne le marque fait. Avec, une occurrence oubliée reste visible jusqu'à ce que je la traite, et la marquer faite ne touche que cette occurrence, jamais la série.
Une dernière convention, dans les notes cette fois : ~30min ou ~1h30 donne une durée estimée, utilisée pour trier les tâches du bloc À faire.
Ligne de commande
Les mêmes opérations sont disponibles en CLI, ce qui a servi à valider le mapping sur le vrai compte avant d'écrire le serveur.
uv run loom-plan next
uv run loom-plan overdue
uv run loom-plan agenda 2026-09-14 2026-09-21
uv run loom-plan tasks --status done
uv run loom-plan find notaire
uv run loom-plan free "2026-09-15 09:00" "2026-09-15 18:00" --min 45
uv run loom-plan add-task "Relancer le devis Dupont" --due 2026-09-19 --notes "~15min"
uv run loom-plan add-event "Regarder mon compte Malt" "2026-09-16 09:00" "2026-09-16 09:30" --recurrence "FREQ=WEEKLY;BYDAY=WE" --tracked
uv run loom-plan set-status task_<liste>/<id> done
uv run loom-plan serve # le serveur MCP en stdio, équivalent de loom-plan-mcpQuelques détails du mapping
Ce sont les points où Google ne dit pas ce qu'on croit, et qui ont chacun coûté une itération.
Un événement sur la journée entière a chez Google une date de fin exclusive : « du 14 au 17 » signifie du 14 au 16. Le serveur expose le vrai dernier jour et refait la conversion à l'écriture.
L'échéance d'une tâche est renvoyée sous la forme 2026-09-14T00:00:00.000Z, mais l'heure n'a aucun sens. Elle est lue comme une date pure et jamais convertie en heure locale, sinon on décale d'un jour.
Les descriptions d'événements sont en HTML. Les balises sont retirées, les sauts de ligne conservés, les entités décodées, et le tout est tronqué à 500 caractères avec un indicateur.
Un événement annulé n'est plus renvoyé par Google sans showDeleted, et pour un événement simple l'annulation équivaut à une mise à la corbeille. Demander status="cancelled" active ce paramètre.
Sur une récurrence, Google renvoie des occurrences dont l'id est celui de la série suffixé par la date. Marquer une occurrence faite crée une exception de la série, ce qui est exactement le comportement voulu.
Développement
uv run pytest -q
uv run ruff check src tests
uv run pyrightLes tests couvrent le mapping sur des payloads Google figés, le service avec les API Google simulées au niveau HTTP (respx), et le contrat des tools exposés par le serveur. Pyright est en mode strict ; les bibliothèques Google n'étant pas typées, une façade Protocol isole la frontière dans auth.py.
La spec complète, avec les décisions prises et leur justification, est dans spec-loom-plan.md.
Ce qui n'est pas là
Pas d'interface HTML interactive. Le crochet MCP Apps est en place (le tool next déclare une ressource ui://loom-plan/next.html), mais Claude Desktop ne rend pas encore les interfaces des serveurs personnalisés, ce qui est un bug connu de son côté. Le jour où ça change, la page minimale s'affichera d'elle-même et il sera temps d'écrire la vraie vue.
Pas de déclencheur d'actions automatiques, pas de synchronisation locale, pas d'agendas partagés tiers. Un seul agenda pour l'instant, mais le format des ids est prêt pour en ajouter un second sans migration.
Licence
Apache 2.0.
Available Tools
13 toolsadd_eventA
ÉCRITURE : uniquement sur demande explicite de Denis. Ne jamais appeler de sa propre initiative, ni pour « aider », ni pour corriger. Renvoie l'objet résultant au format unifié. Crée un événement. start/end : format « AAAA-MM-JJ HH:MM » en heure locale Europe/Paris ; avec all_day=true, « AAAA-MM-JJ » du premier et du dernier jour (inclus). container : nom de l'agenda (défaut : agenda principal). recurrence : règle RRULE sans le préfixe, start/end donnant la première occurrence (ex. FREQ=WEEKLY;BYDAY=WE, FREQ=MONTHLY;BYMONTHDAY=1, FREQ=WEEKLY;BYDAY=MO,FR;COUNT=8). tracked=true (récurrence seulement) pose « [suivi] » : chaque occurrence est une chose à faire et remonte en retard si elle n'est pas marquée faite.
Inputs: end (required), start (required), title (required), notes, +5 more. Returns: result
add_taskA
ÉCRITURE : uniquement sur demande explicite de Denis. Ne jamais appeler de sa propre initiative, ni pour « aider », ni pour corriger. Renvoie l'objet résultant au format unifié. Crée une tâche. due : « AAAA-MM-JJ », optionnel. list_name : nom de la liste (défaut : liste configurée) ; une liste inexistante est refusée, jamais créée. Convention : « ~30min » ou « ~1h30 » dans notes donne une durée estimée.
Inputs: title (required), due, notes, list_name. Returns: result
agendaARead-only
Événements et tâches datées fusionnés et triés sur une fenêtre (31 jours maximum). start/end : format « AAAA-MM-JJ HH:MM » en heure locale Europe/Paris, ou « AAAA-MM-JJ » (minuit). end absent : start + 1 jour. containers : noms d'agendas ou de listes pour restreindre (voir containers). status : open (défaut), done ou cancelled. Présenter en tableau markdown.
Inputs: start (required), end, status, containers. Returns: result
containersARead-only
Agendas Google Calendar et listes Google Tasks disponibles (nom, id, principal, lecture seule). Sert à ranger correctement à l'écriture (container / list_name).
Inputs: no named parameters. Returns: result
deleteADestructive
ÉCRITURE : uniquement sur demande explicite de Denis. Ne jamais appeler de sa propre initiative, ni pour « aider », ni pour corriger. Renvoie l'objet résultant au format unifié. Suppression définitive, réservée aux erreurs de saisie. Pour « ne plus faire », utiliser set_status(cancelled). scope obligatoire pour un événement récurrent (this / series).
Inputs: id (required), scope. Returns: result
findARead-only
Recherche texte (titre, notes) sur événements et tâches, tous statuts. Sans fenêtre : 90 jours en arrière et 90 en avant. start/end : format « AAAA-MM-JJ HH:MM » en heure locale Europe/Paris.
Inputs: query (required), end, start. Returns: result
free_slotsARead-only
Créneaux libres entre start et end, calculés à partir des événements uniquement (les tâches ne bloquent pas de temps), sur les jours ouvrés, dans les horaires de travail (9h-18h par défaut ; work_start/work_end en « HH:MM »). format « AAAA-MM-JJ HH:MM » en heure locale Europe/Paris. min_minutes : durée minimale d'un créneau.
Inputs: end (required), start (required), work_end, work_start, +1 more. Returns: result
nextARead-only
État global de la journée en un seul appel : (1) En retard — tout ce qui est passé et toujours ouvert ; (2) événements de la fenêtre ; (3) créneaux libres ; (4) tâches ouvertes, y compris sans date, triées par échéance puis durée estimée. À appeler pour toute question du type « qu'est-ce que j'ai aujourd'hui / à faire ». Présenter le résultat en markdown, en trois blocs — En retard, Aujourd'hui, À faire — chacun sous forme de tableau (heure ou échéance, titre, durée estimée, agenda/liste). Les créneaux libres s'intercalent dans le tableau Aujourd'hui. Ne pas omettre le bloc En retard s'il est vide : l'indiquer explicitement. Ne pas afficher les ids sauf si Denis les demande. hours : fenêtre en heures à partir de maintenant ; par défaut jusqu'à la fin des horaires de travail du jour (ou minuit si on est déjà après).
Inputs: hours. Returns: result
overdueARead-only
Tout ce qui est passé et toujours ouvert (tâches à échéance dépassée, événements terminés non marqués faits), du plus ancien au plus récent. Sert seul pour « qu'est-ce que je n'ai pas fait » ; sinon son contenu est déjà dans next. Les occurrences d'événements récurrents sont exclues sauf include_recurring=true — à l'exception des séries dont le titre commence par « [suivi] » (convention posée par Denis dans Google Agenda : chaque occurrence est une chose à faire), toujours incluses.
Inputs: include_recurring. Returns: result
set_statusA
ÉCRITURE : uniquement sur demande explicite de Denis. Ne jamais appeler de sa propre initiative, ni pour « aider », ni pour corriger. Renvoie l'objet résultant au format unifié. Change le statut d'un événement ou d'une tâche : open, done, cancelled. Idempotent. Pour un événement récurrent, agit sur l'occurrence seulement. C'est le tool à utiliser pour « c'est fait » / « je ne le ferai pas » (préférer cancelled à delete : on garde la trace).
Inputs: id (required), status (required). Returns: result
tasksARead-only
Tâches Google Tasks, datées ou non — le seul tool qui remonte les tâches sans date en dehors de next. list_name : nom d'une liste (défaut : toutes). status : open (défaut), done ou cancelled.
Inputs: status, list_name. Returns: result
update_eventA
ÉCRITURE : uniquement sur demande explicite de Denis. Ne jamais appeler de sa propre initiative, ni pour « aider », ni pour corriger. Renvoie l'objet résultant au format unifié. Modifie un événement (titre, horaire, notes, lieu). scope obligatoire pour un événement récurrent : « this » (cette occurrence) ou « series » (toute la série) — absent sur un récurrent, le serveur refuse. start et end vont ensemble ; format « AAAA-MM-JJ HH:MM » en heure locale Europe/Paris.
Inputs: id (required), end, notes, scope, +4 more. Returns: result
update_taskA
ÉCRITURE : uniquement sur demande explicite de Denis. Ne jamais appeler de sa propre initiative, ni pour « aider », ni pour corriger. Renvoie l'objet résultant au format unifié. Modifie une tâche : titre, notes, échéance (due « AAAA-MM-JJ », ou clear_due=true pour la retirer), liste (list_name : déplacement).
Inputs: id (required), due, notes, title, +2 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
The read tools overlap in scope: `next` aggregates `overdue`, `agenda`, `tasks`, and `free_slots`, while `agenda`, `tasks`, and `find` cover intersecting data. The descriptions explicitly clarify intended use cases (daily overview vs. unfinished items vs. arbitrary window vs. search), so an agent can usually choose correctly, but the boundaries require careful reading.
Read tools use bare nouns or adjectives (`next`, `agenda`, `tasks`, `overdue`, `containers`, `free_slots`) while write tools use verb_noun or verb patterns (`add_event`, `update_task`, `set_status`, `delete`). This is readable and arguably intentional as a read/write split, but it is not a single predictable naming convention.
13 tools is well within the ideal 3–15 range for a personal calendar and task assistant. Each tool covers a distinct operation—daily overview, search, availability, CRUD for events and tasks—so none feels superfluous.
The surface provides full lifecycle coverage: create, read, update, delete, and status changes for both events and tasks, plus search, free-slot calculation, and container discovery. No obvious domain operation is missing for the stated purpose.
Maintenance
Related MCP Connectors
Read tasks, habits, events and scheduling links; create and update Reclaim tasks and habits.
Lets Claude, ChatGPT, and Muse read and update your beckett tasks, notes, calendar, and lists.
Notes, tasks, habits, journal and 6 more tools Claude can read and change, with undo. Free.
Calendars, tasks, contacts, bookings and agent inboxes for your assistant. EU-hosted, OAuth.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceConnects Claude Desktop to Google Calendar for local calendar management including adding, updating, and deleting events, checking schedules, and calculating travel time between meetings.1ISC
- AlicenseAqualityDmaintenanceIntegrates Google Calendar and Notion to enable Claude to manage your schedule, plan tasks, detect conflicts, and keep appointments and tasks synchronized.17GPL 2.0
- AlicenseNot gradedqualityBmaintenanceEnables read/write access to Google Docs, Sheets, Drive, and Calendar through Claude Desktop, with 24 tools for creating, editing, searching, and managing files and events.873 npmMIT
- AlicenseAqualityDmaintenanceEnables Claude to read, create, update, and delete Google Calendar events directly through natural language.684 npmMIT