{
  "markdown": "# mcp-time-tracker\n\n<!-- mirror-seo:start -->\n\n**MCP server for time tracking: timesheets, a timesheet and a billable hours tracker.** Track billable time without leaving the chat.\n\nWorks with Claude Desktop, Claude Code, Cursor and any Model Context Protocol client. Runs on your own machine, or hosted with no install.\n\n## Install\n\n**Hosted, nothing to install.** Get a token from <https://mcp.zovo.one/mcp/connect> (the connect page) or <https://mcp.zovo.one/mcp/token> (the same token as JSON); a free anonymous one is issued on the spot and a Pro key works the same way. Then point an MCP client at `https://mcp.zovo.one/mcp/time-tracker` over streamable-http and send the token as `Authorization: Bearer <token>`.\n\nIf your client cannot set headers, put the token in the path instead: `https://mcp.zovo.one/mcp/time-tracker/t/<token>`. Both forms work. The bare URL with no token answers 401 on `tools/call`, so the token is not optional.\n\n**Claude Desktop, one click.** Download `time-tracker.mcpb` from the [latest release](https://github.com/theluckystrike/mcp-servers/releases/latest) and double-click it.\n\n**From source.** The mirror is self-contained: every `@theluckystrike/*` dependency is vendored, so a fresh clone builds with no extra setup.\n\n```sh\ngit clone https://github.com/theluckystrike/mcp-time-tracker.git\ncd mcp-time-tracker\nnpm install && npm run build\n```\n\nThen point your client at the built entry point:\n\n```json\n{\n  \"mcpServers\": {\n    \"time-tracker\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/mcp-time-tracker/dist/index.js\"]\n    }\n  }\n}\n```\n\n> `@theluckystrike/mcp-time-tracker` is **not published on npm yet**, so an `npx -y @theluckystrike/mcp-time-tracker` command will fail. The three paths above are the working ones and each is exercised by CI.\n\n![time-tracker demo](https://raw.githubusercontent.com/theluckystrike/mcp-servers/main/assets/demo-time-tracker.gif)\n\nRead-only mirror of [mcp-servers/servers/time-tracker](https://github.com/theluckystrike/mcp-servers/tree/main/servers/time-tracker). See [MIRROR.md](MIRROR.md).\n\n<!-- mirror-seo:end -->\n\nTrack billable time without leaving your AI chat. Say \"start a timer on the acme redesign\", keep working, then\nask for \"my hours this week by project\" or \"invoice lines for acme in August\". It keeps a running timer, lets you\nlog time you forgot to track, applies your hourly rate per project, and turns the result into a report, a CSV file\nor a set of invoice line items. Everything is stored as plain JSON on your own machine.\n\nBuilt by [theluckystrike](https://github.com/theluckystrike).\n\n\n**Track billable time from chat and turn it straight into a report or invoice line items -- zero setup, all local.**\n\n## 60-second install\n\nnpm publish for `@theluckystrike/mcp-time-tracker` is pending. Until then, the `.mcpb` one-click bundle or a clone+build\nis the working path -- both are verified below.\n\n**One-click (.mcpb):** download `time-tracker.mcpb` from the latest release and double-click it in Claude Desktop:\nhttps://github.com/theluckystrike/mcp-servers/releases/latest\n\n**Claude Desktop** (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"time-tracker\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@theluckystrike/mcp-time-tracker\"]\n    }\n  }\n}\n```\n\n**Claude Code:**\n\n```sh\nclaude mcp add time-tracker -- npx -y @theluckystrike/mcp-time-tracker\n```\n\n**Cursor** (`.cursor/mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"time-tracker\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@theluckystrike/mcp-time-tracker\"]\n    }\n  }\n}\n```\n\nThe `npx` form above starts working the moment the package is published. Until then, use the .mcpb bundle above, or\nbuild from source with exactly these three commands:\n\n```sh\ngit clone https://github.com/theluckystrike/mcp-servers.git && cd mcp-servers\nnpm install\nnpm run build -w packages/mcp-license -w servers/time-tracker\n```\n\nThen point your client's `command` at `node` with one arg: the absolute path to `servers/time-tracker/dist/index.js`.\n\nTo run in Pro mode set `MCP_LICENSE_KEY` in the same config block, or call `license_activate` once with your key.\n\n## Tools\n\n| Tool | What it does |\n| --- | --- |\n| `timer_start` | Start a timer on a project (optional task, tags, rate, currency). Starting a new one stops and logs the old one. A partial project name that matches exactly one existing project is used as that project. |\n| `timer_stop` | Stop the running timer, write the entry, return the duration. |\n| `timer_status` | What is running, for how long, and today's total. |\n| `entry_add` | Log time you already worked (start plus end or minutes), with an optional rate and currency: rate \"90 euros an hour\" bills as EUR 225.00 for 2.5 h. Partial project names resolve like `timer_start`. |\n| `entry_list` | Compact table of entries, filtered by date range and project. |\n| `entry_edit` | Change any field of an entry. |\n| `entry_delete` | Delete an entry by id. |\n| `project_set_rate` | Set the hourly rate and currency used for money totals (currency accepts codes or words: EUR, euros, pounds, zl). `apply_to_existing: true` re-rates time already logged for that project; add `only_missing: true` to touch only entries that carry no rate. |\n| `report` | Hours and money for a period, optionally grouped by project, day, task or tag; omit `group_by` for the plain total per currency. Table, JSON or CSV. Hours already invoiced are left out; pass `unbilled_only: false` for the full timesheet. |\n| `export_csv` | Write entries to a CSV file and return the path. |\n| `invoice_summary` | Invoice-ready line items for one project: hours, rate, amount, total, in the currency the time was logged in. One line per task and rate, so no line ever shows a blended rate nobody agreed. Returns the `entry_ids` behind the lines, and skips hours already invoiced (`unbilled_only: false` includes them). Free for the last 7 days, Pro for any period from full history. |\n| `entry_mark_billed` | Stamp the hours that went on an invoice with its number (`ids` from `invoice_summary`, or `project` + `from` + `to`), so `report` and `invoice_summary` stop offering them and the same hours are never billed twice. |\n| `license_status` | Free or Pro, and where to upgrade. |\n| `license_activate` | Activate a Pro key (verified offline). |\n\nAlso exposed: the resource `timetracker://today` (today's summary) and the prompt `daily_standup`\n(writes a standup update from yesterday's and today's tracked time).\n\n## What you can say\n\nNo tool names required. These are the sentences that were actually tested against the server; the tool\ncolumn is what answered them.\n\n| You say | Tool |\n| --- | --- |\n| \"Start a timer for the Acme website project.\" | `timer_start` |\n| \"Stop the timer and tell me how long I worked.\" | `timer_stop` |\n| \"What's running right now, and for how long?\" | `timer_status` |\n| \"Log 2.5 hours yesterday for Acme, design review, at 90 euros an hour.\" | `entry_add` |\n| \"Show me my Acme entries for this week.\" | `entry_list` |\n| \"That last entry should say API refactor, not design review.\" | `entry_edit` |\n| \"Delete the entry I just logged twice.\" | `entry_delete` |\n| \"Set my rate for Acme to 90 EUR an hour.\" | `project_set_rate` |\n| \"How many hours did I bill this month, grouped by project?\" | `report` |\n| \"Give me invoice lines for Acme in August.\" | `invoice_summary` |\n| \"I invoiced those hours as INV-2026-0001.\" | `entry_mark_billed` |\n\nTwo more worth knowing: \"export my time to a CSV for my bookkeeper\" (`export_csv`) and \"write my standup\nupdate from yesterday and today\" (the `daily_standup` prompt).\n\n## Worked example\n\nThis is a real transcript from the audit in `docs/USER_VALUE_R2.md`, numbers unchanged.\n\n```\nYou: Log 2.5 hours yesterday for Acme, design review, at 90 euros an hour.\n\n  entry_add { project: \"Acme\", ... }\n  -> \"Acme\" resolved to the existing project \"Acme website\"\n  -> rate \"90 euros an hour\" parsed as EUR 90.00/h\n\nAssistant: Logged 2.50 h for Acme website - design review at EUR 90.00/h = EUR 225.00.\n\nYou: How much do I bill Acme this week? Give me invoice lines.\n\n  invoice_summary { project: \"Acme website\", from: \"2026-08-31\", to: \"2026-09-02\" }\n\nAssistant: Invoice summary - Acme website\n  design review    2.50 h   EUR 90.00/h   EUR 225.00\n  TOTAL            2.50 h                 EUR 225.00\n```\n\nOne call each. The rate carries its currency all the way through: the report never prints a bare \"225\",\nand it never turns into \"$225\" by accident.\n\nA second worked example, the weekly report and the `daily_standup` prompt:\n\n```\nYou: How many hours did I put into Acme this week, grouped by task?\n\n  report { project: \"Acme website\", from: \"2026-08-27\", to: \"2026-09-02\", group_by: \"task\" }\n\nAssistant: Acme website, 2026-08-27 to 2026-09-02\n  design review   2.50 h   EUR 225.00\n  TOTAL           2.50 h   EUR 225.00\n\nYou: Write my standup update.\n\n  (prompt) daily_standup\n\nAssistant: Yesterday: 2.50 h on Acme website (design review).\nToday: nothing logged yet.\n```\n\n### Billed hours close\n\nAn hour that has been invoiced is finished. `entry_mark_billed {ids, invoice_number}` writes\n`billed_at` and `billed_invoice` onto those entries; from then on `report` and `invoice_summary`\nskip them by default, so next month's \"invoice Acme\" cannot re-bill work already paid for. The\nwhole timesheet is still there: pass `unbilled_only: false` to any of them. `invoice_summary`\nreturns the `entry_ids` it used precisely so they can be handed straight to `entry_mark_billed`\nonce the invoice exists.\n\n`report` and `invoice_summary` answer overlapping questions on purpose: `report` is for \"how much time\nand money,\" grouped any way you like; `invoice_summary` is for \"give me the lines I can put on an\ninvoice,\" which is a narrower, invoice-shaped view of the same entries for one project.\n\n## How it stores data\n\nEntries, projects and rates live in one JSON file:\n`${XDG_DATA_HOME:-~/.local/share}/mcp-servers/time-tracker/data.json`.\n\nEvery write (starting or stopping a timer, adding, editing or deleting an entry, setting a rate) happens\nunder an advisory lock file at `.../time-tracker/.lock`, held across the whole load-mutate-save cycle, so\ntwo overlapping calls cannot interleave and corrupt the file. The save itself writes to a temporary file\nand renames it into place, so a crash or a killed process mid-write leaves either the old file or the new\none, never a half-written one. Reads (`entry_list`, `report`, `timer_status`, `export_csv`) do not take\nthe lock.\n\nTo back up your data, copy the single `data.json` file (and `.lock` if present, though it holds no data).\nThere is no database and no hidden second file.\n\nIf `data.json` is ever unreadable or not valid JSON, the server does **not** treat that as \"no data yet\".\nIt moves the file aside byte-for-byte as `data.json.corrupt-<timestamp>`, writes a `data.json.corrupt`\nmarker and makes every tool -- reads included -- return `data file is corrupt; moved to ...; nothing was\nwritten`. Restore a good `data.json` (the quarantined copy is right there) and delete the marker file to\ncarry on. Nothing is overwritten in the meantime.\n\n## Dates, times and rates\n\n- **Timestamps with no offset are your local time.** `2026-09-02T09:00:00` means 09:00 where you are, not\n  UTC. Pass an explicit offset (`2026-09-02T09:00:00+02:00`) or a trailing `Z` and it is honoured exactly.\n- **Date-only bounds cover whole local days.** `from: \"2026-09-01\"` is 00:00:00 local on the 1st and\n  `to: \"2026-09-30\"` is 23:59:59.999 local on the 30th, so a month reported by dates includes its last\n  day. Timestamps with a time are used as given.\n- **Entries are clipped to the window.** An entry that starts before `from` or ends after `to` counts for\n  the part inside the period, not all of it and not none of it.\n- **Entries are split at local midnight for day grouping.** Work from 23:30 to 01:30 is 0.5 h on the first\n  day and 1.5 h on the next, including across a month boundary. `timer_status` counts only the part of an\n  entry -- or of the running timer -- that falls after midnight today.\n- **Rate strings are parsed, never guessed.** `\"1,200 USD\"` is 1200 (a comma followed by exactly three\n  digits is thousands grouping), `\"12,50 EUR\"` is 12.50 (the unambiguous European decimal shape), and\n  `\"1.200,50\"` is 1200.50. Anything that could mean either thing, such as `\"1,2345\"`, is refused with a\n  worked example instead of being read as the wrong number.\n- **Rates are captured when the time is logged.** `entry_add` and `timer_stop` store the effective hourly\n  rate and currency on the entry, and reports and invoices use that stored rate. `project_set_rate`\n  therefore applies to future entries only; pass `apply_to_existing: true` to re-rate the time already\n  logged for that project. That re-stamps EVERY entry of the project, including entries that already\n  carry a rate, and the response says how many changed and the project's new total. Add\n  `only_missing: true` to touch only entries that captured no rate of their own.\n- **Tag rows overlap.** In `group_by: \"tag\"` an entry tagged `dev` and `meeting` appears in both rows; the\n  total is computed from the entries once, so it is never the sum of the rows.\n\n## Limits and honest caveats\n\n- Free `entry_list`, `report`, `export_csv` and `invoice_summary` only see the last 7 days. Timers and\n  entries themselves are unlimited and nothing is ever deleted -- the window just narrows what a free\n  call can read back.\n- Free tier supports hourly rates on 2 projects; a third rated project needs Pro.\n- Every `report` grouping is free, tag included: the tag total is a correctness fix, not a premium\n  feature. `group_by` itself is optional -- omit it for the plain total per currency.\n- Only one timer can run at a time. Starting a second one stops and logs the first -- there is no\n  concurrent-timer mode.\n- There is no reminder or idle-detection: if you forget to stop a timer, it keeps running until you stop\n  it or start another.\n\n## Troubleshooting\n\n- **`npx` hangs or fails to find the package**: npm publish for this package is pending. Use the `.mcpb`\n  bundle or the clone-and-build path above until it lands.\n- **Using the `.mcpb` bundle**: it installs into Claude Desktop directly; there is no separate path to\n  configure.\n- **Using the clone path**: the server binary is `servers/time-tracker/dist/index.js` after\n  `npm run build`. Point your client's `command` at `node` with that absolute path as the only argument.\n- **Node version**: requires Node >= 18. Check with `node -v`.\n- **Nothing shows up / silent failures**: this server writes logs to stderr only, never stdout (stdout is\n  reserved for the MCP protocol). In Claude Desktop, check Settings -> Developer -> the server's log\n  file; in Claude Code, run with `--mcp-debug` or check the terminal you launched it from.\n- **A Pro key isn't recognized**: run `license_status` to see what the server thinks your tier is, and\n  confirm `MCP_LICENSE_KEY` is set in the same process the client launches (not just your shell).\n\n## Privacy\n\nAll data stays local: entries live in `${XDG_DATA_HOME:-~/.local/share}/mcp-servers/time-tracker/data.json`.\nThe server makes no network requests, has no telemetry, and needs no account. License keys are Ed25519\nsignatures verified offline against a public key compiled into the package -- activation works with no\ninternet connection.\n\n## Pairs with\n\n- [mcp-invoice](../invoice/README.md) -- turn `invoice_summary` output straight into a numbered PDF invoice.\n- [mcp-spreadsheet](../spreadsheet/README.md) -- export a CSV with `export_csv` and query or reshape it.\n- [mcp-price-tracker](../price-tracker/README.md) -- if you also buy things for the client, watch those prices.\n- [office-suite](../office-suite/README.md) -- all four servers behind one install, one config entry.\n- Guide: [Track billable hours in Claude Code and Cursor](https://mcp.zovo.one/guides/track-time-in-claude-code)\n\n## FAQ\n\n**Does this work in Cursor as well as Claude Code and Claude Desktop?**\nYes. All three speak MCP over stdio with the same config shape; the tools and the data file are identical\nregardless of client.\n\n**What happens when the free 7-day window runs out on an old entry?**\nNothing is deleted. The entry stays in `data.json` forever; it just will not appear in `entry_list`,\n`report`, `export_csv` or `invoice_summary` results until you activate Pro, which opens full history.\n\n**Can I bill different clients in different currencies?**\nYes. Currency is set per project (or per entry, overriding the project default) and every total is grouped\nby currency -- a report never adds EUR and USD together.\n\n**What happens if two entries have overlapping times?**\nThe server does not block overlaps; it logs what you tell it. `entry_edit` lets you fix a mistake after\nthe fact.\n\n**Does it need an internet connection?**\nNo. There are no network calls anywhere in this server, including for license activation, which is\nverified with a local public key.\n\n## License\n\nMIT\n\n## One business profile for the whole suite\n\nYour identity is stored once, at `${XDG_DATA_HOME:-~/.local/share}/mcp-servers/profile/business.json`,\nand every server in the suite reads it: the invoice issuer, the docx letterhead, the recurring\nissuer, expense-tracker's default VAT rate, time-tracker's and timezone's home zone, and the\nresume and contract letterheads. Set it once with `business_set` (invoice or docx) - you never\nrepeat it anywhere else. An email address is only ever taken from that profile or from an explicit\nargument; when none is stored, documents show `[add: email]` and the tool says so rather than\nletting anyone improvise an address.\n",
  "bytes": 17680,
  "sha": "c1c8fe54c4c2537298b8c5b1c8bf51fc010af8024f5c781e7f1b6fb638ec5a8e",
  "repo_slug": "theluckystrike/mcp-time-tracker",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_theluckystrike_mcp_time_tracker_0fb6147b/readme"
}