FreeAgent MCP Server
README.md
# FreeAgent MCP Server
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for the [FreeAgent](https://www.freeagent.com) accounting API. Enables LLMs to manage contacts, invoices, estimates, bills, expenses, timeslips, projects, tasks, bank accounts, and more.
> By my own admission, most of this project is vibe coded.
## Features
- **Broad FreeAgent coverage**: contacts, invoices (incl. transitions, email, duplicate, direct debit), estimates, bills, recurring invoices, price list items, expenses, timeslips, projects, tasks, bank accounts/feeds/statements (JSON lines), credit notes + reconciliations, notes, attachments (metadata), journals, capital assets, accounting reports (P&L, balance sheet, trial balance, cashflow, ledger), VAT/corp tax/income tax/final accounts, payroll, properties/stock/hire purchases, CIS, sales tax periods, account locks, categories, company info, and users
- **Intent-bundle tools**: `reconcile_bank_transaction`, `log_expense`, and `invoice_from_timeslips` collapse multi-call sequences into single tool calls and resolve human-friendly hints (names, codes, references) to FreeAgent URLs server-side
- **Optional tool-search mode** (`FREEAGENT_TOOL_SEARCH=true`): collapses the tool catalog behind two meta-tools (`freeagent_search_tools`, `freeagent_call_tool`) so clients only pay the tool-definition token cost for tools they actually use
- **MCP elicitation**: `create_invoice` falls back to a form elicitation when `contact` is omitted (on clients that support it)
- **Two deployment modes**: local (stdio) or cloud (Vercel serverless via Streamable HTTP)
- **OAuth 2.0**: stateless JWT-based auth for serverless, or direct token for local use
- **Tool annotations**: `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` on every tool
- **Zod validation**: strict input schemas with `.describe()` on all fields
- **Dual response formats**: Markdown (human-readable) or JSON (structured)
- **Pagination**: proper header parsing with `x-total-count` and `Link` headers
- **Rate limit handling**: clear error messages with retry-after guidance
- **Sandbox support**: test safely against FreeAgent's sandbox environment
## Deployment Options
### Local (stdio) - for Claude Desktop
1. Install and build:
```bash
bun install
bun run build
```
2. Set environment variables:
```bash
export FREEAGENT_ACCESS_TOKEN="your_access_token"
export FREEAGENT_USE_SANDBOX="true" # optional
```
3. Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"freeagent": {
"command": "node",
"args": ["/path/to/freeagent-mcp-server/dist/index.js"],
"env": {
"FREEAGENT_ACCESS_TOKEN": "your_token",
"FREEAGENT_USE_SANDBOX": "true"
}
}
}
}
```
### Vercel (Streamable HTTP) - for cloud access
See [VERCEL_DEPLOYMENT.md](./VERCEL_DEPLOYMENT.md) for full instructions. Key points:
- Uses `StreamableHTTPServerTransport` in stateless mode (no sessions)
- OAuth 2.0 with PKCE via JWT-encoded tokens (no database needed)
- Handles `POST` (tool calls), `GET` (SSE streaming), and `DELETE` (returns 405 - stateless)
- Set `PRODUCTION_URL` (or rely on `VERCEL_PROJECT_PRODUCTION_URL`) for stable production OAuth callback URLs. Preview OAuth uses the request host (or `VERCEL_URL`) so short per-deploy hosts match FreeAgent `*` wildcards.
Required env vars: `FREEAGENT_CLIENT_ID`, `FREEAGENT_CLIENT_SECRET`, `JWT_SECRET` (stable secret required on Vercel so OAuth JWTs verify across serverless instances)
## Tool-Search Mode (optional)
By default the server registers every catalog tool directly, which makes all ~50 tool definitions part of the MCP client's `tools/list` response. For clients with many connected MCP servers — where tool-definition tokens add up quickly — set:
```bash
export FREEAGENT_TOOL_SEARCH=true
```
In this mode the server exposes only two meta-tools:
| Tool | Purpose |
|------|---------|
| `freeagent_search_tools` | Search the catalog and return JSONSchema for matching tools. Query forms: `select:name1,name2` for direct lookup, `+required optional` for scored search with required keywords, or plain keywords for a ranked search. |
| `freeagent_call_tool` | Invoke any catalog tool by name with validated arguments (`{ name, arguments }`). Pair with `search_tools` to discover schemas on demand. |
The full catalog is still reachable — it's just loaded on demand. This mirrors the deferred-loading pattern used by Claude Code's internal `ToolSearch`.
## Available Tools
See [TOOLS.md](./TOOLS.md) for per-tool parameters and examples. Summary:
### Contacts
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_contacts` | List contacts with pagination and sorting | Yes |
| `freeagent_get_contact` | Get contact details by ID | Yes |
| `freeagent_create_contact` | Create a new contact | No |
### Invoices
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_invoices` | List invoices with status/contact/project filters | Yes |
| `freeagent_get_invoice` | Get invoice details (renders computed discount amount) | Yes |
| `freeagent_create_invoice` | Create a draft invoice (supports `discount_percent`; elicits `contact` if omitted) | No |
| `freeagent_transition_invoice` | mark_as_sent / mark_as_cancelled / mark_as_draft / mark_as_scheduled / convert_to_credit_note | No |
| `freeagent_invoice_from_timeslips` | **Intent bundle**: draft an invoice from a contact's unbilled timeslips | No |
### Estimates
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_estimates` | List estimates with status/contact/project filters | Yes |
| `freeagent_get_estimate` | Get estimate details (renders computed discount amount) | Yes |
| `freeagent_create_estimate` | Draft an estimate (supports `discount_percent`) | No |
| `freeagent_transition_estimate` | mark_as_sent / mark_as_approved / mark_as_rejected / mark_as_cancelled / mark_as_draft / convert_to_invoice | No |
### Bills
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_bills` | List supplier bills with filters | Yes |
| `freeagent_get_bill` | Get bill details | Yes |
| `freeagent_create_bill` | Record a supplier bill | No |
### Recurring Invoices
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_recurring_invoices` | List recurring invoice templates | Yes |
| `freeagent_get_recurring_invoice` | Get template details | Yes |
### Price List Items
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_price_list_items` | List catalog items | Yes |
| `freeagent_get_price_list_item` | Get catalog item details | Yes |
| `freeagent_create_price_list_item` | Add a catalog item | No |
### Expenses
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_expenses` | List expenses with date/view filters | Yes |
| `freeagent_get_expense` | Get expense details (inc. mileage info) | Yes |
| `freeagent_create_expense` | Create expense or mileage claim (with attachments) | No |
| `freeagent_update_expense` | Update an existing expense | No |
| `freeagent_log_expense` | **Intent bundle**: log a regular expense with a positive `amount` + `kind` enum | No |
### Timeslips
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_timeslips` | List time entries with filters | Yes |
| `freeagent_get_timeslip` | Get timeslip details | Yes |
| `freeagent_create_timeslip` | Create a time entry | No |
| `freeagent_update_timeslip` | Update a timeslip (incl. `billed_on_invoice`) | No |
### Bank Accounts & Transactions
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_bank_accounts` | List all bank accounts | Yes |
| `freeagent_get_bank_account` | Get bank account details | Yes |
| `freeagent_list_bank_transactions` | List transactions for an account | Yes |
| `freeagent_get_bank_transaction` | Get bank transaction details | Yes |
| `freeagent_list_bank_transaction_explanations` | List transaction explanations | Yes |
| `freeagent_get_bank_transaction_explanation` | Get explanation details | Yes |
| `freeagent_create_bank_transaction_explanation` | Explain/categorize a bank transaction | No |
| `freeagent_update_bank_transaction_explanation` | Update a transaction explanation | No |
| `freeagent_reconcile_bank_transaction` | **Intent bundle**: explain a transaction with a category name / invoice ref / bill ref | No |
### Projects & Tasks
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_projects` | List projects with status/contact filters | Yes |
| `freeagent_get_project` | Get project details | Yes |
| `freeagent_create_project` | Create a new project | No |
| `freeagent_list_tasks` | List tasks with project/status filters | Yes |
| `freeagent_get_task` | Get task details | Yes |
| `freeagent_create_task` | Create a task within a project | No |
### Categories, Company & Users
| Tool | Description | Read-only |
|------|-------------|-----------|
| `freeagent_list_categories` | List accounting categories | Yes |
| `freeagent_get_category` | Get category by nominal code | Yes |
| `freeagent_get_company` | Get company information | Yes |
| `freeagent_list_users` | List all users | Yes |
## Intentional omissions
- **Timeslip timers** — start/stop timer endpoints are not exposed (poor MCP fit).
- **Binary PDF download tools** — invoice/estimate/credit-note PDF bytes are not returned (no efficient agentic binary channel yet).
- **File-based bank statement upload** — use `freeagent_upload_bank_statement` with JSON lines instead. Nested base64 attachments on expenses/explanations remain supported.
- **Accountancy Practice API** — practice/client endpoints are out of scope for this company-facing server.
## Development
### Prerequisites
- [Bun](https://bun.sh) (used for package management and running scripts)
- Node.js 22.x
### Setup
```bash
bun install
```
### Scripts
| Command | Description |
|---------|-------------|
| `bun run build` | Compile TypeScript |
| `bun run dev` | Watch mode (auto-recompile) |
| `bun run start` | Run the compiled server |
| `bun run lint` | Run ESLint |
| `bun run test` | Run tests |
| `bun run test:watch` | Run tests in watch mode |
| `bun run test:coverage` | Run tests with coverage |
### Project Structure
```
freeagent-mcp-server/
├── src/
│ ├── index.ts # Stdio server entry point
│ ├── constants.ts # Configuration constants & shared utilities
│ ├── types.ts # TypeScript type definitions
│ ├── schemas/
│ │ ├── index.ts # Zod validation schemas for all tools
│ │ ├── projects.ts # Project-specific schemas
│ │ └── schemas.test.ts # Schema validation tests
│ ├── services/
│ │ ├── api-client.ts # FreeAgent API client (Axios)
│ │ ├── api-client.test.ts # API client tests
│ │ ├── formatter.ts # Response formatting utilities (incl. discount amount helper)
│ │ ├── formatter.test.ts # Formatter tests
│ │ ├── resolvers.ts # Shared resolvers (category / user / contact / bill hints → URLs)
│ │ ├── oauth-jwt.ts # JWT-based OAuth provider (Vercel)
│ │ └── freeagent-auth.ts # Token validation
│ └── tools/
│ ├── register.ts # Shared tool definitions, registration, ToolContext (elicitation)
│ ├── contacts.ts # Contact CRUD
│ ├── invoices.ts # Invoice management (incl. elicitation fallback)
│ ├── transition-invoice.ts # Invoice lifecycle transitions
│ ├── invoice-from-timeslips.ts # Intent bundle: draft an invoice from unbilled time
│ ├── estimates.ts # Estimates + transition_estimate
│ ├── bills.ts # Supplier bills
│ ├── recurring-invoices.ts # Recurring invoice templates (read-only)
│ ├── price-list-items.ts # Catalog items
│ ├── expenses.ts # Expense & mileage tracking
│ ├── log-expense.ts # Intent bundle: positive-amount expense logging
│ ├── timeslips.ts # Time tracking (incl. update_timeslip)
│ ├── bank-accounts.ts # Bank accounts & transactions
│ ├── bank-transactions.ts # Transaction explanations
│ ├── reconcile.ts # Intent bundle: reconcile a transaction in one call
│ ├── projects.ts # Project management
│ ├── tasks.ts # Task management
│ ├── categories.ts # Accounting categories
│ └── company.ts # Company info & users
├── api/
│ └── index.ts # Vercel serverless entry point
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI (lint, test, build)
├── eslint.config.js # ESLint flat config
├── vitest.config.ts # Vitest configuration
├── vercel.json # Vercel deployment config
├── package.json
└── tsconfig.json
```
### CI
GitHub Actions runs on every push to `main` and on pull requests:
- **Lint**: ESLint with TypeScript rules
- **Test**: Vitest unit tests (no external API calls)
- **Build**: TypeScript compilation check
## Rate Limiting
| Environment | Limit |
|-------------|-------|
| Production | 15 requests / 60 seconds |
| Sandbox | 5 requests / 60 seconds |
The server returns clear error messages with retry-after timing when rate limited.
## Error Handling
All tool handlers return structured errors via `{ isError: true, content: [...] }` (never thrown exceptions). Error messages are designed for LLM consumption with actionable guidance:
| Status | Meaning |
|--------|---------|
| 401 | Token expired - refresh OAuth token |
| 403 | Insufficient permissions |
| 404 | Resource not found or deleted |
| 422 | Validation error with field-level details |
| 429 | Rate limited - retry after N seconds |
## Security
- Access tokens are never logged or committed
- JWT tokens use HS256 signing with configurable secret
- PKCE is used for the OAuth authorization flow
- Strict Zod schemas reject unexpected input fields
- Bearer auth middleware protects all MCP endpoints
## License
MIT
## Links
- [FreeAgent API Docs](https://dev.freeagent.com/docs)
- [MCP Specification](https://modelcontextprotocol.io)
- [FreeAgent Developer Dashboard](https://dev.freeagent.com)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive