Expose a last-modified timestamp on tasks and notes #1

Closed
opened 2026-08-09 19:51:29 -05:00 by mcp-gateway · 0 comments
Member

The blocker for incremental sync. Filed from halfst/mpx-engine #120 / #137.

What's needed

Every task and note payload should carry a last-modified timestamp — the moment TickTick last changed the object. Naming it modified_date (or modified_time, matching whatever the upstream API calls it) keeps it consistent with the existing snake_case flattening.

Needed on the same four surfaces the engine already reads: list-tasks, list-notes, get-note, and whatever returns a single task.

Why it blocks

The engine's whole sync design is watermark-driven, and the watermark is defined in terms of this field:

  • MPX Engine Spec §3 — "sync | Watermark diff (lastSynced vs modifiedDate)"
  • Sync Operating Spec §3 (People) — "Watermark: lastSynced vs. TickTick modifiedDate"
  • Sync Operating Spec §4 (Projects) — same

The field does not exist in the MCP surface. Confirmed against live data today — here is a full get-note response, which is the richest single-object payload available:

id, project_id, title, content, kind, priority, status, is_all_day,
due, timezone, tags, column_id, assignee, parent_id, sort_order,
pinned, pinned_time, etag

No modified timestamp. list-notes and list-tasks carry the same set.

Why the workarounds are all bad

  • etag tells you that something changed but not when. It can't be compared against a stored lastSynced date, so it can't answer "changed since the last run" — only "differs from the etag I cached," which means keeping a second shadow store of every etag and still learning nothing about ordering.
  • Content hashing means fetching every object in full, every night, purely to diff it. For the 🧖 People mirror that is 40 full note bodies per run to discover that two changed.
  • Doing without means re-rendering and re-pushing all 40 person cards nightly whether or not anything changed — which turns an incremental sync into a full rewrite, and makes every run a potential source of spurious churn.

Likely already available

The server returns sort_order, column_id, pinned_time and etag, which are v2-API fields rather than Open-API v1 ones. The v2 task/note objects carry a modified timestamp, so this may be a matter of stopping it being dropped during payload flattening rather than new API work.

There's precedent: list-projects returns group_id, kind and closed, and the consumer was silently discarding them — fixed today in mpx-engine. Worth checking whether the same is happening one layer up here.

Acceptance

  • A last-modified timestamp on tasks and notes, in list and single-get responses.
  • ISO-8601 with a timezone, or a documented epoch — parseable without guessing.
  • Documented as to whether it changes on any write (including a tag add or a column move) or only on content edits. The engine needs to know which, because "the sync's own push bumped it" is a case it has to not mistake for a user edit.

Unblocks

halfst/mpx-engine #120 (person-sync and project-sync push handlers) and the watermark half of the shared sync core.

**The blocker for incremental sync.** Filed from `halfst/mpx-engine` #120 / #137. ## What's needed Every task and note payload should carry a **last-modified timestamp** — the moment TickTick last changed the object. Naming it `modified_date` (or `modified_time`, matching whatever the upstream API calls it) keeps it consistent with the existing snake_case flattening. Needed on the same four surfaces the engine already reads: `list-tasks`, `list-notes`, `get-note`, and whatever returns a single task. ## Why it blocks The engine's whole sync design is watermark-driven, and the watermark is defined in terms of this field: - `MPX Engine Spec` §3 — *"`sync` | Watermark diff (`lastSynced` vs `modifiedDate`)"* - `Sync Operating Spec` §3 (People) — *"**Watermark:** `lastSynced` vs. TickTick `modifiedDate`"* - `Sync Operating Spec` §4 (Projects) — same The field does not exist in the MCP surface. Confirmed against live data today — here is a full `get-note` response, which is the richest single-object payload available: ``` id, project_id, title, content, kind, priority, status, is_all_day, due, timezone, tags, column_id, assignee, parent_id, sort_order, pinned, pinned_time, etag ``` No modified timestamp. `list-notes` and `list-tasks` carry the same set. ## Why the workarounds are all bad - **`etag`** tells you *that* something changed but not *when*. It can't be compared against a stored `lastSynced` date, so it can't answer "changed since the last run" — only "differs from the etag I cached," which means keeping a second shadow store of every etag and still learning nothing about ordering. - **Content hashing** means fetching every object in full, every night, purely to diff it. For the 🧖 People mirror that is 40 full note bodies per run to discover that two changed. - **Doing without** means re-rendering and re-pushing all 40 person cards nightly whether or not anything changed — which turns an incremental sync into a full rewrite, and makes every run a potential source of spurious churn. ## Likely already available The server returns `sort_order`, `column_id`, `pinned_time` and `etag`, which are v2-API fields rather than Open-API v1 ones. The v2 task/note objects carry a modified timestamp, so this may be a matter of stopping it being dropped during payload flattening rather than new API work. There's precedent: `list-projects` returns `group_id`, `kind` and `closed`, and the *consumer* was silently discarding them — fixed today in mpx-engine. Worth checking whether the same is happening one layer up here. ## Acceptance - [ ] A last-modified timestamp on tasks and notes, in list and single-get responses. - [ ] ISO-8601 with a timezone, or a documented epoch — parseable without guessing. - [ ] Documented as to whether it changes on *any* write (including a tag add or a column move) or only on content edits. The engine needs to know which, because "the sync's own push bumped it" is a case it has to not mistake for a user edit. ## Unblocks `halfst/mpx-engine` #120 (person-sync and project-sync push handlers) and the watermark half of the shared sync core.
ethan closed this issue 2026-08-09 20:07:57 -05:00
ethan referenced this issue from a commit 2026-08-09 20:37:52 -05:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
halfst/ticktick-mcp#1
No description provided.