Things Cloud MCP
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Things Cloud MCPshow my tasks for today"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
|
You want to build a CLI, script, worker, or integration in Python |
|
You want AI agents to read/write your tasks |
|
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.
Related MCP server: Things MCP
Quick Start
cp .env.example .env
# Edit .env with your Things Cloud credentials and a strong API key
docker compose up -dThe 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 |
| Yes | — | Primary API key for authentication. Must be at least 32 characters. Passed via |
| No | — | Optional secondary API key for zero-downtime key rotation. |
| Yes | — | Your Things Cloud account email |
| Yes | — | Your Things Cloud account password |
| No |
| Background sync interval in seconds. |
| No |
| Enable background scheduler in this process. |
| No |
| Distributed scheduler leadership lease duration. Only the lock owner runs background sync. |
| No |
| Lease renewal interval for scheduler leadership. |
| No |
| Lease duration for manual sync lock to prevent overlapping |
| No |
| Number of retry attempts for transient cloud pull/push failures. |
| No |
| Exponential backoff base delay for retries. |
| No |
| Consecutive sync failures required to open the circuit breaker. |
| No |
| Cooldown period while breaker is open before a half-open probe is allowed. |
| No |
| Degrade |
| No |
| Set to |
| No |
| Set to |
| No |
| 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 taskSmart 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 tasksSearch
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 projectAll 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 areasSync
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 |
Manual trigger |
|
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-apiworkspace packages:
things-sdk,things-cloud-mcp
# Install both packages in editable mode
uv syncYou 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 headDeploying with Docker
docker compose up -dThe 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 -dFor 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 creationDocker smoke test: build image, boot container, and verify
/health,/ready, and authenticatedGET /api/tasksPost-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-artifactsTo release:
./scripts/release.sh 0.2.1The script will:
update versions in both
pyproject.tomlfilesrun
uv sync --devrun the full test suite
commit
release: vX.Y.Zcreate tag
vX.Y.Zpush the commit and tag
Useful flags:
./scripts/release.sh 0.2.1 --no-push
./scripts/release.sh 0.2.1 --skip-testsManual 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 --tagsThis will:
Run all tests (preflight gate)
Build and push the Docker image to
ghcr.io/nkootstra/thingswith tags0.2.0,0.2, andlatestPublish
things-sdkto PyPICreate 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 |
|
| Reusable core library — models, cloud client, sync engine, task operations |
| root | FastAPI HTTP service built on top of the SDK |
|
| 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_mcpMaintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nkootstra/things'
If you have feedback or need assistance with the MCP directory API, please join our Discord server