Skip to main content
ClaudeWave

FreshBooks MCP server for Claude — invoices, clients, estimates, and payments via the FreshBooks OAuth2 API

MCP ServersOfficial Registry0 stars0 forksTypeScriptMITUpdated today
ClaudeWave Trust Score
87/100
Trusted
Passed
  • Open-source license (MIT)
  • Actively maintained (<30d)
  • Clear description
  • Documented (README)
Last scanned: 9/11/2026
Install in Claude Code / Claude Desktop
Method: Manual
Claude Code CLI
git clone https://github.com/chrischall/freshbooks-mcp
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "freshbooks-mcp": {
      "command": "node",
      "args": ["/path/to/freshbooks-mcp/dist/index.js"],
      "env": {
        "FRESHBOOKS_CLIENT_SECRET": "<freshbooks_client_secret>",
        "FRESHBOOKS_REFRESH_TOKEN": "<freshbooks_refresh_token>"
      }
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Clone https://github.com/chrischall/freshbooks-mcp and follow its README for install instructions.
Detected environment variables
FRESHBOOKS_CLIENT_SECRETFRESHBOOKS_REFRESH_TOKEN
Use cases

MCP Servers overview

# freshbooks-mcp

MCP server for [FreshBooks](https://www.freshbooks.com) — invoices, clients, estimates and
payments, exposed to Claude as typed tools.

> This project was developed and is maintained by AI (Claude Code). Use at your own discretion.

## Install

```sh
npm install -g @chrischall/freshbooks-mcp
```

## Setup

FreshBooks is **OAuth2 only** — there is no API key and no personal access token, so a
one-time browser authorization is required.

1. Register an app at <https://my.freshbooks.com/#/developer>. The redirect URI must be
   **HTTPS with no query string**; `https://localhost` works and never needs to resolve.
2. Note the **Client ID** and **Client Secret**.
3. Obtain a refresh token, either way:
   - **From the server itself** (no script): set `FRESHBOOKS_CLIENT_ID` and
     `FRESHBOOKS_CLIENT_SECRET`, start it, then call `freshbooks_auth_url`, open
     the URL it returns, approve, and pass the URL you land on to
     `freshbooks_auth_exchange`. Those two tools need no refresh token — minting
     one is what they are for. This is also the path mcp-host's `authFlow`
     drives, so a hosted connector can do it without you pasting anything.
   - **From the script**, if you prefer it outside the server — see
     [`skills/freshbooks-curl`](skills/freshbooks-curl/SKILL.md).
4. Configure:

```sh
FRESHBOOKS_CLIENT_ID=...
FRESHBOOKS_CLIENT_SECRET=...
FRESHBOOKS_REFRESH_TOKEN=...       # from the bootstrap
FRESHBOOKS_REDIRECT_URI=https://localhost   # optional; must match what you registered
FRESHBOOKS_TOKEN_STORE=~/.freshbooks-mcp/session.json   # optional
```

### ⚠️ Refresh tokens rotate

FreshBooks issues a **new refresh token on every refresh and immediately invalidates the
old one**. This server persists each rotation to `FRESHBOOKS_TOKEN_STORE` (mode `0600`)
before the refresh is considered complete, and prefers the stored token over the
environment value — the stored one has rotated past it.

Two consequences worth knowing:

- **Do not point two tools at the same store.** The MCP server and the `freshbooks-curl`
  skill keep separate state files on purpose; sharing one makes them spend each other's
  tokens and locks both out.
- **If the store is lost, re-run the bootstrap.** A spent refresh token cannot be
  recovered.

Changing `FRESHBOOKS_REFRESH_TOKEN` to a freshly bootstrapped value is detected and
adopted, so re-bootstrapping is the supported recovery path.

## Tools

| Tool | Purpose |
| --- | --- |
| `freshbooks_get_identity` | Resolve accountId / businessId / businessUuid |
| `freshbooks_auth_url` | Get the consent URL to authorise this connection |
| `freshbooks_auth_exchange` | Exchange the authorization code (or pasted redirect URL) for a refresh token |
| `freshbooks_healthcheck` | Verify the OAuth credential and FreshBooks reachability; distinguishes "no credential" from "rejected" from "FreshBooks is down" |
| `freshbooks_list_invoices` / `freshbooks_get_invoice` | Browse and fetch invoices |
| `freshbooks_list_clients` / `freshbooks_get_client` | Browse and fetch clients |
| `freshbooks_list_estimates` / `freshbooks_get_estimate` | Browse and fetch estimates |
| `freshbooks_list_payments` / `freshbooks_get_payment` | Browse and fetch payments |
| `freshbooks_list_items` / `freshbooks_get_item` | Browse and fetch catalogue items |
| `freshbooks_create_client` | Create a client — confirm-gated |
| `freshbooks_create_invoice` | Create an invoice — confirm-gated |
| `freshbooks_update_invoice` | Update an invoice — confirm-gated |
| `freshbooks_record_payment` | Record a payment against an invoice — confirm-gated |
| `freshbooks_accept_estimate` | Accept an estimate (`action_accept`) — confirm-gated, idempotent |
| `freshbooks_update_estimate` | Update an estimate's lines, notes, terms, presentation — confirm-gated |
| `freshbooks_send_estimate` | Email an estimate to the client (`action_email`) — confirm-gated |
| `freshbooks_decline_estimate` | Always fails: FreshBooks has no decline. Answers with the alternatives |
| `freshbooks_list_expenses` / `freshbooks_get_expense` | Browse and fetch expenses |
| `freshbooks_list_expense_categories` | Categories supplying `categoryid` for new expenses |
| `freshbooks_create_expense` | Record an expense — confirm-gated |
| `freshbooks_list_projects` / `freshbooks_get_project` | Projects (businessId-keyed) |
| `freshbooks_create_project` | Create a project — confirm-gated |
| `freshbooks_list_time_entries` | Tracked time, with `total_logged` / `total_unbilled` |
| `freshbooks_create_time_entry` | Log time in seconds — confirm-gated |
| `freshbooks_list_services` | Billable work types for projects and time entries |
| `freshbooks_list_records` / `freshbooks_get_record` | Generic accessor for the accounting long tail (taxes, credit notes, invoice profiles, tasks, staff, gateways, bills, bill vendors, bill payments, other income) |

**Confirm-gated** means the tool makes *no* network call unless `confirm: true` is passed;
without it you get a dry-run preview of exactly what would be sent.

### Estimate writes

Acceptance is an **action on the estimate**, not a status field: `status` (int),
`display_status` and `ui_status` are computed and read-only, and they disagree with each
other by design (a viewed estimate reads `status: 3`, `display_status: "viewed"`,
`ui_status: "open"`). Accepting is `PUT estimates/estimates/{id}` with
`{"estimate": {"action_accept": true}}` — see
[`docs/FRESHBOOKS-API.md`](docs/FRESHBOOKS-API.md) for where that shape comes from.

- **Accept is idempotent.** An estimate already accepted (or invoiced) comes back with
  `changed: false` and no write is sent — acceptance cannot be undone through the API, so
  a repeat call must not re-fire it.
- **There is no decline.** FreshBooks' estimate statuses are draft / sent / viewed /
  replied / accepted / invoiced; no declined state, no `action_deny`, no
  `estimate.decline` webhook. `freshbooks_decline_estimate` exists only to say so and
  point at the alternatives, rather than leave an agent to invent a write that changes
  nothing.
- **Every write returns the re-fetched estimate**, plus `before` / `after` state and
  `changed` / `changedFields`, so success is verified against the record rather than
  inferred from a `200`. `changed` covers the status fields *and* the fields that write
  actually set, so a successful notes edit reports `changed: true` even though no status
  moves. On `freshbooks_send_estimate` it describes the record only — emailing an
  already-sent estimate moves nothing, and retrying on `changed: false` would send the
  client a second copy.

## Writes require an owner/admin accounting account

FreshBooks separates the role you hold on a *business* from the role you hold on an
*accounting account*. You can own a business that has **no** accounting account
(`account_id: null`) while being only a **client** on the account you can actually see —
in which case reads succeed and every write returns `403 Permission Denied`, even though
your OAuth token carries all the `:write` scopes.

`freshbooks_get_identity` reports `accountRole` and `businessRole` so this is visible up
front. If `accountRole` is `client`, the invoicing write tools will not work against that
account — that is an account permission, not a configuration problem.

### Two things the API reports misleadingly

- **`total` counts records you may not be able to read.** Expenses reported `total: 16`
  while returning zero rows. List results attach a `note` when that happens, so it reads
  as a permission boundary rather than an empty account.
- **Projects and time tracking are keyed by `businessId`, not `accountId`**, and paginate
  under a `meta` block instead of flat `page`/`pages`/`total`. They also work on a
  business with no accounting account at all.

## The three identifiers

FreshBooks hands out three non-interchangeable ids, and using the wrong one returns a bare
**404** that reads like a missing record:

| Identifier | Used by |
| --- | --- |
| `accountId` (alphanumeric) | `/accounting/account/…`, `/payments/account/…` |
| `businessId` (integer) | `/projects/business/…`, `/timetracking/business/…` |
| `businessUuid` (UUID) | `/accounting/businesses/…` |

Call `freshbooks_get_identity` first. Full API notes, including the four different error
envelopes, are in [`docs/FRESHBOOKS-API.md`](docs/FRESHBOOKS-API.md).

## Shell access without the server

[`skills/freshbooks-curl`](skills/freshbooks-curl/SKILL.md) covers the same API from a
shell with `curl` + `jq`, including the OAuth bootstrap and rotation-safe token handling.

## Development

```sh
npm install
npm run build
npm test
```

## License

MIT

What people ask about freshbooks-mcp

What is chrischall/freshbooks-mcp?

+

chrischall/freshbooks-mcp is mcp servers for the Claude AI ecosystem. FreshBooks MCP server for Claude — invoices, clients, estimates, and payments via the FreshBooks OAuth2 API It has 0 GitHub stars and its last recorded update is dated 2026-09-10.

How do I install freshbooks-mcp?

+

You can install freshbooks-mcp by cloning the repository (https://github.com/chrischall/freshbooks-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is chrischall/freshbooks-mcp safe to use?

+

Our security agent has analyzed chrischall/freshbooks-mcp and assigned a Trust Score of 87/100 (tier: Trusted). See the full breakdown of passed checks and flags on this page.

Who maintains chrischall/freshbooks-mcp?

+

chrischall/freshbooks-mcp is maintained by chrischall. The last recorded GitHub activity is dated 2026-09-10, with 0 open issues.

Are there alternatives to freshbooks-mcp?

+

Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.

Deploy freshbooks-mcp to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: chrischall/freshbooks-mcp
[![Featured on ClaudeWave](https://claudewave.com/api/badge/chrischall-freshbooks-mcp)](https://claudewave.com/repo/chrischall-freshbooks-mcp)
<a href="https://claudewave.com/repo/chrischall-freshbooks-mcp"><img src="https://claudewave.com/api/badge/chrischall-freshbooks-mcp" alt="Featured on ClaudeWave: chrischall/freshbooks-mcp" width="320" height="64" /></a>

More MCP Servers

freshbooks-mcp alternatives