@mpurdon/mcp-freshbooks
v0.1.0
Published
Local stdio MCP server for FreshBooks invoices, clients, items, and time entries.
Maintainers
Readme
freshbooks-mcp
A local MCP (Model Context Protocol) server that gives Claude Desktop access to your FreshBooks invoices. List, view, create, update, send, and (soft-)delete invoices, plus list clients and items so you can construct invoices end-to-end.
- Transport: stdio (single user, runs on your machine)
- Auth: OAuth2 with refresh-token persistence at
~/.freshbooks-mcp/tokens.json(mode 0600) - Runtime: Node 20+, TypeScript built to ESM
Prerequisites
- Node.js 20 or newer
- A FreshBooks account with API access
- Claude Desktop (https://claude.ai/download)
- mkcert — generates a locally-trusted TLS cert so the OAuth callback can run over HTTPS (required by FreshBooks):
brew install mkcert mkcert -install # installs the local CA — only needed once per machine
1. Create a FreshBooks developer app
FreshBooks uses OAuth2, so you need a developer app to get a Client ID and Secret.
- Sign in at https://my.freshbooks.com.
- Open https://my.freshbooks.com/#/developer.
- Click Create an App. Give it a name like
Claude MCP (local)and a short description. The app type is "Private app" or similar — there is no review process for private apps. - Under Redirect URIs, add exactly:
(If you need a different port, sethttps://localhost:8765/callbackFRESHBOOKS_REDIRECT_URIin the environment before running setup, and add the matching URL to your app. Must be HTTPS.) - Save. Copy the Client ID and Client Secret somewhere private — you'll paste them into a
.envfile in the next step.
The required scopes are the defaults (read/write on accounting resources). FreshBooks will prompt you to consent during the OAuth flow.
2. Install and build
npm install
npm run build3. Run setup (one-time OAuth)
Create a .env in the project root (copy from .env.example):
FRESHBOOKS_CLIENT_ID=your_client_id
FRESHBOOKS_CLIENT_SECRET=your_client_secretThen:
npm run setupWhat happens:
- Your default browser opens to
https://auth.freshbooks.com/oauth/authorize/.... Sign in and approve. - FreshBooks redirects to
https://localhost:8765/callbackwith an auth code. The setup script catches it via a one-shot local HTTPS listener (using a mkcert-generated cert) and exchanges it for tokens. - The script calls
/auth/api/v1/users/meto discover youraccount_idand business name. - Tokens are written to
~/.freshbooks-mcp/tokens.jsonwith mode 0600. The directory~/.freshbooks-mcpis created with mode 0700. - The script prints a Claude Desktop config snippet — copy it.
If you have multiple businesses on your FreshBooks account, the script picks the first one and prints a note. To target a different one, set FRESHBOOKS_ACCOUNT_ID and FRESHBOOKS_BUSINESS_ID in the server env (these override the discovered values). See "Multiple businesses on one FreshBooks account" below.
4. Add to Claude Desktop config
Open the Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Paste the snippet that npm run setup printed into the mcpServers object. It looks like:
{
"mcpServers": {
"freshbooks": {
"command": "node",
"args": ["/Users/mp/.claude/mcp-servers/freshbooks/dist/index.js"],
"env": {
"FRESHBOOKS_CLIENT_ID": "your_client_id",
"FRESHBOOKS_CLIENT_SECRET": "your_client_secret"
}
}
}
}Both FRESHBOOKS_CLIENT_ID and FRESHBOOKS_CLIENT_SECRET are required at runtime: the server uses them to refresh the access token when it expires (every ~12h).
The server also reads a .env file in the project root on startup as a fallback, so any FRESHBOOKS_* var not present in the env block above is picked up from there. The env block always takes precedence. Recognized vars: FRESHBOOKS_CLIENT_ID, FRESHBOOKS_CLIENT_SECRET, FRESHBOOKS_REDIRECT_URI, FRESHBOOKS_TOKENS_PATH, FRESHBOOKS_ACCOUNT_ID, FRESHBOOKS_BUSINESS_ID — see .env.example.
Restart Claude Desktop. You should see freshbooks appear in the tools menu.
Available tools
All tools accept structured JSON input. Below are the inputs for each.
Invoices
list_invoices—{ status?, client_id?, date_from?, date_to?, search?, page?, per_page? }Filter byv3_status(draft,sent,viewed,paid,overdue, ...), client, date range, or invoice number substring. Defaultper_pageis 20.get_invoice—{ invoice_id: number }create_invoice—{ client_id, lines: [{ description, qty, unit_cost, name?, taxName1?, taxAmount1? }], currency_code?, due_offset_days?, notes?, terms?, invoice_number?, create_date? }unit_costis a decimal (e.g.75.00for $75), not cents. Tax amounts are percentages (e.g.13for 13%).update_invoice—{ invoice_id, notes?, terms?, due_offset_days?, invoice_number?, lines? }Partial update. Note: if you passlines, it replaces all line items on the invoice.send_invoice—{ invoice_id, recipients?, subject?, body? }Emails the invoice (FreshBooksaction_email: true). Defaults to the client's email on file.delete_invoice—{ invoice_id }Soft delete. Setsvis_state=1. The invoice is recoverable from the FreshBooks UI's deleted items view; this server does not expose a permanent-delete tool.
Clients
list_clients—{ search?, page?, per_page? }get_client—{ client_id: number }
Items
list_items—{ search?, page?, per_page? }Saved line items, useful when constructing invoices.
Account
get_account_info—{}Returns{ account_id, business_id, business_name }. Use this to verify setup.
Example prompts in Claude Desktop
- "List all overdue invoices."
- "Show me invoice 12345."
- "Find clients matching 'Acme'."
- "Draft an invoice to client 678910 for 8 hours of consulting at $150/hr, due in 30 days."
- "Email invoice 12345 to [email protected] with subject 'March invoice'."
Claude will ask for confirmation before destructive actions like delete_invoice or send_invoice — review the proposed input before approving.
Security notes
- Tokens are stored at
~/.freshbooks-mcp/tokens.jsonwith mode 0600. The server refuses to start if the file is group- or world-readable. - The client secret is loaded from environment variables, not from disk.
Authorizationheaders are never logged. The client redacts request bodies in error messages.- All tool inputs are validated with strict Zod schemas (extra fields are rejected).
- The OAuth setup uses a CSRF
stateparameter and verifies it on the callback. - The server binds the OAuth callback listener to
127.0.0.1only.
Troubleshooting
Refusing to read ~/.freshbooks-mcp/tokens.json: permissions are 644, must be 600
The token file is too permissive. Fix:
chmod 600 ~/.freshbooks-mcp/tokens.json
chmod 700 ~/.freshbooks-mcpFreshBooks token refresh failed ... Re-run npm run setup to re-authorize.
The refresh token has been revoked or expired. Re-run npm run setup. Common causes:
- You changed your FreshBooks password.
- You revoked the app's access in your FreshBooks settings.
- The refresh token has been unused for a very long time.
Could not refresh FreshBooks access token at startup
Same as above. The server proactively refreshes if the access token is within 60s of expiry, so this surfaces stale-refresh-token problems early.
Claude Desktop says the server crashed / disappeared
- Check Claude Desktop's log file (Help -> Open Developer Tools -> Console for the renderer; the main process log is in the same directory as the config file).
- Run the server manually to see its stderr:
It will sit waiting for stdio JSON-RPC; press Ctrl+C to exit. If startup failed it printsFRESHBOOKS_CLIENT_ID=... FRESHBOOKS_CLIENT_SECRET=... node /Users/mp/.claude/mcp-servers/freshbooks/dist/index.js[freshbooks-mcp] fatal: ...to stderr. - Common fix: re-run
npm run buildafter pulling updates.
429 Too Many Requests showing up
The server respects FreshBooks' Retry-After header automatically (single retry). If you're hitting the limit consistently, increase per_page to fetch more per call, or cache results in your conversation.
Multiple businesses on one FreshBooks account
npm run setup picks the first business and prints a note. To target a different one, set these in the server's env block (they take precedence over the discovered values in tokens.json):
"env": {
"FRESHBOOKS_CLIENT_ID": "your_client_id",
"FRESHBOOKS_CLIENT_SECRET": "your_client_secret",
"FRESHBOOKS_ACCOUNT_ID": "abc123",
"FRESHBOOKS_BUSINESS_ID": "456"
}FRESHBOOKS_ACCOUNT_ID is used for /accounting and /uploads paths (invoices, clients, items, attachments); FRESHBOOKS_BUSINESS_ID is used for /projects and /timetracking paths (time entries, timesheets). You can find both by hitting https://api.freshbooks.com/auth/api/v1/users/me with your access token, or by running the get_account_info tool. (Editing ~/.freshbooks-mcp/tokens.json directly still works too — the server re-reads it on each request — but env vars are the cleaner option.)
File layout
src/
index.ts # MCP server entry (stdio)
setup.ts # OAuth setup CLI
freshbooks/
auth.ts # token load/save/refresh
client.ts # API client with auto-refresh + 429 handling
types.ts # FreshBooks DTOs
tools/
invoices.ts # list/get/create/update/send/delete
clients.ts # list/get
items.ts # list
account.ts # get_account_infoLicense
Private — for personal use.
