freshbooks-mcp-server
by bitovi
README.md
# FreshBooks MCP Server
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server for the [FreshBooks API](https://www.freshbooks.com/api). Connect Claude to your FreshBooks account to read invoices, clients, expenses, projects, services, and more, and to manage payments and time entries.
Supports two modes:
- **stdio** — for Claude Desktop and MCP Inspector (no server needed)
- **HTTP + SSE** — for claude.ai custom connectors (requires a public HTTPS URL)
## Tools
| Tool | Description |
|---|---|
| `get_current_user` | Get the authenticated user's profile and account/business IDs |
| `list_team_members` | List all team members in the business with their identity_id, name, email, and role |
| `get_team_member` | Get a team member by their identity_id |
| `list_clients` | List clients with optional search and filters |
| `get_client` | Get a client by ID |
| `list_invoices` | List invoices filtered by client, status, or date range |
| `get_invoice` | Get an invoice by ID including line items |
| `list_expenses` | List expenses filtered by client, project, or date range |
| `get_expense` | Get an expense by ID |
| `list_payments` | List payments filtered by invoice or date range |
| `get_payment` | Get a payment by ID |
| `list_projects` | List projects filtered by client or active status |
| `get_project` | Get a project by ID |
| `list_time_entries` | List time entries filtered by project, client, or date range |
| `get_time_entry` | Get a time entry by ID |
| `create_time_entry` | Log a time entry against a project; automatically associates the project's client and accepts an optional `service_id` |
| `update_time_entry` | Update a time entry |
| `delete_time_entry` | Delete a time entry |
| `list_items` | List items (products/services) |
| `get_item` | Get an item by ID |
| `list_services` | List all services defined for the business |
| `get_service` | Get a service by ID |
| `get_service_rate` | Get the global billing rate for a service |
| `list_project_service_rates` | List per-project billing rate overrides for all services on a project (undocumented endpoint) |
## Prerequisites
- Node.js v20.6 or later
- A FreshBooks account
- A FreshBooks app (free) — create one at [my.freshbooks.com/#/developer](https://my.freshbooks.com/#/developer)
## Setup
```bash
git clone https://github.com/bitovi/freshbooks-mcp-server
cd freshbooks-mcp-server
npm install
npm run build
cp .env.example .env
```
Edit `.env` and set your FreshBooks app credentials:
```
FRESHBOOKS_CLIENT_ID=your_client_id
FRESHBOOKS_CLIENT_SECRET=your_client_secret
```
## Claude Desktop (stdio)
This is the simplest way to use the server. Claude Desktop communicates with it directly over stdio — no HTTP server or public URL required.
### 1. Get a FreshBooks refresh token
Start the HTTP server to complete the OAuth flow once:
```bash
npm run dev:http
```
In a separate terminal, print the auth URL:
```bash
npm run auth-url
```
Open the printed URL in your browser and log in with FreshBooks. Your `refresh_token` and `session_token` are displayed in the browser. FreshBooks tokens are also saved to `~/.freshbooks-mcp/sessions.json`.
Copy the `refresh_token` value and add it to `.env`:
```
FRESHBOOKS_REFRESH_TOKEN=your_refresh_token
```
The server exchanges this for an access token on startup and handles renewal automatically — you only need to do this once unless you revoke the app's access in FreshBooks.
### 2. Add to Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"freshbooks": {
"command": "/path/to/node",
"args": ["/path/to/freshbooks-mcp-server/dist/index.js"],
"env": {
"MODE": "stdio"
}
}
}
}
```
Replace `/path/to/node` with the full path to your Node.js binary (`which node`) and `/path/to/freshbooks-mcp-server` with the absolute path to this repo.
> **nvm users:** Claude Desktop doesn't inherit your shell environment. Use the full path to your node binary, e.g. `/Users/you/.nvm/versions/node/v22.8.0/bin/node`.
Restart Claude Desktop. The FreshBooks tools will appear in the hammer menu in the chat input.
## claude.ai Custom Connector (HTTP + SSE)
Claude.ai custom connectors require a public HTTPS URL. You can test the connector with [ngrok](https://ngrok.com/) or by [a deploy to AWS](#deploying-to-aws-ec2--nginx).
### Testing locally
**1. Start the server**
```bash
npm run dev:http
```
**2. Trust the self-signed certificate**
Open `https://localhost:3443` in your browser. Click **Advanced → Proceed to localhost** to accept the self-signed cert. You only need to do this once per browser session — otherwise the OAuth redirect will be blocked.
**3. Add the callback URI to your FreshBooks app**
In your FreshBooks developer console, add:
```
https://localhost:3443/oauth/callback
```
**4. Get the auth URL**
```bash
npm run auth-url
```
Open the printed URL in your browser, log in with FreshBooks, and you'll see your session token displayed on the page.
**5. Test the SSE endpoint**
```bash
curl -sk https://localhost:3443/sse -H "Authorization: Bearer <session_token>"
```
### Setting up ngrok
If you want to test in claude.ai locally with a real certificate, use ngrok:
```bash
# Add to .env:
HTTPS=false
SERVER_URL=https://your-subdomain.ngrok-free.app
```
```bash
npm run dev:http # Terminal 1
ngrok http 3000 # Terminal 2
```
Add `https://your-subdomain.ngrok-free.app/oauth/callback` to your FreshBooks app, then run `npm run auth-url`.
### Adding to claude.ai
Go to **Settings → Integrations → Add Integration** and enter your SSE URL:
```
https://your-subdomain.ngrok-free.app/sse # ngrok
https://freshbooks-mcp.yourdomain.com/sse # production
```
Claude will prompt you to log in with FreshBooks. After authenticating, the tools are available in your conversations.
## Deploying to AWS (EC2 + nginx)
The recommended setup is an EC2 instance running nginx as a reverse proxy, with Let's Encrypt for a free TLS certificate. Sessions are stored on disk so they survive restarts.
### Architecture
```
claude.ai → ALB (or Elastic IP) → nginx (HTTPS/443) → Node.js (HTTP/3000)
```
You can skip the ALB and use nginx + Let's Encrypt directly on EC2 if you don't need auto-scaling.
### 1. Launch an EC2 instance
- **AMI:** Amazon Linux 2023 (or Ubuntu 22.04)
- **Instance type:** t3.micro (free tier) or t3.small
- **Security group inbound rules:**
- SSH (22) — your IP only
- HTTP (80) — anywhere (needed for Let's Encrypt verification)
- HTTPS (443) — anywhere
- Attach an **Elastic IP** so your DNS record stays stable across reboots
### 2. Point a domain at the instance
In Route 53 (or any DNS provider), create an **A record** pointing your domain to the Elastic IP:
```
freshbooks-mcp.yourdomain.com → <Elastic IP>
```
### 3. Install Node.js 22 and nginx
```bash
# Amazon Linux 2023
sudo dnf install -y nginx git
curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash -
sudo dnf install -y nodejs
# Ubuntu 22.04
sudo apt update && sudo apt install -y nginx git
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt install -y nodejs
```
Install PM2 globally:
```bash
sudo npm install -g pm2
```
### 4. Deploy the app
```bash
git clone https://github.com/bitovi/freshbooks-mcp-server /srv/freshbooks-mcp
cd /srv/freshbooks-mcp
npm install
npm run build
mkdir -p logs
cp .env.example .env
nano .env # fill in credentials (see Environment variables below)
```
Minimum `.env` for production:
```
FRESHBOOKS_CLIENT_ID=your_client_id
FRESHBOOKS_CLIENT_SECRET=your_client_secret
MODE=http
HTTPS=false
SERVER_URL=https://freshbooks-mcp.yourdomain.com
```
`PORT` defaults to `3000` when `HTTPS=false`, so no need to set it explicitly.
### 5. Configure nginx
Create `/etc/nginx/conf.d/freshbooks-mcp.conf`:
```nginx
server {
listen 80;
server_name freshbooks-mcp.yourdomain.com;
# Let's Encrypt challenge + redirect everything else to HTTPS
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
server_name freshbooks-mcp.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/freshbooks-mcp.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/freshbooks-mcp.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for SSE — disable buffering so events stream immediately
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
# Keep SSE connections open for up to 24 hours
proxy_read_timeout 86400s;
chunked_transfer_encoding on;
}
}
```
Test and reload:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
### 6. Get a TLS certificate
```bash
sudo dnf install -y python3-certbot-nginx # Amazon Linux 2023
# or: sudo apt install -y certbot python3-certbot-nginx # Ubuntu
sudo certbot --nginx -d freshbooks-mcp.yourdomain.com
```
Certbot auto-configures nginx and sets up auto-renewal via a systemd timer.
### 7. Start the server with PM2
```bash
cd /srv/freshbooks-mcp
pm2 start ecosystem.config.cjs
pm2 save # persist across reboots
pm2 startup # follow the printed command to enable on boot
```
Useful commands:
```bash
pm2 logs freshbooks-mcp # tail logs
pm2 reload freshbooks-mcp # zero-downtime restart after code changes
pm2 status # check process health
```
### 8. Update FreshBooks and claude.ai
In your FreshBooks developer console, add the production redirect URI:
```
https://freshbooks-mcp.yourdomain.com/oauth/callback
```
In claude.ai → **Settings → Integrations → Add Integration**, enter:
```
https://freshbooks-mcp.yourdomain.com/sse
```
Claude will walk you through the FreshBooks OAuth flow. After that, all tools are live.
### Updating the server
```bash
cd /srv/freshbooks-mcp
git pull
npm install
npm run build
pm2 reload freshbooks-mcp
```
### Notes
- **Sessions** are stored in `~/.freshbooks-mcp/sessions.json` on the EC2 instance. Since EC2 has a persistent filesystem, sessions survive restarts and deploys.
- **ALB:** If you later add an Application Load Balancer, set its **idle timeout to 3600 seconds** (default 60 will drop long-lived SSE connections). nginx's `proxy_read_timeout` handles this when going direct.
- **Logs** go to `./logs/` in the project directory and are managed by PM2.
## Environment variables
| Variable | Required | Description |
|---|---|---|
| `FRESHBOOKS_CLIENT_ID` | Yes | Your FreshBooks app's client ID |
| `FRESHBOOKS_CLIENT_SECRET` | Yes | Your FreshBooks app's client secret |
| `FRESHBOOKS_ACCESS_TOKEN` | For stdio | A valid FreshBooks access token |
| `FRESHBOOKS_REFRESH_TOKEN` | Optional | Refresh token — used to auto-renew the access token |
| `MODE` | No | `stdio` (default) or `http` |
| `PORT` | No | Listen port. Defaults to `3443` when `HTTPS=true`, `3000` when `HTTPS=false` |
| `SERVER_URL` | For HTTP mode | Public base URL. Defaults to `https://localhost:3443` |
| `HTTPS` | No | `true` (default) — self-signed cert on the Node process; `false` — plain HTTP behind a proxy |
| `SESSIONS_FILE` | No | Path for persisted sessions (default: `~/.freshbooks-mcp/sessions.json`) |
| `FRESHBOOKS_API_BASE` | No | Override the FreshBooks API base URL (default: `https://api.freshbooks.com`) |
## Project structure
```
src/
index.ts Entry point — picks stdio or HTTP based on MODE
load-env.ts Minimal .env loader (no dotenv dependency)
config.ts Config from environment variables
mcp-server.ts Creates the McpServer and registers all tools
http-server.ts Express server with SSE transport and OAuth2 proxy
stdio-server.ts Stdio transport with token resolution
freshbooks/
client.ts FreshBooks API client
types.ts TypeScript types for API responses
tools/
users.ts get_current_user, list_team_members, get_team_member
clients.ts Client tools
invoices.ts Invoice tools
expenses.ts Expense tools
payments.ts Payment tools
projects.ts Project tools
time-entries.ts Time entry tools
items.ts Item tools
services.ts Service tools
scripts/
auth-url.ts Prints the FreshBooks OAuth URL for local testing
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive