@hazem.hashad/toggl-mcp
v1.5.15
Published
Toggl Focus MCP server — @toggl/mcp on npm, @toggl/mcp-internal on GitHub Packages
Readme
Toggl Focus MCP Server
A local MCP (Model Context Protocol) server that connects to the Toggl Focus API. Works with any MCP client including Claude Code and Claude Desktop. Exposes generated entity-domain tools (tasks, projects, time-blocks, time-entries, and related domains), workspace/auth tools, and Focus query artifacts from packages/focus-queries.
Published as @toggl/mcp. The public package will install from npmjs.org once the Toggl npm org is live; until then it may be available from GitHub Packages (see below).
Each GitHub release also includes per-platform .mcpb bundles (standalone executables; no Node.js on PATH) so users can install without registry access.
Install from npm (public, target)
npm install @toggl/mcp
# or: pnpm add @toggl/mcpNo GitHub Packages token is required once @toggl/mcp is published to npm.
Install from GitHub Packages (Toggl staff)
Internal builds publish as @toggl/mcp-internal. You need a GitHub token with read:packages and access to the Toggl org, or installs can return 404.
- Copy
.npmrc.exampleinto your project or~/.npmrcand replace<GH_NPM_TOKEN>with a PAT. - Install:
npm install @toggl/mcp-internal
# or: pnpm add @toggl/mcp-internalRun the CLI (after install):
npx @toggl/mcp authFor one-off runs without a local install: npx -y @toggl/mcp auth (still requires .npmrc with registry + token).
Install from GitHub Release (.mcpb)
If you do not have access to the private package registry, install from the release bundle instead:
Open the latest GitHub release.
Download the
.mcpbfor your OS and CPU (and the matching.sha256if you verify checksums):- Apple Silicon macOS:
toggl-focus-mcp-<version>-darwin-arm64.mcpb - Intel macOS:
toggl-focus-mcp-<version>-darwin-x64.mcpb - Linux x64:
toggl-focus-mcp-<version>-linux-x64.mcpb - Windows x64:
toggl-focus-mcp-<version>-win32-x64.mcpb
- Apple Silicon macOS:
Open the
.mcpbfile in a compatible MCP client (for example, Claude Desktop) to install.
These bundles embed a compiled server and do not require Node.js or @toggl/mcp registry access.
For development or debugging you can still package a Node-based MCPB (requires end users to have Node 20+): pnpm run build:mcpb:node from this package (outputs toggl-focus-mcp-<version>.mcpb).
Setup (from source)
git clone https://github.com/toggl/toggl-mcp.git
cd toggl-mcp
pnpm install
pnpm run build
node build/index.js authThis will:
- Open your browser to sign in with your Toggl account
- Automatically discover your workspaces
- Let you select your workspace (if you have multiple)
Authentication uses OAuth2 with PKCE — no API keys or passwords are entered in the terminal. Access tokens refresh automatically (1-hour access token, 4-week refresh token).
Add to your MCP client
Claude Code
claude mcp add toggl-focus -- npx @toggl/mcpClaude Desktop
Add to your Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"toggl-focus": {
"command": "node",
"args": ["/absolute/path/to/toggl-mcp/build/index.js"]
}
}
}Replace /absolute/path/to/toggl-mcp with the actual path to your clone, or use npx with command/args pointing at @toggl/mcp if installed from GitHub Packages (with .npmrc configured). Restart Claude Desktop to pick up the change.
Authentication
Uses OAuth2 with PKCE via the Toggl Accounts service. Credentials are stored in ~/.toggl/focus-tools.json (mode 0600). Access tokens refresh automatically — re-run npx @toggl/mcp auth only to switch workspaces or if your refresh token expires (after 4 weeks of inactivity).
Upgrading from an older version? Sessions previously stored at
~/.toggl-focus-mcp/config.jsonare still read automatically — no re-authentication needed.
Unified config vs legacy file: MCP logout (and config cleanup helpers) only remove credentials when there is a matching profile row under
~/.toggl/focus-tools.jsonwithactive.mcpset. If you authenticate solely via the legacy read shim and nothing was written to the unified file yet, logout may report that nothing was cleared—runnpx @toggl/mcp authonce so tokens land infocus-tools.json, or remove the legacy file manually if you intend to discard that session.
Alternative: Environment Variables
For CI or headless environments where browser-based OAuth is not available:
claude mcp add toggl-focus \
-s user \
-e TOGGL_WORKSPACE_ID=<your-workspace-id> \
-e TOGGL_API_TOKEN=<your-api-token> \
-e TOGGL_ORGANIZATION_ID=<your-org-id> \
-e TOGGL_USER_ID=<your-user-id> \
-- npx @toggl/mcpTools
Entity tools
MCP exposes one tool per catalog entity domain — the tool id equals mcp.entityTool from @toggl/operations (for example tasks, projects, time-blocks, organization, org-invitations, shared-working-hours). Each tool accepts:
action(required) — discriminator for the operationdata(optional) — payload for that actionconfirm_token(optional) — second step for mutating operations (see below)dry_run(optional) — whentrue, validatesdataand returns{ dry_run, operation, input }without calling the API (CLI--dry-runparity)
CLI bulk mutations are nested (toggl tasks bulk patch, etc.); MCP uses hyphenated action values such as bulk-patch on the same entity tools.
Mutation confirmation: mutating tools use a two-step flow. First call returns confirm_required with confirm_token; call the same tool again with confirm_token to execute once. dry_run: true skips execution and does not consume a token.
Tool inventory (full action list)
The server builds MCP instructions at startup from @toggl/operations, so clients always see the current domains and actions. CI checks durable inventory invariants in packages/mcp/src/__tests__/tool-inventory-invariants.test.ts instead of snapshotting the full generated action list.
Workspaces, profiles & auth
- workspace-list — List cached workspaces; pass
{ "refresh": true }to refetch from Accounts first (CLIworkspace list --refreshparity) - workspace-switch — Switch the active workspace for MCP
- profile-list — List rows in
~/.toggl/focus-tools.jsonwith MCP active flag (active.mcp) - profile-switch — Set
active.mcpto an existing profile name (shared with CLI profiles) - profile-remove — Remove a profile row (shared storage; affects CLI too if it used that row). Same
confirm_tokentwo-step flow as entity mutations: first call returnsconfirm_required, second call passesconfirm_tokenwith the sameprofile_name. - auth — Authenticate the MCP server via OAuth
- logout — Clear the current MCP active profile credentials
Agent-oriented guidance: skills/toggl-mcp/SKILL.md (bundled in the npm package under skills/).
Maintainers: regenerate Focus API coverage
pnpm run generate:focus-queries
pnpm run generate:focus-operationsSet GH_AUTH_TOKEN in packages/focus-queries/.env before generation (see packages/focus-queries/README.md). Querygen refreshes Focus request/type/schema artifacts first; the operations generator then exposes most generated endpoints to CLI/MCP by default, excluding internal, deprecated, unsafe, file-upload, and unsupported-signature endpoints blocklisted in packages/operations/src/focus/blocklist.ts.
Architecture
src/
├── index.ts # Entry point — CLI auth handler + MCP server bootstrap
├── instructions.ts # MCP server instructions + tool inventory from @toggl/operations
├── auth.ts # OAuth2 PKCE auth flow, token refresh, config file management
├── client.ts # Re-export of @toggl/cli-core FocusClient
├── authenticated-client.ts
├── utils.ts # Date helpers (@toggl/stdlib + cli-core), markdown tables, MCP response helpers
├── runtime/
│ └── safety/ # Mutation confirmation gate
└── tools/
├── generated-entity-tools.ts # Registers MCP entity tools from @toggl/operations
├── mcp-schema.ts
├── focus-tool-utils.ts
├── payload-helpers.ts
└── workspace.ts # Workspace/auth tools
packages/
├── cli-core/ # Auth/config/token primitives and FocusClient
├── operations/ # Shared operation catalog used by CLI and MCP
└── focus-queries/ # Querygen mirror for Focus API types, schemas, and requestsHow it works
The server runs as a stdio MCP process. Claude Code spawns it as a subprocess and communicates via JSON-RPC over stdin/stdout (stderr is used for logging).
Startup flow:
index.tschecks for theauthCLI argument — if present, runs the auth flow and exits.- Otherwise, it loads credentials from
~/.toggl/focus-tools.jsonwhen available (falls back to~/.toggl-focus-mcp/config.jsonfor sessions created by older versions). - Creates an auth-aware
FocusClientproxy that resolves credentials lazily per tool call. - Creates an
McpServerand registers entity tools from@toggl/operations, plus workspace/auth tools. - Connects via
StdioServerTransportand waits for tool calls.
Environment configuration:
The server reads an optional .env file from the project root and defaults to production (focus.toggl.com). You can override URLs directly via TOGGL_FOCUS_API_URL and TOGGL_ACCOUNTS_API_URL environment variables.
Authentication details
The auth flow (auth.ts) uses OAuth2 Authorization Code with PKCE:
- Generates a PKCE code verifier and challenge (SHA-256, base64url).
- Starts a local HTTP callback server on
localhost:8716. - Opens the browser to
accounts.toggl.com/focus/login/with OAuth parameters. - User logs in via the browser — the callback receives the authorization code.
- Exchanges the code for access + refresh tokens via
POST /api/oauth/token. - Fetches user info (
GET /api/me) and workspaces (GET /org/api/organizations/me). - Saves tokens and workspace info to
~/.toggl/focus-tools.json(mode 0600).
Token refresh happens automatically: before each API request, the client checks the access token's expiry and refreshes it if it expires within 60 seconds. The refresh token is valid for 4 weeks.
The stored config looks like:
{
"access_token": "<OAuth access token>",
"refresh_token": "<OAuth refresh token>",
"expires_at": 1700000000,
"user_id": 1234567,
"organization_id": 12345,
"workspace_id": 67890,
"workspace_name": "My Workspace",
"accounts_api_url": "https://accounts.toggl.com",
"focus_api_url": "https://focus.toggl.com",
"authenticated_at": "2025-01-15T10:00:00.000Z",
"workspaces": [...]
}HTTP client (@toggl/cli-core/focus-client)
The MCP package re-exports FocusClient from @toggl/cli-core. That client uses generated request functions from @toggl/focus-queries and @toggl/queries where available, and handles:
- Auth: Resolves a fresh token via the token provider before each request (handles OAuth2 refresh transparently).
- Required headers: Includes
X-Toggl-PostHog-Data: {"platform_origin":"web"}on all requests — the Focus API rejects write operations without this header. - Generated request execution: Calls generated request helpers for Focus, org, and shared-data endpoints instead of hand-building paths in the MCP package.
- Validation: Validates outgoing mutation payloads with generated Valibot schemas and soft-validates responses for typed callers.
- Error handling: Returns a discriminated union (
ApiResponse<T>) — callers check.okbefore accessing.data.
Runtime design
- Entity tool handlers are generated at startup from the shared
@toggl/operationscatalog. - Input schemas come from operation metadata; most payload schemas are generated Valibot schemas from query packages, with small hand-written wrappers for route IDs and curated list filters.
- Mutation gate (
src/runtime/safety/mutation-gate.ts) issuesconfirm_tokenvalues until the caller re-invokes the same mutating tool with that token.
Key Focus API concepts
| Concept | Description |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Task | Core work item. Has name, priority, status, project, assignees, dates, description, notes. |
| Project | Groups tasks. Has name, color, dates, estimated time. |
| Time Block | A scheduled calendar slot for a task (planned time). Has start time + duration in seconds. |
| Time Entry | Actual tracked time. Created by start/stop tracking. Types: activity or break. |
| Status | Workflow state (e.g. Todo, Doing, Done). Workspace-level, fetched from /workspaces/{id}/statuses. |
| User ID | user_account_id from the workspace API response. Used as assignee_user_id in tasks and for the toggl_user_id query filter. |
Maintenance model
The intended maintenance direction is to keep MCP and CLI surfaces driven by generated API artifacts:
- Regenerate
@toggl/focus-querieswhen the Focus API changes. - Prefer generated request functions, generated Valibot payload schemas, and generated response schemas over hand-written HTTP/path/payload logic.
- Keep
@toggl/operationsas the curated exposure layer for now: it defines which generated endpoints become public CLI/MCP operations, plus safety flags, command grouping, descriptions, and examples. - When exposing new endpoints, add operation metadata and
FocusClientbindings only where the generated request layer cannot be consumed directly yet.
Longer term, the main low-maintenance opportunity is generating the base operation catalog from querygen output and applying a small override map for visibility, safety, names, examples, and agent guidance.
MCP Apps (inline UI)
Cursor 2.6+, Claude Desktop, and other MCP App hosts can render view tools inline in chat. The stdio server is unchanged; view tools are additive alongside entity tools (tasks, projects, …).
- Resource:
ui://toggl-focus/app.html(single React iframe bundled with@toggl/design-system) - When to use: Prefer view tools when the user asks to show, see, or visualize Focus data. Use entity tools for mutations, scripting, and non-UI hosts.
- Refresh: The iframe re-calls the same view tool via
app.callServerTool— there are no separate*-refreshtools.
View tools
| Tool | Use when |
| ------------------------------ | ---------------------------- |
| focus-tasks-inbox | Task list / backlog |
| focus-task-detail | Single task deep-dive |
| focus-status-board | Kanban-style status snapshot |
| focus-projects-overview | Project list |
| focus-project-detail | Single project overview |
| focus-milestones-roadmap | Milestones by due date |
| focus-search-results | Search across entities |
| focus-week-at-a-glance | Week plan vs tracked time |
| focus-day-today | Today's schedule |
| focus-time-log | Time entries table |
| focus-time-blocks | Scheduled blocks |
| focus-report-workload | Team workload / utilization |
| focus-report-time-by-project | Time grouped by project |
| focus-report-time-by-member | Time grouped by member |
| focus-report-billable | Billable summary |
| focus-report-task-throughput | Task completion throughput |
| focus-team-roster | Workspace members |
| focus-clients-directory | Client list |
| focus-time-off-calendar | Time off in range |
| focus-holidays-calendar | Public holidays |
| focus-mutation-review | Confirm pending mutation |
| focus-dry-run-preview | Preview dry-run payload |
Hosts without MCP App support still receive the same JSON tool results as entity tools.
Adding a new entity action
- Regenerate
@toggl/focus-queriesif the endpoint or schema is new. - Reuse the generated request function and Valibot schemas in
@toggl/cli-core/@toggl/operations. - Add or update operation metadata in
packages/operations/src. - Rebuild (
pnpm run build) and update docs/skill guidance if the user-facing workflow changes.
Development
pnpm install # Install dependencies
pnpm run build # Build MCP App iframe + compile TypeScript → build/
pnpm run build:mcpb # Node-based .mcpb (same as build:mcpb:node)
pnpm run build:mcpb:binary -- --target darwin-arm64 # Binary .mcpb (requires Bun); targets: darwin-arm64, darwin-x64, linux-x64, win32-x64
pnpm run lint # Run Oxlint checks
pnpm run lint:fix # Auto-fix lint issues when possible
node build/index.js auth # (Re-)authenticateReleases use Changesets: run pnpm run changeset on a branch when you need a version bump, merge to main, and CI publishes to GitHub Packages when pending changesets exist.
The project uses TypeScript with ES2022 target and Node16 module resolution. Output goes to build/. Package tests: pnpm --filter @toggl/mcp test (tool inventory invariants vs @toggl/operations).
Publishing
CI publishes @toggl/mcp to GitHub Packages when pending Changesets are merged to main.
The same publish workflow also cross-compiles binary MCP bundles with Bun and attaches, per platform:
toggl-focus-mcp-<version>-darwin-arm64.mcpb(+.sha256)toggl-focus-mcp-<version>-darwin-x64.mcpb(+.sha256)toggl-focus-mcp-<version>-linux-x64.mcpb(+.sha256)toggl-focus-mcp-<version>-win32-x64.mcpb(+.sha256)
These assets are attached to the GitHub release tag @toggl/mcp@<version>.
