MCP server for AI agents to manage Dida365 / TickTick tasks and projects
A Python MCP server built on FastMCP that connects AI agents (Claude Code, Cursor, Windsurf, etc.) to the Dida365 / TickTick Open API. Manage tasks and projects through natural language.
Supports both Dida365 (China) and TickTick (International) — switch with a single env var.
- 40 tools — 19 V1 (official Open API) + 21 V2 (private API: tags, search, habits, folders, parent tasks)
- Dual transport —
stdiofor local clients;streamable-httpfor remote agents - Dual platform — Dida365 and TickTick via
DIDA365_REGIONconfig - One-click OAuth — script auto-opens browser, receives callback, saves token
- Docker ready —
docker compose upto deploy - Agent-friendly — structured JSON responses, clear tool annotations, actionable error messages
Four options, ordered from simplest to most complex.
No git clone, no venv. Requires only uv.
1. Get API credentials
Create an app at the developer portal and copy the Client ID and Secret:
- Dida365: https://developer.dida365.com/manage
- TickTick: https://developer.ticktick.com/manage
Set Redirect URI to http://localhost:8000/oauth/callback.
2. Create .env and authorize
# Create config file
cat > .env << 'EOF'
DIDA365_REGION=china
DIDA365_CLIENT_ID=your_client_id
DIDA365_CLIENT_SECRET=your_client_secret
EOF
# Run OAuth — opens browser, saves token (~180 days)
uvx --from dida365-agent-mcp dida365-oauth3. Connect your AI client
Claude Code
Edit ~/.claude/mcp.json:
{
"mcpServers": {
"dida365": {
"command": "uvx",
"args": ["dida365-agent-mcp"]
}
}
}Cursor
Go to Settings > MCP > Add new global MCP server:
{
"mcpServers": {
"dida365": {
"command": "uvx",
"args": ["dida365-agent-mcp"]
}
}
}pip install dida365-agent-mcpThen follow the same steps as Option A, replacing uvx --from dida365-agent-mcp dida365-oauth with dida365-oauth and uvx dida365-agent-mcp with dida365-mcp.
git clone https://github.com/linhai0872/dida365-agent-mcp.git
cd dida365-agent-mcp
uv sync
cp .env.example .env # fill in CLIENT_ID and CLIENT_SECRET
uv run python scripts/oauth_flow.pyMCP client config:
{
"mcpServers": {
"dida365": {
"command": "uv",
"args": ["--directory", "/path/to/dida365-agent-mcp", "run", "dida365-mcp"]
}
}
}For always-on or team deployments.
1. Get the access token first (run OAuth from Option A, B, or C)
2. Deploy
git clone https://github.com/linhai0872/dida365-agent-mcp.git
cd dida365-agent-mcp
cp .env.example .env
# Set DIDA365_REGION and DIDA365_ACCESS_TOKEN in .env
docker compose up -dConnect to http://your-host:8000/mcp.
The container cannot open a browser, so set
DIDA365_ACCESS_TOKENdirectly instead of running the OAuth script inside Docker.
Add one of the following to your .env (or MCP client env block):
# Method 1: Session token (manual, most secure, 30-day expiry)
# Browser DevTools → Application → Cookies → copy 't' value
DIDA365_V2_SESSION_TOKEN=your_token
# Method 2: Auto-login (convenient, does not support 2FA)
DIDA365_USERNAME=your_email
DIDA365_PASSWORD=your_passwordWithout V2 config, you still get 19 V1 tools. With V2, you get all 40.
Talk to your AI agent naturally:
- "List all my projects"
- "Create a high-priority task 'Review PR' in Work, due tomorrow"
- "What tasks did I complete this week?"
- "Move 'Design review' to the Archive project"
- "Search tasks containing 'meeting'" (V2)
- "List all my tags" (V2)
- "Did I check in my reading habit today?" (V2)
| Tool | Description |
|---|---|
dida365_create_task |
Create a task with title, project, dates, priority, tags, reminders, recurrence |
dida365_update_task |
Update any task fields (partial update) |
dida365_complete_task |
Mark a task as completed |
dida365_delete_task |
Permanently delete a task |
dida365_get_task |
Get full details of a single task |
dida365_move_task |
Move a task between projects |
| Tool | Description |
|---|---|
dida365_get_project_tasks |
Get all uncompleted tasks in a project |
dida365_filter_tasks |
Filter by project, date range, priority, tags, status |
dida365_get_completed_tasks |
Get completed tasks within a time range |
| Tool | Description |
|---|---|
dida365_list_projects |
List all projects (call this first to get IDs) |
dida365_get_project |
Get project details |
dida365_create_project |
Create a project (list / kanban / timeline view) |
dida365_update_project |
Update project properties |
dida365_delete_project |
Permanently delete a project and all its tasks |
| Tool | Description |
|---|---|
dida365_get_task_by_id |
Get a task by ID only (no project_id needed) |
dida365_list_undone_tasks |
List undone tasks by date range or project |
dida365_batch_create_tasks |
Batch create multiple tasks |
dida365_batch_update_tasks |
Batch update multiple tasks |
dida365_batch_complete_tasks |
Batch complete multiple tasks |
| Tool | Description |
|---|---|
dida365_search_tasks |
Server-side full-text search with keyword, project, tag, status, date filters |
dida365_list_tags |
List all tags |
dida365_create_tags |
Batch create tags |
dida365_update_tags |
Batch update tags |
dida365_delete_tags |
Batch delete tags |
dida365_delete_tag |
Delete a single tag by name |
dida365_set_task_parent |
Set parent-child task relationship |
dida365_unset_task_parent |
Remove parent, make task top-level |
dida365_pin_task |
Pin or unpin a task |
dida365_list_habits |
List all habits |
dida365_create_habit |
Batch create habits |
dida365_update_habit |
Batch update habits |
dida365_delete_habit |
Batch delete habits |
dida365_checkin_habit |
Check in a habit for a date |
dida365_undo_checkin |
Undo a habit checkin |
dida365_list_habit_checkins |
List checkin records for a habit |
dida365_list_habit_sections |
List habit sections/groups |
dida365_list_folders |
List project folders |
dida365_create_folder |
Create a project folder |
dida365_update_folder |
Update a project folder |
dida365_delete_folder |
Delete a project folder |
| Variable | Description | Default |
|---|---|---|
DIDA365_REGION |
china (dida365.com) or international (ticktick.com) |
china |
DIDA365_CLIENT_ID |
OAuth Client ID | — |
DIDA365_CLIENT_SECRET |
OAuth Client Secret | — |
DIDA365_ACCESS_TOKEN |
Access token (set directly to skip OAuth) | — |
DIDA365_REDIRECT_URI |
OAuth callback URL | http://localhost:8000/oauth/callback |
DIDA365_V2_SESSION_TOKEN |
V2 session token (browser cookie t, 30-day expiry) |
— |
DIDA365_USERNAME |
V2 auto-login email/phone (alternative to session token) | — |
DIDA365_PASSWORD |
V2 auto-login password (does not support 2FA) | — |
TRANSPORT |
stdio, streamable-http, or sse (legacy) |
stdio |
HOST |
Bind address (http / sse only) | 0.0.0.0 |
PORT |
Port (http / sse only) | 8000 |
| Item | Detail |
|---|---|
| Validity | ~180 days |
| Auto-refresh | Not supported (API limitation) |
| Expiry detection | Built-in, warns 24h before expiry |
| Renewal | Re-run dida365-oauth (or uvx --from dida365-agent-mcp dida365-oauth) |
| Storage | ~/.dida365-agent-mcp/token.json (auto-loaded) |
uv sync # Install dependencies
uv run pytest tests/ -v # Run tests
uv run ruff check src/ tests/ scripts/ # Lint
uv run ruff format src/ tests/ scripts/ # Format
uv run pyright src/ # Type check
uv run dida365-mcp # Run (stdio)
TRANSPORT=streamable-http uv run dida365-mcp # Run (Streamable HTTP)dida365-agent-mcp/
├── src/dida365_agent_mcp/
│ ├── server.py # FastMCP server + 19 V1 tool definitions + MCP resource
│ ├── server_v2.py # 21 V2 tool definitions (tags, search, habits, folders)
│ ├── client.py # V1 async API client (httpx + OAuth)
│ ├── client_v2.py # V2 async API client (httpx + session cookie)
│ ├── auth.py # OAuth2 flow + token management
│ ├── models.py # Pydantic data models (V1 + V2)
│ └── config.py # Region-aware configuration
├── scripts/
│ └── oauth_flow.py # One-click OAuth script
├── tests/ # 62 unit tests (respx)
├── Dockerfile # Multi-stage build
└── docker-compose.yml
- Dida365 / TickTick Open API
- FastMCP — Python MCP framework
- Model Context Protocol