Skip to main content
Glama

mcp-timely

An MCP server for Timely time tracking, built for asking questions rather than wrapping endpoints. It is for Timely users who want Claude, or any other MCP client, to answer "where did my time go?" reliably. Its three questions are each answered in a single upstream call with Timely doing the aggregation server-side, so the model never pages through raw entries to sum hours (and never gets the sum wrong). Four write tools log time, correct entries that were logged wrong, and mark invoiced hours billed.

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 their id) and notes for a range; standup, diary or invoicing material

create_entry

Log time I already spent. One entry on a project and day; external_id makes a repeat call a no-op

update_entry

Fix an entry. Move it to another project, or change its minutes, note, day, labels or billable flag; returns it before and after

delete_entry

Remove an entry, e.g. a duplicate. Does nothing unless confirm is true

mark_billed

I've invoiced this. Marks my billable, unbilled entries on a project in a date range as billed; a dry run unless confirm is true

Everything is scoped to the authorized user; there are no user or team parameters. update_entry and delete_entry refuse entries that are billed, invoiced, locked, running a timer, or belong to someone else. When Timely rejects a change, the error carries Timely's reason (for example labels the target project does not allow).

Requirements

  • Python 3.12 or newer and uv

  • FastMCP 4 (fastmcp>=4.0.10,<5.0.0, installed by uv sync)

  • A Timely account and a Timely OAuth application

  • Docker, only for the containerized HTTP deployment

Related MCP server: time-tracker-mcp

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

    cp .env.example .env   # fill in TIMELY_CLIENT_ID and TIMELY_CLIENT_SECRET
  3. Authorize (once)

    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 (stdio)

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

Configuration

Settings come from environment variables (see .env.example).

Variable

Default

Purpose

TIMELY_CLIENT_ID

empty

Timely OAuth application ID.

TIMELY_CLIENT_SECRET

empty

Timely OAuth application secret.

TIMELY_TOKEN_FILE

timely_tokens.json

Where the token pair is stored. Must be writable and persistent.

TIMELY_API_BASE

https://api.timelyapp.com

Timely API base URL.

TRANSPORT

stdio

stdio or http (streamable HTTP on /mcp).

HOST

127.0.0.1

Bind address for http.

PORT

8000

Listen port for http.

MCP_API_KEY

empty

Bearer token for the MCP endpoint. Required when TRANSPORT=http.

APP_VERSION

unset

Version string reported by /health; set by the release image build.

Authentication

There are two separate credentials:

  • Upstream (Timely): OAuth 2.0, bootstrapped once with mcp-timely auth. Timely rotates refresh tokens on every refresh and its access tokens carry no expiry. The server uses the access token until a 401, then refreshes once, writes the new pair atomically before continuing, and retries the request once. The token file is the only state. Running mcp-timely auth again rotates the token family, so any other running instance needs the new file. Otherwise re-authorization is only needed if the grant is revoked.

  • Clients (HTTP only): with TRANSPORT=http, every request needs Authorization: Bearer <MCP_API_KEY> (constant-time compare). The server refuses to start over HTTP without a key.

HTTP deployment

TRANSPORT=http serves streamable HTTP on /mcp and liveness on /health (also /healthz). The Dockerfile builds a non-root image with the token file at /data/timely_tokens.json; compose.yaml runs the published image on host port 8011 with a persistent timely-data volume. Run mcp-timely auth first and place the resulting token file in that volume.

docker compose up

Usage telemetry

A small middleware (src/mcp_timely/usage.py) writes one JSON line per tool call to stderr: server name, tool name, duration, outcome and MCP protocol version. It never records arguments or results. Tool failures raise ToolError, so they are logged with outcome: error.

Development

uv sync
uv run pytest        # in-memory MCP client, no network
uv run ruff check .

CI (.github/workflows/ci.yml) runs ruff and the tests as the test check, which is required before a pull request can merge into main.

Releases

Every push to main that touches non-doc files releases automatically: tests and a security audit run, the next vX.Y.Z tag is pushed (no version-bump commit), and a multi-arch image is published to ghcr.io/caseyro/mcp-timely with that version baked in (reported by /health). Consequences:

  • The git tag is the version; version in pyproject.toml is not bumped.

  • Add [skip ci] to a commit message to skip a release.

  • Markdown-only and test-only changes don't trigger a release.

Support

If this server saves you time, you can buy me a coffee.

License

MIT, see LICENSE.

Available Tools

3 tools
projects_overviewProjects overviewA
Read-onlyIdempotent

[timely] What's on my plate? Every project with client, logged hours, budget burn, and unbilled amounts, sorted by hours logged.

Inputs: include_inactive. Returns: count, summary, projects

time_spentTime spentA
Read-onlyIdempotent

[timely] Where did my time go? Server-side rollups for a date range (YYYY-MM-DD), grouped by project, client, label, or day, with billable split. Totals are computed by Timely, not client-side. Day buckets follow the Timely account's timezone (a day's bucket equals the sum of that day's entries).

Inputs: since (required), until (required), group_by. Returns: since, until, groups, summary, +5 more

work_logWork logA
Read-onlyIdempotent

[timely] What did I actually do? Individual entries with notes for a date range (YYYY-MM-DD) — standup, diary, and invoicing raw material.

Inputs: upto (required), since (required). Returns: upto, since, entries, summary, +1 more

Tool Schema Changelog

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

Loading tool schema changelog…

    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

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers