{"id":"980e4a1d-2af3-4ca5-9f70-ca0eb67b65c0","slug":"stack74-easybill-mcp","kind":"mcp","name":"easybill-mcp","summary":"MCP server for the easybill REST API — invoices, customers, articles, payments, time tracking","owner":"stack74","repoUrl":"https://github.com/stack74/easybill-mcp","license":"MIT","licenseTier":"permissive","trust":"community","stars":0,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-08-04T14:09:57.000Z","addedAt":"2026-10-14T11:25:54.919Z","logoUrl":"https://avatars.githubusercontent.com/u/958540?u=025e2c77a88cef821527329da93b7983f89e6fa1&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["productivity","cloud-infra","memory-knowledge"],"tags":["claude","easybill","invoicing","typescript"],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-17T21:10:49.288Z","sample":false,"url":"https://conduits.dev/tools/stack74-easybill-mcp","language":"TypeScript","transports":["stdio"],"install":{"npm":"easybill-mcp"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# easybill MCP Server\n\nAn [MCP](https://modelcontextprotocol.io) server for the [easybill REST API](https://www.easybill.de/api/).\nGives any MCP-capable assistant — Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Zed,\nCodex CLI, Gemini CLI, Cline — access to your invoices, customers, article catalogue, payments,\nprojects and tracked time.\n\nBuilt on the official [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk)\n(TypeScript), talking stdio.\n\n- **54 tools** across 12 easybill resources\n- **Read-only by default** — create/update/delete tools stay unregistered unless you opt in\n- **Workflow-aware** — ships server instructions so the agent resolves the customer before\n  writing a document, instead of guessing where to start\n- **Fuzzy customer search** the easybill API itself does not offer\n- **Self-throttling** to respect easybill's 10–60 requests/minute cap\n- **Context-aware responses** — compact field sets, pagination metadata, automatic truncation\n\n---\n\n## Setup\n\n### 1. Get an easybill API key\n\nIn easybill: **Einstellungen → App-Center → API**. Create a key and copy it.\n\n### 2. Register it with your client\n\nThis is a plain stdio MCP server published on npm, so any MCP client can run it with `npx` —\nno clone, no build, no global install:\n\n```bash\nnpx -y easybill-mcp\n```\n\nConfiguration is entirely through environment variables (full list under\n[Configuration](#configuration)). Two matter now:\n\n- Leave `EASYBILL_ENABLE_WRITES` unset to start **read-only**.\n- Set `EASYBILL_RATE_LIMIT_PER_MINUTE` to **10** on easybill's PLUS plan or **60** on BUSINESS.\n\nMost clients use the same JSON shape under an `mcpServers` key:\n\n```json\n{\n  \"mcpServers\": {\n    \"easybill\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"easybill-mcp\"],\n      \"env\": {\n        \"EASYBILL_API_KEY\": \"your_api_key_here\",\n        \"EASYBILL_RATE_LIMIT_PER_MINUTE\": \"10\"\n      }\n    }\n  }\n}\n```\n\nWhere that block goes:\n\n| Client | Config location |\n|---|---|\n| **Claude Desktop** | Settings → Developer → Edit Config, or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) · `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows) |\n| **Cursor** | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project) |\n| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |\n| **Cline / Roo Code** | MCP Servers panel → Configure MCP Servers (`cline_mcp_settings.json`) |\n| **Gemini CLI** | `~/.gemini/settings.json` |\n| **Claude Code** | `claude mcp add`, see below |\n| **VS Code / Copilot** | `.vscode/mcp.json`, different key — see below |\n| **Zed** | `settings.json` → `context_servers`, see below |\n| **Codex CLI** | `~/.codex/config.toml`, TOML instead of JSON — see below |\n\n> **Windows:** if a client fails to start `npx`, use `\"command\": \"cmd\"` with\n> `\"args\": [\"/c\", \"npx\", \"-y\", \"easybill-mcp\"]`.\n\n#### Claude Code\n\n```bash\nclaude mcp add easybill \\\n  --env EASYBILL_API_KEY=your_api_key_here \\\n  --env EASYBILL_RATE_LIMIT_PER_MINUTE=10 \\\n  -- npx -y easybill-mcp\n```\n\n`claude mcp add` does not overwrite an existing entry — to change the environment later,\n`claude mcp remove easybill -s local` first, then add it again.\n\n#### VS Code (GitHub Copilot agent mode)\n\nVS Code uses `servers`, not `mcpServers`, and can prompt for the key instead of storing it\nin the file. Put this in `.vscode/mcp.json` (workspace) or open **MCP: Open User\nConfiguration**:\n\n```json\n{\n  \"inputs\": [\n    { \"id\": \"easybill_key\", \"type\": \"promptString\", \"description\": \"easybill API key\", \"password\": true }\n  ],\n  \"servers\": {\n    \"easybill\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"easybill-mcp\"],\n      \"env\": {\n        \"EASYBILL_API_KEY\": \"${input:easybill_key}\",\n        \"EASYBILL_RATE_LIMIT_PER_MINUTE\": \"10\"\n      }\n    }\n  }\n}\n```\n\n#### Zed\n\nIn `settings.json`:\n\n```json\n{\n  \"context_servers\": {\n    \"easybill\": {\n      \"source\": \"custom\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"easybill-mcp\"],\n      \"env\": {\n        \"EASYBILL_API_KEY\": \"your_api_key_here\",\n        \"EASYBILL_RATE_LIMIT_PER_MINUTE\": \"10\"\n      }\n    }\n  }\n}\n```\n\n#### Codex CLI\n\nIn `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.easybill]\ncommand = \"npx\"\nargs = [\"-y\", \"easybill-mcp\"]\nenv = { EASYBILL_API_KEY = \"your_api_key_here\", EASYBILL_RATE_LIMIT_PER_MINUTE = \"10\" }\n```\n\n#### Hosted clients that only speak remote MCP\n\nSome clients (ChatGPT connectors, hosted agent platforms) accept only HTTP/SSE endpoints, not\nstdio. Bridge it locally:\n\n```bash\nnpx -y supergateway --stdio \"npx -y easybill-mcp\"\n```\n\nOnly do this if you understand the exposure: anything that reaches that endpoint reaches your\naccounting data. Keep it bound to localhost or behind a tunnel with authentication, and never\nrun the bridge with writes enabled unless the endpoint is protected.\n\n### 3. Verify the connection\n\nAsk the assistant to call `easybill_get_server_info`. It reports the mode, the limits and\nwhether easybill accepted the credentials:\n\n```json\n{\n  \"server_version\": \"1.0.1\",\n  \"writes_enabled\": false,\n  \"tools_registered\": { \"read_only\": 28, \"write\": 0, \"withheld_because_writes_disabled\": 26 },\n  \"rate_limit_per_minute\": 10,\n  \"connection\": \"ok — credentials accepted by easybill\"\n}\n```\n\n### 4. Enable writes, once you trust it\n\nAdd `EASYBILL_ENABLE_WRITES=true` to the server's environment:\n\n```bash\nclaude mcp add easybill \\\n  --env EASYBILL_API_KEY=your_api_key_here \\\n  --env EASYBILL_RATE_LIMIT_PER_MINUTE=10 \\\n  --env EASYBILL_ENABLE_WRITES=true \\\n  -- npx -y easybill-mcp\n```\n\nThe 26 write tools are registered at startup, so the change only takes effect once the client\nrestarts the server process — editing the config in a running session is not enough. Confirm\nwith `easybill_get_server_info`: `writes_enabled` must read `true`.\n\n---\n\n## Running from a local clone\n\nFor development, or to run a version you have not published:\n\n```bash\ngit clone https://github.com/stack74/easybill-mcp.git\ncd easybill-mcp\nnpm install\nnpm run build\n```\n\nThen point the client at the built entry point instead of `npx` — `\"command\": \"node\"`,\n`\"args\": [\"/absolute/path/to/easybill-mcp/dist/index.js\"]`, same `env` block. Or straight from\nGitHub without cloning, which is slower because it compiles TypeScript on install via the\n`prepare` script and needs a toolchain:\n\n```bash\nnpx -y github:stack74/easybill-mcp\n```\n\n### Test locally first\n\nCopy `.env.example` to `.env`, put your key in it, then run the read-only self-test:\n\n```bash\ncp .env.example .env\n$EDITOR .env\nnpm run smoke\n```\n\nIt makes five read-only calls and prints what came back:\n\n```\neasybill-mcp-server v1.x.y self-test\nAPI: https://api.easybill.de/rest/v1\nThrottle: 60 req/min | writes disabled\n\n  OK    Auth check (customer groups): total=2  first=Stammkunden\n  OK    Customers: total=42  first=Example GmbH\n  OK    Invoices: total=128  first=RE-2026-0001\n  OK    Unpaid invoices: total=7  first=RE-2026-0117\n  OK    Articles (positions): total=95  first=ART-001\n\nAll probes passed. The server is ready to register with an MCP client.\n```\n\n(Numbers above are illustrative — you will see your own account's totals.)\n\nOn a bad key it fails on the first call and stops, so it never burns your rate limit.\n\nTo poke at individual tools interactively:\n\n```bash\nnpm run inspect     # MCP Inspector, loads .env\n```\n\n> **Note:** `.env` is only read by these npm scripts, via Node's `--env-file`. The server\n> itself reads plain environment variables — when you register it with an MCP client, pass\n> the key through the client's config, not via `.env`.\n\n### Publishing to npm\n\n```bash\nnpm login                  # once, needs an npmjs.com account\nnpm run build && npm run smoke\nnpm publish                # unscoped packages are public by default\n```\n\nVerify what ships before you publish — `npm pack --dry-run` lists the tarball contents.\nOnly `dist/`, `README.md`, `LICENSE` and `package.json` are included; `src/` and `.env`\nare not.\n\nFor later releases bump the version first (`npm version patch|minor|major`), which also\ncreates a git tag. npm does not allow republishing an existing version.\n\n---\n\n## Configuration\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `EASYBILL_API_KEY` | — | **Required.** Your easybill API key. |\n| `EASYBILL_ENABLE_WRITES` | *unset* | Set to `true` to register create/update/delete tools. Otherwise read-only. |\n| `EASYBILL_RATE_LIMIT_PER_MINUTE` | `60` | Client-side throttle. Use `10` for PLUS, `60` for BUSINESS. |\n| `EASYBILL_CHARACTER_LIMIT` | `25000` | Max characters per response before truncation. |\n| `EASYBILL_TIMEOUT_MS` | `30000` | HTTP request timeout. |\n| `EASYBILL_MAX_RETRIES` | `3` | Retries on HTTP 429 / 5xx. |\n| `EASYBILL_BASE_URL` | easybill production | Override for a proxy or test double. |\n\n---\n\n## Tools\n\n### Customer lookup\n\n| Tool | Access |\n|---|---|\n| `easybill_find_customer` | read — **start here**: fuzzy search by name, email or number |\n\nThe easybill API has no full-text search, so this tool tries exact filters first, then pages\nthe customer list and scores candidates locally. It tolerates legal forms (`GmbH & Co. KG`),\npunctuation (`M.B. MUSTERMANN` vs `MB Mustermann`), umlauts and word order.\n\n### Documents — invoices, offers, credit notes, deliveries, orders\n\n| Tool | Access |\n|---|---|\n| `easybill_list_documents` | read |\n| `easybill_get_document` | read |\n| `easybill_download_document` | read — saves PDF / ZUGFeRD / XRechnung to disk |\n| `easybill_list_document_versions` | read — audit trail |\n| `easybill_create_document` | write |\n| `easybill_update_document` | write |\n| `easybill_complete_document` | write — finalises a draft, assigns the invoice number |\n| `easybill_cancel_document` | write — creates a STORNO document |\n| `easybill_convert_document` | write — OFFER → INVOICE, INVOICE → DUNNING, … |\n| `easybill_send_document` | write — **sends real email / fax / post** |\n| `easybill_delete_document` | write, destructive |\n\n### Customers, contacts, groups\n\n`easybill_list_customers`, `easybill_get_customer`, `easybill_create_customer`,\n`easybill_update_customer`, `easybill_delete_customer`,\n`easybill_list_contacts`, `easybill_get_contact`, `easybill_create_contact`,\n`easybill_update_contact`, `easybill_delete_contact`,\n`easybill_list_customer_groups`, `easybill_get_customer_group`\n\n### Article catalogue and stock\n\n`easybill_list_positions`, `easybill_get_position`, `easybill_create_position`,\n`easybill_update_position`, `easybill_delete_position`,\n`easybill_list_stock_entries`, `easybill_get_stock_entry`, `easybill_create_stock_entry`\n\n### Payments\n\n`easybill_list_document_payments`, `easybill_get_document_payment`,\n`easybill_create_document_payment`, `easybill_delete_document_payment`\n\n### Projects and time tracking\n\n`easybill_list_projects`, `easybill_get_project`, `easybill_create_project`,\n`easybill_update_project`, `easybill_delete_project`,\n`easybill_list_time_trackings`, `easybill_get_time_tracking`,\n`easybill_create_time_tracking`, `easybill_update_time_tracking`, `easybill_delete_time_tracking`\n\n### Attachments and templates\n\n`easybill_list_attachments`, `easybill_get_attachment`, `easybill_download_attachment`,\n`easybill_delete_attachment`, `easybill_list_text_templates`, `easybill_get_text_template`,\n`easybill_list_pdf_templates`\n\n### Server\n\n`easybill_get_server_info` — reports mode, limits and whether the credentials work.\n\n---\n\n## Things worth knowing\n\n### The server tells the agent how to sequence work\n\nA plain set of endpoint wrappers leads agents astray. Handed an inbound customer inquiry,\nClaude's first instinct was to call `easybill_list_documents` — which cannot answer the actual\nfirst question, *is this company already a customer?*\n\nThe server therefore ships MCP **server instructions** (see `src/instructions.ts`) with the\nordering for common tasks, and `easybill_create_document` opens with the same sequence:\n\n```\n1. easybill_find_customer     — does the customer exist?\n2. easybill_create_customer   — only if not\n3. easybill_create_document   — creates a DRAFT\n4. review with the user\n5. easybill_complete_document — assigns the official number, locks it\n6. easybill_send_document     — only when explicitly asked\n```\n\n`easybill_list_documents` explicitly says it is *not* the tool for orienting yourself. If you\nextend this server, keep that pattern: state prerequisites at the top of the description of\nthe tool an agent will reach for first.\n\n### All money is in cents\n\neasybill represents every monetary value as an **integer in cents**, even where the API\ntypes it as a float. `1999` means **19.99 EUR**. This is stated in every tool description\nthat touches an amount, because getting it wrong produces invoices off by 100×.\n\nTimes in time tracking (`timer_value`) are in **minutes**.\n\n### Drafts vs. completed documents\n\n`easybill_create_document` produces a **draft**. It has no official document number and can\nstill be edited. Call `easybill_complete_document` to finalise it — easybill then assigns the\nnumber and locks it.\n\nTo void a completed invoice, use `easybill_cancel_document` (which creates a proper STORNO\ndocument), not `easybill_delete_document`. Completed invoices generally cannot be deleted for\nlegal reasons.\n\n### Rate limits are low\n\neasybill allows **10 requests/minute on PLUS** and **60 on BUSINESS**. The server throttles\nitself to `EASYBILL_RATE_LIMIT_PER_MINUTE` and, if a 429 slips through, retries while honouring\n`Retry-After`. Set the variable to match your plan — otherwise a broad question that pages\nthrough many documents will stall.\n\nBecause of this, prefer narrow filters over paging: `easybill_list_documents` with\n`type='INVOICE'` and `paid_at='null'` costs one request, where scanning everything costs dozens.\n\n### Context management\n\n`Document` has 69 fields and `Customer` 79. List tools therefore return a **compact default\nfield set**, and single-record lookups return everything. Both accept `fields`:\n\n- omit it → compact defaults\n- `fields: [\"number\", \"amount\"]` → just those (plus `id`)\n- `fields: [\"all\"]` → every field\n\nResponses over `EASYBILL_CHARACTER_LIMIT` are truncated, halving the item count until they\nfit, and say so in `truncation_message`.\n\n### No fuzzy customer search\n\nThe easybill API has no full-text search. Customer filters match **exact** values (each\naccepts several comma-separated). To find a customer by partial name, either use the exact\nspelling or page through and match locally.\n\n### Useful filter recipes\n\n| Question | Call |\n|---|---|\n| Which invoices are unpaid? | `easybill_list_documents` `type='INVOICE'`, `paid_at='null'` |\n| Revenue in 2025? | `easybill_list_documents` `type='INVOICE'`, `document_date='2025-01-01,2025-12-31'` |\n| One customer's history | `easybill_list_documents` `customer_id=<id>` |\n| Only real, non-cancelled invoices | add `is_draft='0'`, `cancel_id='null'` |\n| Unbilled tracked time | `easybill_list_time_trackings`, then filter `cleared_at = null` |\n\n---\n\n## Development\n\n```bash\nnpm run build      # compile to dist/\nnpm run typecheck  # tsc --noEmit\nnpm test           # offline suite — 37 tests, no credentials needed\nnpm run test:live  # read-only tests against a real account (needs .env)\nnpm run test:all   # both\nnpm run dev        # watch mode\nnpm run smoke      # read-only self-test against the live API (needs .env)\nnpm run inspect    # MCP Inspector against the built server (needs .env)\n```\n\n### Tests\n\n`npm test` runs offline and needs no easybill account — it covers response shaping,\nthe HTTP client against a mock API, the rate-limit throttle, and the MCP surface\n(schemas, annotations, write-gating, error handling) driven over the real stdio protocol.\n\n```\ntest/\n├── helpers/mcp-client.mjs  minimal MCP stdio client + mock API server\n├── format.test.mjs         field selection, pagination, truncation, markdown\n├── client.test.mjs         auth headers, query serialisation, 429 retry, error mapping\n├── rate-limit.test.mjs     sliding-window throttle\n├── server.test.mjs         tool registration, workflow guidance, validation, errors\n└── live.test.mjs           optional: read-only checks against a real account\n```\n\n`live.test.mjs` skips itself unless `EASYBILL_API_KEY` is set, so contributors without an\naccount still get a green suite. It only reads — nothing in the test suite writes to easybill.\n\nProject layout:\n\n```\nsrc/\n├── index.ts              entry point, stdio transport, env validation, --selftest\n├── instructions.ts       MCP server instructions: workflow ordering for the agent\n├── constants.ts          config, summary field sets, enums\n├── schemas/common.ts     shared Zod building blocks\n├── services/\n│   ├── client.ts         HTTP, auth, throttling, retries, error mapping\n│   └── format.ts         field selection, pagination, markdown, truncation\n└── tools/\n    ├── registry.ts       registration + write-gating\n    ├── crud.ts           generic CRUD tool factory\n    ├── search.ts         fuzzy customer lookup\n    ├── documents.ts      documents + lifecycle actions\n    ├── customers.ts      customers, contacts, groups\n    ├── catalog.ts        positions, stock\n    ├── finance.ts        payments\n    ├── work.ts           projects, time tracking\n    ├── files.ts          attachments, templates\n    └── meta.ts           server info\n```\n\nMost resources are generated from a declarative config in `crud.ts`; only genuinely\nresource-specific behaviour (document lifecycle, downloads, sending) is hand-written.\n\n## Contributing\n\nIssues and pull requests welcome. The tool surface is derived from easybill's official\nOpenAPI spec (`https://api.easybill.de/rest/v1/swagger.json`) — when adding a tool, check\nfield names and types against the spec rather than guessing, and mind that monetary fields\nare cents even where the spec types them as floats.\n\n## Disclaimer\n\nThis is an **unofficial**, community-maintained project. It is not affiliated with,\nendorsed by, or supported by easybill GmbH. \"easybill\" is a trademark of its respective\nowner and is used here only to describe what this software talks to.\n\nThe software is provided as-is under the MIT license, without warranty. It can create,\nmodify and delete records in your accounting system, and can send invoices to your\ncustomers. You are responsible for what you let it do — start read-only, and review what\nwrite tools produce before relying on it.\n\n## License\n\n[MIT](LICENSE) © stack74\n"}