Skip to main content
Glama
braveva

ATracker MCP Server

by braveva
README.md
# ATracker MCP Server

MCP server + standalone CLI for syncing [ATracker](https://atracker.pro) time tracking data to a local SQLite database. Works with Claude Desktop, Claude Code, or any MCP client.

## What it does

1. Calls the ATracker cloud API (reverse-engineered web sync endpoint)
2. Stores tasks, entries, tags, and task-tag relationships in a local SQLite DB
3. Supports incremental sync (only fetches changes since last sync) and full sync
4. Exposes 7 MCP tools for querying/exporting the data
5. Also works as a standalone CLI script (`atracker_sync.py`)

## Files

```
├── src/atracker_mcp/
│   ├── __init__.py          # Package init, version
│   └── server.py            # MCP server (7 tools: sync, timeline, summary, etc.)
├── atracker_sync.py         # Standalone sync CLI (no MCP dependency needed)
├── ecosystem.config.js      # PM2 config (optional, for auto-restart)
├── .env.example             # Template for credentials
├── .gitignore               # Excludes .env, *.db, venv, exports
├── pyproject.toml            # Python package config (hatchling)
├── requirements.txt          # Minimal deps: mcp, requests, python-dotenv
└── data/                    # Created at runtime
    ├── atracker.db          # SQLite database (gitignored)
    └── .sync_state.json     # Last sync timestamp (gitignored)
```

## Setup

### 1. Install dependencies

```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```

### 2. Get credentials

1. Open https://atracker.pro/Web/ and log in
2. Open browser DevTools (F12) → **Network** tab → filter "Fetch/XHR"
3. Do any action in the app (or refresh the page)
4. Click the `UpdateFromIOS` request
5. From **Headers**: copy the `ARRAffinity` cookie value
6. From **Payload**: copy the `uuid` value

### 3. Configure

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

Edit `.env`:
```env
ATRACKER_COOKIE=your_arraffinity_cookie_value
ATRACKER_UUID=your-uuid-from-payload
```

## Running

### Standalone CLI (simplest)

```bash
# First sync — pulls all historical data
python3 atracker_sync.py --full

# Subsequent syncs — only new changes
python3 atracker_sync.py

# Export to CSV
python3 atracker_sync.py --export timeline.csv

# Export last 7 days to JSON
python3 atracker_sync.py --export week.json --days 7

# Export today only
python3 atracker_sync.py --export today.csv --today
```

### Cron (production setup)

Add to crontab for automatic sync every 15 minutes:

```bash
crontab -e
```

```cron
*/15 * * * * cd /path/to/ATracker-MCP && source venv/bin/activate && python3 atracker_sync.py >> /var/log/atracker-sync.log 2>&1
```

### PM2 (alternative)

```bash
pm2 start ecosystem.config.js
```

This runs the sync script and restarts it every 5 minutes (via `restart_delay`).

### MCP Server (for Claude Desktop / Claude Code)

Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "atracker": {
      "command": "python3",
      "args": ["-m", "atracker_mcp.server"],
      "cwd": "/path/to/ATracker-MCP",
      "env": {
        "PYTHONPATH": "/path/to/ATracker-MCP/src"
      }
    }
  }
}
```

## MCP Tools

| Tool | Description |
|------|-------------|
| `atracker_sync` | Sync from ATracker cloud. `full=true` for all data, default incremental |
| `atracker_current_task` | Get the currently running/active task |
| `atracker_timeline` | Query entries. Filters: `limit`, `date` (YYYY-MM-DD), `days`, `task` (name search) |
| `atracker_summary` | Time totals grouped by task. Filters: `date`, `days` |
| `atracker_tasks` | List all tasks with entry counts |
| `atracker_export` | Export to CSV/JSON. Filters: `format`, `days`, `output` filename |
| `atracker_stats` | DB stats: task/entry counts, date range, last sync time |

## Database Schema

SQLite with 4 tables:

```sql
tasks (task_id TEXT PK, name TEXT, category TEXT, color TEXT, icon TEXT, tag_id TEXT)
entries (entry_id TEXT PK, task_id TEXT FK, start_time REAL, end_time REAL, notes TEXT)
tags (tag_id TEXT PK, name TEXT, color TEXT)
task_tags (task_id TEXT, tag_id TEXT, PK(task_id, tag_id))
```

- `start_time` / `end_time` are Unix timestamps (seconds)
- Running tasks have `end_time = 0` or `end_time < start_time`
- Indexes on `entries(start_time)` and `entries(task_id)`

## Querying Tags

Tags are linked to tasks via the `task_tags` junction table. Here's how to query them:

### Get all tasks with their tags

```sql
SELECT 
    t.task_id,
    t.name AS task_name,
    GROUP_CONCAT(g.name) AS tags
FROM tasks t
LEFT JOIN task_tags tt ON t.task_id = tt.task_id
LEFT JOIN tags g ON tt.tag_id = g.tag_id
GROUP BY t.task_id, t.name;
```

### Get timeline entries with tags

```sql
SELECT 
    e.entry_id,
    t.name AS task_name,
    datetime(e.start_time, 'unixepoch') AS started,
    datetime(e.end_time, 'unixepoch') AS ended,
    GROUP_CONCAT(g.name) AS tags
FROM entries e
JOIN tasks t ON e.task_id = t.task_id
LEFT JOIN task_tags tt ON t.task_id = tt.task_id
LEFT JOIN tags g ON tt.tag_id = g.tag_id
GROUP BY e.entry_id
ORDER BY e.start_time DESC
LIMIT 20;
```

### Filter entries by tag

```sql
SELECT 
    t.name AS task_name,
    SUM(e.end_time - e.start_time) / 3600.0 AS hours
FROM entries e
JOIN tasks t ON e.task_id = t.task_id
JOIN task_tags tt ON t.task_id = tt.task_id
JOIN tags g ON tt.tag_id = g.tag_id
WHERE g.name = 'Work'
  AND e.start_time >= strftime('%s', 'now', '-7 days')
GROUP BY t.name
ORDER BY hours DESC;
```

### List all tags with task counts

```sql
SELECT 
    g.name AS tag,
    g.color,
    COUNT(DISTINCT tt.task_id) AS task_count
FROM tags g
LEFT JOIN task_tags tt ON g.tag_id = tt.tag_id
GROUP BY g.tag_id
ORDER BY task_count DESC;
```

## API Details

The sync uses ATracker's web sync endpoint (`PUT /api/services/app/ATUpdateFromIOS/UpdateFromIOS`). Key points:

- Auth via `ARRAffinity` cookie (same value in both `ARRAffinity` and `ARRAffinitySameSite`)
- Header `Abp.TenantId: 1` is required
- `lastSyncTimeStampInApp` controls incremental sync (0 = full sync, Unix timestamp = changes since)
- Response contains `result.updateTask`, `result.updateTaskEntry`, `result.updateTag`, `result.updateTaskTag` arrays
- Cookie typically valid ~1 week; re-login at web app to refresh

## Privacy

- Credentials in `.env` (gitignored)
- All data stored locally in SQLite
- Only communicates with `atracker.pro` official API