feat(tasks): add recurrency and reminder write support to update_task (v1.1.0)

Verified parameters from JS-Bundle xb-Encoder/fc-Encoder now wired up:
recurrencyDescriptor (recurrency, recurrencyInterval, rrule) and reminder
(reminderUnit, reminderValue). Adds clear_recurrency and clear_reminder flags.
SPEC.md, CHANGELOG.md, CLAUDE.md updated accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 21:15:05 +02:00
parent 3e021bf01a
commit f5eb0a46c8
5 changed files with 113 additions and 4 deletions
+12
View File
@@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This project follows Semantic Versioning (SemVer). This project follows Semantic Versioning (SemVer).
Breaking changes (removed tools, changed parameters) increment the major version. Breaking changes (removed tools, changed parameters) increment the major version.
## [1.1.0] 2026-04-17
### Added
- `update_task`: recurrency parameters (`recurrency`, `recurrency_interval`, `rrule`,
`clear_recurrency`) — set or remove task recurrence rules via `recurrencyDescriptor`
- `update_task`: reminder parameters (`reminder_unit`, `reminder_value`, `clear_reminder`)
— set or remove task reminders via `reminder`
- SPEC.md: documented `recurrencyDescriptor` and `reminder` fields for `taskupdate2`
(verified via JS-Bundle xb-Encoder / fc-Encoder)
---
## [1.0.1] 2026-04-17 ## [1.0.1] 2026-04-17
### Added ### Added
+4 -2
View File
@@ -27,7 +27,7 @@ und wird in Claude Desktop eingebunden.
## Aktueller Stand ## Aktueller Stand
### Version: **v1.0.0** ← aktuell ### Version: **v1.1.0** ← aktuell
### Implementierte Tools ### Implementierte Tools
@@ -130,8 +130,10 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
| Endpoint | Parameter | Wert/Format | | Endpoint | Parameter | Wert/Format |
|---|---|---| |---|---|---|
| `taskcreate2` | `taskListId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee` | | | `taskcreate2` | `taskListId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee` | |
| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | | | `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId`, `recurrencyDescriptor`, `reminder` | |
| `taskupdate2` | `dueDate` löschen | `$empty` | | `taskupdate2` | `dueDate` löschen | `$empty` |
| `taskupdate2` | `recurrencyDescriptor` | `{recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence}`; löschen: `{recurrency: "NONE"}` |
| `taskupdate2` | `reminder` | `{reminderUnit: "MINUTE"\|"HOUR"\|"DAY", reminderValue: int, reminderType: "SNOOZE", localId: 0}`; löschen: `{reminderUnit: "", reminderValue: 0, ...}` |
| `taskmark` | `taskId`, `complete` | `"true"`/`"false"` | | `taskmark` | `taskId`, `complete` | `"true"`/`"false"` |
| `metadelete` | `id` | metaId des Tasks / Rezepts | | `metadelete` | `id` | metaId des Tasks / Rezepts |
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like | | `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
+27
View File
@@ -201,9 +201,36 @@ POST https://api.familywall.com/api/taskupdate2
| `dueDate` | nein | ISO 8601 oder `$empty` zum Löschen | | `dueDate` | nein | ISO 8601 oder `$empty` zum Löschen |
| `assignee` | nein | Member-accountId (mehrfach sendbar), `""` zum Entfernen aller | | `assignee` | nein | Member-accountId (mehrfach sendbar), `""` zum Entfernen aller |
| `taskListId` | nein | neue Listen-metaId (verschiebt Task) | | `taskListId` | nein | neue Listen-metaId (verschiebt Task) |
| `recurrencyDescriptor` | nein | Objekt mit Wiederholungs-Feldern (siehe unten) |
| `reminder` | nein | Objekt mit Erinnerungs-Feldern (siehe unten) |
**Hinweis:** `taskListId` ist NICHT Pflicht beim Update. **Hinweis:** `taskListId` ist NICHT Pflicht beim Update.
**`recurrencyDescriptor`-Felder** (verifiziert via JS-Bundle xb-Encoder):
| Feld | Wert |
|---|---|
| `recurrency` | `"DAILY"` \| `"WEEKLY"` \| `"MONTHLY"` \| `"YEARLY"` \| `"NONE"` |
| `recurrencyInterval` | int (z.B. `2` für "alle 2 Wochen") |
| `rrule` | vollständige iCal-RRULE (z.B. `"FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"`) |
| `byDay` | z.B. `"FR"`, `"1SA"`, `"MO,TU,WE,TH,FR"` |
| `byMonthDay` | int (z.B. `7` für "am 7. des Monats") |
| `recurrencyEndDate` | ISO-Datum (z.B. `"2026-12-31"`) |
| `endOccurence` | int (nach N Wiederholungen aufhören) |
Wiederholung entfernen: `recurrencyDescriptor = {"recurrency": "NONE"}`
**`reminder`-Felder** (verifiziert via JS-Bundle fc-Encoder):
| Feld | Wert |
|---|---|
| `reminderUnit` | `"MINUTE"` \| `"HOUR"` \| `"DAY"` |
| `reminderValue` | int (`0` = zum Zeitpunkt, `15` = 15 min vorher, `1` = 1 Einheit vorher) |
| `reminderType` | immer `"SNOOZE"` |
| `localId` | immer `0` |
Erinnerung entfernen: `reminder = {"reminderUnit": "", "reminderValue": 0, "reminderType": "SNOOZE", "localId": 0}`
**Response:** **Response:**
``` ```
a00.r.r → vollständiges Task-Objekt a00.r.r → vollständiges Task-Objekt
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" name = "mcp-familywall"
version = "1.0.1" version = "1.1.0"
description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude" description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+69 -1
View File
@@ -926,6 +926,13 @@ def update_task(
clear_due_date: bool = False, clear_due_date: bool = False,
assignee_ids: list[str] | None = None, assignee_ids: list[str] | None = None,
list_id: str | None = None, list_id: str | None = None,
recurrency: str | None = None,
recurrency_interval: int | None = None,
rrule: str | None = None,
clear_recurrency: bool = False,
reminder_unit: str | None = None,
reminder_value: int | None = None,
clear_reminder: bool = False,
) -> str: ) -> str:
"""Update an existing task's fields. """Update an existing task's fields.
@@ -948,12 +955,40 @@ def update_task(
list_id: Move the task to a different list by providing the target list ID list_id: Move the task to a different list by providing the target list ID
from get_lists (e.g. ``"taskList/23431854_29740942"``). Omit to keep in from get_lists (e.g. ``"taskList/23431854_29740942"``). Omit to keep in
current list. current list.
recurrency: Recurrence frequency — ``"DAILY"``, ``"WEEKLY"``, ``"MONTHLY"``,
``"YEARLY"``, or ``"NONE"``. Examples: daily → ``recurrency="DAILY",
recurrency_interval=1``; every 2 weeks on Friday →
``recurrency="WEEKLY", recurrency_interval=2,
rrule="FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"``.
Cannot be used together with *clear_recurrency*.
recurrency_interval: Repeat every N units (e.g. ``2`` for "every 2 weeks").
Only meaningful when *recurrency* is set.
rrule: Full iCalendar RRULE string (e.g. ``"FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"``).
Overrides simple recurrency fields when set.
clear_recurrency: Set to ``True`` to remove the recurrence rule from the task.
Cannot be used together with *recurrency*.
reminder_unit: Unit for the reminder offset — ``"MINUTE"``, ``"HOUR"``, or
``"DAY"``. Must be provided together with *reminder_value*. Requires a
*due_date* to be set on the task.
reminder_value: Offset before the due date/time (e.g. ``0`` = at the time,
``15`` = 15 min before, ``1`` with unit ``"DAY"`` = 1 day before).
Must be provided together with *reminder_unit*.
clear_reminder: Set to ``True`` to remove the reminder from the task.
Cannot be used together with *reminder_unit*/*reminder_value*.
Returns: Returns:
JSON success indicator or an error message. JSON success indicator or an error message.
""" """
if clear_due_date and due_date is not None: if clear_due_date and due_date is not None:
return _err("'clear_due_date' and 'due_date' cannot be used together.") return _err("'clear_due_date' and 'due_date' cannot be used together.")
if clear_recurrency and recurrency is not None:
return _err("'clear_recurrency' and 'recurrency' cannot be used together.")
if clear_reminder and (reminder_unit is not None or reminder_value is not None):
return _err(
"'clear_reminder' cannot be used together with 'reminder_unit'/'reminder_value'."
)
if (reminder_unit is None) != (reminder_value is None):
return _err("'reminder_unit' and 'reminder_value' must be provided together.")
if ( if (
text is None text is None
@@ -963,10 +998,16 @@ def update_task(
and not clear_due_date and not clear_due_date
and assignee_ids is None and assignee_ids is None
and list_id is None and list_id is None
and recurrency is None
and not clear_recurrency
and reminder_unit is None
and not clear_reminder
): ):
return _err( return _err(
"At least one of 'text', 'description', 'category_id', 'due_date'," "At least one of 'text', 'description', 'category_id', 'due_date',"
" 'clear_due_date', 'assignee_ids', or 'list_id' must be provided." " 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency',"
" 'clear_recurrency', 'reminder_unit'/'reminder_value', or"
" 'clear_reminder' must be provided."
) )
params: dict[str, Any] = {"metaId": task_id} params: dict[str, Any] = {"metaId": task_id}
@@ -986,6 +1027,33 @@ def update_task(
if list_id is not None: if list_id is not None:
params["taskListId"] = list_id params["taskListId"] = list_id
recurrency_descriptor: dict[str, Any] = {}
if clear_recurrency:
recurrency_descriptor["recurrency"] = "NONE"
elif recurrency is not None:
recurrency_descriptor["recurrency"] = recurrency
if recurrency_interval is not None:
recurrency_descriptor["recurrencyInterval"] = recurrency_interval
if rrule is not None:
recurrency_descriptor["rrule"] = rrule
if recurrency_descriptor:
params["recurrencyDescriptor"] = recurrency_descriptor
if clear_reminder:
params["reminder"] = {
"reminderUnit": "",
"reminderValue": 0,
"reminderType": "SNOOZE",
"localId": 0,
}
elif reminder_unit is not None and reminder_value is not None:
params["reminder"] = {
"reminderUnit": reminder_unit,
"reminderValue": reminder_value,
"reminderType": "SNOOZE",
"localId": 0,
}
try: try:
_authenticated_call("taskupdate2", params) _authenticated_call("taskupdate2", params)
except RuntimeError as exc: except RuntimeError as exc: