Skip to main content
Glama

mad-invoice-mcp

CLAUDE.md10.6 kB
# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview `mad-invoice-mcp` is an MCP (Model Context Protocol) server for creating, storing, and rendering invoices as JSON + LaTeX + PDF. Data is stored locally in `.mad_invoice/` (gitignored) with no external database dependency. The project is designed for single-company/single-user workflows. ## Development Commands ### Start the server ```bash ./bin/dev # create .venv and start uvicorn on 127.0.0.1:8000 MCP_ENABLE_WRITES=1 ./bin/dev # enable write-capable tools ``` ### Docker ```bash docker build -t mad-invoice-mcp . docker run --rm -p 8000:8000 -e MCP_ENABLE_WRITES=1 \ -v $(pwd)/.mad_invoice:/app/.mad_invoice \ mad-invoice-mcp ``` ### Environment Setup - Python 3.11+ - `pdflatex` from TeX Live 2024+ required for PDF rendering - Auto-discovered in system PATH, `~/.local/texlive/`, `/usr/local/texlive/` - Override with `PDFLATEX_PATH=/path/to/pdflatex` - Docker image includes TeX Live 2025 to avoid pdflatex bugs (optional) - Requirements: `pip install -r requirements.txt` ## Architecture ### Data Model (`bridge/backends/invoices_models.py`) - **Party**: Represents supplier or customer. Supports: - `name`: Natural person name (required, appears first) - `business_name`: Optional trade/brand name (renders as second line) - Address, contact, and tax fields with validation - **LineItem**: Invoice line with description, quantity, unit, unit_price - **Invoice**: Main entity with: - Strict Pydantic validation (`extra="forbid"` rejects unexpected fields) - `status`: Lifecycle flag ("draft" | "final") - `payment_status`: Payment tracking ("open" | "paid" | "overdue" | "cancelled") - `small_business`: Boolean for German §19 UStG (VAT exemption) - `vat_rate`: Float 0-1 for VAT calculation when `small_business=False` - `language`: "de" | "en" for localization - Validation: due_date >= invoice_date, subtotal >= 0 ### Storage (`bridge/backends/invoices_storage.py`) Directory structure under `.mad_invoice/`: ``` .mad_invoice/ invoices/<invoice-id>.json # Individual invoice files index.json # Aggregated list for quick queries sequence.json # Yearly counters for invoice numbering build/<invoice-id>/ # LaTeX and PDF outputs invoice.tex invoice.pdf ``` Key functions: - `get_invoice_root()`: Resolves storage location (respects `MAD_INVOICE_ROOT` env var) - `ensure_structure()`: Creates required directories - `save_invoice()`, `load_invoice()`: JSON serialization with Pydantic validation - `build_index()`, `save_index()`: Rebuild index from all invoice files - `with_index_lock()`: File-based mutex using portalocker for safe concurrent writes - `next_invoice_number()`: Atomic yearly sequence generation (format: YYYY-####) ### MCP Tools (`bridge/backends/invoices.py`) #### Draft/Final Lifecycle Invoices follow a **draft → final** workflow: - **Draft (`status="draft"`)**: Fully editable, can be deleted - **Final (`status="final"`)**: Immutable - only PDF rendering allowed - **One-way street**: Cannot change `final` back to `draft` #### Tools 1. **create_invoice_draft(invoice: Invoice)** - Validates and persists invoice JSON (always starts as draft) - Rebuilds index atomically - Requires `MCP_ENABLE_WRITES=1` 2. **update_invoice_draft(invoice_id: str, invoice: Invoice)** - Updates complete invoice content - Only works for `status="draft"` - Cannot edit finalized invoices 3. **delete_invoice_draft(invoice_id: str)** - Permanently deletes invoice - Only works for `status="draft"` - Cannot delete finalized invoices 4. **render_invoice_pdf(invoice_id: str)** - Loads invoice, fills `templates/invoice.tex` placeholders - Runs pdflatex twice (for reference resolution) - Escapes LaTeX special chars and formats currency/dates by language - Works for both draft and final invoices 5. **update_invoice_status(invoice_id, payment_status, status?)** - Updates payment tracking and lifecycle status - Prevents `final` → `draft` changes (immutability guard) - Use `status="final"` to finalize invoice - Rebuilds index 6. **generate_invoice_number(separator="-")** - Returns next invoice number from yearly sequence - Default format: "YYYY-####" (pass `separator=""` for "YYYY####") 7. **get_invoice_template()** - Returns example Invoice payload with field documentation ### LaTeX Rendering - Template: `templates/invoice.tex` with `%%PLACEHOLDER%%` markers - Logic in `_invoice_replacements()`: escapes text, formats dates/currency by language, handles VAT conditionally - Two-pass pdflatex execution for correct page references - Fonts: lmodern + microtype for sharp output - Party names render on multiple lines when `business_name` is set ### Web UI (`bridge/api/routes.py`, `bridge/web/`) Minimal FastHTML interface: - `GET /invoices` – overview table from index.json - `GET /invoices/{id}` – detail view with line items - `POST /invoices/{id}/render` – trigger PDF generation - `POST /invoices/{id}/mark-paid` – set payment_status="paid" ### Application Wiring (`bridge/app.py`) - SSE-based MCP server with single-connection guard (409 on conflict) - State management: readiness tracking, connection lifecycle logging - API routes: `/api/state`, `/api/openapi.json` - Startup: `configure()` registers tools and logging ## Design Constraints **Critical principles to maintain:** 1. **No database**: JSON files are the source of truth 2. **Strict validation**: Pydantic models reject invalid/unexpected fields with hard errors 3. **Write gate**: All write operations must check `MCP_ENABLE_WRITES` and use `record_write_attempt()` 4. **Deterministic behavior**: Operations succeed cleanly or fail loudly; no partial writes 5. **Single-project scope**: One `.mad_invoice/` store per repository; no multi-project switching ## Key Implementation Patterns ### Adding New MCP Tools 1. Define in `bridge/backends/invoices.py` within `register(server)` function 2. Use `@server.tool()` decorator 3. Call `_require_writes_enabled()` for mutations 4. Call `record_write_attempt()` for audit trail 5. Use `with_index_lock()` when modifying multiple files 6. Raise `ToolError` (from `mcp.server.fastmcp.exceptions`) for user-facing errors ### Modifying Invoice Model 1. Update Pydantic models in `bridge/backends/invoices_models.py` 2. Add field validators if needed (`@field_validator`, `@model_validator`) 3. Use `extra="forbid"` to reject unknown fields 4. Update `to_index_entry()` if field should appear in index 5. Update LaTeX replacements in `_invoice_replacements()` if rendering changes ### LaTeX Customization - Edit `templates/invoice.tex` directly for branding (logo, colors, layout) - Add new placeholders as `%%VARIABLE%%` and populate in `_invoice_replacements()` - Escape all text with `_escape_tex()` or `_escape_multiline()` to prevent LaTeX injection - Use `_LABELS` dict for multi-language label support ### Testing Patterns - No test suite currently exists (TODO) - Manual testing via Web UI or direct MCP tool calls - Docker setup useful for testing pdflatex reliability ## Common Pitfalls 1. **Forgetting LaTeX escaping**: Always use `_escape_tex()` for user input to prevent compilation errors 2. **Index out of sync**: Use `with_index_lock()` and rebuild index after every invoice write 3. **Pydantic validation bypass**: Never construct Invoice from `dict` without `.model_validate()` 4. **Write operations without gate**: Check `_require_writes_enabled()` first 5. **Missing party.name vs. business_name**: `name` is required (natural person); `business_name` is optional trade name ## File Organization ``` bridge/ backends/ invoices_models.py # Pydantic models (Party, LineItem, Invoice) invoices_storage.py # Filesystem operations, JSON serialization invoices.py # MCP tool implementations api/ routes.py # HTTP API routes tools.py # Tool registration orchestration envelopes.py # API response wrappers web/ # FastHTML web UI utils/ config.py # Environment config (ENABLE_WRITES) logging.py # Structured logging setup app.py # Application factory, SSE/API wiring cli.py # CLI entrypoint (if needed) templates/ invoice.tex # LaTeX template with placeholders bin/ dev # Development server startup script ``` ## Configuration ### Environment Variables - `MCP_ENABLE_WRITES`: Set to `1` to enable write operations (default: disabled) - `MAD_INVOICE_ROOT`: Override storage location (default: `.mad_invoice/` in repo root) - `PDFLATEX_PATH`: Override pdflatex binary location (auto-discovered if not set) ### Storage Location Priority 1. `MAD_INVOICE_ROOT` env var (absolute or relative to cwd) 2. Explicit base_path parameter (internal use) 3. Repository root (parent of bridge/) – default ## Localization Language support via `Invoice.language` field ("de" | "en"): - Date formatting: `_format_date()` respects `Invoice.date_style` ("iso" | "locale") with language-aware defaults - Currency formatting: `_format_currency()` uses comma (de) or dot (en) as decimal separator - LaTeX labels: `_LABELS` dict provides translations for invoice title, dates, totals - Expand by adding entries to `_LABELS` and updating `_invoice_replacements()` ## VAT Handling - **Small business mode** (`small_business=True`): No VAT calculation, shows `small_business_note` - **VAT mode** (`small_business=False`): Requires `vat_rate` (0-1), calculates VAT amount, shows gross total - VAT line conditionally rendered via `%%VAT_LINE%%` placeholder replacement - `Invoice.total()` returns subtotal (small business) or subtotal + VAT (regular) ## Party Naming Convention Critical for LLM callers: - `supplier.name`: Natural person or primary entity name (required, appears first) - `supplier.business_name`: Optional trade/brand name (renders as second line in header/footer) - Example: `name="Max Mustermann"`, `business_name="M.A.D. Solutions"` - Both supplier and customer support this pattern ## Planning Context `.plan/` directory (not part of runtime): - `ORIENTATION.md`: High-level design constraints and agent expectations - `TODO.md`: Task backlog with done/pending items - `tasks.manifest.json`, `state.json`: Task tracking metadata When implementing features, consult `.plan/TODO.md` for context on pending work.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BadlyDrawnBoy/mad-invoice-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server