Skip to main content
Glama

Things App MCP

English | 한국어

MCP server for Things 3 on macOS. It adds, reads, and updates to-dos through the Things URL scheme and JXA, and npm run sync copies the Today list into a Notion database.

The server talks to the client over stdio on your Mac.

Requirements

  • macOS

  • Things 3

  • Node.js 18 or newer

  • Things URLs turned on: Things > Settings > General > Enable Things URLs

  • Automation permission for whichever app starts the server (Terminal, Claude, Cursor, or Codex). osascript has to be allowed to control Things.

Related MCP server: Things Cloud MCP

Install

git clone https://github.com/lucas-flatwhite/things-app-mcp.git
cd things-app-mcp
npm install
npm run build

You can also skip the clone and let the client run npx -y things-app-mcp@latest. The configs below do that.

Connect a client

Adding and reading to-dos works without a token. Changing an existing item, or applying a reschedule, needs the Things auth token.

In Things: Settings > General > Enable Things URLs > Manage.

Set THINGS_AUTH_TOKEN in the client config. If you pass authToken on each call, that token lands in the client log. Replies strip the token, notes, and JSON data. The URL given to open still has the token for a moment. Things expects it there.

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "things": {
      "command": "npx",
      "args": ["-y", "things-app-mcp@latest"],
      "env": {
        "THINGS_AUTH_TOKEN": "your-token-here"
      }
    }
  }
}

Cursor

.cursor/mcp.json

{
  "mcpServers": {
    "things": {
      "command": "npx",
      "args": ["-y", "things-app-mcp@latest"],
      "env": {
        "THINGS_AUTH_TOKEN": "your-token-here"
      }
    }
  }
}

Codex

~/.codex/config.toml

[mcp_servers.things]
command = "npx"
args = ["-y", "things-app-mcp@latest"]
startup_timeout_sec = 20
tool_timeout_sec = 120

[mcp_servers.things.env]
THINGS_AUTH_TOKEN = "your-token-here"

Gemini CLI

export THINGS_AUTH_TOKEN="your-token-here"
gemini mcp add things npx -y things-app-mcp@latest

For a local build, use node and the absolute path to dist/index.js instead of npx.

Tools

Writing

Tool

Description

add-todo

New to-do: title, notes, when, deadline, tags, checklist, project, or area.

add-project

New project. Put to-do titles in todos, one per line.

update-todo

Change a to-do by id, including completing or canceling it. Needs the token.

update-project

Change a project by id. Needs the token.

show

Open a list, project, area, tag, or to-do in Things.

search

Open Things search.

add-json

Nested projects, headings, to-dos, and checklist items. Updates need the token.

When a write succeeds, macOS has opened the URL. Read the item again if you need to see that Things saved it. add-todo does not return the new id.

Reading

Tool

Description

get-todos

To-dos from one list, project, area, or tag.

get-todo-by-id

One to-do by id.

get-projects

Every project.

get-project-by-id

One project by id.

get-areas

Every area.

get-tags

Every tag.

search-todos

To-dos whose title or notes contain the query.

get-recent-todos

To-dos changed in the last N days. Default is 7.

Call get-todos with one of list, project, area, or tag. If you leave them all out, you get Today.

Lists: Inbox, Today, Anytime, Upcoming, Someday, Logbook.

Results include titles and notes.

Clearing Today

reschedule-distant-todos looks through Today for open items whose deadline is still far off, and moves the start date to a few days before that deadline.

No arguments means preview only, and only when a token is set. To apply:

{
  "daysThreshold": 7,
  "bufferDays": 3,
  "dryRun": false,
  "confirm": true
}
  • daysThreshold (default 7): a closer deadline stays in Today.

  • bufferDays (default 3): new start date, this many days before the deadline.

  • An item you put on today yourself (activationDate is today) stays where it is.

  • dryRun: false without confirm: true returns an error and leaves Today unread.

  • confirm is a tool argument. A client that reads destructiveHint may ask again before it runs.

  • The update is one JSON batch.

update-todo, update-project, and updates through add-json do not take confirm. With the token set, those calls can edit or complete items.

Examples

Add a to-do:

{
  "title": "Buy groceries",
  "when": "today",
  "tags": "Errand"
}

Create a project and its to-dos:

{
  "title": "Launch Website",
  "area": "Work",
  "todos": "Design mockups\nBuild frontend\nDeploy"
}

Read Today:

{ "list": "Today" }

Read one project:

{ "project": "Launch Website" }

