Skip to main content
Glama
README.md
# ticktick-md-mcp

A Python tool for importing and exporting TickTick notes and tasks to Markdown and JSON formats.

**Three ways to use:**
1. šŸ–„ļø **Terminal CLI** - Interactive command-line interface
2. šŸ **Python Library** - Import and use in your own code
3. šŸ¤– **MCP Server** - AI integration with Claude (via Model Context Protocol)

## Features

- OAuth 2.0 authentication with TickTick
- **Import tasks from Markdown files** with tags and project assignment
- Export tasks filtered by tag or entire projects
- Multiple export formats (Markdown, JSON)
- Automatic date extraction from task titles
- Extensible exporter architecture
- **šŸ†• MCP Server** - Expose functionality to Claude AI via Model Context Protocol

## Installation

### Prerequisites

- Python 3.11 or higher
- TickTick account
- TickTick OAuth app credentials ([Setup Guide](#ticktick-oauth-setup))

### Install Dependencies

```bash
# Using pip
pip install -r requirements.txt

# Or using uv (recommended)
uv pip install -r requirements.txt
```

## Quick Start

### 1. Set Up Environment Variables

Create a `.env` file in the project root:

```bash
cp .env.example .env
```

Edit `.env` and add your TickTick OAuth credentials:

```env
TICKTICK_CLIENT_ID=your_client_id_here
TICKTICK_CLIENT_SECRET=your_client_secret_here
TICKTICK_REDIRECT_URI=http://localhost:8000/auth/ticktick/callback
TICKTICK_ACCESS_TOKEN=your_access_token_here
```

### 2. Get Access Token

Run the token helper to authenticate:

```bash
python -m src.utils.get_token
```

Follow the prompts to:
1. Visit the authorization URL
2. Authorize the app
3. Copy the authorization code from the redirect URL
4. The script will save your access token to `.env`

### 3. Run the CLI

```bash
python ticktick_cli.py
```

The interactive menu provides all functionality:
1. **Import tasks from Markdown** - Create tasks from markdown files with tags
2. **Export notes by tag** - Filter and export tasks by tag
3. **Export entire projects** - Export all tasks from a project
4. **List tags in a project** - See available tags
5. **Exit**

Choose your output format (Markdown or JSON) when exporting.

## Usage

The CLI provides a unified interface for all operations:

### Importing Tasks from Markdown

Run the CLI and select option 1:

```bash
python ticktick_cli.py
# Then select: 1. Import tasks from Markdown
```

You'll be prompted to:
1. Enter the markdown file path
2. Choose a default project (or leave empty for Inbox)
3. Preview (dry run) or import directly

#### Markdown Format

Tasks should be formatted as bullet lists with checkboxes:

```markdown
- [ ] Review pull request
  Tags: code-review, urgent
  Project: Work Tasks
  Check for performance issues and security

- [ ] Buy groceries
  Tags: shopping, personal
  Milk, bread, eggs

- [x] Completed task
  Tags: done
```

**Metadata Fields:**
- **Tags:** Comma-separated list of tags (e.g., `Tags: work, urgent`)
- **Project:** TickTick project/list name (e.g., `Project: Work Tasks`)
- **Description:** Multi-line task description (any non-metadata text after the title)

**Task States:**
- `- [ ]` - Unchecked task (will be imported)
- `- [x]` - Completed task (skipped during import)

### Exporting Tasks

Run the CLI and select option 2 or 3:

```bash
python ticktick_cli.py
# Then select:
# 2. Export notes by tag
# 3. Export entire project
```

**Export options:**
- Choose between Markdown or JSON format
- Specify output filename

### Programmatic Usage

If you need to use the functionality in your own Python scripts:

```python
from src.exporters.notes_exporter import NotesManager, MarkdownExporter, JSONExporter
from src.importers.task_importer import TaskImporter

# Initialize
manager = NotesManager()

# Export by tag (Markdown)
manager.export_by_tag(
    tag_name="journal",
    output_file="journal.md",
    project_name="On My Mind"
)

# Export by tag (JSON)
manager.export_by_tag(
    tag_name="work",
    output_file="work_notes.json",
    project_name="Work Tasks",
    exporter=JSONExporter()
)

# Export entire project
manager.export_project(
    project_name="Personal",
    output_file="personal_notes.md"
)

# List all projects
manager.list_projects()

# List tags in a project
manager.list_tags(project_name="On My Mind")
```

### API Usage

Use the TickTick API wrapper directly:

```python
from src.api.ticktick_api import TickTickAPI, TickTickOAuth
import os

# Initialize API
api = TickTickAPI(os.getenv('TICKTICK_ACCESS_TOKEN'))

# Get user info
user = api.get_user_info()

# Get all projects
projects = api.get_projects()

# Get project data with tasks
project_data = api.get_projects_data(project_id)

# Create a task
task = api.create_task(
    title="New Task",
    content="Task description",
    project_id="project_id_here",
    tags=["work", "urgent"]
)
```

## MCP Server - AI Integration

The MCP (Model Context Protocol) server allows Claude AI to directly interact with your TickTick account for importing and exporting tasks.

### What is MCP?

MCP is a protocol that lets AI assistants like Claude access external tools and data sources. With the TickTick MCP server, you can ask Claude to:
- "Export my work tasks to markdown"
- "Import these tasks to my Home project"
- "List all my TickTick projects"
- "Create a task in my Work project"

### Quick Start

1. **Install MCP dependency:**
   ```bash
   pip install mcp>=1.0.0
   ```

2. **The `.mcp.json` file is already configured** in the project root - no setup needed!

3. **Set your access token:**
   ```bash
   export TICKTICK_ACCESS_TOKEN=your_token_here
   ```

4. **Use with Claude Code:**
   - Open this project in Claude Code
   - The MCP server will be automatically detected
   - Ask Claude to interact with TickTick!

### Available MCP Tools

The server exposes these tools to Claude:

#### `list-projects`
List all your TickTick projects/lists.
```
Example: "Show me all my TickTick projects"
```

#### `list-tags`
List all tags in a specific project.
```
Parameters:
- project_name: Name of the project

Example: "What tags are in my Work Tasks project?"
```

#### `export-by-tag`
Export tasks filtered by a specific tag.
```
Parameters:
- tag_name: Tag to filter by
- project_name: Project to export from
- format: 'markdown' or 'json' (default: markdown)

Example: "Export all tasks tagged 'urgent' from Work Tasks"
```

#### `export-project`
Export all tasks from an entire project.
```
Parameters:
- project_name: Project to export
- format: 'markdown' or 'json' (default: markdown)

Example: "Export my Personal project as markdown"
```

#### `import-from-markdown`
Import tasks from markdown content into TickTick.
```
Parameters:
- markdown_content: Markdown formatted task list
- project_name: Target project (defaults to Inbox)
- dry_run: Preview without creating (default: true)

Example: "Import these tasks to my Work project:
- [ ] Review documentation
  Tags: work, urgent
- [ ] Schedule meeting
  Tags: work"
```

#### `create-task`
Create a single task in TickTick.
```
Parameters:
- title: Task title
- project_name: Project to add to (optional)
- content: Task description (optional)
- tags: List of tags (optional)

Example: "Create a task 'Buy groceries' with tags shopping and personal"
```

### Example Claude Conversations

**Export tasks:**
```
You: "Export all my work tasks tagged 'urgent' as markdown"
Claude: *uses export-by-tag tool*
Claude: "Here are your urgent work tasks: [markdown content]"
```

**Import tasks:**
```
You: "Here's a list of tasks, import them to my Home project:
- [ ] Fix leaky faucet
  Tags: home, maintenance
- [ ] Plant garden
  Tags: home, weekend"

Claude: *uses import-from-markdown tool with dry_run=false*
Claude: "I've imported 2 tasks to your Home project"
```

**List and organize:**
```
You: "What projects do I have in TickTick?"
Claude: *uses list-projects tool*
Claude: "You have 5 projects: Work Tasks, Personal, Home Tasks, Shopping, Ideas"
```

### MCP Configuration

The `.mcp.json` file in the project root is pre-configured:

```json
{
  "mcpServers": {
    "ticktick": {
      "command": "python3",
      "args": ["ticktick_mcp_server.py"]
    }
  }
}
```

Make sure `TICKTICK_ACCESS_TOKEN` is set in your environment or `.env` file.

**For Claude Desktop** (if you use it), add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "ticktick": {
      "command": "python3",
      "args": ["/absolute/path/to/pyTickTick/ticktick_mcp_server.py"],
      "env": {
        "TICKTICK_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}
```

### Architecture

The MCP server reuses all existing code:
- Same API wrapper (`src/api/ticktick_api.py`)
- Same import logic (`src/importers/`)
- Same export logic (`src/exporters/`)

**No duplication** - the MCP server is a thin wrapper that exposes existing functionality to Claude via the Model Context Protocol.

### Troubleshooting MCP

**"Server not detected":**
- Ensure `.mcp.json` exists in project root
- Check that `TICKTICK_ACCESS_TOKEN` is set in environment
- Restart Claude Code

**"Authentication failed":**
- Run `python -m src.utils.get_token` to get a fresh token
- Update `.env` file with new token
- Set environment variable: `export TICKTICK_ACCESS_TOKEN=token`

**"Import not working":**
- Set `dry_run=False` in the import command
- Default is `dry_run=True` for safety (preview mode)

## Export Formats

### Markdown

Exports tasks as a formatted Markdown document:

```markdown
# Journal Notes

Exported on January 03, 2026 at 07:31 PM

Total notes: 7

---

## 2025-12-31 My Note Title

**Date:** December 31, 2025

Note content here...

---
```

**Features:**
- Tasks sorted by date (most recent first)
- Dates extracted from task titles (format: `YYYY-MM-DD`)
- Fallback to API dates if available
- Metadata header with export info

### JSON

Exports tasks as structured JSON:

```json
{
  "metadata": {
    "exported_at": "2026-01-03T19:31:00.123456",
    "total_tasks": 7,
    "tag_name": "journal",
    "project_name": "On My Mind"
  },
  "tasks": [
    {
      "id": "...",
      "title": "2025-12-31 My Note",
      "content": "Note content...",
      "tags": ["journal"],
      ...
    }
  ]
}
```

## Date Handling

The exporter intelligently handles dates:

1. **From Title** - Extracts dates from titles like `2025-12-31 Task Name`
   - Supports optional leading whitespace
   - Format: `YYYY-MM-DD` at the start of the title
   - Displays as: "December 31, 2025"

2. **From API** - Falls back to `modifiedTime` or `createdTime` if available
   - Note: TickTick's Open API may not return these fields for notes

3. **No Date** - Shows "No date" if neither source is available

## Utilities

### Get Token Helper

Quickly get a new OAuth access token:

```bash
python -m src.utils.get_token
```

### Debug Tags

Inspect task structure and find tags:

```bash
python -m src.utils.debug_tags
```

Shows:
- All projects in your account
- Sample task structures
- Tasks containing specific keywords
- Available tags

## Project Structure

```
pyTickTick/
ā”œā”€ā”€ šŸš€ ticktick_cli.py        # Main CLI - Interactive terminal interface
ā”œā”€ā”€ šŸ¤– ticktick_mcp_server.py # MCP Server - AI integration with Claude
│
ā”œā”€ā”€ src/                      # Source code (organized by function)
│   ā”œā”€ā”€ api/
│   │   └── ticktick_api.py   # TickTick API wrapper (OAuth + API client)
│   │
│   ā”œā”€ā”€ exporters/
│   │   └── notes_exporter.py # Export tasks to Markdown/JSON
│   │
│   ā”œā”€ā”€ importers/
│   │   ā”œā”€ā”€ task_importer.py  # Import tasks from Markdown
│   │   └── markdown_parser.py# Parse markdown task format
│   │
│   └── utils/
│       ā”œā”€ā”€ get_token.py      # OAuth token helper
│       └── debug_tags.py     # Task structure debugger
│
ā”œā”€ā”€ tests/                    # Comprehensive test suite
│   ā”œā”€ā”€ conftest.py           # Pytest fixtures and configuration
│   ā”œā”€ā”€ test_markdown_parser.py
│   ā”œā”€ā”€ test_task_importer.py
│   ā”œā”€ā”€ test_notes_exporter.py
│   └── test_ticktick_api.py
│
ā”œā”€ā”€ šŸ“ Config:
│   ā”œā”€ā”€ .env.example          # Template for credentials (copy to .env)
│   ā”œā”€ā”€ .mcp.json             # MCP server configuration for Claude
│   ā”œā”€ā”€ requirements.txt      # Python dependencies
│   ā”œā”€ā”€ pyproject.toml        # Project metadata
│   └── pytest.ini            # Pytest configuration
│
ā”œā”€ā”€ LICENSE                   # MIT License
└── README.md                 # This file
```

**Three Ways to Use:**
1. **Terminal CLI**: Run `python ticktick_cli.py` for interactive interface
2. **Python Library**: Import and use `src/` modules in your own code
3. **MCP with Claude**: Open project in Claude Code - MCP auto-detected!

**Getting Started:**
1. Run `python -m src.utils.get_token` once to authenticate
2. **CLI**: Run `python ticktick_cli.py` for all import/export operations
3. **MCP**: Open project in Claude Code and ask Claude to interact with TickTick!
4. (Optional) Run `python -m src.utils.debug_tags` to inspect task structures

## TickTick OAuth Setup

### 1. Create a TickTick Developer App

1. Go to [TickTick Developer Portal](https://developer.ticktick.com/)
2. Sign in with your TickTick account
3. Create a new app
4. Set the **Redirect URI** to: `http://localhost:8000/auth/ticktick/callback`
5. Copy your **Client ID** and **Client Secret**

### 2. Configure Environment

Add your credentials to `.env`:

```env
TICKTICK_CLIENT_ID=your_client_id_from_developer_portal
TICKTICK_CLIENT_SECRET=your_client_secret_from_developer_portal
TICKTICK_REDIRECT_URI=http://localhost:8000/auth/ticktick/callback
```

### 3. Get Access Token

Run `python -m src.utils.get_token` and follow the prompts.

## Troubleshooting

### "No access token found"

Run `python -m src.utils.get_token` to authenticate and get a new token.

### "Failed to fetch projects"

- Verify your access token is valid
- Check that your OAuth app has the correct scopes: `tasks:read tasks:write`
- Try getting a new token with `python -m src.utils.get_token`

### "No date" showing for all tasks

- The TickTick Open API doesn't return `createdTime`/`modifiedTime` for notes
- Add dates to your task titles in format `YYYY-MM-DD Task Name`
- The exporter will automatically extract and format these dates

### Import errors

Make sure all dependencies are installed:

```bash
pip install -r requirements.txt
```

## Development

### Testing

The project includes a comprehensive test suite using pytest.

#### Running Tests

```bash
# Install test dependencies
pip install -r requirements.txt

# Run all tests
pytest

# Run with coverage report
pytest --cov=src --cov-report=html

# Run specific test file
pytest tests/test_markdown_parser.py

# Run tests with specific marker
pytest -m unit        # Run only unit tests
pytest -m integration # Run only integration tests
pytest -m api         # Run only API tests

# Run with verbose output
pytest -v
```

#### Test Structure

- **tests/conftest.py** - Shared fixtures and test configuration
- **tests/test_markdown_parser.py** - Tests for markdown parsing (40+ tests)
- **tests/test_task_importer.py** - Tests for task import functionality
- **tests/test_notes_exporter.py** - Tests for export functionality
- **tests/test_ticktick_api.py** - Tests for API wrapper

#### Test Coverage

The test suite covers:
- Markdown parsing with various formats
- Task import with dry-run and actual creation
- Export to Markdown and JSON formats
- API wrapper with mocked responses
- Edge cases and error handling
- Project and tag filtering

#### Writing New Tests

```python
# tests/test_example.py
import pytest

@pytest.mark.unit
def test_example(sample_markdown_content):
    """Test description"""
    # Your test code here
    assert True
```

Available fixtures:
- `sample_markdown_content` - Sample markdown for testing
- `mock_ticktick_api` - Mocked API instance
- `mock_env_token` - Mocked environment token
- `sample_tasks` - Sample task data

### File Responsibilities

- **src/api/ticktick_api.py** - Pure API wrapper, no business logic
- **src/exporters/notes_exporter.py** - Export logic and functionality
- **src/importers/task_importer.py** - Import logic and functionality
- **src/utils/get_token.py** - Simple OAuth flow helper
- **src/utils/debug_tags.py** - Debugging and inspection utility
- **ticktick_cli.py** - Main unified CLI interface

### Adding New Export Formats

Extend the `BaseExporter` class:

```python
from src.exporters.notes_exporter import BaseExporter

class CSVExporter(BaseExporter):
    def export(self, tasks, output_file, metadata):
        # Your CSV export logic here
        pass
```

Then use it:

```python
manager.export_by_tag(
    tag_name="notes",
    output_file="notes.csv",
    exporter=CSVExporter()
)
```

## Requirements

- python-dotenv >= 1.1.1
- requests >= 2.26.0

See `requirements.txt` for complete list.

## License

MIT License - see [LICENSE](LICENSE) for details.

## Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

## Roadmap

Future ideas under consideration:
- Full CLI interface with Click
- Additional export formats (CSV, HTML, PDF)
- Task management features (update, delete, complete)
- Configuration system
- Package distribution on PyPI

## Support

For issues or questions:
1. Check the [Troubleshooting](#troubleshooting) section
2. Review the [TickTick API Documentation](https://developer.ticktick.com/)
3. Open an issue (once repository is public)