I run a small analytics consulting business. That means I do two things: actual consulting work, and everything else — invoicing, payment tracking, chasing overdue bills, bookkeeping admin. The second part is nobody's favorite.
I've been using Claude Code as my daily driver for development work, and at some point I started wondering: what if my AI assistant could handle the invoicing too? Not through some pre-built integration, but by talking directly to my accounting system?
So I built one. A custom MCP server that connects Claude Code to iDoklad, the Czech invoicing platform I use. Now I can say "invoice Client X for 40 hours at €120" and it just happens.
What's MCP?
The Model Context Protocol is an open standard that lets AI models interact with external tools and data sources. Think of it as a USB port for AI — a standardized way to plug in capabilities.
Claude Code supports MCP natively. You define a server, expose some tools, and Claude can call them during conversation. No API wrappers, no prompt engineering tricks. The model sees your tools as first-class functions it can invoke.
The key insight: MCP servers are just programs that speak a specific protocol over stdin/stdout. You can write them in Python, TypeScript, or anything else. They run locally on your machine, which means your data never leaves your computer (except for the actual API calls to the service you're integrating with).
Why iDoklad?
iDoklad is a popular invoicing platform in the Czech Republic and Slovakia. It has a solid REST API (v3), handles VAT correctly for European invoicing, and generates proper PDF invoices. It's what I've been using for years.
The problem: every month I'd open the web interface, manually create invoices, send them out, then periodically check which ones got paid. It's not hard work, but it's the kind of repetitive admin that eats into your day in small bites.
With an MCP server, I can stay in my terminal and let Claude handle the mechanics.
What It Does
The server exposes 15 tools across four categories:
Invoicing — Create invoices, list them with filters, get details, download PDFs, and send them via email. The creation flow pulls iDoklad's default template first, then overlays your data, so the output matches your existing invoice style.
Contacts — Search clients, view details, register new ones. When I onboard a new client, I just tell Claude their company name, ID number, and address.
Payments — Track what's been paid, mark invoices as settled, and pull up all unpaid or overdue invoices. This is the one I use most. "What's unpaid?" is probably my most-typed question.
Analytics — Revenue by client, revenue by month, invoice summaries, top clients by total billing. Useful for quarterly reviews or when I just want to know how the year is going.
How It Works
The architecture is straightforward. Python, async HTTP via httpx, and the official MCP Python SDK.
server.py → MCP entry point (stdio transport)
├── config.py → Environment-based settings
├── auth.py → OAuth2 token lifecycle
├── client.py → HTTP wrapper for iDoklad API
└── tools/
├── invoices.py
├── contacts.py
├── payments.py
└── statistics.py
Each tool module follows the same pattern: a get_tools() function that declares what's available, and a handle_tool() function that executes it. Claude sees the tool descriptions and parameters, decides when to call them, and the server handles the rest.
Authentication
This was the trickiest part. iDoklad uses OAuth2 Authorization Code flow — the kind where a browser opens, you log in, grant permission, and get redirected back with a token.
For a CLI tool, that's awkward. The solution: a separate idoklad-mcp-login command that:
- Spins up a temporary local server on port 11470
- Opens your browser to iDoklad's auth page
- Catches the callback with the authorization code
- Exchanges it for access + refresh tokens
- Saves them to
~/.idoklad-mcp/tokens.json
You do this once. After that, the server automatically refreshes tokens before they expire (with a 60-second buffer). If the refresh token itself expires, you just run the login command again.
Creating an Invoice
This is where it gets interesting. When Claude calls create_issued_invoice, the server:
- Fetches iDoklad's default invoice template (so VAT settings, numbering, and formatting match your account)
- Overlays the partner ID and line items you specified
- Posts the result to the API
- Returns the created invoice details
You can specify VAT rate types per line item (zero, reduced, or standard 21%), and it handles the math. A typical interaction looks like:
"Create an invoice for ACME Corp — 40 hours of analytics consulting at €120/hour, standard VAT."
Claude looks up the client, calls the tool, and the invoice exists. Then I can say "send it" and it emails the PDF.
Daily Usage
Here's what a typical month looks like now:
End of month: "Create invoices for all active clients based on this month's tracked hours." I paste the hours, Claude creates each invoice, and sends them.
Mid-month check: "What invoices are still unpaid?" Claude pulls the list, shows me who's late, and I can decide whether to send a reminder.
Quarterly review: "Show me revenue by client for Q4." A table appears with totals. No spreadsheet, no dashboard — just a question and an answer.
New client: "Add ACME Corp to iDoklad — here's their info." Contact created, ready for invoicing.
The whole invoicing workflow that used to involve switching to a web app, clicking through forms, and manually tracking payments now happens in the same terminal where I do everything else.
What I Learned Building It
MCP is simpler than it looks. The protocol documentation can feel dense, but building a server is essentially: define tools with JSON schemas, handle calls, return results. The Python SDK handles the protocol layer.
OAuth2 in CLI tools is annoying but solvable. The browser-redirect dance feels hacky for a terminal tool, but it's the right approach for security. You get proper authorization without storing passwords.
Tool descriptions matter more than you think. Claude uses your tool descriptions to decide when and how to call them. Vague descriptions lead to wrong tool calls. I spent more time refining the descriptions than writing the actual logic.
Modular beats monolithic. Splitting tools into separate files by domain (invoices, contacts, payments, statistics) made development much faster. Each module is independent, testable, and easy to extend.
Start with what you actually use. I didn't build all 15 tools at once. I started with "list unpaid invoices" because that's what I checked most often. Then added creation, then payments, then analytics. Build for your actual workflow, not a feature checklist.
What's Next
Right now, invoicing is semi-automated. I tell Claude what to invoice, and it handles the creation and sending. But I still have to look up the hours and figure out what goes on each invoice. That's the next piece to eliminate.
The plan is to connect this with my task management system — GitHub Issues and Projects, where I already track all client work. Every task has a client, a project, and logged time. At the end of the month, the workflow should be one prompt:
"Invoice all clients for January."
Claude would pull all completed tasks and tracked hours from GitHub, group them by client and project, create the corresponding invoices in iDoklad, and send them out. End-of-month billing in one sentence instead of an afternoon.
That's the real promise of MCP — not just connecting one tool, but chaining multiple integrations together so the AI can handle entire workflows end to end.
Resources:
