Repository navigation
v1.11.0 - isError signalling, offset paging, shutdown and scheduling fixes
Added
offsetpaging onget_inbox/get_today/get_upcoming/get_anytime/get_somedayplus anext_offsetcursor (hq-gix). Same semantics asget_trash/search_todos/get_logbook(offset >= 0, windows over the full things.py-ordered set,total= full count). Offset-aware envelopes carrynext_offset(=offset + count) whenever more items remain (budget truncation or window shorter thantotal), omitted at the end. Fixes items past the ~80KB budget being unreachable on large Anytime lists.- Successful
update_todoandmove_recordcalls now return write receipts containing the stable targettodo_id, an explicitreadback_succeededstate, and, when the read succeeds, an observeditemsnapshot in the same shape asget_todo_by_id. A successful readback does not claim the requested fields or destination were applied. If readback fails after the write reports success, the response keepssuccess: true, setsreadback_succeeded: false, and includesreadback_errorplus a no-auto-retry warning. Structured write failures are returned unchanged and do not trigger a readback.
Changed
- Failed write-tool calls now set the MCP
isErrorflag. Anysuccess: falseresult from a write tool is flagged, whatever itserrorcode (includingdelete_todo's lower_snake codes), while the payload stays intact for machine-readable handling.bulk_update_todoswith zero successes andbulk_move_recordswith zero successful moves gainerror: "BULK_OPERATION_FAILED"(existing keys unchanged); a partially failedbulk_move_recordscarrieserror: "BULK_PARTIAL_FAILURE"and is flagged too, with per-item results insuccessful_moves/failed_moves. Read and utility tools are never flagged. - Package metadata now requires Python 3.10+. This matches FastMCP 3.4's runtime floor and prevents dependency resolution from considering unsupported Python 3.8/3.9 environments.
Fixed
when='someday'/when='anytime'no longer take ~7s. They now go straight to the Someday/Anytime list move instead of first attempting two date strategies that always failed (each retried with backoff).add_todo,update_todo,bulk_update_todos,add_projectandupdate_projectdrop from ~7s to under 1s for these values; the end state is unchanged. Pre-existing since before v1.10.0.truncation_hintnow reports the true total (e.g.406 of 720 items returned) instead of the post-limit count, and names theoffsetto resume from, on offset-aware tools (hq-gix).- URL-scheme updates on trashed targets no longer trigger a modal Things alert (hq-4w4).
update_todo(when='evening',when='...@HH:MM',heading),bulk_update_todos(evening/@HH:MM), the three checklist tools, andupdate_project(@HH:MM) now reject a trashed target (or a to-do/heading under a trashed project) withNOT_FOUND(trashed: true, plustrashedViaParent: truewhen transitive) before any write, via one shared pre-check. Bulk calls list such ids innot_found. Checklist tools also now returnNOT_FOUND/VALIDATION_ERRORfor unknown/wrong-type ids, and empty ids are rejected. Trashed projects are also rejected aslist_iddestinations ofadd_todo/update_todo. Pure-AppleScript updates are unchanged. - Boot watchdog no longer wedges process/test-suite shutdown. The one-shot
faulthandlerboot watchdog was never cancelled, so if interpreter finalization overlapped its deadline the stack dump raced shutdown and could hang the process (root cause of the unit-suite hang). It is now cancelled when the server stops and when any non-server CLI command (doctor,--health-check,--version, etc.) finishes, and the pytest session cancels it on finish. --health-checkand--test-applescriptnow validate--timeout/--retry-countagainst config bounds, and--timeoutaccepts fractions. Out-of-range values (e.g.--retry-count 11,--timeout 0) and a missing--env-filenow print a one-line error to stderr and exit 2 instead of passing through or tracebacking. The server and subcommands share one override helper (apply_execution_overrides).- SIGINT/SIGTERM now terminate the
mcp-server-thingsCLI. The handlers previously only logged, so Ctrl+C/SIGTERM never stopped the server. They now raiseKeyboardInterruptso FastMCP's lifespan teardown runs (http transport exits gracefully in ~1s). On stdio, a blocked stdin read can stall teardown, so a 3s grace timer forcesos._exit(0). Exit code is 0 for both signals. - Server disconnects now stop the operation queue on its owning event loop. Queue cleanup runs in FastMCP's lifespan teardown, before the event loop closes, instead of attempting async cleanup later from synchronous
stop, signal, oratexithandlers. --timeoutand--retry-countnow configure the live AppleScript executor. The normal server-start path accepted both CLI flags but discarded them when constructingThingsMCPServer, so the executor always used its constructor defaults. Explicit CLI values now override the loaded configuration and are passed toAppleScriptManager.retry_count/--retry-count/THINGS_MCP_APPLESCRIPT_RETRY_COUNTnow means retries after the initial attempt (#17). The AppleScript executor previously treated it as the total number of attempts, so--retry-count 0executed the script zero times and failures reported "Failed after N attempts" with the wrong count.0now runs the script exactly once with no retry sleeps, andNallows N+1 total attempts; the failure message reports attempts actually made. The default changed from 3 to 2 so default total attempts stay 3. Direct construction ofAppleScriptExecutorwith a negative value raisesValueError.
Thanks to @scamden for #13, #14, #15 and #16, and for reporting #17.