npm package: @tailormade/teamleader-focus-mcp
Description: MCP server for Teamleader Focus CRM API — 112 tools covering contacts, companies, deals, tickets, invoices, subscriptions, meetings, projects, time tracking, files, notes, and smart cache-first task resolution.
Author: Tailormade
Latest version: 3.2.5

# Teamleader Focus MCP

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that wraps the **Teamleader Focus CRM API**. Gives AI assistants (Claude Code, Claude Desktop, Cursor, etc.) the ability to manage contacts, companies, deals, tasks, events, invoices, time tracking, and projects — including a smart task resolution workflow for automated time logging.

Inspired by [globodai-group/mcp-teamleader](https://github.com/globodai-group/mcp-teamleader). Rewritten and extended with full API coverage, time tracking, projects v2, and smart cache-first task resolution.

---

## Features

- **112 tools** covering the full Teamleader Focus API
- **Contacts** — Full CRUD + tag/untag + company link management
- **Companies** — Full CRUD + tag/untag
- **Deals** — Full CRUD + win/lose/move + pipelines/phases/sources management
- **Tasks** — Standalone tasks: full CRUD + complete/reopen/schedule
- **Events** — List, get, create, update, cancel
- **Meetings** — Full CRUD + complete + reports
- **Invoices** — Full lifecycle: draft → book → send → payment → credit
- **Tickets** — Full CRUD + messages + reply/internal
- **Time Tracking** — List, add, update, delete + start/stop/resume timers + timesheet
- **Projects v2** — Full CRUD + groups + tasks + relationships + lifecycle
- **Subscriptions** — Full CRUD + deactivate
- **Quotations** — Full CRUD + accept/send/download
- **Materials** — Full CRUD + assign/unassign/duplicate
- **Files** — List, get, download, delete, upload
- **Notes** — List, create, update
- **Calls** — List, get, add, update, complete
- **Smart Resolution** — Cache-first Company > Project > Group > Task navigation + time logging
- **OAuth2** — Browser-based login flow + automatic token refresh with rotation

---

## Prerequisites

A **Teamleader Focus** account with API access:

1. Go to [Developer Portal](https://developer.focus.teamleader.eu/)
2. Register an integration to get your **Client ID** and **Client Secret**
3. Complete the OAuth2 flow to obtain a **Refresh Token** (see [QUICKSTART.md](QUICKSTART.md))

---

## Quick Start

### Option A — npx (recommended, no install needed)

Add to `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/.claude.json`:

```json
{
  "mcpServers": {
    "teamleader": {
      "command": "npx",
      "args": ["-y", "@tailormade/teamleader-focus-mcp"],
      "env": {
        "TEAMLEADER_CLIENT_ID": "your_client_id",
        "TEAMLEADER_CLIENT_SECRET": "your_client_secret",
        "TEAMLEADER_REFRESH_TOKEN": "your_refresh_token"
      }
    }
  }
}
```

### Option B — Build from source

```bash
git clone https://github.com/tailormade-eu/Tailormade.TeamleaderFocusMcp.git
cd Tailormade.TeamleaderFocusMcp
npm install
npm run build
```

Add to your Claude config:

```json
{
  "mcpServers": {
    "teamleader": {
      "command": "node",
      "args": ["C:\\ABSOLUTE\\PATH\\TO\\dist\\index.js"],
      "env": {
        "TEAMLEADER_CLIENT_ID": "your_client_id",
        "TEAMLEADER_CLIENT_SECRET": "your_client_secret",
        "TEAMLEADER_REFRESH_TOKEN": "your_refresh_token"
      }
    }
  }
}
```

---

## Configuration

### Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `TEAMLEADER_CLIENT_ID` | Yes | OAuth2 Client ID |
| `TEAMLEADER_CLIENT_SECRET` | Yes | OAuth2 Client Secret |
| `TEAMLEADER_REFRESH_TOKEN` | No | OAuth2 Refresh Token (auto-rotated, persisted to `~/.teamleader-tokens.json`). If omitted, only `teamleader_login` is available — use it to obtain tokens via browser. |

### Cache Files

| File | Purpose |
|------|---------|
| `~/.teamleader-cache.json` | Entity cache (companies, projects, groups, tasks, task trees) |
| `~/.teamleader-tasks-{slug}.yaml` | Full task tree written by `teamleader_load_tasks` (contains all IDs) |
| `~/.teamleader-tokens.json` | Persisted OAuth2 tokens (auto-managed) |

---

## Available Tools

### Contacts

| Tool | Description |
|------|-------------|
| `teamleader_list_contacts` | List contacts with optional filtering by term, tags, or updated_since |
| `teamleader_get_contact` | Get full details of a specific contact by ID |
| `teamleader_create_contact` | Create a new contact (first name, last name, email, phone, tags, language) |
| `teamleader_update_contact` | Update an existing contact |
| `teamleader_delete_contact` | Delete a contact |
| `teamleader_link_contact_to_company` | Link a contact to a company (with optional position/decision_maker) |
| `teamleader_unlink_contact_from_company` | Unlink a contact from a company |
| `teamleader_update_contact_company_link` | Update position or decision_maker on an existing link |
| `teamleader_tag_contact` | Add tags to a contact |
| `teamleader_untag_contact` | Remove tags from a contact |

### Companies

| Tool | Description |
|------|-------------|
| `teamleader_list_companies` | List companies with filtering by term, tags, or VAT number |
| `teamleader_get_company` | Get full details of a specific company by ID |
| `teamleader_create_company` | Create a new company (name, email, phone, VAT, website, tags) |
| `teamleader_update_company` | Update an existing company |
| `teamleader_delete_company` | Delete a company |
| `teamleader_tag_company` | Add tags to a company |
| `teamleader_untag_company` | Remove tags from a company |

### Deals

| Tool | Description |
|------|-------------|
| `teamleader_list_deals` | List deals with filtering by term, phase, or responsible user |
| `teamleader_get_deal` | Get full details of a specific deal by ID |
| `teamleader_create_deal` | Create a new deal (title, customer, phase, value, probability) |
| `teamleader_update_deal` | Update an existing deal (title, value, dates, responsible user) |
| `teamleader_delete_deal` | Delete a deal |
| `teamleader_lose_deal` | Mark a deal as lost (optional reason_id + extra_info) |
| `teamleader_win_deal` | Mark a deal as won |
| `teamleader_move_deal` | Move a deal to a different phase |
| `teamleader_list_lost_reasons` | List available lost reasons (for deals.lose) |
| `teamleader_list_deal_phases` | List deal phases (for deals.move/create) |
| `teamleader_list_deal_sources` | List deal sources (for deals.create) |
| `teamleader_list_deal_pipelines` | List deal pipelines |
| `teamleader_create_deal_pipeline` | Create a deal pipeline |
| `teamleader_update_deal_pipeline` | Update a deal pipeline |
| `teamleader_delete_deal_pipeline` | Delete a deal pipeline |
| `teamleader_duplicate_deal_pipeline` | Duplicate a deal pipeline |
| `teamleader_mark_deal_pipeline_default` | Mark a pipeline as default |
| `teamleader_create_deal_phase` | Create a deal phase |
| `teamleader_update_deal_phase` | Update a deal phase |
| `teamleader_delete_deal_phase` | Delete a deal phase |
| `teamleader_duplicate_deal_phase` | Duplicate a deal phase |
| `teamleader_move_deal_phase` | Reorder a deal phase |

### Tasks (standalone)

| Tool | Description |
|------|-------------|
| `teamleader_list_tasks` | List tasks with optional filtering and pagination |
| `teamleader_create_task` | Create a new task |
| `teamleader_get_task` | Get full details of a specific task by ID |
| `teamleader_update_task` | Update task title, description, due date, or assignee |
| `teamleader_delete_task` | Delete a task |
| `teamleader_complete_task` | Mark a task as completed |
| `teamleader_reopen_task` | Reopen a completed task |
| `teamleader_schedule_task` | Schedule a task as a calendar event |

### Events

| Tool | Description |
|------|-------------|
| `teamleader_list_events` | List calendar events with optional date range filter |
| `teamleader_get_event` | Get full details of a specific event by ID |
| `teamleader_create_event` | Create a new calendar event (title, activity type, start/end, attendees) |
| `teamleader_update_event` | Update an existing event |
| `teamleader_cancel_event` | Cancel a calendar event |

### Meetings

| Tool | Description |
|------|-------------|
| `teamleader_list_meetings` | List meetings with filter by employee, date range, or term |
| `teamleader_get_meeting` | Get full details of a specific meeting |
| `teamleader_schedule_meeting` | Schedule a new meeting with attendees and optional customer/deal |
| `teamleader_update_meeting` | Update meeting details |
| `teamleader_complete_meeting` | Mark a meeting as done |
| `teamleader_delete_meeting` | Delete a meeting |
| `teamleader_create_meeting_report` | Create a report attached to a contact, company, or deal |

### Invoices

| Tool | Description |
|------|-------------|
| `teamleader_list_invoices` | List invoices with filtering by status, date range, or department |
| `teamleader_get_invoice` | Get full details of a specific invoice by ID |
| `teamleader_create_invoice` | Create a new draft invoice with line items |
| `teamleader_book_invoice` | Book a draft invoice (→ outstanding) |
| `teamleader_send_invoice` | Send invoice via email |
| `teamleader_send_invoice_peppol` | Send invoice via Peppol e-invoicing |
| `teamleader_delete_invoice` | Delete a draft invoice |
| `teamleader_update_invoice` | Update a draft invoice |
| `teamleader_update_booked_invoice` | Update limited fields on a booked invoice |
| `teamleader_register_payment` | Register a payment on an invoice |
| `teamleader_remove_payments` | Remove all payments from an invoice |
| `teamleader_copy_invoice` | Copy an invoice to a new draft |
| `teamleader_credit_invoice` | Create a full credit note |
| `teamleader_credit_invoice_partially` | Create a partial credit note |
| `teamleader_download_invoice` | Get a temporary download URL (PDF/UBL) |
| `teamleader_list_mail_templates` | List mail templates (for invoice sending) |
| `teamleader_list_payment_methods` | List payment methods (for registering payments) |

### Tickets

| Tool | Description |
|------|-------------|
| `teamleader_list_tickets` | List tickets with filter by customer, project, or status |
| `teamleader_get_ticket` | Get full details of a specific ticket |
| `teamleader_create_ticket` | Create a new ticket (subject, customer, team, assignee) |
| `teamleader_update_ticket` | Update ticket subject, description, status, or assignee |
| `teamleader_list_ticket_messages` | List messages on a ticket (filter by type or date) |
| `teamleader_get_ticket_message` | Get a single message by ID |
| `teamleader_reply_ticket` | Add a customer-visible reply to a ticket |
| `teamleader_internal_message_ticket` | Add an internal/private message to a ticket |
| `teamleader_import_ticket_message` | Import a raw message into a ticket |

### Subscriptions

| Tool | Description |
|------|-------------|
| `teamleader_list_subscriptions` | List recurring invoices with filtering |
| `teamleader_get_subscription` | Get subscription details |
| `teamleader_create_subscription` | Create a new recurring invoice |
| `teamleader_update_subscription` | Update subscription settings |
| `teamleader_deactivate_subscription` | Deactivate a subscription |

### Quotations

| Tool | Description |
|------|-------------|
| `teamleader_list_quotations` | List quotations with filtering |
| `teamleader_get_quotation` | Get quotation details |
| `teamleader_create_quotation` | Create a new quotation |
| `teamleader_update_quotation` | Update an existing quotation |
| `teamleader_delete_quotation` | Delete a quotation |
| `teamleader_accept_quotation` | Accept a quotation |
| `teamleader_send_quotation` | Send quotation via email |
| `teamleader_download_quotation` | Get a temporary download URL |

### Credit Notes

| Tool | Description |
|------|-------------|
| `teamleader_list_credit_notes` | List credit notes with filtering |
| `teamleader_get_credit_note` | Get credit note details |
| `teamleader_download_credit_note` | Get a temporary download URL |
| `teamleader_send_credit_note_peppol` | Send credit note via Peppol |

### Materials

| Tool | Description |
|------|-------------|
| `teamleader_list_materials` | List materials with filtering |
| `teamleader_get_material` | Get material details |
| `teamleader_create_material` | Create a new material |
| `teamleader_update_material` | Update a material |
| `teamleader_delete_material` | Delete a material |
| `teamleader_assign_material` | Assign material to a project task |
| `teamleader_unassign_material` | Unassign material from a task |
| `teamleader_duplicate_material` | Duplicate a material |

### Calls

| Tool | Description |
|------|-------------|
| `teamleader_list_calls` | List calls with filtering |
| `teamleader_get_call` | Get call details |
| `teamleader_add_call` | Add a new call |
| `teamleader_update_call` | Update a call |
| `teamleader_complete_call` | Mark a call as complete |

### Orders

| Tool | Description |
|------|-------------|
| `teamleader_list_orders` | List orders with filtering |
| `teamleader_get_order` | Get order details |

### Time Tracking

| Tool | Description |
|------|-------------|
| `teamleader_list_timetracking` | List time entries filtered by user, date range, or subject |
| `teamleader_get_timetracking` | Get details of a specific time entry by ID |
| `teamleader_add_timetracking` | Add a new time entry (user, work type, start/end, subject) |
| `teamleader_update_timetracking` | Update an existing time entry |
| `teamleader_delete_timetracking` | Delete a time entry |
| `teamleader_start_timer` | Start a running timer (one active timer per user) |
| `teamleader_stop_timer` | Stop the current running timer |
| `teamleader_get_current_timer` | Get the currently running timer |
| `teamleader_update_timer` | Update a running timer (work type, subject, description) |
| `teamleader_resume_timetracking` | Start a new timer from an existing time entry |
| `teamleader_timesheet` | Daily timesheet — all entries for a date, grouped by project/task. Formats: md, beauty, manictime |

### Projects v2

| Tool | Description |
|------|-------------|
| `teamleader_list_projects_v2` | List projects with filtering by status, company, or search term |
| `teamleader_get_project_v2` | Get full details of a specific project by ID |
| `teamleader_create_project_v2` | Create a new project with customer, dates, and responsible user |
| `teamleader_update_project_v2` | Update an existing project |
| `teamleader_close_project_v2` | Close a project |
| `teamleader_reopen_project_v2` | Reopen a closed project |
| `teamleader_delete_project_v2` | Delete a project |
| `teamleader_duplicate_project_v2` | Duplicate a project to a new draft |
| `teamleader_add_project_customer` | Add a customer (company/contact) to a project |
| `teamleader_remove_project_customer` | Remove a customer from a project |
| `teamleader_add_project_deal` | Link a deal to a project |
| `teamleader_remove_project_deal` | Unlink a deal from a project |
| `teamleader_add_project_owner` | Add an owner/member to a project |
| `teamleader_remove_project_owner` | Remove an owner/member from a project |
| `teamleader_assign_project` | Assign a user or team to a project |
| `teamleader_unassign_project` | Unassign a user or team from a project |
| `teamleader_list_project_groups` | List project groups (phases/milestones) for a project |
| `teamleader_create_project_group` | Create a new group (phase) within a project |
| `teamleader_update_project_group` | Update a project group title, description, or dates |
| `teamleader_list_project_tasks_v2` | List tasks by project or project group |
| `teamleader_create_project_task_v2` | Create a task within a project or group |
| `teamleader_complete_project_task` | Mark a project task as complete |
| `teamleader_reopen_project_task` | Reopen a completed project task |
| `teamleader_delete_project_task` | Delete a project task |
| `teamleader_remove_task_from_group` | Remove a task from its group |
| `teamleader_add_project_line_to_group` | Add/move a task or material to a group |
| `teamleader_list_project_lines` | List all lines (tasks + materials) in a project |
| `teamleader_get_project_group` | Get project group details |
| `teamleader_duplicate_project_group` | Duplicate a project group |
| `teamleader_assign_project_group` | Assign a user/team to a project group |
| `teamleader_unassign_project_group` | Unassign from a project group |
| `teamleader_update_project_task` | Update a project task |
| `teamleader_get_project_task` | Get project task details |
| `teamleader_assign_project_task` | Assign a user to a project task |
| `teamleader_unassign_project_task` | Unassign from a project task |
| `teamleader_duplicate_project_task` | Duplicate a project task |
| `teamleader_add_project_quotation` | Link a quotation to a project |
| `teamleader_remove_project_quotation` | Unlink a quotation from a project |

### Users

| Tool | Description |
|------|-------------|
| `teamleader_list_users` | List all users in the account |
| `teamleader_get_user` | Get full details of a specific user by ID |

### Departments

| Tool | Description |
|------|-------------|
| `teamleader_list_departments` | List all departments |
| `teamleader_get_department` | Get full details of a specific department |

### Files

| Tool | Description |
|------|-------------|
| `teamleader_list_files` | List files attached to a contact, company, deal, invoice, project, or ticket |
| `teamleader_get_file` | Get file metadata (name, size, mime type, uploader) |
| `teamleader_download_file` | Get a temporary download URL for a file |
| `teamleader_delete_file` | Delete a file |
| `teamleader_upload_file` | Upload a file to an entity (two-step: get URL, then upload) |

### Notes

| Tool | Description |
|------|-------------|
| `teamleader_list_notes` | List notes for a contact, company, deal, or project |
| `teamleader_create_note` | Create a note on a contact, company, deal, or project |
| `teamleader_update_note` | Update an existing note by ID |

### Lookup Lists

| Tool | Description |
|------|-------------|
| `teamleader_list_activity_types` | List activity types (needed for events.create) |
| `teamleader_list_tax_rates` | List tax rates (needed for invoice line items) |
| `teamleader_list_payment_terms` | List payment terms (needed for invoices) |
| `teamleader_list_ticket_statuses` | List ticket statuses (needed for tickets.update) |
| `teamleader_list_products` | List products (for invoice line items) |
| `teamleader_list_product_categories` | List product categories |
| `teamleader_list_work_types` | List work types (for time tracking) |
| `teamleader_list_teams` | List teams (needed for tickets) |
| `teamleader_list_tags` | List all tags used in the account |
| `teamleader_list_expenses` | List expense entries with optional filters |
| `teamleader_list_business_types` | List business types |
| `teamleader_list_call_outcomes` | List call outcomes |
| `teamleader_list_currencies` | List currencies and exchange rates |
| `teamleader_list_document_templates` | List document templates |
| `teamleader_list_commercial_discounts` | List commercial discounts |
| `teamleader_list_lost_reasons` | List lost reasons |
| `teamleader_list_price_lists` | List price lists |
| `teamleader_list_units_of_measure` | List units of measure |
| `teamleader_list_withholding_tax_rates` | List withholding tax rates |
| `teamleader_list_bookkeeping_submissions` | List bookkeeping submissions |
| `teamleader_add_product` | Add a product |
| `teamleader_get_product` | Get product details |
| `teamleader_update_product` | Update a product |
| `teamleader_delete_product` | Delete a product |

### Smart Resolution (Cache-first)

These tools implement the recommended workflow for time logging. They maintain a local cache to avoid repeated API calls.

| Tool | Description |
|------|-------------|
| `teamleader_find_task` | Navigate Company > Project > Group > Task hierarchy interactively. Numbered selection when multiple options. Tasks auto-created if not found. |
| `teamleader_log_time` | Register time for a task. Resolves from cache. Supports deduplication and overlap detection. |
| `teamleader_load_tasks` | Load and display the full Project > Group > Task tree for a company. Cache-first (30 min TTL). Writes YAML with all IDs. |
| `teamleader_task_action` | Maintenance actions on tasks: close, create, move a time entry, or delete a project group. |
| `teamleader_cache_stats` | Show cache statistics and all cached tasks. |
| `teamleader_clear_cache` | Clear the entire local cache (forces fresh API lookups on next call). |

### Authentication

| Tool | Description |
|------|-------------|
| `teamleader_login` | Start OAuth login flow — opens browser for Teamleader authorization, captures callback on localhost:19836, exchanges code for tokens, persists to `~/.teamleader-tokens.json`. Use for initial setup or expired refresh token recovery. |

---

## Recommended Workflow: Time Logging

### Option A — Interactive (first use or unknown task)

```
1. teamleader_find_task(company_name="Acme", group_name="Sprint 12", task_name="Bug fix login")
   → resolves company > project > group > task, caches result

2. teamleader_log_time(company_name="Acme", task_name="Bug fix login", started_on="09:00", ended_on="11:30")
   → reads from cache, logs time entry, checks for duplicates
```

### Option B — Load tree first (recommended for sessions with many log entries)

```
1. teamleader_load_tasks(company_name="Acme")
   → fetches full tree, writes ~/.teamleader-tasks-acme.yaml, shows summary

2. teamleader_load_tasks(company_name="Acme", visual=true)
   → shows numbered ASCII tree

   Company: Acme Corp
   === Project Alpha ===
     [Sprint 12]
       1. Bug fix login (to_do)
       2. Add unit tests (in_progress)

3. teamleader_load_tasks(company_name="Acme", task_selection=1)
   → caches task #1 for direct use

4. teamleader_log_time(company_name="Acme", task_name="Bug fix login", started_on="09:00", ended_on="11:30")
   → resolves from cache, logs time
```

### Option C — Direct task_id (fastest, when ID is already known)

```
teamleader_log_time(company_name="Acme", task_id="abc-123", started_on="09:00", ended_on="11:30")
```

---

## Smart Resolution: `teamleader_find_task` Parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `company_name` | Yes | Partial match on company name |
| `group_name` | Yes | Partial match on group/phase name (searched across all projects) |
| `task_name` | Yes | Partial match on task name (auto-created if not found) |
| `project_selection` | Auto | Select project by number when multiple found |
| `task_selection` | Auto | Select task by number from listed options |
| `confirm_create_group` | Auto | Confirm creation of new group (requires project_selection) |
| `confirm_create_project` | Auto | Confirm creation of new project |
| `only_open` | Optional | Only show open tasks — to_do, in_progress, on_hold (default: true) |
| `_company_id` | Internal | Resolved company ID, pass between calls to avoid re-resolving |
| `_project_id` | Internal | Resolved project ID |
| `_group_id` | Internal | Resolved group ID |
| `_group_title` | Internal | Resolved group title |

---

## Smart Resolution: `teamleader_log_time` Parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `company_name` | Yes | Partial match on company name |
| `task_name` | Conditional | Task name (partial match, must be in cache). Required if task_id not given. |
| `task_id` | Conditional | Direct task ID — skips cache lookup. Use when ID is known from load_tasks. |
| `started_on` | Yes | Start time: ISO 8601, `YYYY-MM-DD HH:MM`, or `HH:MM` (uses today) |
| `ended_on` | Yes | End time: same formats as started_on |
| `description` | Optional | Description of work done |
| `work_type_id` | Optional | Override the default work type |
| `date` | Optional | Date for HH:MM format (YYYY-MM-DD, default: today) |
| `force` | Optional | Skip deduplication and overlap checks entirely |
| `confirm_overlap` | Optional | Confirm registration when overlap with another task is detected |

---

## Deduplication Rules (`teamleader_log_time`)

| Rule | Condition | Action |
|------|-----------|--------|
| Exact duplicate | Same task ID, same start time (to second) | Block with error |
| Overlap | Any time range overlap with existing entry | Warn, ask `confirm_overlap=true` |
| Force | `force=true` | Skip all checks |

---

## `teamleader_load_tasks` Parameters

| Parameter | Description |
|-----------|-------------|
| `company_name` | Company to load (partial match) |
| `project_filter` | Filter projects by name (partial match) |
| `group_filter` | Filter groups by name (partial match) |
| `only_open` | Only include open tasks (default: true) |
| `visual` | Show ASCII tree with numbered tasks instead of summary |
| `task_selection` | Cache a specific task by number (from visual output) |
| `force_refresh` | Force reload from API, bypass 30 min cache |
| `_company_id` | Internal: resolved company ID |

---

## Cache TTL

| Entity | TTL |
|--------|-----|
| active_user | No expiry (manual clear only) |
| work_types | 7 days |
| companies | 24 hours |
| projects | 2 hours |
| groups | 1 hour |
| tasks | 1 hour |
| task_trees | 30 minutes |

Auto-prune: expired entries are removed on every cache write.

---

## Teamleader API Quirks

These are critical for any future development or debugging.

### `projects-v2/projectLines.list`

- `project_id` must be **top-level** in the request body — NOT inside `filter`
- `project_group_id` is **NOT** a server-side filter — must filter client-side on `l.group?.id`

### `projects-v2/tasks.list`

- Filter by `ids` array works server-side
- Status is **NOT** a server-side filter — filter client-side on status field

### `projects-v2/tasks.create`

- Use `group_id` (NOT `project_group_id`)
- Use `tasks.create` endpoint (NOT `projectLines.create`)

### `timeTracking.list`

- Returns `subject.type: "todo"` — the ID differs from the `nextgenTask` ID
- Deduplication must match on **start time** (to second precision), not on subject ID

### `invoices.registerPayment`

- Uses `paid_at` (NOT `payment_date`)
- Payment is a nested object: `{ payment: { amount: { amount, currency }, paid_at, payment_method_id } }`

### `invoices.creditPartially`

- Line item tax uses `unit_price.tax: "excluding"` (NOT a currency field)

### `tickets.list`

- Customer filter uses `relates_to: { type, id }` (NOT `customer_id`)
- Status filter uses `exclude.status_ids` array (no direct status filter)

### `projects.close`

- Requires `closing_strategy`: `"mark_tasks_and_materials_as_done"` or `"none"`

### `projects.delete`

- Requires `delete_strategy`: `"unlink_tasks_and_time_trackings"` or `"delete_tasks_and_time_trackings"`

### `projects.assign` / `projects.addCustomer`

- `assign` uses `assignee: { type, id }` object
- `addCustomer` / `removeCustomer` use `customer: { type, id }` object

### `projectGroups.update`

- Uses `start_date` / `end_date` (NOT `starts_on` / `due_on`)

### `files.upload`

- Two-step process: call `files.upload` to get a pre-signed URL, then POST binary directly to that URL (no auth needed for step 2)

### General

- All API calls use **POST** with JSON body
- Base URL: `https://api.focus.teamleader.eu`
- Pagination uses `page.number` and `page.size`

---

## File Structure

```
src/
  api/
    auth.ts           — OAuth2 token management (auto-rotate, persist to ~/.teamleader-tokens.json)
    client.ts         — HTTP client (all POST requests)
    cache.ts          — Cache types, TTL constants, CRUD functions, task tree operations
  tools/
    resolve.ts        — Smart tools: find_task, log_time, load_tasks, task_action, cache_stats, clear_cache
    contacts.ts       — Contact CRUD + tag/link
    companies.ts      — Company CRUD + tag
    deals.ts          — Deal CRUD + pipelines/phases/sources
    tasks.ts          — Standalone tasks
    events.ts         — Events
    meetings.ts       — Meetings + reports
    invoices.ts       — Invoices + payment + credit
    timetracking.ts   — Time tracking + timers + timesheet
    projects.ts       — Projects v2 + groups + lines + project tasks
    tickets.ts        — Tickets + messages
    subscriptions.ts  — Subscriptions
    quotations.ts     — Quotations
    creditnotes.ts    — Credit notes
    materials.ts      — Materials
    calls.ts          — CRM calls
    orders.ts         — Orders
    files.ts          — Files
    notes.ts          — Notes
    users.ts          — Users
    departments.ts    — Departments
    lookups.ts        — All reference data lists
    login.ts          — OAuth login flow
  index.ts            — MCP server entry point, tool registration
docs/
  api/                — Full Teamleader Focus API docs (360 pages, scraped 2026-03-05)
  BusinessLogica/     — Business logic docs per domain
```

---

## Development

```bash
# Install
npm install

# Build (TypeScript → dist/)
npm run build

# Type check only
npm run typecheck

# Dev mode (watch)
npm run dev

# Test with MCP inspector
npx @modelcontextprotocol/inspector node dist/index.js
```

---

## References

- [Teamleader Focus API](https://developer.focus.teamleader.eu/) — OAuth2 REST API (all POST, JSON body)
- [Model Context Protocol](https://modelcontextprotocol.io/) — MCP specification
- [globodai-group/mcp-teamleader](https://github.com/globodai-group/mcp-teamleader) — Original inspiration
- Local scraped API docs: `docs/api/` (360 pages, updated 2026-03-05)

## API Reference

- Base URL: `https://api.focus.teamleader.eu`
- All endpoints use POST with JSON body
- Authentication: OAuth2 with automatic token refresh

---

## License

[MIT](LICENSE)