time-tracker-mcp
by mikka-oldst
README.md
# time-tracker-mcp
A local MCP server that lets Claude Code read your logged hours from our internal
time tracker.
Phase 0 of bench project **BP-006 — Remote MCP Skill**
([remote-mcp-skill](https://github.com/Old-St-Labs/remote-mcp-skill)). This is the
reference implementation: get the tool logic right locally, then port it to AWS.
## Why
Open Google Calendar, read last week's meetings, and transcribe
them into the tracker by hand. Claude can already see your calendar via the Google
Calendar connector — it just can't see the tracker. This closes that gap:
```
You: What meetings from last week haven't I logged yet?
Claude: [reads calendar, reads tracker, diffs]
Logged 4h. Calendar shows 7h 45m. Missing:
- Thu 30 Jul, Prism Standup, 15m
- Fri 31 Jul, Masterclass, 3h
Flag: Fri Masterclass overlaps Mentors x Interns 16:30-17:00.
```
Read-only. It tells you what to enter; it does not enter it.
## Tools
| Tool | Returns |
|---|---|
| `list_time_entries(start, end)` | entries in a date range — `day`, `start`, `hours`, `project`, `task`, `description`, `billable` |
| `list_projects()` | project ids and names |
| `get_active_timer()` | the running timer, or null |
Any range works — a day, a week, a month. Totals and grouping are left to the
caller: the tools return rows, Claude does the arithmetic.
## Install
```bash
git clone https://github.com/mikka-oldst/time-tracker-mcp.git
cd time-tracker-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env # then fill in — see Auth below
.venv/bin/python server.py --smoke
```
`--smoke` checks the date conversions offline, then makes one live call per tool.
Run it before wiring anything up; it fails loudly and specifically.
Then register it (absolute paths — Claude spawns this as a subprocess with no
shell context):
```bash
claude mcp add -s user time-tracker -- \
/full/path/to/time-tracker-mcp/.venv/bin/python \
/full/path/to/time-tracker-mcp/server.py
```
Restart your session, confirm with `/mcp`, and ask it something.
## Auth
Two cookie values, pasted once. The server exchanges them for a fresh Convex JWT
on demand, so you never hand-paste a token.
DevTools → Application → Cookies → the tracker's domain:
| Cookie | `.env` key |
|---|---|
| `__session` | `CLERK_SESSION_COOKIE` |
| `__clerk_db_jwt` | `CLERK_DB_JWT` |
The Clerk session id is read from the cookie's `sid` claim, so there is nothing
else to copy. Leave `CLERK_JWT` blank — if it is set, it overrides refresh.
Tokens come from Clerk's `convex` JWT template and last an hour. The *default*
template mints 60-second tokens that expire before you can use them, which is
why the template name matters.
## How it works, and what surprised us
The tracker is Next.js on Vercel with Clerk auth and a **Convex** backend. It has
no REST API — the browser talks to Convex over a WebSocket. Probing `/api/*`
returns 404 for everything, and the Network tab looks empty unless you filter to
`WS` and read the frames.
Function names and argument shapes came from the `ModifyQuerySet` frames in that
WebSocket. `timeEntries:listByWeek` takes an arbitrary range despite its name —
the UI calls it with a single day.
Three things worth knowing if you extend this:
- **Epoch milliseconds, Manila midnight.** Not UTC. Getting it wrong shifts
entries by a day. Asserted against real captured frames in `smoke()`.
- **Convex returns HTTP 200 on function failure.** Check `status` in the response
body, not the status code.
- **Entries carry 21 fields**, most of it Clockify/ClickUp sync plumbing. They get
trimmed to 7 before reaching Claude; passing the rest floods the context.
We use `httpx` against Convex's HTTP query endpoint rather than the official
`convex` Python client, which hung indefinitely with no timeout option.
## Limitations
- **Local only.** The cookie-refresh flow uses Clerk's undocumented client API and
authenticates as *you*. It cannot run on Lambda. A remote deployment needs a
Convex deploy key or a Clockify API key.
- **Read-only.** No writing entries. A human should confirm hours.
- **Calendar shows invitations, not attendance.** RSVPs are often `needsAction`, so
a meeting on the calendar is not proof you attended it.
- **Overlapping meetings need a human rule.** The tools surface the conflict rather
than guessing which one wins.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues