Project documentation

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.

REPO

Source, full tool reference, and the extension build script live in the GitHub repository. MIT licensed; not affiliated with FreshBooks.

Recommended path

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.

  1. Log in at my.freshbooks.com and open the Developer Portal (profile menu → Developers).
  2. Create an App — name it something like Timesheet MCP.
  3. Set the Redirect URI to exactly https://localhost/callback. FreshBooks requires HTTPS and matches character-for-character.
  4. 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.

FieldValue
Client ID / Client SecretFrom step 1 — stored in your OS credential store
Redirect URIhttps://localhost/callback (must match step 1)
Timezonee.g. America/Denver
Default daily hours / Max daysDefaults are fine (8 / 31)

Authorize once

Say "Authorize FreshBooks." The agent returns a URL — open it and approve. You'll be redirected to https://localhost/callback?code=…; the page won't load, and that's expected. Copy the code out of the address bar and paste it back into the chat.

  1. 1
    start_auth

    Builds the authorization URL for your app and redirect URI.

  2. 2
    finish_auth

    Exchanges the pasted code for tokens and stores them in the OS keychain / Credential Manager.

  3. 3
    Automatic refresh

    From then on the server refreshes access tokens itself, persisting each new rotating refresh token before use.

The code is single-use and expires in minutes.

If authorization fails with invalid_client, ask the agent to run auth_debug — it fingerprints the loaded credentials without revealing them.

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.

VariableRequiredNotes
FRESHBOOKS_CLIENT_IDRequiredOAuth app client id
FRESHBOOKS_CLIENT_SECRETRequiredOAuth app secret
FRESHBOOKS_REDIRECT_URIRequiredMust exactly match the app's redirect URI
FRESHBOOKS_BUSINESS_IDAuto-discovered via /me if blank
FRESHBOOKS_IDENTITY_IDAuto-discovered via /me if blank
FRESHBOOKS_TOKEN_BACKENDkeyring (default) or file
FRESHBOOKS_TOKEN_PATHEncrypted token file path (file backend only)
FRESHBOOKS_TOKEN_KEYFernet key for the file backend
TZDay/week/month boundary timezone (default UTC)
DEFAULT_DAILY_HOURSExpected hours per day (default 8)
DEFAULT_START_TIMELocal start time for logged entries (default 09:00)
MAX_LOG_DAYSSafety 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:

ParamDefaultNotes
project_idRequired No silent default — the agent must confirm first
datetodayAnchor date for the period
off_days[]YYYY-MM-DD dates to skip (PTO, holidays)
note"Logged via MCP"Entry note
billablefalseIf true, client_id is required
client_id / service_idFor billable entries and the billing rate
skip_existingtrueSkip days that already have entries
dry_runfalsePreview 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

SymptomFix
Extension "incompatible with your device"Claude Desktop didn't find system Python. Install it and retry.
Extension won't start / uv not foundInstall uv system-wide and restart Claude Desktop.
invalid_client during authCredentials don't match a live app, or have stray whitespace. Run auth_debug to confirm what's loaded.
invalid_grant during authThe code expired or was reused. Run start_auth again and paste a fresh code quickly.
403 insufficient_scopeThe 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 authRe-running start_authfinish_auth overwrites the token. To clear it: delete freshbooks-timesheet-mcp from Keychain Access or Credential Manager.
Everything shows as missingYou haven't logged yet, or the timezone is wrong. Set the extension's Timezone field or TZ.
(Docker) Fernet key errorWrite a literal generated key into secrets/fb_token_key.env doesn't run $(...).
Read-only smoke test

python scripts/smoke.py exercises every read tool and names any missing scope, without writing anything.

Test suite

pytest runs the unit tests against the source tree; the Docker image has a test target too.

NEXT STEP Read the full tool reference in the repository
Browse the repository