Skip to content

v1.11.0 - isError signalling, offset paging, shutdown and scheduling fixes

Choose a tag to compare

@ebowman ebowman released this 14 Oct 14:43
· 12 commits to main since this release

Added

  • offset paging on get_inbox/get_today/get_upcoming/get_anytime/get_someday plus a next_offset cursor (hq-gix). Same semantics as get_trash/search_todos/get_logbook (offset >= 0, windows over the full things.py-ordered set, total = full count). Offset-aware envelopes carry next_offset (= offset + count) whenever more items remain (budget truncation or window shorter than total), omitted at the end. Fixes items past the ~80KB budget being unreachable on large Anytime lists.
  • Successful update_todo and move_record calls now return write receipts containing the stable target todo_id, an explicit readback_succeeded state, and, when the read succeeds, an observed item snapshot in the same shape as get_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 keeps success: true, sets readback_succeeded: false, and includes readback_error plus 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 isError flag. Any success: false result from a write tool is flagged, whatever its error code (including delete_todo's lower_snake codes), while the payload stays intact for machine-readable handling. bulk_update_todos with zero successes and bulk_move_records with zero successful moves gain error: "BULK_OPERATION_FAILED" (existing keys unchanged); a partially failed bulk_move_records carries error: "BULK_PARTIAL_FAILURE" and is flagged too, with per-item results in successful_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_project and update_project drop from ~7s to under 1s for these values; the end state is unchanged. Pre-existing since before v1.10.0.
  • truncation_hint now reports the true total (e.g. 406 of 720 items returned) instead of the post-limit count, and names the offset to 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, and update_project (@HH:MM) now reject a trashed target (or a to-do/heading under a trashed project) with NOT_FOUND (trashed: true, plus trashedViaParent: true when transitive) before any write, via one shared pre-check. Bulk calls list such ids in not_found. Checklist tools also now return NOT_FOUND/VALIDATION_ERROR for unknown/wrong-type ids, and empty ids are rejected. Trashed projects are also rejected as list_id destinations of add_todo/update_todo. Pure-AppleScript updates are unchanged.
  • Boot watchdog no longer wedges process/test-suite shutdown. The one-shot faulthandler boot 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-check and --test-applescript now validate --timeout/--retry-count against config bounds, and --timeout accepts fractions. Out-of-range values (e.g. --retry-count 11, --timeout 0) and a missing --env-file now 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-things CLI. The handlers previously only logged, so Ctrl+C/SIGTERM never stopped the server. They now raise KeyboardInterrupt so 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 forces os._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, or atexit handlers.
  • --timeout and --retry-count now configure the live AppleScript executor. The normal server-start path accepted both CLI flags but discarded them when constructing ThingsMCPServer, so the executor always used its constructor defaults. Explicit CLI values now override the loaded configuration and are passed to AppleScriptManager.
  • retry_count / --retry-count / THINGS_MCP_APPLESCRIPT_RETRY_COUNT now means retries after the initial attempt (#17). The AppleScript executor previously treated it as the total number of attempts, so --retry-count 0 executed the script zero times and failures reported "Failed after N attempts" with the wrong count. 0 now runs the script exactly once with no retry sleeps, and N allows 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 of AppleScriptExecutor with a negative value raises ValueError.

Thanks to @scamden for #13, #14, #15 and #16, and for reporting #17.