tic-mcp
# tic-mcp
An **unofficial** MCP server for [Tic](https://tic.kasvith.me), the free
floating sticky-note task app for macOS.
It lets an AI assistant read your Tic lists. You can ask "what is on my list?"
and get an answer. You can also paste a screenshot onto a task and ask the
assistant to look at it.
This project is not made by the Tic developer and is not connected to them.
Tic's own source code is at [kasvith/tic](https://github.com/kasvith/tic).
Tested against Tic 0.4.0.
## What it can do
| Tool | What it does |
| --- | --- |
| `tic_lists` | Shows every list, and how many tasks are open or done |
| `tic_read` | Shows the tasks on one list |
| `tic_search` | Finds tasks by their text |
| `tic_image` | Shows the image attached to a task |
You name a list or a task by part of its text. Capital letters do not matter.
## It only reads
The server never writes to your notes. Every connection opens the database in
read-only mode, so it cannot change your data even if there is a bug.
This is on purpose. See [Why it does not write](#why-it-does-not-write).
## Install
You need macOS, Tic, and [uv](https://github.com/astral-sh/uv).
```bash
git clone https://github.com/charith-heymilo/tic-mcp.git
cd tic-mcp
uv sync
```
Add it to Claude Code:
```bash
claude mcp add -s user tic -- "$PWD/.venv/bin/python" -m tic_mcp.server
```
Then restart Claude Code. The tools show up after a restart.
To run the tests:
```bash
uv run pytest
```
The server reads `~/Library/Application Support/Tic/tic.sqlite` by default.
Set `TIC_DB` to point it somewhere else.
## How Tic stores notes
Tic uses SQLite through GRDB. There are three tables.
| Table | What it holds |
| --- | --- |
| `note` | One list, plus the position of its window |
| `task` | One line item. `indentLevel` sets how deep it is nested |
| `taskImage` | An optional PNG for a task, kept in the database |
Some details are easy to get wrong:
- **IDs are 16-byte blobs, not text.** Each one is a UUID.
- **Times are UTC, but no time zone is stored.** They look like local time. If
you read them as local time, every value is off by your own offset.
- **`sortIndex` has no gaps.** It runs 0, 1, 2 and so on. To insert a task you
must renumber the ones after it.
- **Foreign keys are off by default**, so `ON DELETE CASCADE` does not run
unless you turn them on.
- Tic uses `journal_mode=delete`, so a writer locks the whole file.
## Images
Tic stores plain PNG files, so a screenshot you paste onto a task reads back
as-is.
Big images are made smaller first. The longest edge is capped at 1568 pixels.
Claude reduces images to that size anyway, so no detail is lost and large
screenshots do not waste tokens.
Resizing uses `sips`, which comes with macOS. This keeps the project free of
an image library.
**The crop box is reported, not applied.** Tic saves a crop box for every
image. Applying it needs a real image library, so the server sends the whole
image and tells you when a crop exists. You see all of the screenshot, which
is usually what you want.
## Why it does not write
Tic has no API. There is no URL scheme, no AppleScript support, and no command
line tool. The SQLite file is the only way in.
Writing is possible, but it works badly. What follows was measured, not
guessed.
**Tic does not see writes from other programs.** Tic watches its database with
GRDB, which only reports changes that Tic itself makes. A task added from
outside stays hidden until Tic writes to the same table for its own reasons:
| What you do in Tic | Does the outside task appear? |
| --- | --- |
| Open the list | No |
| Change the list title | No |
| Add a line item | **Yes** |
**Writes from outside are not lost, though.** Tic updates one record at a
time. It does not rewrite a whole list. It also reads the database when it
picks the next `sortIndex`. So a task added from outside stays where it is,
and the order stays correct.
**Restarting Tic loses your window layout.** Tic decides where to put each
window when it starts, then saves those new positions over the old ones. The
notes open stacked on top of each other, and the old positions are gone.
Writing the old positions back does not help, because the next start
overwrites them again.
This rules out the obvious plan of "quit Tic, write, then start it again". It
would cost you your window layout every time.
If writing is added later, the better way is to write while Tic runs, and tell
the user that the change shows up when they next touch that list.
## Files
```
tic_mcp/db.py Read-only queries. Decodes UUIDs and UTC times
tic_mcp/images.py Reads PNG sizes, and makes images smaller
tic_mcp/server.py The four tools, and how their output is written
tests/ 119 tests, including real stdio round trips
```
`db.py` returns data. `server.py` does all the formatting.
The server also checks Tic's list of migrations. If Tic adds one this code does
not know about, the tool output says so. You get a warning instead of wrong
data.
## License
MIT
TDQS
Scored across 4 tools
The tools are mostly distinct: listing notes, reading tasks, searching tasks, and viewing images are clear operations. However, tic_read and tic_search both return task text and can be confused when an agent wants to find a specific task, though their purposes (read one note vs. search across all) are distinct.
All tool names use the prefix 'tic_' followed by a clear verb (list, read, search, image). The pattern is consistent, though 'tic_image' is slightly less descriptive than 'view_image' or 'get_image', but it still fits the verb_noun pattern.
With 4 tools, the count is slightly low for a note-taking app that likely needs creation, update, and deletion. However, it's a reasonable scope for a read-only or retrieval-focused server, and each tool serves a distinct purpose.
The tools cover listing, reading, searching, and viewing images, but there are no tools to create, update, mark complete, or delete tasks or notes. This leaves the lifecycle incomplete, preventing agents from making any changes, which is a significant gap for a task management domain.