# MCP Server

Frihet's MCP server implements the [Model Context Protocol](https://modelcontextprotocol.io) so AI assistants can interact directly with your ERP. Create invoices, log expenses, look up clients, or manage quotes — all from your IDE or terminal, in natural language.

**Current version: 1.15.2** | 157 tools | 11 resources | 10 prompts | Structured output | Compatible with 30+ AI agents.

```
You:     "Create an invoice for TechStart Ltd, 40 hours of consulting at 75 EUR/hour, due March 1st"
Claude:  Done. Invoice INV-2026-089 created. Total: 3,000.00 EUR + 21% VAT = 3,630.00 EUR.
```

---

## What to expect (and what not)

The MCP server is a **stateless bridge** between your AI assistant and the Frihet REST API. Each tool call translates into an HTTP request to the API.

**What it DOES:**
- Full CRUD on invoices, expenses, clients, products, quotes, and webhooks
- Invoice search by client name
- Access to reference data (tax rates, fiscal calendar, categories)
- Guided workflows (monthly close, tax prep, overdue follow-up)

**What it DOES NOT:**
- Document OCR (that's handled by the AI assistant inside the app)
- PDF generation (use the REST API directly: `GET /v1/invoices/:id/pdf`)
- Payment processing (payments are handled via Stripe Connect in the app)

---

## Requirements

- Frihet account with API access (paid plans)
- API key generated from the dashboard

### Getting your API key

1. Sign in at [app.frihet.io](https://app.frihet.io/?utm_source=frihet_io&utm_medium=docs&utm_campaign=docs_cta)
2. Go to **Settings > Developers > API Keys**
3. Click **Create API key**
4. Copy the key (starts with `fri_`) — it is only shown once

---

## Installation

### Universal (30+ agents)

The fastest way to install the MCP server and the business skill:

```bash
npx skills add Frihet-io/frihet-mcp
```

Works with Claude Code, Cursor, Copilot, Codex, Windsurf, Gemini CLI, Goose, Roo Code, and [30+ more agents](https://agentskills.io).

### MCP Registry

The server is registered as `io.frihet/erp` on the [official MCP Registry](https://registry.modelcontextprotocol.io/?q=io.frihet). MCP clients that support the registry can discover and install it automatically by its canonical name.

### Direct npx

```bash
npx -y @frihet/mcp-server@latest
```

No global installation needed. `npx` downloads and runs the latest version automatically.

### Manual configuration

There are two connection modes: **local** (the server runs on your machine via `npx`) and **remote** (direct connection to `mcp.frihet.io`, no installation needed).

### Local (stdio)

```json
{
  "mcpServers": {
    "frihet": {
      "command": "npx",
      "args": ["-y", "@frihet/mcp-server@latest"],
      "env": {
        "FRIHET_API_KEY": "fri_your_key_here"
      }
    }
  }
}
```

### Remote (streamable-http)

```json
{
  "mcpServers": {
    "frihet": {
      "type": "streamable-http",
      "url": "https://mcp.frihet.io/mcp",
      "headers": {
        "Authorization": "Bearer fri_your_key_here"
      }
    }
  }
}
```

### Configuration by client

The JSON structure is identical across all clients. Only the config file location changes.

| Client | Config file |
|--------|-------------|
| Claude Code | `~/.claude/mcp.json` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |
| Cursor | `.cursor/mcp.json` or `~/.cursor/mcp.json` |
| Windsurf | `~/.windsurf/mcp.json` |
| Cline | VS Code settings or `.cline/mcp.json` |
| Codex CLI | `~/.codex/config.toml` (MCP section) |

:::tip
If you use Claude Code, you can add the server with a single command:

```bash
claude mcp add frihet -- npx -y @frihet/mcp-server@latest
```

Then set the `FRIHET_API_KEY` variable in the resulting `~/.claude/mcp.json` file.
:::

### Environment variables

| Variable | Required | Default |
|----------|----------|---------|
| `FRIHET_API_KEY` | Yes | — |
| `FRIHET_API_URL` | No | `https://api.frihet.io/v1` |
| `FRIHET_MCP_DEBUG` | No | `0` |

- `FRIHET_API_URL` is useful if you're pointing to a staging environment or a custom instance. Must be `https://` with a hostname under `frihet.io`.
- `FRIHET_MCP_DEBUG=1` enables debug-level logs (useful for troubleshooting).

---

## OAuth flow

The MCP server supports OAuth authentication in addition to manual API key setup. When an MCP client initiates the OAuth flow:

1. The user authenticates in Frihet (email, Google, GitHub, or Microsoft)
2. The client sends the session token to `POST /api/oauth/api-key`
3. The server provisions a new `fri_xxx...` key labeled "MCP OAuth" with a 365-day expiration
4. The key is used automatically for subsequent calls

This lets users connect the MCP server without manually copying keys.

---

## Available tools

The server exposes 157 tools organized across 30 categories. The tables below document the core resource tools; the full set (HR, payroll, deposits, banking, fiscal, e-invoicing, Stay/POS, etc.) is listed via `npm install @frihet/mcp-server`. Every tool includes structured output (`outputSchema` + `structuredContent`) for reliable parsing, plus security and content annotations. Bilingual (EN/ES).

### Invoices (6 tools)

| Tool | Description | Key parameters |
|------|-------------|----------------|
| `list_invoices` | List invoices with pagination | `limit`, `offset` |
| `get_invoice` | Get a single invoice by ID | `id` |
| `create_invoice` | Create an invoice with line items | `clientName`, `items[]` (description, quantity, unitPrice), `status`, `dueDate`, `notes`, `taxRate` |
| `update_invoice` | Update fields on an invoice (partial) | `id`, plus any field to modify |
| `delete_invoice` | Permanently delete an invoice | `id` |
| `search_invoices` | Search invoices by client name | `clientName`, `limit`, `offset` |

**Possible invoice statuses:** `draft`, `sent`, `paid`, `overdue`, `cancelled`

### Expenses (5 tools)

| Tool | Description | Key parameters |
|------|-------------|----------------|
| `list_expenses` | List expenses with pagination | `limit`, `offset` |
| `get_expense` | Get a single expense by ID | `id` |
| `create_expense` | Log a new expense | `description`, `amount`, `category`, `date`, `vendor`, `taxDeductible` |
| `update_expense` | Modify an existing expense (partial) | `id`, plus any field to modify |
| `delete_expense` | Permanently delete an expense | `id` |

### Clients (5 tools)

| Tool | Description | Key parameters |
|------|-------------|----------------|
| `list_clients` | List all clients | `limit`, `offset` |
| `get_client` | Get a single client by ID | `id` |
| `create_client` | Register a new client | `name`, `email`, `phone`, `taxId`, `address` (street, city, state, postalCode, country) |
| `update_client` | Update client details (partial) | `id`, plus any field to modify |
| `delete_client` | Permanently delete a client | `id` |

### Products (5 tools)

| Tool | Description | Key parameters |
|------|-------------|----------------|
| `list_products` | List products and services | `limit`, `offset` |
| `get_product` | Get a single product by ID | `id` |
| `create_product` | Create a product or service | `name`, `unitPrice`, `description`, `taxRate` |
| `update_product` | Modify an existing product (partial) | `id`, plus any field to modify |
| `delete_product` | Permanently delete a product | `id` |

### Quotes (5 tools)

| Tool | Description | Key parameters |
|------|-------------|----------------|
| `list_quotes` | List quotes | `limit`, `offset` |
| `get_quote` | Get a single quote by ID | `id` |
| `create_quote` | Create a quote for a client | `clientName`, `items[]` (description, quantity, unitPrice), `validUntil`, `notes`, `status` |
| `update_quote` | Modify a quote (partial) | `id`, plus any field to modify |
| `delete_quote` | Permanently delete a quote | `id` |

**Possible quote statuses:** `draft`, `sent`, `accepted`, `rejected`, `expired`

### Webhooks (5 tools)

| Tool | Description | Key parameters |
|------|-------------|----------------|
| `list_webhooks` | List configured webhooks | `limit`, `offset` |
| `get_webhook` | Get webhook configuration | `id` |
| `create_webhook` | Register a webhook endpoint | `url`, `events[]`, `active`, `secret` |
| `update_webhook` | Modify an existing webhook | `id`, plus any field to modify |
| `delete_webhook` | Permanently delete a webhook | `id` |

**Available webhook events:** `invoice.created`, `invoice.updated`, `invoice.deleted`, `invoice.paid`, `expense.created`, `expense.updated`, `expense.deleted`, `client.created`, `client.updated`, `client.deleted`, `product.created`, `product.updated`, `product.deleted`, `quote.accepted`. See [Webhooks](/desarrolladores/webhooks) for payload format and HMAC verification.

---

## Available resources (8)

MCP resources are reference data the assistant can query without making API calls. They are static and always accessible.

| Resource | URI | Description |
|----------|-----|-------------|
| API schema | `frihet://api/schema` | Endpoint summary, authentication, rate limits, and error codes |
| Tax rates | `frihet://tax/rates` | VAT (21/10/4%), IGIC (7/3/0%), IPSI, intra-community, IRPF (15%/7%) |
| Tax calendar | `frihet://tax/calendar` | Quarterly filing dates (Modelo 303, 130, 420, 390) |
| Expense categories | `frihet://config/expense-categories` | 8 categories with deductibility rules and tax treatment |
| Invoice statuses | `frihet://config/invoice-statuses` | Status flow (draft, sent, paid, overdue, cancelled) with triggers |
| Vendors | `frihet://config/vendors` | List of registered vendors with tax and contact data |
| Active integrations | `frihet://config/integrations` | Status of connected integrations (Stripe, Shopify, etc.) |
| Business configuration | `frihet://config/business` | Company tax data, fiscal zone, currency, and preferences |

---

## Available prompts (7)

Prompts are guided workflows the assistant executes step by step. Invoke them by name.

| Prompt | Description | Parameters |
|--------|-------------|------------|
| `monthly-close` | Monthly close: review unpaid invoices, categorize expenses, verify tax obligations, generate summary | `month` (optional) |
| `onboard-client` | Client onboarding: determine tax type by location, create record, generate welcome quote | `clientName`, `country`, `region` |
| `quarterly-tax-prep` | Quarterly tax prep: gather invoices, calculate VAT/IGIC, generate Modelo 303/130 preview | `quarter`, `fiscalZone` |
| `overdue-followup` | Overdue follow-up: identify overdue invoices, group by client, draft collection messages | — |
| `expense-batch` | Batch expense processing: categorize, apply taxes, verify deductibility, create with confirmation | `fiscalZone` |
| `vendor-analysis` | Vendor analysis: group expenses by vendor, calculate totals, identify trends and savings opportunities | `period` (optional) |
| `business-health` | Business health check: key KPIs, month-over-month comparison, alerts, and actionable recommendations | `month` (optional) |

:::tip
You can invoke a prompt directly: *"Run the monthly close for February"* or *"Prep my Q1 taxes"*.
:::

---

## Usage examples

These are real natural-language requests that the assistant translates into MCP tool calls.

### Create an invoice

```
"Create an invoice for Acme Ltd with 10 hours of consulting at 95 EUR/hour, due March 15th"
```

The assistant calls `create_invoice` with `clientName: "Acme Ltd"`, one line item (10 x 95), and `dueDate: "2026-03-15"`. The total is calculated automatically.

### Log an expense

```
"Log an expense of 59.99 EUR for Adobe Creative Cloud, category software, tax deductible"
```

Calls `create_expense` with `description`, `amount: 59.99`, `category: "software"`, and `taxDeductible: true`.

### Search invoices by client

```
"Find all invoices for TechStart Ltd"
```

Calls `search_invoices` with `clientName: "TechStart Ltd"` and returns matches with totals and statuses.

### Check overdue invoices

```
"Show me all unpaid invoices"
```

Calls `list_invoices` and filters by `status: "sent"` or `"overdue"`, displaying overdue invoices sorted by amount.

### Add a new client

```
"New client: Design Studio Ltd, VAT GB123456789, email hello@designstudio.co.uk, London EC1A 1BB"
```

Calls `create_client` with name, tax ID, email, and address.

### Set up automation

```
"Create a webhook to notify https://my-app.com/hook when an invoice is paid"
```

Calls `create_webhook` with `url` and `events: ["invoice.paid"]`.

### Update a product

```
"Raise the hourly consulting rate to 85 EUR"
```

Calls `update_product` with the product `id` and `unitPrice: 85`. Only the specified field is modified.

---

## Observability

The MCP server v1.15.2 includes structured logging and tool metrics.

### Structured logging

All logs are emitted as JSON to stderr (MCP uses stdout for protocol messages). Each entry includes:

- `level`: `debug`, `info`, `warn`, `error`
- `service`: always `frihet-mcp`
- `timestamp`: ISO 8601
- `tool`: tool name (when applicable)
- `operation`: operation type (`tool_call`, `api_call`, `api_retry`, `startup`, `shutdown_metrics`)
- `durationMs`: execution time in milliseconds
- `error`: error details (message, code, statusCode)

Enable debug logs with `FRIHET_MCP_DEBUG=1`.

### Tool metrics

The server tracks in-memory metrics for each tool call: invocation count, errors, and average duration. On shutdown (SIGINT/SIGTERM), it emits a summary:

```json
{
  "level": "info",
  "message": "Shutdown after 3600s — 42 calls, 1 errors",
  "operation": "shutdown_metrics",
  "metadata": {
    "tools": {
      "list_invoices": { "calls": 15, "errors": 0, "avgMs": 230 },
      "create_invoice": { "calls": 8, "errors": 1, "avgMs": 450 }
    },
    "uptime": 3600
  }
}
```

### Automatic rate-limit retry

When the API responds with `429`, the server retries automatically with exponential backoff (up to 3 retries). Retries are logged:

```json
{
  "level": "warn",
  "message": "Rate limited, retrying GET /invoices (attempt 2, delay 2000ms)",
  "operation": "api_retry"
}
```

You don't need to handle rate limiting manually — the server does it for you.

---

## Transport

Frihet's MCP server supports two transport modes. Both expose the same 157 tools, 11 resources, and 10 prompts.

### Local (stdio)

The server runs as a local process on your machine. Communication between the MCP client and server uses standard input/output (stdin/stdout).

- **Requires:** Node.js installed (downloaded automatically via `npx`)
- **Advantage:** Lower latency, works offline (except for API calls)
- **Use case:** Daily development, heavy usage, corporate environments with network restrictions

### Remote (streamable-http)

The server runs on Cloudflare Workers. Your MCP client connects directly to `https://mcp.frihet.io/mcp` over HTTP.

- **Requires:** Only an internet connection
- **Advantage:** No local installation, no dependencies, works on any device
- **Use case:** Quick setup, teams that prefer not to install packages, clients that only support HTTP transport

:::info
If your MCP client doesn't support `streamable-http` (some older clients only support `stdio`), use local mode.
:::

---

## Error handling

### Rate limiting

The API allows **100 requests per minute** per key. If the limit is exceeded, the server returns a `429` error with the wait time in `retryAfter`.

The MCP server handles rate limiting automatically with **exponential backoff**: it retries the request after waiting the indicated time, with no user intervention needed. Maximum 3 retries.

### Authentication errors

| Code | Cause | Solution |
|------|-------|----------|
| `401` | Invalid, expired, or missing API key | Verify the key in your config starts with `fri_` and hasn't expired |
| `403` | The key doesn't have permissions for this resource | Generate a new key with the required permissions |

### Other errors

| Code | Description |
|------|-------------|
| `400` | Invalid parameters or missing required fields |
| `404` | The requested resource doesn't exist |
| `408` | Request timeout (30 seconds) |
| `413` | The request body exceeds 1 MB |
| `422` | Valid data but not processable (e.g., fiscal profile not configured) |
| `429` | Rate limit exceeded (retried automatically) |
| `500` | Internal server error |

All errors return a descriptive bilingual message (EN/ES) so the assistant can communicate the problem clearly to the user.

---

## Limits

| Concept | Value |
|---------|-------|
| Requests per minute | 100 per API key |
| Results per page | 100 maximum (50 default) |
| Request body | 1 MB maximum |
| Webhook payload | 100 KB maximum |
| Webhooks per account | 20 maximum |
| Request timeout | 30 seconds |
| Rate limit retries | 3 maximum |

---

## MCP Server vs REST API

| | MCP Server | REST API |
|---|---|---|
| **Designed for** | AI assistants (Claude, Cursor, Windsurf) | Applications, scripts, integrations |
| **Communication** | Natural language through the MCP client | Direct HTTP/JSON |
| **Authentication** | Environment variable in client config (or OAuth) | `X-API-Key` header or `Authorization: Bearer` |
| **Format** | Formatted text + structured output for the assistant | Raw JSON |
| **Rate limiting** | Handled automatically (exponential backoff, 3 retries) | Manual (consumer must implement retries) |
| **Observability** | Structured logging + per-tool metrics | Request logs via `X-Request-Id` |
| **Use case** | Talk to your ERP from your IDE | Build programmatic integrations |

Internally, the MCP server translates each tool call into a REST API request. It doesn't duplicate logic — it's a stateless bridge.

---

## Development

To contribute or run the server in development mode:

```bash
git clone https://github.com/Frihet-io/frihet-mcp.git
cd frihet-mcp
npm install
npm run build
```

Run locally:

```bash
FRIHET_API_KEY=fri_your_key node dist/index.js
```

Run with debug logs:

```bash
FRIHET_MCP_DEBUG=1 FRIHET_API_KEY=fri_your_key node dist/index.js
```

Test with the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector):

```bash
npx @modelcontextprotocol/inspector node dist/index.js
```

---

## Related resources

- [REST API](/desarrolladores/api-rest) — Full endpoint reference, authentication, and error codes
- [Webhooks](/desarrolladores/webhooks) — Available events, HMAC-SHA256 verification, and retry policy
- [Claude Code Skill](/desarrolladores/skill-claude-code) — Business intelligence layer on top of the MCP server
- [Source code (GitHub)](https://github.com/Frihet-io/frihet-mcp) — MCP server repository
- [npm package](https://www.npmjs.com/package/@frihet/mcp-server) — `@frihet/mcp-server@1.15.2`
- [MCP Registry](https://registry.modelcontextprotocol.io/?q=io.frihet) — `io.frihet/erp`
- [Remote endpoint](https://mcp.frihet.io) — MCP server hosted on Cloudflare Workers
- [MCP specification](https://modelcontextprotocol.io) — Protocol documentation

---

[Previous: Webhooks](/desarrolladores/webhooks) | [Next: Claude Code Skill](/desarrolladores/skill-claude-code)
