toggl-focus-mcp
v1.0.2
Published
MCP server for Toggl Focus (2.0) — API-key auth, curated tools for Claude, Cursor, and Codex
Maintainers
Readme
toggl-focus-mcp
MCP server for Toggl Focus (2.0) — not Toggl Track 1.0.
Connect Claude, Cursor, Codex, or OpenCode to your Focus workspace with a Bearer API key (toggl_sk_…) and npx. No OAuth browser flow required.
npx -y toggl-focus-mcp| | |
| --- | --- |
| Auth | Focus Bearer API key (Authorization: Bearer toggl_sk_…) |
| API | https://focus.toggl.com/api |
| Transport | stdio MCP |
| Node | ≥ 20 (CI / .nvmrc → latest Current) |
Track “API Token” values (Basic Auth / profile token for Track) do not work. Create a Focus / Toggl 2.0 key in settings. Official OAuth MCP:
@togglhq/mcp.
Prerequisites
- A Toggl Focus / 2.0 API key (
toggl_sk_…) — shown once when created - Your organization ID and workspace ID (strongly recommended)
- Node.js 20+ (for
npx)
Find org/workspace IDs in the Focus URL, e.g.https://focus.toggl.com/.../organizations/21504582/workspaces/21503801/...
Setting both IDs avoids extra discovery calls and protects against Focus hourly quota limits.
Environment variables
| Variable | Required | Description |
| --- | --- | --- |
| TOGGL_FOCUS_API_TOKEN | Yes* | Focus API key (toggl_sk_…) |
| TOGGL_API_KEY | Yes* | Alias for the Focus API key |
| TOGGL_API_TOKEN | Yes* | Additional alias |
| TOGGL_ORGANIZATION_ID | Recommended | Default organization ID |
| TOGGL_WORKSPACE_ID | Recommended | Default workspace ID |
| TOGGL_FOCUS_BASE_URL | No | API base (default https://focus.toggl.com/api) |
* Exactly one of TOGGL_FOCUS_API_TOKEN, TOGGL_API_KEY, or TOGGL_API_TOKEN is required.
Most tools accept optional organization_id / workspace_id arguments that override the env defaults for that call.
Install
Replace toggl_sk_..., 123, and 456 with your real key and IDs.
Claude Code
claude mcp add toggl-focus \
-e TOGGL_FOCUS_API_TOKEN=toggl_sk_... \
-e TOGGL_ORGANIZATION_ID=123 \
-e TOGGL_WORKSPACE_ID=456 \
-- npx -y toggl-focus-mcpVerify:
claude mcp listClaude Desktop
Edit claude_desktop_config.json, then restart Claude Desktop.
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"toggl-focus": {
"command": "npx",
"args": ["-y", "toggl-focus-mcp"],
"env": {
"TOGGL_FOCUS_API_TOKEN": "toggl_sk_...",
"TOGGL_ORGANIZATION_ID": "123",
"TOGGL_WORKSPACE_ID": "456"
}
}
}
}Cursor
Add to project .cursor/mcp.json or your global Cursor MCP settings:
{
"mcpServers": {
"toggl-focus": {
"command": "npx",
"args": ["-y", "toggl-focus-mcp"],
"env": {
"TOGGL_FOCUS_API_TOKEN": "toggl_sk_...",
"TOGGL_ORGANIZATION_ID": "123",
"TOGGL_WORKSPACE_ID": "456"
}
}
}
}Enable the server in Cursor Settings → MCP, then confirm tools like focus_whoami appear.
Codex
CLI (recommended):
codex mcp add toggl-focus \
--env TOGGL_FOCUS_API_TOKEN=toggl_sk_... \
--env TOGGL_ORGANIZATION_ID=123 \
--env TOGGL_WORKSPACE_ID=456 \
-- npx -y toggl-focus-mcpOr edit ~/.codex/config.toml (or project .codex/config.toml in a trusted repo):
[mcp_servers.toggl-focus]
command = "npx"
args = ["-y", "toggl-focus-mcp"]
[mcp_servers.toggl-focus.env]
TOGGL_FOCUS_API_TOKEN = "toggl_sk_..."
TOGGL_ORGANIZATION_ID = "123"
TOGGL_WORKSPACE_ID = "456"To keep secrets out of the file, set them in your shell and forward by name:
[mcp_servers.toggl-focus]
command = "npx"
args = ["-y", "toggl-focus-mcp"]
env_vars = ["TOGGL_FOCUS_API_TOKEN", "TOGGL_ORGANIZATION_ID", "TOGGL_WORKSPACE_ID"]Shared across Codex CLI, IDE extension, and ChatGPT desktop Codex host. Docs: developers.openai.com/codex/mcp.
OpenCode
Add to opencode.json / opencode.jsonc (project or global):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"toggl-focus": {
"type": "local",
"command": ["npx", "-y", "toggl-focus-mcp"],
"enabled": true,
"environment": {
"TOGGL_FOCUS_API_TOKEN": "toggl_sk_...",
"TOGGL_ORGANIZATION_ID": "123",
"TOGGL_WORKSPACE_ID": "456"
}
}
}
}You can also reference shell env vars with OpenCode’s {env:VAR} syntax where supported. Docs: opencode.ai/docs/mcp-servers.
Quick start prompts
Once the server is connected, try:
- “Who am I in Toggl Focus?”
- “List my projects”
- “Start a timer on project X”
- “Create a task called Ship MCP docs on project Y”
- “Show time entries for this week”
Creates, updates, deletes, and timer start/stop apply immediately (no confirmation token).
Tools
24 tools. All are Focus/2.0 only. Shared optional args on almost every tool:
| Arg | Type | Notes |
| --- | --- | --- |
| organization_id | number | Overrides TOGGL_ORGANIZATION_ID |
| workspace_id | number | Overrides TOGGL_WORKSPACE_ID |
| page | number | Default 1 (list tools) |
| per_page | number | Default 50, max 100 (list tools) |
Account & context
focus_whoami
Account metadata + user settings for the authenticated Focus key.
| Arg | Type | Required | Description |
| --- | --- | --- | --- |
| include_settings | boolean | No | Default true — also fetch GET /users/me/settings |
| organization_id / workspace_id | number | No | Optional scope |
focus_get_workspace_context
Resolve and cache organization_id / workspace_id for later tools. Prefer setting both env vars. Does not call the session-only /workspaces/{id}/context endpoint (that returns 403 for API keys).
focus_list_org_users
List organization users (assignee lookup). Read-only.
| Arg | Type | Required | Description |
| --- | --- | --- | --- |
| filter | string | No | Search name/email |
| active_status | string | No | Active-status filter when supported |
| page / per_page | number | No | Pagination |
Projects
focus_list_projects
List workspace projects.
| Arg | Type | Description |
| --- | --- | --- |
| name | string | Filter by project or client name |
| archived / completed / pinned | boolean | Status filters |
| client_id | number[] | Filter by client IDs |
| tag_id | number[] | Filter by tag IDs |
focus_get_project
| Arg | Type | Required |
| --- | --- | --- |
| project_id | number | Yes |
focus_create_project
Creates immediately. Requires name.
| Arg | Type | Description |
| --- | --- | --- |
| name | string | Required |
| description | string | |
| client_id | number | Supported on create |
| color | string | |
| private / pinned / billable | boolean | |
| start_date / end_date | string | YYYY-MM-DD |
| tag_ids | number[] | Applied via follow-up PATCH; may be forbidden on some plans/roles — project still creates and a warning is returned |
| parent_project_id | number | |
focus_update_project
Partial update (PATCH). Requires project_id. Same optional fields as create, plus nullable client_id and optional archived.
Tasks
focus_list_tasks
| Arg | Type | Description |
| --- | --- | --- |
| name | string | Name filter |
| project_id | number[] | Project IDs |
| status_id | number[] | Status IDs |
| assignee_user_id | number[] | Assignees |
| tag_id | number[] | Tags |
| pinned / archived | boolean | |
| parent_task_id | number[] | Parent tasks |
| include_assignees | boolean | Expand assignees |
focus_get_task
Requires task_id. Optional include_assignees.
focus_create_task
Creates immediately. Requires name.
| Arg | Type | Description |
| --- | --- | --- |
| name | string | Required |
| description / notes | string | |
| project_id / parent_task_id / status_id | number | |
| assignee_user_ids / tag_ids | number[] | |
| estimated_mins | number | |
| priority | "none" | "low" | "medium" | "high" | |
| pinned / private / billable | boolean | |
| start_date / end_date | string | YYYY-MM-DD |
focus_update_task
Partial update. Requires task_id. Same mutable fields as create (all optional).
Live tracking
focus_get_current_timer
Returns the running timer for the workspace, or { "running": false }.
focus_start_timer
Starts immediately. type defaults to "activity".
| Arg | Type | Description |
| --- | --- | --- |
| type | "activity" | "break" | Default activity |
| task_id / project_id | number | |
| description | string | |
| billable | boolean | |
| tag_ids | number[] | |
| time_block_id | number | |
| start | string | RFC3339; server default = now |
focus_stop_timer
Stops the running timer. Optional end (RFC3339); defaults to current UTC.
Time entries
focus_list_time_entries
Requires a date range. Accepts YYYY-MM-DD or RFC3339 (YYYY-MM-DD is expanded to start/end of day UTC).
| Arg | Type | Required | Description |
| --- | --- | --- | --- |
| date_from | string | Yes | Range start |
| date_to | string | Yes | Range end |
| task_id | number | No | |
| type | "activity" | "break" | No | |
| archived / include_taskless | boolean | No | |
| time_block_id | number | No | |
focus_create_time_entry
Creates a completed entry immediately. If task_id is set, uses the task-scoped endpoint; otherwise creates a taskless entry. type defaults to "activity".
| Arg | Type | Description |
| --- | --- | --- |
| task_id / project_id | number | |
| description | string | |
| start | string | RFC3339 |
| duration | number | Seconds |
| billable | boolean | |
| tag_ids | number[] | |
| time_block_id | number | |
| type | "activity" | "break" | |
focus_update_time_entry
Partial update. Requires time_entry_id. Optional task_id selects the task-scoped path.
focus_delete_time_entry
Deletes immediately. Requires time_entry_id. Optional task_id for the task-scoped path.
Clients
focus_list_clients
Optional name filter plus pagination.
focus_create_client
Requires name. Optional currency (ISO 4217).
Tags
focus_list_tags
Optional name, tag_id (array), pagination.
focus_create_tag
Requires name and color (hex or Focus color token).
Statuses & search
focus_list_statuses
List task statuses. Optional name, type, pagination.
focus_search
Unified search across time entries, tasks, and projects.
| Arg | Type | Required | Description |
| --- | --- | --- | --- |
| keyword | string | Yes | Search text |
| per_group | number | No | Results per group |
| project_id / task_id | number | No | Scope |
| only_me / include_drafts | boolean | No | |
Behavior notes
- Mutations are immediate — create / update / delete / start / stop have no confirmation step.
- Project tags on create — some Focus plans/roles reject
tag_idsonPOST. The tool creates the project first, then PATCHes tags; on failure you still get the project plus awarning. - Workspace context — session cookie endpoints are not used; set org + workspace env vars.
- Quota — Focus enforces per-user-per-org hourly limits (e.g. Free ~30/hour). HTTP 402 means quota exhausted; wait for
X-Toggl-Quota-Resets-In. Prefer env IDs so tools do not rediscover context every call.
Development
git clone https://github.com/copperline-ai/toggl-focus-mcp.git
cd toggl-focus-mcp
npm install
npm run check # typecheck + unit/mocked MCP tests + build (CI gate)
npm run test:live # optional — real Focus API; local only, needs .env
node dist/index.jsnpm run check is the same gate used by GitHub Actions on PRs and before publish. It uses mocked Focus responses — CI never calls your real Toggl account.
Live MCP tests (local only)
Optional suite against a real Focus workspace. Not run in CI.
- Copy
.env.example→.envand set your Focus key + org/workspace IDs - Run:
npm run test:liveReuses stable fixtures (client Toggl Focus, project Toggl Focus MCP, task MCP live test, tag MCP live). Disposable time entries are deleted. Paces ~1 req/sec for rate limits. .env is gitignored and not published.
Publishing
Releases use release-it + AI Gateway (semver bump, changelog, GitHub Release, npm publish):
npm run release:dry-run
npm pack --dry-runCI secrets: NPM_ACCESS_TOKEN, AI_GATEWAY_API_KEY.
Vendored OpenAPI: openapi/focus.json from Focus OpenAPI.
Related
- Focus docs: engineering.toggl.com/docs/focus
- Focus auth: Bearer API keys
- Official OAuth MCP:
@togglhq/mcp
License
MIT
