# Things 3 - Data Access and MCP
Things 3 accepts writes through its MCP connector and its URL scheme, but neither channel reports failures honestly. Tags drop silently, success strings do not prove a landed write, and read fields misreport scheduling. Re-read every write, treat `start_date` as authoritative, and know how to recover from a database lockout.
## Key points
- `add_todo` and `update_todo` return success and silently drop tags that do not exist in Things. `get_tagged_items` rejects the same tags. Tag-based provenance is unworkable until the tag exists. Notes markers are the only reliable channel.
- The Things URL scheme has the same limit. It only applies tags that already exist. Unknown tags drop silently, and the update still reports success.
- Tool success strings are not proof. `update_todo` reported `Updated` while it silently dropped the tag, and `update_todo when` can silently no-op on items that are already in Today. A database outage rolled back a todo that was already created and verified. A verified write is not a safe write. Re-read every write before you count it.
- `get_upcoming` reports every scheduled item as `start: Someday`, even when `start_date` is set. `start_date` is authoritative. Bucket counts built on the `start` field come out wrong.
- A project with `start: Someday` hides its children from Today, even when the children carry real start dates. `Someday` plus a `start_date` is just a scheduled date. Schedule the project itself.
- Connector reads may be partial. One Upcoming read returned 40 of 158 items. A capacity claim that spans weeks needs per-week reads, or the report must state that the numbers are unverified.
- `get_upcoming` overflows to a file the sandbox cannot see at about 150 items. Re-page with `limit: 36` and offsets of 36.
- SQLite error strings discriminate outages. `unable to open database file` means a path or TCC problem. `database is locked` means the database is busy.
- macOS TCC read access is evaluated per-process with caching, and it can flip mid-session. Reads succeeded at two points in one session, then all calls failed together and stayed dead until the server restarted.
- Two `ThingsData-*` directories make the connector's database-resolving glob ambiguous. This is a latent bug in the connector.
- A full lockout where every read fails for 35 minutes or longer is host-level, not per-item. Hold all writes, keep the already-gathered read-only findings, and report a fix pointer. Do not retry writes blind.
- A deliberately invalid `get_tagged_items` call returns the full valid tag list. This is the cheapest way to discover the tag vocabulary.
- The logbook spans multiple days. Isolate the day's items by completion timestamp when you use it as a fallback. Grep the canceled list by UUID instead of reading it. The list runs about 2.5M characters.
- The Things SQLite database lives at `~/Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac/ThingsData-*/Things Database.thingsdatabase/main.sqlite`. The `TMTask` table holds the items. The `start` field separates inbox items (0) from anytime, today, and scheduled items.
- A capacity model beats a backlog dump for scheduling. Size items in units: XS 0.5, S 1, M 2, L 4, XL 8. XXL is not schedulable. One unit is about 45 minutes, and a weekday holds 6 units.
## How to apply
1. Create a tag in the Things app before you tag items in bulk. To discover the exact tag vocabulary, send an invalid `get_tagged_items` call and read the returned list.
2. Mark provenance in notes, never in tags.
3. Re-read each item after a write. Count the write only when the re-read confirms it.
4. Build scheduling buckets from `start_date`, never from `start`.
5. Page `get_upcoming` with `limit: 36` and offsets `0, 36, 72, 108, 144`.
6. On `unable to open database file`, run `lsof -c Things | grep -i sqlite` to find the live `ThingsData-*` directory. Pin the `THINGSDB` environment variable. Restart the host app so the MCP server process gets a fresh TCC evaluation.
7. When every read fails for a long stretch, stop writes. Keep the read-only findings you already gathered. Report a fix pointer: restart the connector and check Full Disk Access.
## Observations
- [things] `add_todo` and `update_todo` return success and drop unknown tags. #mcp (source: agent sessions, 2026-09)
- [things] An invalid `get_tagged_items` call returns the full valid tag list. #things (source: agent sessions, 2026-09)
- [macos] TCC read access is per-process and cached. It can flip mid-session. #macos (source: agent sessions, 2026-09)
- [mcp] Tool success strings are not proof of a landed write. Re-read before counting. #mcp (source: agent sessions, 2026-10)
- [things] `get_upcoming` reports scheduled items as `start: Someday`. `start_date` is authoritative. #things (source: agent sessions, 2026-10)
- [things] A Someday project hides its children from Today. #things (source: agent sessions, 2026-10)
- [tooling] A host-level lockout calls for holding writes and reporting a fix pointer. #mcp (source: agent sessions, 2026-10)
## Relations
- relates_to [[resources/documentation/Things 3 on Linux]] (alternate data path into the same task database)
- relates_to [[resources/concepts/MCP Tool Optimization Framework]] (general MCP read and write reliability patterns)
- relates_to [[resources/documentation/Agents/Agents - MCP Server Onboarding and Credentials]] (same env pinning and host restart recovery pattern)
- relates_to [[resources/documentation/macOS - Developer Environment Pitfalls]] (TCC and Full Disk Access behavior)
## Sources
Personal agent-session notes, April 2026 to October 2026