Complete a to-do once you have its id:

{
  "id": "the-todo-id",
  "completed": true
}

Preview a reschedule, then apply it:

{ "dryRun": true }
{ "dryRun": false, "confirm": true }

Dates, lists, and JSON

Write tools follow the Things URL scheme.

When and deadline

Format

Example

Named

today, tomorrow, evening, anytime, someday

Date

2026-03-15

Date and time

2026-03-15@14:00

Natural language

next friday, in 3 days (Things reads these in English)

show list ids

inbox, today, anytime, upcoming, someday, logbook, tomorrow, deadlines, repeating, all-projects, logged-projects

add-json types

Type

What it is

to-do

A task: title, notes, when, deadline, tags, checklist items.

project

A project: title, notes, nested items.

heading

A section in a project.

checklist-item

One line in a to-do.

To update, set "operation": "update" and "id". That needs the auth token.

Layout

src/index.ts            MCP tools
src/things-url.ts       things:/// URLs, and stripping secrets from replies
src/applescript.ts      JXA reads
src/reschedule-mode.ts  preview versus apply
src/notion-sync.ts      `npm run sync`, separate from the MCP server
src/notion-sync/        plan, Notion client, Things read and write

Writes build a things:/// URL and hand it to open. Reads run JXA through osascript.

Development

npm install
npm run build
npm run dev    # tsc --watch
npm start      # node dist/index.js

Testing

See TESTING.md.

# No Things required.
node scripts/test-unit.js

# Sync plan, hashes, and the Notion client. No network and no Things.
npm run test:sync

# macOS and Things 3. Writes stay off.
npm run test:tools

THINGS_MCP_TEST_ALLOW_WRITES=1 npm run test:tools

One case in the unit script reads Today when Things is installed. Skip it with:

THINGS_UNIT_SKIP_LIVE=1 node scripts/test-unit.js

Notion sync

npm run sync copies the Things Today list into Notion database 9ac2752989a646918bfa93ab900dfb8c. That list is whatever Things is showing under Today: items for today, this evening, and overdue items still sitting there. Inbox, Upcoming, Anytime, and Someday are left as they are.

You start each run yourself.

Setup

Put the Notion token in the macOS keychain. Every run reads it from there.

security add-generic-password -s things-notion-sync -a "$USER" -w

Set THINGS_AUTH_TOKEN when the plan changes a Things to-do. A run that only creates or edits Notion rows can go without it.

If a column is missing, the dry-run exits 2 and prints a database patch. This command adds the missing columns and then writes the plan:

npm run sync -- --apply --ensure-schema

시작일 is the calendar formula. A missing column, or a column of some other type, prints a warning and the sync continues. Paste this into Notion:

if(test(prop("언제"), "^\d{4}-\d{2}-\d{2}"), parseDate(slice(prop("언제"), 0, 10)), parseDate(""))

Run

npm run build
npm run sync                  # print the plan
npm run sync -- --apply       # write it

The table columns are action, things_id, title, fields, and detail. With nothing to do, the command prints 변경 없음. The same text is appended to ~/Library/Logs/things-notion-sync.log, with ids, actions, and field names. Tokens and note text are stripped first.

Columns

Column

What the sync does

제목, 메모, 마감일, 태그

Copied both ways while the to-do is in Today.

언제

Copied both ways for 오늘, 오늘 저녁, and a date on or before today. 받은함, 언제든, 언젠가, and a later date stay on the Notion row.

상태

열림, 완료, or 취소, both ways, while the to-do is in Today.

시작일

Shown on a calendar. The sync skips this column.

프로젝트, 프로젝트 id, 영역, 영역 id

Filled from the Things to-do.

things_id

Things id for the row. The next run finds the row with this.

동기화 해시

Field hashes from the last write. The next run compares against this. Edit the hash and a later 완료 can be overwritten by the old Things status.

마지막 동기화

When that write happened.

A tag name Things has never seen is dropped. Checklists, headings, and repeat rules stay in Things. A repeat that is in Today is copied like any other to-do. If Things ignores a when or deadline change, that Things value is written back to the row.

Marking a to-do done

On the Notion page, set 상태 to 완료. Leave things_id and 동기화 해시 alone. Then run npm run sync -- --apply while the to-do is still in Today, and Things completes it. The same works the next day if it is still in Today when you run the command. 취소 cancels the to-do. 열림 opens it again.

After Things completes it, the to-do leaves Today and the row stays 완료. The next run fills any other differing fields from Things. From then on, edits to that row stay in Notion.

A new Notion row with an empty 언제, or with 오늘 or 오늘 저녁, is added to Today when you apply.

Deleting rows

Rows are matched by things_id. Delete an 열림 row while its to-do is still in Today, and the next run creates the row again. The to-do stays open.

Finished rows can stay in the database. A completed to-do has left Today, so that row is left alone. A row can also be deleted after its to-do moves to Inbox, Anytime, Upcoming, or Someday. It comes back if the to-do returns to Today.

License

MIT

Available Tools

15 tools
add-jsonAdd via JSONA

Create complex projects and to-dos using the Things JSON command. Supports nested projects with headings, checklist items, and to-dos. The data should be an array of objects with "type" (to-do, project, heading, checklist-item) and "attributes" fields. For updates, include "operation": "update" and "id" fields, and provide auth-token.

Inputs: data (required), authToken, reveal.

add-projectAdd ProjectA

Create a new project in Things. Supports setting title, notes, when/deadline dates, tags, area assignment, and initial to-dos.

Inputs: title, notes, when, deadline, +9 more.

add-todoAdd To-DoA

Create a new to-do in Things. Supports setting title, notes, when/deadline dates, tags, checklist items, and assigning to projects/areas. Uses the Things URL scheme.

Inputs: title, titles, notes, when, +13 more.

get-areasGet AreasA
Read-only

Get all areas from Things. Uses AppleScript (macOS only).

Inputs: no named parameters.

get-project-by-idGet Project by IDA
Read-only

Get a specific project by its ID. Uses AppleScript (macOS only).

Inputs: id (required).

get-projectsGet ProjectsB
Read-only

Get all projects from Things. Uses AppleScript (macOS only).

Inputs: no named parameters.

get-recent-todosGet Recent To-DosA
Read-only

Get recently modified to-dos. Uses AppleScript (macOS only).

Inputs: days.

get-tagsGet TagsB
Read-only

Get all tags from Things. Uses AppleScript (macOS only).

Inputs: no named parameters.

get-todo-by-idGet To-Do by IDA
Read-only

Get a specific to-do by its ID. Uses AppleScript (macOS only).

Inputs: id (required).

get-todosGet To-DosA
Read-only

Get to-dos from Things by list, project, area, or tag. Specify exactly one source. Uses AppleScript (macOS only).

Inputs: list, project, area, tag.

search-todosSearch To-DosA
Read-only

Search for to-dos by title or notes content. Uses AppleScript (macOS only).

Inputs: query (required).

showShow in ThingsA
Read-only

Navigate to and show a list, project, area, tag, or to-do in Things. Built-in list IDs: inbox, today, anytime, upcoming, someday, logbook, tomorrow, deadlines, repeating, all-projects, logged-projects.

Inputs: id, query, filter.

update-projectUpdate ProjectB

Update an existing project in Things. Requires the project ID and your Things auth-token.

Inputs: authToken (required), id (required), title, notes, +14 more.

update-todoUpdate To-DoA

Update an existing to-do in Things. Requires the to-do ID and your Things auth-token. Supports changing title, notes, dates, tags, checklist, list assignment, and status.

Inputs: authToken (required), id (required), title, notes, +19 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 15 tools

    Disambiguation4/5

    Most tools are clearly distinct by resource and action, such as add-project vs. update-project. However, some potential confusion exists: 'search' opens the search screen, while 'search-todos' performs a specific search; 'get-todos' retrieves by source, and 'get-recent-todos' gets recent ones, which might overlap in use cases. Descriptions help clarify, but minor ambiguity remains.

    Naming Consistency5/5

    Tool names follow a consistent verb_noun pattern throughout, with clear actions like 'add', 'get', 'update', and 'search' paired with specific nouns like 'project', 'todo', or 'areas'. All names use snake_case uniformly, making them predictable and easy to parse.

    Tool Count5/5

    With 15 tools, this server is well-scoped for managing tasks and projects in Things. It covers core CRUD operations for projects and to-dos, plus additional utilities like listing areas and tags, which fits the domain appropriately without being overwhelming or insufficient.

    Completeness4/5

    The tool set provides strong coverage for the task management domain, including creation, retrieval, and updates for projects and to-dos, plus listing areas and tags. Minor gaps exist: there's no explicit delete tool for projects or to-dos, and 'add-json' might overlap with other add tools, but agents can likely work around these with updates or existing methods.

    Related MCP Connectors

    Related MCP Servers