README.md•9 kB
# Toggl MCP Server
A FastMCP-based Model Context Protocol (MCP) server that fetches and aggregates Toggl time tracking data with intelligent Fibery entity reference parsing and caching.
## Features
- **Time Entry Aggregation**: Fetch and aggregate time entries from Toggl API
- **Fibery Entity Parsing**: Automatically extract Fibery entity references from Toggl descriptions (#ID [DB] [TYPE])
- **Caching**: Smart caching with SQLite index and JSON file storage (1-hour TTL)
- **Rate Limiting**: Exponential backoff handling for Toggl API rate limits
- **User Filtering**: Optional filtering by single user or retrieve all workspace users
- **Date Range Validation**: Enforces ISO 8601 format and max 7-day ranges per request
## Installation
### Prerequisites
- Python 3.11+
- uv (package manager)
### Setup
1. Clone the repository:
```bash
git clone <repository-url>
cd toggl-mcp-custom
```
2. Create virtual environment:
```bash
uv venv
source .venv/bin/activate
```
3. Install dependencies:
```bash
uv pip install -r requirements.txt
```
4. Configure environment:
```bash
cp .env.example .env
# Edit .env with your Toggl API credentials
```
## Configuration
Create a `.env` file in the project root with your Toggl credentials:
```env
TOGGL_API_TOKEN=your_api_token_here
TOGGL_WORKSPACE_ID=your_workspace_id_here
```
Get your credentials:
- **API Token**: Available at https://track.toggl.com/app/profile
- **Workspace ID**: Available in Toggl settings or API responses
## Using with Claude Code
### Option 1: Project-Local Integration (Recommended)
The easiest way is to add the MCP server to your project's `.mcp.json` file:
```json
{
"mcpServers": {
"toggl": {
"command": "/path/to/toggl-mcp-custom/.venv/bin/python",
"args": ["-m", "toggl_mcp.server"],
"cwd": "${workspaceRoot}"
}
}
}
```
Replace `/path/to/toggl-mcp-custom` with the actual path to this project.
Claude Code will automatically discover and use this server. The tools will be available in your conversations.
### Option 2: Global Integration
To use this server across all projects, add it to your global MCP config:
**macOS/Linux:**
```bash
mkdir -p ~/.config/claude-code
```
Then add to `~/.config/claude-code/mcp.json`:
```json
{
"mcpServers": {
"toggl": {
"command": "/path/to/toggl-mcp-custom/.venv/bin/python",
"args": ["-m", "toggl_mcp.server"],
"cwd": "/path/to/toggl-mcp-custom"
}
}
}
```
### Using in Other Projects
To add this MCP server to another project:
1. **Copy the `.mcp.json` template:**
```bash
cat > /path/to/other-project/.mcp.json << 'EOF'
{
"mcpServers": {
"toggl": {
"command": "/path/to/toggl-mcp-custom/.venv/bin/python",
"args": ["-m", "toggl_mcp.server"],
"cwd": "/path/to/toggl-mcp-custom"
}
}
}
EOF
```
2. **Update the paths** to point to your toggl-mcp-custom installation
3. **Restart Claude Code** - it will automatically discover the server
## Running the Server Manually
If you want to run the server directly:
```bash
source .venv/bin/activate
python -m toggl_mcp.server
```
The server will start and expose two MCP tools:
### 1. `get_workspace_users()`
Lists all users in the Toggl workspace.
**Response:**
```json
{
"status": "success",
"data": [
{"id": "12345", "email": "user@example.com", "name": "John Doe"},
{"id": "12346", "email": "jane@example.com", "name": "Jane Smith"}
],
"error": null
}
```
### 2. `get_toggl_aggregated_data(start_date, end_date, user_id=None)`
Fetches and aggregates time entries for a date range.
**Parameters:**
- `start_date` (string): ISO 8601 format (YYYY-MM-DD), e.g., "2025-10-06"
- `end_date` (string): ISO 8601 format, must be >= start_date and <= start_date + 7 days
- `user_id` (optional string): Filter by single user ID
**Response:**
```json
{
"status": "success",
"data": {
"users": {
"user@example.com": {
"user_email": "user@example.com",
"matched_entities": [
{
"entity_database": "Scrum",
"entity_type": "Task",
"entity_id": "456",
"description": "Design UI",
"duration_hours": 7.5
}
],
"unmatched_activities": [
{
"description": "Team meeting",
"duration_hours": 1.0
}
],
"statistics": {
"total_duration_hours": 8.5,
"matched_duration_hours": 7.5,
"unmatched_duration_hours": 1.0
}
}
},
"statistics": {
"total_users": 1,
"total_duration_hours": 8.5,
"total_matched_duration_hours": 7.5,
"total_unmatched_duration_hours": 1.0
}
},
"error": null,
"metadata": {
"source": "api",
"entries_fetched": 15
}
}
```
## Description Parsing
The server automatically parses Toggl entry descriptions to extract Fibery entity references.
**Format:** `"Description text #ID [DATABASE] [TYPE] [PROJECT]"`
**Examples:**
- `"Design UI #456 [Scrum] [Task] [Moneyball]"` → Matched entity
- `"#123 [Dev]"` → Minimal matched entity
- `"Team meeting"` → Unmatched activity
**Parsing Rules:**
- Extracts the rightmost `#ID` if multiple exist
- Captures first 3 bracket values as DATABASE, TYPE, and PROJECT
- Text before #ID becomes the description
## Architecture
### Services
- **CacheService**: Manages caching with MD5 hash keys, SQLite index, and JSON file storage
- **TogglService**: Handles Toggl API client with HTTP Basic Auth and rate limiting
- **ParserService**: Parses descriptions to extract Fibery entity references
- **AggregatorService**: Groups and aggregates time entries by user and entity
### Models
- **User**: Toggl workspace user
- **TimeEntry**: Raw Toggl time entry
- **ParsedEntry**: Entry with extracted metadata
- **UserAggregation**: Aggregated data for single user
- **AggregatedData**: Complete aggregated response
## Testing
Run unit tests:
```bash
python -m pytest tests/ -v
```
Test coverage:
- **26 unit tests** covering:
- Description parsing (7 tests)
- Data aggregation (5 tests)
- Cache operations (5 tests)
- Date validation and utilities (9 tests)
## Project Structure
```
toggl-mcp-custom/
├── src/toggl_mcp/
│ ├── __init__.py
│ ├── server.py # FastMCP server + tools
│ ├── config.py # Configuration from env vars
│ ├── models.py # Pydantic models
│ ├── utils.py # Utility functions
│ └── services/
│ ├── __init__.py
│ ├── cache_service.py # SQLite + JSON caching
│ ├── toggl_service.py # Toggl API client
│ ├── parser_service.py # Description parsing
│ └── aggregator_service.py # Data aggregation
├── tests/
│ ├── __init__.py
│ ├── test_parser_service.py
│ ├── test_aggregator_service.py
│ ├── test_cache_service.py
│ └── test_utils.py
├── cache/ # Runtime cache (auto-created)
│ ├── data/ # JSON cache files
│ └── index.db # SQLite index
├── requirements.txt # Python dependencies
├── .env.example # Environment template
└── README.md # This file
```
## Performance Considerations
### Rate Limiting
- Toggl API limit: 3 requests per second
- Exponential backoff on 429 (rate limit) responses:
- Attempt 1: 60 seconds
- Attempt 2: 120 seconds
- Attempt 3: 240 seconds
- After 3 retries: failure
### Caching
- Default TTL: 1 hour
- Cache keys: MD5 hash of date range + optional user_id
- Storage: SQLite index + JSON files
- Automatic cleanup of expired entries on retrieval
### Date Range Limits
- Maximum range: 7 days per request
- For larger ranges, make multiple requests and aggregate client-side
- Day-by-day pagination to optimize API efficiency
## Error Handling
The server returns structured error responses with error codes:
- `INVALID_DATE_FORMAT` — Dates not in ISO 8601 format
- `INVALID_DATE_RANGE` — end_date < start_date
- `DATE_RANGE_EXCEEDS_LIMIT` — Range exceeds 7 days
- `USER_NOT_FOUND` — User ID not found in workspace
- `API_ERROR` — Toggl API error
- `SERVICE_NOT_INITIALIZED` — Services not properly initialized
- `INTERNAL_ERROR` — Server error
## Development
### Adding New Features
1. Add models to `models.py`
2. Implement service methods
3. Write unit tests in `tests/`
4. Update MCP tool in `server.py`
### Running Tests
```bash
# All tests
python -m pytest tests/ -v
# Specific test file
python -m pytest tests/test_parser_service.py -v
# With coverage
python -m pytest tests/ --cov=src/toggl_mcp
```
## License
MIT
## Support
For issues or questions, please refer to the implementation specification in `/docs/features/1-initial-implementation/IMPLEMENTATION.md`