Things 3 MCP Server
A Model Context Protocol (MCP) server that connects Claude and other AI assistants to Things 3 for natural language task management.
Prerequisites
- macOS 12+
- Things 3 installed and opened at least once
- uv (
brew install uv) - macOS will ask for Automation permission for Things 3 on the first write — that's expected (AppleScript is what enables delete/move operations other servers lack).
Install
Claude Desktop
Option A: One-click .mcpb
Download the latest .mcpb file from the releases page and double-click it to install into Claude Desktop.
The bundle launches the server via uvx, so uv must be installed and on PATH (brew install uv). Note: the .mcpb/uvx path works from the next PyPI release onward — the currently published wheel predates the console-script fix that makes uvx mcp-server-things resolve correctly.
Option B: config CLI
mcp-server-things config --client claude-desktop --write
Safely adds/updates the things entry in your Claude Desktop config (--force overwrites an existing, different entry instead of refusing).
Option C: Manual JSON
{
"mcpServers": {
"things": {
"command": "uvx",
"args": ["mcp-server-things"]
}
}
}
Claude Code
claude mcp add-json things '{"command":"uvx","args":["mcp-server-things"]}'
claude mcp add-json things '{"command":"uvx","args":["mcp-server-things"]}' -s user
mcp-server-things config --client claude-code prints these exact commands.
Any MCP client
{
"command": "uvx",
"args": ["mcp-server-things"]
}
Verify
Run mcp-server-things doctor (or uvx mcp-server-things doctor) to confirm Things 3, permissions, and the database are all reachable.
Then ask your client "What's in my Things inbox?".
Advanced: pip, virtualenv, from source, existing installs
Upgrading from an existing install? See docs/UPGRADING.md.
Option 1: From PyPI
- Create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activate # On macOS/Linux
- Install the package:
pip install mcp-server-things
Option 2: From Source (Development)
- Clone the repository:
git clone https://github.com/ebowman/mcp-server-things.git
cd mcp-server-things
- Create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activate # On macOS/Linux
- Install dependencies:
pip install -r requirements.txt
- Install in development mode:
pip install -e .
Claude Desktop Configuration
config CLI
mcp-server-things config --client <claude-desktop|claude-code|generic> [--via uvx|current-python] [--write] [--force]
prints the MCP client configuration for the requested client (or, for
claude-desktop --write, safely merges it into
~/Library/Application Support/Claude/claude_desktop_config.json, backing up
the previous file first and refusing to clobber an existing, different
things entry unless --force is also passed). Run
mcp-server-things config --client claude-desktop --write or
mcp-server-things config --client claude-code instead of hand-editing JSON
or memorizing the claude mcp add-json syntax.
Shortcut for venv/pip installs: mcp-server-things config --client claude-desktop --via current-python targets the currently-running interpreter (sys.executable -m things_mcp) instead of the default uvx mcp-server-things.
For PyPI Installation
Add to your Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"things": {
"command": "/path/to/your/venv/bin/python",
"args": ["-m", "things_mcp"],
"env": {
"THINGS_MCP_LOG_LEVEL": "INFO",
"THINGS_MCP_APPLESCRIPT_TIMEOUT": "30"
}
}
}
}
For Source Installation
Add to your Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"things": {
"command": "/path/to/mcp-server-things/venv/bin/python",
"args": ["-m", "things_mcp"],
"env": {
"PYTHONPATH": "/path/to/mcp-server-things/src",
"THINGS_MCP_LOG_LEVEL": "INFO",
"THINGS_MCP_APPLESCRIPT_TIMEOUT": "30"
}
}
}
}
Notes:
- PyPI: Replace
/path/to/your/venv/bin/pythonwith your virtual environment's Python path - Source: Replace
/path/to/mcp-server-thingswith your actual installation path and include thePYTHONPATH - Use the full path to the Python executable in your virtual environment
- See Configuration section below for environment variable options
Creating tasks with natural language through Claude
📚 Documentation
- User Examples - Rich examples of how to use Things 3 with AI assistants
- Architecture Overview - Technical design and implementation details
- Troubleshooting - Common issues and solutions
Features
Core Todo Operations
- Create: Add todos with full metadata (tags, deadlines, projects, notes)
- Read: Get todos by ID, project, or built-in lists (Today, Inbox, Upcoming, etc.)
- Update: Modify existing todos with partial updates
- Delete: Remove todos safely
- Search: Find todos by title, notes, or advanced filters
Project & Area Management
- Get all projects and areas with optional task inclusion
- Create new projects with initial todos
- Update project metadata and status
- Create and rename areas, including tags (
add_area,update_area) - Organize todos within project hierarchies
Built-in List Access
- Inbox: Capture new items
- Today: Items scheduled for today
- Upcoming: Future scheduled items
- Anytime: Items without specific dates
- Someday: Items for future consideration
- Logbook: Completed items history
- Trash: Deleted items
Advanced Features
- Tag Management: Full tag support with AI creation control, plus usage reporting (
get_tag_usage) for weekly-review cleanup - Date-Range Queries: Get todos due/activating within specific timeframes
- URL Schemes: Native Things 3 URL scheme integration
- Health Monitoring: System health checks and queue status monitoring
- Error Handling: Robust error handling with configurable retries
- Logging: Structured logging with configurable levels
- Concurrency Support: Multi-client safe operation with operation queuing
- Input Validation: Configurable limits for titles, notes, and tags
- Structured Output: Every read tool returns both human-readable text and machine-readable
structured_content(via FastMCP 3.x) with a consistent{items, count, total, mode, limit, offset}shape ({item: {...}}for single-item lookups likeget_todo_by_id), so clients can consume results programmatically without re-parsing text
Requirements
- macOS: This server requires macOS (tested on macOS 12+)
- Things 3: Things 3 must be installed and accessible
- Python: Python 3.8 or higher
- Permissions: AppleScript permissions for Things 3 access
Quick Start
Once installed, Claude (or other MCP clients) can automatically discover and use all available tools. No additional setup required.
Configuration
The server uses environment variables for configuration, settable via system environment variables or a .env file (auto-loaded from the current directory, or pointed to with --env-file). The env vars that matter most:
| Variable | Default | Description |
|---|---|---|
THINGS_MCP_LOG_LEVEL |
INFO |
Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL |
THINGS_MCP_AI_CAN_CREATE_TAGS |
false |
Whether the AI can create new tags (false = existing tags only) |
THINGS_MCP_APPLESCRIPT_TIMEOUT |
30.0 |
AppleScript execution timeout in seconds (1-300) |
THINGS_MCP_TRANSPORT |
stdio |
Transport to use: stdio or http |
THINGS_MCP_PORT |
8000 |
Port to bind to when THINGS_MCP_TRANSPORT=http |
See .env.example for the full list of options, including validation limits, retry counts, the auth-token file, and THINGS_MCP_HOST.
HTTP Transport
By default the server speaks MCP over stdio. It can optionally run an HTTP transport instead, which is the reliable fix when a client's stdio subprocess lacks Automation (TCC) access to Things 3: run the server from a Terminal that has been granted access, then point the client at the HTTP URL instead of launching it as a subprocess (see Troubleshooting for details).
| Variable | Default | Description |
|---|---|---|
THINGS_MCP_TRANSPORT |
stdio |
Transport to use: stdio or http |
THINGS_MCP_HOST |
127.0.0.1 |
Host to bind to when THINGS_MCP_TRANSPORT=http |
THINGS_MCP_PORT |
8000 |
Port to bind to when THINGS_MCP_TRANSPORT=http |
THINGS_MCP_TRANSPORT=http THINGS_MCP_PORT=8000 uvx mcp-server-things
Then add it to Claude Code as an HTTP server:
claude mcp add --transport http things http://127.0.0.1:8000/mcp
--transport, --host, and --port CLI flags are also available and take
precedence over the environment variables above.
Command Line Options
The server supports several command-line options:
# Start with debug logging
python -m things_mcp --debug
# Use a custom .env file
python -m things_mcp --env-file ~/my-config.env
# Check system health
python -m things_mcp --health-check
# Test AppleScript connectivity
python -m things_mcp --test-applescript
# Show version
python -m things_mcp --version
# Customize timeout and retry settings
python -m things_mcp --timeout 60 --retry-count 5
# Run with HTTP transport instead of stdio
python -m things_mcp --transport http --host 127.0.0.1 --port 8000
Claude Desktop Environment Variables
You can set environment variables directly in your Claude Desktop configuration:
{
"mcpServers": {
"things": {
"env": {
"THINGS_MCP_LOG_LEVEL": "DEBUG",
"THINGS_MCP_AI_CAN_CREATE_TAGS": "true",
"THINGS_MCP_APPLESCRIPT_TIMEOUT": "60"
}
}
}
}
Available MCP Tools
Todo Management
get_todos(project_uuid?, include_items?)- List todosadd_todo(title, ...)- Create new todoupdate_todo(id, ...)- Update existing todobulk_update_todos(todo_ids, ...)- Update multiple todos in one operationget_todo_by_id(todo_id)- Get specific tododelete_todo(todo_id)- Delete todo
Project Management
get_projects(include_items?)- List projectsadd_project(title, ...)- Create new projectupdate_project(id, ...)- Update existing project
Area Management
get_areas(include_items?)- List areasadd_area(title, tags?)- Create new areaupdate_area(id, title?, tags?)- Update existing area
List Access
get_inbox()- Get Inbox todosget_today()- Get Today's todosget_upcoming(days?)- Get upcoming todos (with optional days filter)get_anytime()- Get Anytime todosget_someday(include_project_tasks?)- Get Someday todos. By default only returns items whose own start state is Someday; passinclude_project_tasks=trueto also include tasks that live inside Someday projects (markedinheritedSomeday: true). Today/Anytime/Upcoming always exclude tasks that belong to a Someday project, regardless of this flag.get_logbook(limit?, period?)- Get completed todosget_trash()- Get trashed todos
Date-Range Queries
get_due_in_days(days)- Get todos due within specified daysget_activating_in_days(days)- Get todos activating within days
Search & Tags
search_todos(query)- Basic searchsearch_advanced(...)- Advanced search with filtersget_tags(include_items?)- List tagsget_tag_usage(only_unused?, mode?)- Per-tag open/total/area usage counts, sorted by usage, for cleanup. Caveats: tags sharing an identical title are merged into one row (uuid picks the last match), and area-only tags are counted viaarea_count/total_countbut never affectopen_count.create_tag(name)- Create a new tagget_tagged_items(tag)- Get items with specific tagadd_tags(todo_id, tags)- Add tags to a todoremove_tags(todo_id, tags)- Remove tags from a todoget_recent(period)- Get recently created items
Bulk Operations
move_record(record_id, to_parent_uuid)- Move single recordbulk_move_records(record_ids, to_parent_uuid)- Move multiple records
System & Utilities
health_check()- Check server and Things 3 statusqueue_status()- Check operation queue status and statisticsget_server_capabilities()- Get server features and configurationget_usage_recommendations()- Get usage tips and best practicescontext_stats()- Get context-aware response statistics
Troubleshooting
Run mcp-server-things doctor first. It's a read-only diagnostic that checks
Things 3 installation, whether it's running, macOS Automation permission,
database readability (Full Disk Access/TCC), uv/uvx availability, the
optional auth token, and environment/version info - printing a PASS/FAIL/WARN
table with a one-line fix hint per row (exits non-zero only if something
actually needs fixing). Use mcp-server-things doctor --json for
machine-readable output, or python -m things_mcp doctor if you're running
from source.
Reads fail but writes work ("unable to open database file")
| Operation | Result |
|---|---|
Read tools (get_today, get_inbox, search_todos, ...) |
Fail instantly with unable to open database file |
Write tools (add_todo, update_todo, ...) |
Work normally |
| URL-scheme features needing the auth token | Also fail (the token is read from the same database) |
Cause: Under Claude Desktop, MCP servers are spawned via
/Applications/Claude.app/Contents/Helpers/disclaimer, which disclaims TCC
(privacy/permissions) responsibility for the process it launches. As a
result, the spawned server does not inherit Claude Desktop's Full Disk
Access grant, even though Claude.app itself has it. The Things 3 SQLite
database lives under the TCC-protected
~/Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac/ThingsData-*/Things Database.thingsdatabase/main.sqlite
(confirmed via things.database.Database().filepath - the same read path
this server uses for every read tool), so any process without Full Disk
Access gets unable to open database file immediately. Granting Full Disk
Access to Claude.app does not fix this, since the disclaimer helper is
what actually launches the server process.
Fix ladder (try in order):
- Recommended: Run the server with HTTP transport from a Terminal,
which already has disk access, instead of letting Claude Desktop spawn it
directly:
THINGS_MCP_TRANSPORT=http THINGS_MCP_PORT=8000 uvx mcp-server-things
Then point Claude Code at it:claude mcp add --transport http things http://127.0.0.1:8000/mcp
Stdio-only clients (including Claude Desktop) can bridge to the HTTP server withmcp-remote:npx mcp-remote http://127.0.0.1:8000/mcp
- Grant Full Disk Access directly to the actual launched binary (e.g. the
uvxexecutable or the venvpythonin uv's cache) via System Settings -> Privacy & Security -> Full Disk Access. This works but is fragile - the grant can silently break when Homebrew/uv updates the binary, and the FDA picker sometimes greys out these paths, requiring drag-and-drop from Finder to add them. - Run
mcp-server-things doctorto confirm the fix - the "Database readable" row reports PASS once Full Disk Access (or the HTTP transport workaround) is in place.
This is the same failure mode reported upstream in
hald/things-mcp#62; we've
verified it applies here too since we read the Things database via the same
things.py library and code path.
Common Issues
Permission Denied Errors
# Grant AppleScript permissions to your terminal/IDE
# System Preferences > Security & Privacy > Privacy > Automation
# Enable access for your terminal application to control Things 3
Things 3 Not Found
# Verify Things 3 is installed and running
python -m things_mcp.main --health-check
# Check if Things 3 is in Applications folder
ls /Applications/ | grep -i things
Connection Timeouts
# Increase timeout value via environment variable
export THINGS_MCP_APPLESCRIPT_TIMEOUT=60
# Or in your .env file
THINGS_MCP_APPLESCRIPT_TIMEOUT=60
Debug Mode
# Enable debug logging
python -m things_mcp.main --debug
# Check logs
tail -f things_mcp.log
Health Diagnostics
# Comprehensive health check
python -m things_mcp.main --health-check
# Test specific components
python -m things_mcp.main --test-applescript
Boot diagnostics
If the server appears to hang before an MCP client can connect (especially on a cold start), the process writes timestamped boot-phase markers to stderr:
things-mcp boot: 2026-07-20T09:00:00.000+00:00 +0.001s process-start
things-mcp boot: 2026-07-20T09:00:00.010+00:00 +0.011s watchdog-armed (25.0s)
things-mcp boot: 2026-07-20T09:00:00.050+00:00 +0.051s things-import-start
things-mcp boot: 2026-07-20T09:00:00.120+00:00 +0.121s things-import-done
A one-shot startup watchdog also runs in the background: if boot doesn't
complete the MCP handshake within the deadline, it dumps every thread's stack
to stderr (Timeout (0:00:25)! followed by a traceback for each thread). On a
healthy, long-running server this fires exactly once, at the deadline, and is
harmless - it's stderr-only and does not affect the MCP stdio protocol (which
only uses stdout).
Relevant environment variables:
# Startup watchdog deadline in seconds. 0 (or any value <= 0) disables it.
THINGS_MCP_BOOT_WATCHDOG_SECS=25
# Timeout for lazily importing the third-party `things` package, in seconds.
# 0 (or any value <= 0) makes the import unbounded (blocking).
THINGS_MCP_THINGS_IMPORT_TIMEOUT_SECS=10
To diagnose a cold-start hang from a client's debug log: find the last
things-mcp boot: marker line - the phase named there is where boot stalled.
If a watchdog stack dump follows, its traceback shows exactly where each
thread was blocked at that moment.
Performance
- Startup Time: Less than 2 seconds
- Response Time: Less than 500ms for most operations
- Memory Usage: 15MB baseline, 50MB under concurrent load
- Concurrent Requests: Serialized write operations to prevent conflicts
- Throughput: Multiple operations per second depending on complexity
- Queue Processing: Less than 50ms latency for operation enqueuing
Security
- No network access required (local AppleScript only)
- No data stored outside of Things 3
- Minimal system permissions needed
- Secure AppleScript execution with timeouts
- Input validation on all parameters
Contributing
Contributions are welcome! Please follow these guidelines:
- Set up a virtual environment and install dependencies
- Follow existing code style and patterns
- Add tests for new features
- Submit pull requests with clear descriptions
Documentation
- Troubleshooting Guide - Common issues and solutions
- Development Roadmap - Implementation status and missing features
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: ebowman@boboco.ie
Built for the Things 3 and MCP community.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_server_things-1.6.0.tar.gz.
File metadata
- Download URL: mcp_server_things-1.6.0.tar.gz
- Upload date:
- Size: 152.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3aa0334041fc1b2592ca83097d7afd4f9316652c0ee71cc303451fa74c2d093
|
|
| MD5 |
a5069c0787040aec67ba491fbff69c7a
|
|
| BLAKE2b-256 |
2bb91bbbe5c7b1474e3f6d73e2486c733bb9c24789364c9899aff5f03e61cd59
|
Provenance
The following attestation bundles were made for mcp_server_things-1.6.0.tar.gz:
Publisher:
publish.yml on ebowman/mcp-server-things
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_server_things-1.6.0.tar.gz -
Subject digest:
d3aa0334041fc1b2592ca83097d7afd4f9316652c0ee71cc303451fa74c2d093 - Sigstore transparency entry: 2515138151
- Sigstore integration time:
-
Permalink:
ebowman/mcp-server-things@dfee7668fe867489c82cfa8973925c81db15bd2e -
Branch / Tag:
refs/tags/v1.6.0 - Owner: https://github.com/ebowman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@dfee7668fe867489c82cfa8973925c81db15bd2e -
Trigger Event:
release
-
Statement type:
File details
Details for the file mcp_server_things-1.6.0-py3-none-any.whl.
File metadata
- Download URL: mcp_server_things-1.6.0-py3-none-any.whl
- Upload date:
- Size: 164.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b250af579a367a07edb0179627a8b900db1ffa8de2ad1cd56a2e8ba4d4525c6f
|
|
| MD5 |
3126d92a785c17487f6ccea7d7c7561d
|
|
| BLAKE2b-256 |
f6411b6983281e37b19a670deafc830c510a4bca8bb329c39f1f8bb1efdac1bb
|
Provenance
The following attestation bundles were made for mcp_server_things-1.6.0-py3-none-any.whl:
Publisher:
publish.yml on ebowman/mcp-server-things
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_server_things-1.6.0-py3-none-any.whl -
Subject digest:
b250af579a367a07edb0179627a8b900db1ffa8de2ad1cd56a2e8ba4d4525c6f - Sigstore transparency entry: 2515138181
- Sigstore integration time:
-
Permalink:
ebowman/mcp-server-things@dfee7668fe867489c82cfa8973925c81db15bd2e -
Branch / Tag:
refs/tags/v1.6.0 - Owner: https://github.com/ebowman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@dfee7668fe867489c82cfa8973925c81db15bd2e -
Trigger Event:
release
-
Statement type: