Things Cloud MCP
MCP server that gives AI agents read/write access to your Things3 tasks via the Things API.
README
Things API
RESTful API over Things3 data. Syncs bidirectionally with Things Cloud via the reverse-engineered sync protocol and exposes your tasks, projects, areas, and tags over HTTP/HTTPS.
This repository ships three related products:
things-api— a ready-to-run HTTP/HTTPS servicethings-sdk— a standalone Python SDK for scripts, CLIs, workers, and integrationsthings-cloud-mcp— an MCP server that gives AI agents (Claude, Codex, etc.) read/write access to your tasks
Looking for the Python library instead of the HTTP service? See
packages/things-sdk/README.md. Want to connect your AI agent? Seepackages/things-mcp/README.md.
Which package should I use?
| Use case | What to use |
|---|---|
| You want a hosted/self-hosted HTTP/HTTPS API | things-api |
| You want to build a CLI, script, worker, or integration in Python | things-sdk |
| You want AI agents to read/write your tasks | things-cloud-mcp (requires a running things-api) |
If you just want to run a server and call it over HTTP/HTTPS, continue with the API docs below. If you want to embed the core functionality directly in Python, jump to the SDK README.
Quick Start
cp .env.example .env
# Edit .env with your Things Cloud credentials and a strong API key
docker compose up -d
The API is available at http://localhost:3117. Interactive docs at http://localhost:3117/docs.
Configuration
All settings are configured via environment variables (or a .env file):
| Variable | Required | Default | Description |
|---|---|---|---|
API_KEY |
Yes | — | Primary API key for authentication. Must be at least 32 characters. Passed via X-API-Key header. |
API_KEY_NEXT |
No | — | Optional secondary API key for zero-downtime key rotation. |
THINGS_EMAIL |
Yes | — | Your Things Cloud account email |
THINGS_PASSWORD |
Yes | — | Your Things Cloud account password |
SYNC_INTERVAL_SECONDS |
No | 0 |
Background sync interval in seconds. 0 disables background sync. Recommended: 60. |
ENABLE_SCHEDULER |
No | true |
Enable background scheduler in this process. |
SCHEDULER_LOCK_SECONDS |
No | 30 |
Distributed scheduler leadership lease duration. Only the lock owner runs background sync. |
SCHEDULER_HEARTBEAT_SECONDS |
No | 10 |
Lease renewal interval for scheduler leadership. |
MANUAL_SYNC_LOCK_SECONDS |
No | 120 |
Lease duration for manual sync lock to prevent overlapping POST /api/sync runs. |
SYNC_RETRY_ATTEMPTS |
No | 3 |
Number of retry attempts for transient cloud pull/push failures. |
SYNC_RETRY_BASE_SECONDS |
No | 0.25 |
Exponential backoff base delay for retries. |
SYNC_CIRCUIT_BREAKER_FAILURES |
No | 3 |
Consecutive sync failures required to open the circuit breaker. |
SYNC_CIRCUIT_BREAKER_COOLDOWN_SECONDS |
No | 60 |
Cooldown period while breaker is open before a half-open probe is allowed. |
READINESS_MAX_SYNC_ERRORS |
No | 5 |
Degrade /ready when total sync errors exceed this threshold. |
LOG_FORMAT |
No | text |
Set to json to enable structured JSON logging. |
ENABLE_METRICS |
No | false |
Set to true to expose GET /metrics (Prometheus-compatible counters). |
DATABASE_URL |
No | sqlite+aiosqlite:///./data/things.db |
SQLAlchemy database URL |
API Endpoints
All /api/* endpoints require the X-API-Key header.
Tasks
GET /api/tasks # List all non-trashed tasks
GET /api/tasks/{uuid} # Get a single task
POST /api/tasks # Create a task
PATCH /api/tasks/{uuid} # Update a task
DELETE /api/tasks/{uuid} # Soft-delete (trash) a task
Smart Lists
GET /api/tasks/inbox # Unscheduled tasks
GET /api/tasks/today # Tasks for today or earlier
GET /api/tasks/upcoming # Tasks scheduled for the future
GET /api/tasks/anytime # Tasks available anytime
GET /api/tasks/someday # Low-priority ideas
GET /api/tasks/logbook # Completed tasks (default: last 30 days, ?since=<epoch>)
GET /api/tasks/trash # Trashed tasks
Search
GET /api/tasks/search?q=<text> # Full-text search (title, notes, checklist items)
GET /api/tasks/search/advanced?... # Multi-predicate filter (status, type, schedule, area, project, tag, date ranges, modified/completed since)
Projects
GET /api/projects # List active projects (?include_completed=true to include completed)
POST /api/projects # Create a project
PATCH /api/projects/{uuid} # Update a project
POST /api/projects/{uuid}/complete # Mark a project as completed
DELETE /api/projects/{uuid} # Soft-delete (trash) a project
All smart lists, GET /api/tasks, and GET /api/tasks/by-tag/{tag} accept
optional ?limit=<int>&offset=<int> query params. Pagination is opt-in:
omit both to fetch the complete result set in a single response, which is
the recommended path for agents and scripts that need every task. To page
through a very large list, increment offset by limit and stop when the
returned array is shorter than limit.
Tags
GET /api/tags # List all tags
POST /api/tags # Create a tag
PATCH /api/tags/{uuid} # Update a tag
DELETE /api/tags/{uuid} # Delete a tag
GET /api/tasks/by-tag/{tag} # List tasks by tag UUID or name (?include_descendants=true&limit=&offset=)
Tasks now include a tags field in all responses. Pass tags: ["uuid-or-name", ...] when creating or updating tasks.
Areas
GET /api/areas # List all areas
Sync
GET /api/sync/status # Current sync state (status, head index, last sync time, errors)
POST /api/sync # Manually trigger a full pull + push cycle (rate limited; overlap protected by lock)
Health
GET /health # Liveness check (no auth required)
GET /ready # Readiness check (DB + sync degradation/circuit state)
GET /metrics # Prometheus-compatible counters (disabled by default, set ENABLE_METRICS=true)
Create a task
curl -X POST http://localhost:3117/api/tasks \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"title": "Buy milk", "schedule": 1}'
Update a task
curl -X PATCH http://localhost:3117/api/tasks/{uuid} \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"status": 3}'
Status values: 0 = pending, 2 = cancelled, 3 = completed.
Schedule values: 0 = inbox, 1 = anytime, 2 = someday.
Type values: 0 = task, 1 = project.
How Sync Works
The sync mechanism mirrors how Things3 itself operates:
| Trigger | Behavior |
|---|---|
| API write (create/update/delete) | Task is flagged for push. Next sync cycle sends it to Things Cloud. |
| Background interval | Pulls remote changes, then pushes local changes. Configurable via SYNC_INTERVAL_SECONDS. |
| Manual trigger | POST /api/sync runs an immediate pull + push cycle. |
Things Cloud uses an event-sourced model with a monotonically increasing index. Each sync pulls all changes since the last known index and applies them locally. Conflicts are resolved with remote-wins semantics.
Local Development
The repository uses a uv workspace:
- root package:
things-api - workspace packages:
things-sdk,things-cloud-mcp
# Install both packages in editable mode
uv sync
You can then run the API or import things_sdk directly in local scripts/tests.
Requires Python 3.12+ and uv.
# Install dependencies
uv sync
# Run the dev server
uv run uvicorn things_api.main:app --reload
# Run tests
uv run pytest -v
# Run type checks (current typed foundation)
uv run pyright
# Run migrations
uv run alembic upgrade head
Deploying with Docker
docker compose up -d
The Docker setup uses a named volume (things-data) to persist the SQLite database across container restarts. The docker-compose.yml pulls the published image from ghcr.io/nkootstra/things.
For local development, build from source instead:
docker compose -f docker-compose.dev.yml up -d
For production, put a reverse proxy (Caddy, nginx, Traefik) in front for TLS termination:
┌──────────┐ ┌──────────────┐
HTTPS :443 ───▶ │ Caddy │ ───▶ │ Things API │
│ (TLS) │ │ :8000 │
└──────────┘ └──────────────┘
CI / Release smoke checks
The automation now verifies both build artifacts and published artifacts:
- SDK smoke test: build wheel, install it into a clean virtualenv, import
things_sdk, and verify basic engine creation - Docker smoke test: build image, boot container, and verify
/health,/ready, and authenticatedGET /api/tasks - Post-release verification: after publication, install
things-sdk==<version>from PyPI and pullghcr.io/nkootstra/things:<version>from GHCR, then run the same basic checks against the published artifacts
This means a green release is not just "built" — it is also verified as installable from PyPI and runnable from GHCR.
Releasing a New Version
Releases are fully automated via GitHub Actions. Pushing a version tag triggers the pipeline:
preflight (tests) ─┬─▶ build (Docker image) ─▶ release (GitHub release)
├─▶ publish-sdk (PyPI)
└─▶ verify-published-artifacts
To release:
./scripts/release.sh 0.2.1
The script will:
- update versions in both
pyproject.tomlfiles - run
uv sync --dev - run the full test suite
- commit
release: vX.Y.Z - create tag
vX.Y.Z - push the commit and tag
Useful flags:
./scripts/release.sh 0.2.1 --no-push
./scripts/release.sh 0.2.1 --skip-tests
Manual fallback:
git add pyproject.toml packages/things-sdk/pyproject.toml
git commit -m "release: v0.2.1"
git tag v0.2.1
git push && git push --tags
This will:
- Run all tests (preflight gate)
- Build and push the Docker image to
ghcr.io/nkootstra/thingswith tags0.2.0,0.2, andlatest - Publish
things-sdkto PyPI - Create a GitHub release with auto-generated release notes
Note: PyPI publishing uses trusted publishers. You must configure the GitHub Actions publisher for
things-sdkon PyPI before the first publish.
Project Structure
This project is a monorepo with two packages:
| Package | Path | Description |
|---|---|---|
things-sdk |
packages/things-sdk/ |
Reusable core library — models, cloud client, sync engine, task operations |
things-api |
root | FastAPI HTTP service built on top of the SDK |
things-cloud-mcp |
packages/things-mcp/ |
MCP server for AI agents (Claude, Codex, etc.) |
You can use them together (run the API) or install only the SDK for scripts, CLIs, or other integrations.
SDK standalone usage
from things_sdk import ThingsClient, TaskService, configure_sync, create_engine_and_session, init_db, pull_sync
engine, session_factory = create_engine_and_session("sqlite+aiosqlite:///data/things.db")
await init_db(engine)
configure_sync(my_config)
client = ThingsClient(email="...", password="...")
async with session_factory() as session:
await pull_sync(client, session)
tasks = await TaskService().list_tasks(session)
await client.close()
See packages/things-sdk/README.md for full SDK documentation.
Directory layout
packages/things-sdk/src/things_sdk/ # SDK (reusable core)
├── __init__.py # Public API exports
├── protocols.py # CloudClientProtocol, SyncConfig
├── tasks.py # TaskService (CRUD + smart lists)
├── tags.py # TagService (CRUD + hierarchy resolution)
├── cloud/
│ ├── client.py # ThingsCloudClient
│ ├── handlers.py # Entity handler strategy pattern
│ ├── schema.py # Wire format Pydantic models
│ └── sync.py # Sync engine + circuit breaker
└── db/
├── engine.py # Engine factory
└── models.py # Domain models (Task, Tag, TaskTag, Area, etc.)
src/things_api/ # API (HTTP adapter)
├── main.py # FastAPI app, lifespan, scheduler
├── config.py # pydantic-settings configuration
├── auth.py # API key authentication
├── api/
│ └── routes.py # HTTP endpoints (tasks, smart lists, tags)
├── cloud/
│ └── scheduler.py # Background sync loop
└── services/
├── contracts.py # API-layer service protocols
├── health_service.py # Readiness checks
├── sync_service.py # Manual sync orchestration
├── task_service.py # Re-exports SDK TaskService
├── tag_service.py # Re-exports SDK TagService
├── task_command_mapper.py# Request DTO mapping
├── scheduler_leadership.py # Distributed lock
└── scheduler_runtime.py # Scheduler lifecycle
packages/things-mcp/src/things_mcp/ # MCP server
├── __init__.py # Entry point
├── server.py # FastMCP tools (22 tools)
├── client.py # HTTP client for things-api
└── __main__.py # python -m things_mcp
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。