This document outlines guidelines and best practices for contributors working on ticktick-access. It covers version management, development setup, testing, and documentation standards.
Single source of truth: ticktick/__init__.py contains __version__
When to bump versions:
| Change Type | Bump | Example |
|---|---|---|
| Bug fix, minor tweak | Patch | 0.2.0 → 0.2.1 |
| New feature (backwards compatible) | Minor | 0.2.0 → 0.3.0 |
| Breaking change | Major | 0.2.0 → 1.0.0 |
Release workflow:
- Make changes, commit normally
- When ready to release:
# Update version in ticktick/__init__.py # Commit the version bump git add ticktick/__init__.py git commit -m "chore: bump version to X.Y.Z" # Tag the release git tag vX.Y.Z # Push with tags git push && git push --tags
Why version bumps matter:
pip install --upgradeonly installs if version number is higher- Same version number = pip thinks nothing changed, skips update
- Editable installs (
pip install -e .) always use live code regardless of version
For development: Use editable install to avoid version concerns:
pipx install -e . # or: pip install -e .To upgrade an existing installation:
# Upgrade via pipx
pipx upgrade ticktick-access
# Or reinstall from latest
pipx uninstall ticktick-access
pipx install git+https://github.com/USER/ticktick-access.gitAll business logic must live in the SDK layer. The MCP and CLI layers must remain thin wrappers that simply call SDK functions.
ticktick-access/
├── ticktick/
│ ├── __init__.py # Package version
│ ├── config.py # Config/credential management
│ ├── auth.py # OAuth flow
│ ├── sdk/ # Core SDK layer - ALL logic here
│ │ ├── __init__.py
│ │ ├── client.py # TickTick API client (HTTP layer)
│ │ ├── projects.py # Project operations
│ │ └── tasks.py # Task operations
│ ├── cli.py # CLI commands (thin, calls SDK)
│ └── mcp/
│ └── server.py # MCP server (thin, calls SDK)
| Layer | Responsibility | Example |
|---|---|---|
| SDK | All business logic, API calls, data transformation | sdk.tasks.create_task(project_id, title, reminders=[...]) |
| MCP | Tool decorators, parameter descriptions, error wrapping | Wraps SDK call in @mcp.tool() |
| CLI | Argument parsing, user output formatting | Calls SDK, prints results |
When adding a new feature (e.g., reminders support):
- Implement in SDK first - Add the logic to the appropriate SDK module
- Expose in MCP - Add a thin tool wrapper that calls the SDK
- Expose in CLI (optional) - Add a command that calls the SDK
Example - Adding reminders:
# sdk/tasks.py - The actual implementation
async def create_task(project_id, title, reminders=None, ...):
task_data = {"projectId": project_id, "title": title}
if reminders:
task_data["reminders"] = reminders
return await client.post("task", task_data)
# mcp/server.py - Thin wrapper
@mcp.tool()
async def create_task(project_id: str, title: str, reminders: list[str] = None):
"""Create a new task.""" # MCP-specific description
return await sdk.tasks.create_task(project_id, title, reminders=reminders)- Single source of truth - Logic in one place, not duplicated
- Testable - Test SDK once, all clients benefit
- Consistent - CLI and MCP behave identically
- Extensible - Third-party apps can use the SDK directly
For local development, install in editable mode:
To run tests and linting, you'll need to install the development dependencies:
-
Create and activate a virtual environment:
python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
-
Install the project with development dependencies:
pip install -e ".[dev]"This installs:
pytestandpytest-asynciofor running testsrespxfor mocking HTTP requests in testsrufffor linting and code formatting
The project includes a comprehensive test suite using pytest. All tests use mocked HTTP requests (via respx) so they run without network calls.
# Ensure virtual environment is activated
source venv/bin/activate
# Run all tests with verbose output
pytest tests/unit/test_server.py -v# Run a specific test by name
pytest tests/unit/test_server.py::test_get_ticktick_projects -v
# Run tests matching a pattern
pytest tests/unit/test_server.py -k "update_task" -vThe test suite currently includes 11 tests covering:
- ✅ Core HTTP request functionality (
make_ticktick_request) - ✅ Getting list of projects (
get_ticktick_projects) - ✅ Getting list of tasks in a project (
get_ticktick_tasks) - ✅ Getting details of a task (via
make_ticktick_requestwith GETtask/{task_id}) - ✅ Updating a task (
update_task) - ✅ Error handling for all operations
- ✅ Authorization header verification
The project uses ruff for linting and code formatting.
# Ensure virtual environment is activated
source venv/bin/activate
# Check for linting errors
ruff check server.py tests/# Fix auto-fixable linting issues
ruff check --fix server.py tests/# Check if files need formatting
ruff format --check server.py tests/
# Format files automatically
ruff format server.py tests/Linting rules are configured in pyproject.toml:
- Line length: 120 characters
- Enabled checks: pycodestyle errors/warnings, pyflakes, isort, pep8-naming, pyupgrade, flake8-comprehensions, flake8-bugbear
- Complexity checks enabled (C901)
Before committing code, ensure:
- ✅ All tests pass:
pytest tests/unit/test_server.py -v - ✅ No linting errors:
ruff check server.py tests/ - ✅ Code is properly formatted:
ruff format --check server.py tests/
For detailed instructions on manual functional integration testing, see INTEGRATION-TESTING.md.
Quick Start (Recommended):
# Simple Python script that tests functions directly (no Docker/HTTP needed)
source venv/bin/activate
source .env
python test_integration_simple.pyThis guide covers:
- Simple Python script - Direct function testing (recommended, no Docker/SSE complexity)
- Testing
list_projectstool - Testing
list_taskstool - Testing getting task details
- Testing
update_tasktool - Error case testing
- Docker + curl testing (advanced, requires SSE handling)
When you make changes to existing tools (e.g., in server.py) or add new ones, you need to ensure these changes are properly tested within a consistent environment. The recommended approach for testing tool changes is by using Docker.
-
Modify Tool Code: Make your changes to
server.pyor any other relevant Python files defining your tools. -
Rebuild Docker Image:
cd ticktick-mcp # Navigate to the project root docker build -t ticktick-mcp-server:latest .
This command rebuilds your Docker image, incorporating your latest code changes.
-
Stop and Remove Existing Container (if running):
docker stop ticktick-mcp-server || true # `|| true` prevents error if container isn't running docker rm ticktick-mcp-server || true # `|| true` prevents error if container isn't running
These commands clear out any old running container instances to ensure a fresh start with your updated image.
-
Run a New Container:
source .env # Ensure your TICKTICK_ACCESS_TOKEN is loaded from your .env file docker run -d \ --name ticktick-mcp-server \ -p 8000:8000 \ -e TICKTICK_ACCESS_TOKEN="$TICKTICK_ACCESS_TOKEN" \ ticktick-mcp-server:latest
This starts a fresh Docker container with your updated code. The
-dflag runs it in detached mode. -
Test with your MCP Client (e.g., Gemini CLI): Once the container is running, use your MCP client to interact with the server and test your updated/new tools. For instance, to test an
update_tasktool:call:update_task(task_id="GENERIC_TASK_ID", project_id="GENERIC_PROJECT_ID", title="New Title", content="New Content")Important: Always use generic placeholders like
GENERIC_TASK_IDorGENERIC_PROJECT_IDin your documentation and examples. Never commit real task IDs, project IDs, or other sensitive personal information to the repository.
- Tool Not Found Error: If your MCP client (e.g., Gemini CLI) reports a "Tool not found in registry" error after updating code and restarting the container, it might indicate that the client's internal tool registry has not refreshed. A full restart of your MCP client session might be necessary to force it to re-discover tools.
VENDOR-API.md: Strictly for raw API interaction documentation.README.md: For usage and configuration of the MCP server.CONTRIBUTING.md(this file): For developer-specific guides and maintenance information.GEMINI.md: For internal notes and guidelines for the Gemini CLI agent.
TICKTICK_ACCESS_TOKEN: This token is sensitive and must always be loaded from a secure source (e.g.,.envfile or environment variables). It should never be hardcoded or committed to the repository.
By following these guidelines, we can ensure a consistent and secure development experience.