Set it up in ten minutes.
Create a private FreshBooks OAuth app, install the server, authorize once, and start asking your agent about your timesheet. Every install path starts with the app in step 1.
Source, full tool reference, and the extension build script live in the GitHub repository. MIT licensed; not affiliated with FreshBooks.
The one-click Claude Desktop extension (.mcpb). Docker and local Python are alternatives for Claude Code, other MCP clients, or headless use. Claude Desktop is macOS/Windows only.
Create a FreshBooks app
The MCP talks to FreshBooks as an OAuth2 app. Create one once and copy its credentials — they identify the app, not a person, so a team can share one app and each member authorizes with their own login.
- Log in at my.freshbooks.com and open the Developer Portal (profile menu → Developers).
- Create an App — name it something like
Timesheet MCP. - Set the Redirect URI to exactly
https://localhost/callback. FreshBooks requires HTTPS and matches character-for-character. - Add the scopes below, save, and copy the Client ID and Client Secret.
user:profile:read # identity / business discovery (/me)
user:time_entries:read # check_timesheet, list_time_entries
user:time_entries:write # log_time, update_time_entry
user:projects:read # list_projects
user:clients:read # list_clients
user:billable_items:read # list_services
Changing scopes later does not update tokens you've already issued. After editing scopes you must re-authorize, or the affected tool returns 403 insufficient_scope.
Install the Claude Desktop extension
Install uv
Claude Desktop bundles Node.js — not uv or Python — so this extension needs uv installed system-wide where the GUI can see it on PATH.
# macOS
$ brew install uv
# Windows
$ winget install --id=astral-sh.uv -e
If the install fails with "incompatible with your device," Claude Desktop didn't find system Python. Install it too (brew install python@3.12 / winget install Python.Python.3.12); uv still manages the real runtime.
Get the bundle
Easiest is to have a teammate share the built .mcpb — then you only need uv, no clone or toolchain. To build it yourself (needs Node/npx and python3):
$ git clone https://github.com/SantiaGoMode/freshbooks-timesheet-mcp.git
$ cd freshbooks-timesheet-mcp
$ ./extension/build.sh
Configure
In Claude Desktop → Settings → Extensions, drag in the .mcpb and fill in the prompts.
| Field | Value |
|---|---|
| Client ID / Client Secret | From step 1 — stored in your OS credential store |
| Redirect URI | https://localhost/callback (must match step 1) |
| Timezone | e.g. America/Denver |
| Default daily hours / Max days | Defaults are fine (8 / 31) |
Use it
Ask in plain language. The agent picks the tool and, for anything that writes, confirms the project first.
› Which days am I missing this week?
› Log 8 hours Mon–Fri last week to the Acme project.
› Log 8h a day Mon–Thu on Acme, PTO Friday — dry run first.
› Move Tuesday's entry to the Northwind project.
A status question never writes. A logging request requires an explicit project, skips days that already have entries, and never touches weekends.
Docker and local Python
Docker
For Claude Code, other MCP clients, or headless use. Credentials come from Docker secrets and tokens persist in a named volume — the container can't reach the OS keychain, so it uses the encrypted-file backend.
$ cp .env.example .env # non-secret settings only
$ mkdir -p secrets
$ printf %s 'PASTE_CLIENT_ID' > secrets/fb_client_id
$ printf %s 'PASTE_CLIENT_SECRET' > secrets/fb_client_secret
$ python3 -c "from cryptography.fernet import Fernet;print(Fernet.generate_key().decode())" > secrets/fb_token_key
$ docker compose build
$ docker compose run --rm app freshbooks-mcp-auth # one-time auth
$ docker compose run --rm app python scripts/smoke.py # read-only check
The Fernet key encrypts your tokens — keep secrets/fb_token_key stable. Regenerating it means re-running auth.
Local Python
Runs directly; tokens go in your OS keychain / Credential Manager.
$ python -m venv .venv && source .venv/bin/activate
$ pip install ".[dev]"
# put FRESHBOOKS_CLIENT_ID / FRESHBOOKS_CLIENT_SECRET in .env, then:
$ freshbooks-mcp-auth # prints URL, prompts for code
$ python scripts/smoke.py
Register the server with your MCP client:
{
"mcpServers": {
"freshbooks": {
"command": "/abs/path/.venv/bin/freshbooks-mcp",
"env": { "TZ": "America/Denver" }
}
}
}
On Windows the command path is C:\abs\path\.venv\Scripts\freshbooks-mcp.exe.
Configuration
Set in .env (loaded automatically) or through the extension's fields. Client credentials come from my.freshbooks.com → Developer → your app.
| Variable | Required | Notes |
|---|---|---|
FRESHBOOKS_CLIENT_ID | Required | OAuth app client id |
FRESHBOOKS_CLIENT_SECRET | Required | OAuth app secret |
FRESHBOOKS_REDIRECT_URI | Required | Must exactly match the app's redirect URI |
FRESHBOOKS_BUSINESS_ID | — | Auto-discovered via /me if blank |
FRESHBOOKS_IDENTITY_ID | — | Auto-discovered via /me if blank |
FRESHBOOKS_TOKEN_BACKEND | — | keyring (default) or file |
FRESHBOOKS_TOKEN_PATH | — | Encrypted token file path (file backend only) |
FRESHBOOKS_TOKEN_KEY | — | Fernet key for the file backend |
TZ | — | Day/week/month boundary timezone (default UTC) |
DEFAULT_DAILY_HOURS | — | Expected hours per day (default 8) |
DEFAULT_START_TIME | — | Local start time for logged entries (default 09:00) |
MAX_LOG_DAYS | — | Safety cap on days per log_time call (default 31) |
Tool reference
check_timesheet(period, date?, expected_hours?) reports logged, under-logged, and missing weekdays for a "day", "week", or "month". list_time_entries(period, date?) returns individual entries with their ids, and update_time_entry(entry_id, …) edits one of them — only the fields you pass change.
log_time(period, hours, project_id, …) is the write path:
| Param | Default | Notes |
|---|---|---|
project_id | — | Required No silent default — the agent must confirm first |
date | today | Anchor date for the period |
off_days | [] | YYYY-MM-DD dates to skip (PTO, holidays) |
note | "Logged via MCP" | Entry note |
billable | false | If true, client_id is required |
client_id / service_id | — | For billable entries and the billing rate |
skip_existing | true | Skip days that already have entries |
dry_run | false | Preview the plan without writing |
Weekends are always excluded, hours must be 0 < hours ≤ 24, and a single call may not exceed MAX_LOG_DAYS.
Troubleshooting
| Symptom | Fix |
|---|---|
| Extension "incompatible with your device" | Claude Desktop didn't find system Python. Install it and retry. |
Extension won't start / uv not found | Install uv system-wide and restart Claude Desktop. |
invalid_client during auth | Credentials don't match a live app, or have stray whitespace. Run auth_debug to confirm what's loaded. |
invalid_grant during auth | The code expired or was reused. Run start_auth again and paste a fresh code quickly. |
403 insufficient_scope | The app is missing that tool's scope. Add it in step 1, then re-authorize — existing tokens don't gain new scopes. |
| "redirect_uri mismatch" | The app's redirect URI must exactly equal the connector's. |
| Want to reset auth | Re-running start_auth → finish_auth overwrites the token. To clear it: delete freshbooks-timesheet-mcp from Keychain Access or Credential Manager. |
| Everything shows as missing | You haven't logged yet, or the timezone is wrong. Set the extension's Timezone field or TZ. |
| (Docker) Fernet key error | Write a literal generated key into secrets/fb_token_key — .env doesn't run $(...). |
python scripts/smoke.py exercises every read tool and names any missing scope, without writing anything.
pytest runs the unit tests against the source tree; the Docker image has a test target too.