Skip to main content
Glama
clockify-mcp

clockify-mcp

clockify-mcp

A local MCP server that lets Claude start, stop, and log Clockify time entries.

It runs as a child process over stdio — no port, nothing listening, no inbound network surface. Claude spawns it, they exchange JSON-RPC over stdin/stdout, and the server talks to api.clockify.me over HTTPS on your behalf.

Claude Code ──JSON-RPC/stdio──► clockify-mcp ──HTTPS + x-api-key──► Clockify

Setup

1. Get a Clockify API key. In the Clockify web app: Preferences → Advanced → API → generate. It's a static personal key — no OAuth, no expiry — and it carries exactly your own permissions.

2. Configure.

cp .env.example .env
# then edit .env and paste the key after CLOCKIFY_API_KEY=

.env is gitignored. The server reads it from its own directory, so the key stays out of ~/.claude.json.

CLOCKIFY_WORKSPACE_ID is optional — the server reads your active workspace from GET /v1/user on first use. Only set it if you belong to several workspaces and want to pin one.

3. Build.

npm install
npm run build

4. Register with Claude Code (user scope makes it available in every project):

claude mcp add clockify --scope user -- node D:/UniqProject/clockify-mcp/dist/index.js
claude mcp list          # expect: clockify: ... ✔ Connected

If you would rather keep the key in Claude's config than in .env, add -e CLOCKIFY_API_KEY=<key> before the --.

Related MCP server: Claude-LMStudio-Bridge

Tools

Tool

Arguments

What it does

list_projects

name?, includeArchived?

Find a projectId. Active projects only by default.

list_tasks

projectId, name?

Find a taskId. Active tasks only.

list_tags

—

Find tagIds.

current_timer

—

What's running, and for how long.

start_timer

description, projectId?, taskId?, tagIds?, billable?, start?

Start a timer.

stop_timer

end?

Stop the running timer, report the duration.

log_time

description, start, end, projectId?, taskId?, tagIds?, billable?

Back-fill a finished entry.

list_time_entries

start?, end?, projectId?, pageSize?

Entries for a period with a total. Defaults to today.

In practice you don't call these by name — you say "clock 2 hours on the launcher bug" and Claude looks up the project, then logs it.

Times

Every time argument accepts:

You write

Interpreted as

2026-08-01T09:30

09:30 local time

2026-08-01 09:30

same

2026-08-01T09:30:00Z

09:30 UTC

2026-08-01T09:30:00+07:00

as written

2026-08-01

local midnight

Output is shown in your Clockify account's timezone (read from your profile, e.g. Asia/Saigon), so it matches what the Clockify web UI shows. Clockify stores UTC; the conversion happens here.

Gotcha: the list endpoint filters by wall clock, not UTC

GET /v1/workspaces/{ws}/user/{id}/time-entries takes start/end in a format ending in Z, but it does not compare them as UTC — it compares them against wall-clock time in the account's timezone. Verified against the live API on an Asia/Saigon (UTC+7) account:

start / end sent

Result

2026-07-31T17:00:00Z → 2026-08-01T16:59:59Z(honest UTC for local Aug 1)

returns Jul 31's entries

2026-08-01T00:00:00Z → 2026-08-01T23:59:59Z(local wall clock, Z kept)

returns Aug 1's entries ✓

Entry timestamps in POST/PUT bodies are ordinary UTC — the quirk is filter-only, which makes it easy to miss. toZoneFilter in src/time.ts is the one place that compensates; don't "fix" it back to UTC.

Notes

  • Clockify permits only one running timer. start_timer therefore stops any timer already running — the tool reports this explicitly rather than letting it happen silently.

  • stop_timer with nothing running answers "No timer was running." rather than erroring.

  • There is deliberately no update or delete tool, so the server cannot modify or destroy existing entries. The worst case is a spurious new entry you can remove in the web UI.

Development

npm run watch    # tsc --watch

Smoke-test the protocol without involving Claude:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node dist/index.js

Only protocol messages go to stdout — anything else would corrupt the stream, so logs go to stderr.

Security

The API key is a bearer credential with full access to your Clockify time data. It sits in plaintext in .env, readable by anything running as your user; there is no OS keychain integration here.

The key never leaves your machine — it goes into an outbound header and is not sent to Anthropic. Tool results (project names, entry descriptions, durations) do become part of the conversation, so they reach the model like any other tool output.

Layout

src/time.ts       local <-> UTC conversion, ISO-8601 durations
src/clockify.ts   HTTP client, error mapping, cached identity lookup
src/tools.ts      the 8 tool registrations
src/index.ts      env loading, McpServer, stdio transport

Built on @modelcontextprotocol/server v2. Requires Node >= 20.

Available Tools

8 tools
current_timerShow the running timerA
Read-only

Show the currently running Clockify timer, with elapsed time so far. Returns a plain message when nothing is running.

Inputs: no named parameters.

list_projectsList Clockify projectsA
Read-only

List projects in the Clockify workspace. Use this to find the projectId needed by start_timer and log_time. Active projects only unless includeArchived is set.

Inputs: name, includeArchived.

list_tagsList Clockify tagsA
Read-only

List tags in the workspace, for the tagIds argument of start_timer / log_time.

Inputs: no named parameters.

list_tasksList tasks in a projectA
Read-only

List active tasks belonging to a Clockify project. Use this to find the taskId for start_timer and log_time. Requires a projectId from list_projects.

Inputs: projectId (required), name.

list_time_entriesList logged time entriesA
Read-only

List your Clockify time entries for a period, with a total. Defaults to today. Times are shown in local time.

Inputs: end, start, pageSize, projectId.

log_timeLog a finished time entryA

Create a completed Clockify time entry for a past interval. Use this to back-fill work you did not time live. Does not touch the running timer.

Inputs: end (required), start (required), description (required), tagIds, +3 more.

start_timerStart a Clockify timerA

Start a running Clockify timer. Clockify allows only one running timer at a time, so this stops any timer already running. Use list_projects / list_tasks first if you need ids.

Inputs: description (required), start, tagIds, taskId, +2 more.

stop_timerStop the running Clockify timerA
Idempotent

Stop the currently running Clockify timer and report the logged duration. Reports plainly when no timer was running.

Inputs: end.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool targets a distinct resource or action: live timer controls, historical entry listing/creation, and reference lookups. start_timer and log_time are clearly separated by live vs. past work, and current_timer is clearly distinct from list_time_entries.

    Naming Consistency4/5

    Seven of eight tools follow a consistent verb_noun pattern (start_timer, stop_timer, log_time, list_*). current_timer is the only outlier, using a noun phrase instead of a verb-led name.

    Tool Count5/5

    Eight tools is well-scoped for a focused Clockify time-tracking server. Each tool earns its place: timer state, entry creation/listing, and ID lookups for projects, tasks, and tags.

    Completeness3/5

    The core timer loop and time listing are well covered, but updating or deleting time entries is missing. An agent cannot correct or remove a mistakenly logged entry, which is a notable workflow dead end.

    Maintenance

    ActivityStale
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers