Skip to main content
ClaudeWave

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

MCP ServersRegistry oficial0 estrellas0 forksTypeScriptMITActualizado 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
Casos de uso

Resumen de MCP Servers

# 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

Lo que la gente pregunta sobre freshbooks-mcp

¿Qué es chrischall/freshbooks-mcp?

+

chrischall/freshbooks-mcp es mcp servers para el ecosistema de Claude AI. FreshBooks MCP server for Claude — invoices, clients, estimates, and payments via the FreshBooks OAuth2 API Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-09-10.

¿Cómo se instala freshbooks-mcp?

+

Puedes instalar freshbooks-mcp clonando el repositorio (https://github.com/chrischall/freshbooks-mcp) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar chrischall/freshbooks-mcp?

+

Nuestro agente de seguridad ha analizado chrischall/freshbooks-mcp y le ha asignado un Trust Score de 87/100 (tier: Trusted). Revisa el desglose completo de comprobaciones superadas y flags en esta página.

¿Quién mantiene chrischall/freshbooks-mcp?

+

chrischall/freshbooks-mcp es mantenido por chrischall. La última actividad registrada en GitHub es del 2026-09-10, con 0 issues abiertos.

¿Hay alternativas a freshbooks-mcp?

+

Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.

Despliega freshbooks-mcp en tu cloud

Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.

¿Mantienes este repo? Añade un badge a tu README

Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.

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>

Más MCP Servers

Alternativas a freshbooks-mcp