Skip to main content
Glama
README.md
# mcp-timely

MCP server for [Timely](https://timelyapp.com) time tracking — built for asking
questions, not wrapping endpoints. Three read-only tools, each answered in a
single upstream call with Timely doing the aggregation server-side, so an LLM
never pages through raw entries to sum hours (and never gets the sum wrong).

| Tool | The question it answers |
|------|------------------------|
| `projects_overview` | *What's on my plate?* — projects with client, logged hours, budget burn, unbilled amounts |
| `time_spent` | *Where did my time go?* — date-range rollups grouped by project, client, label, or day, with billable split |
| `work_log` | *What did I actually do?* — individual entries with notes for a range; standup/diary/invoicing material |

Scope is deliberate: **read-only**, and always scoped to the authorized user.
There are no write tools and no user/team parameters.

## Setup

1. **Create a Timely OAuth app** at
   `https://app.timelyapp.com/<account>/oauth_applications` with callback URL
   `urn:ietf:wg:oauth:2.0:oob`.

2. **Configure**

   ```bash
   cp .env.example .env   # fill in TIMELY_CLIENT_ID and TIMELY_CLIENT_SECRET
   ```

3. **Authorize (once)**

   ```bash
   uv sync
   uv run mcp-timely auth
   ```

   Open the printed URL, authorize, paste the code. Tokens land in
   `TIMELY_TOKEN_FILE` (default `timely_tokens.json`) and refresh themselves
   from then on.

4. **Add to Claude Code**

   ```bash
   claude mcp add timely -- uv run --directory /path/to/mcp-timely mcp-timely
   ```

## Token handling

Timely rotates refresh tokens on every refresh and its access tokens carry no
expiry. The session layer uses the access token until a 401, then refreshes
once — persisting the new pair atomically *before* continuing — and retries the
request once. The token file is the only state; keep it on a persistent volume
in Docker. Re-authorization is only needed if the grant is revoked.

## HTTP deployment

Set `TRANSPORT=http` and `MCP_API_KEY` (the server refuses to start
unauthenticated HTTP). Serves streamable-http on `/mcp`, liveness on
`/health`. See `compose.yaml`.

## Development

```bash
uv run pytest        # unit tests (in-memory MCP client, no network)
uv run ruff check .
```

## Releases

Every push to `main` that touches non-doc files releases automatically: tests
and a security audit run, the patch version bumps, a `vX.Y.Z` tag lands, and a
multi-arch image is published to `ghcr.io/caseyro/mcp-timely`. Consequences:

- Don't edit `version` in `pyproject.toml` by hand — CI owns it.
- Add `[skip ci]` to a commit message to skip a release.
- Rebase PRs on `main` before merging so the auto-bump commit doesn't race yours.
- Markdown-only and test-only changes don't trigger a release.

## License

MIT

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a clearly distinct purpose: projects_overview provides project-level summaries, time_spent aggregates time data, and work_log returns raw entries. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent pattern of [noun]_[descriptor] (e.g., projects_overview, time_spent, work_log), making them predictable and easy to understand.

Tool Count5/5

Three tools are appropriate for a read-only time tracking reporting server, covering high-level overview, aggregated time, and detailed logs without unnecessary bloat.

Completeness4/5

The tools cover the main read operations for time tracking reporting, but lack write/update actions. For a reporting-focused server, this is acceptable, though a tool for client or user listing could enhance coverage.

Maintenance

ActivityMaintained
ResponsivenessNo issues