Papierkram MCP

Eigener MCP-Server unter mcp-papierkram mit voller Abdeckung der Papierkram.de API v1 (BETA). Gebaut 2026-04-20 fuer Vibe Factory — parallel zu m365 und ticketpay im Bestandsaufnahme-Agent.

Was kann der MCP

~70 dedizierte Tools + 4 Raw-Escape-Hatches, gruppiert in 14 Ressourcen:

RessourceOperationen
Info, UsersRead-only
Contact/Companies + PersonsFull CRUD + Archive
Income/InvoicesCRUD + Archive + Cancel + Deliver + PDF
Income/EstimatesCRUD + Archive + Cancel + Deliver + PDF
Income/Propositions (Produkte)CRUD + Archive
Income/PaymentTermsRead-only
Expense/VouchersCRUD + Archive + Cancel + Generalumkehr + Pay + PDF + Document-Upload/Delete
ProjectsCRUD + Archive
Tracker/TasksCRUD + Archive
Tracker/TimeEntriesCRUD + Archive (Filter nach Projekt/User/Zeitraum)
Banking/Connections + TransactionsRead-only
Settings/CustomAttributesCRUD + Archive
Rawraw_get, raw_post, raw_put, raw_delete

Setup

1. API-Token erstellen

Im Papierkram-Account einloggen → https://<subdomain>.papierkram.de/einstellungen/api → neuer Token. Kein OAuth, kein Scope-System.

Tarif-Check: API ist nur in Paket M und L freigeschaltet.

Subdomain immer beim Kunden erfragen, nicht aus Firmennamen ableiten. Lessons-Learned 2026-04-25 (Vibe Factory): Default-Annahme vibefactory aus Firmennamen fuehrte 30 min lang zu 401, obwohl Token korrekt — echte Subdomain war vibefactorygmbh (Rechtsform-Suffix). Papierkram lehnt mit identischer Fehlermeldung ab egal ob Token oder Subdomain falsch — vor Token-Debug immer erst Subdomain in Login-URL des Kunden verifizieren.

2. Env

PAPIERKRAM_SUBDOMAIN=<exakte-subdomain>     # Teil vor .papierkram.de in Login-URL
PAPIERKRAM_TOKEN=<token>                    # NACKTER Token, ohne "Bearer "-Prefix

Das Bearer-Prefix wird im MCP-Code automatisch ergaenzt. Wer es selbst mit reinpackt → 401.

3. Install

uv tool install --force --editable ~/source/mcps/mcp-papierkram
# Claude Code neu starten -> mcp__papierkram__*

Default-Transport: streamable-http auf 127.0.0.1:8767. Stdio: MCP_TRANSPORT=stdio.

Was geht NICHT in der API (UI-only)

  • DATEV-Export (Buchhalter-Export)
  • Umsatzsteuer-Voranmeldung, Zusammenfassende Meldung, BWA, GuV, Reports
  • Mahnwesen (Mahnungen erstellen, Stufen, Versand)
  • Fahrtenbuch, Kassenbuch, Anlagenverzeichnis / AfA
  • Mitarbeiter-Stammdaten schreiben (Users read-only)
  • Bankverbindungen anlegen / FinTS-Sync (Banking-Endpoints sind read-only)
  • Transaction-Matching (Bank-Umsatz → Rechnung/Beleg zuordnen)
  • Belege-Inbox per Email (Scan-to-Papierkram)
  • OCR auf Beleg-Uploads
  • Webhooks — nur Polling, wegen Credits teuer

Konsequenz: Bestandsaufnahme + laufende Automation laufen voll ueber API. Was UI-Klick braucht: Monats-/Quartals-DATEV-Export, Mahnlauf, USt-VA, FinTS-Sync. Wenn das wichtig wird → Browser-Automation oder anderes Tool fuer den Teil-Workflow (lexware / sevdesk haben DATEV-Export in API).

Quirks

  • Credits-Quota statt Rate-Limit. Paket M = 10.000/Monat, L = 20.000. Jeder Endpoint zieht unterschiedlich. Response liefert _quota: {remaining, consumed}. Ueberzogen → HTTP 429.
  • PUT, nicht PATCH. Teilfelder trotzdem erlaubt.
  • Request-Bodies sind flat — keine Wrapping-Envelopes wie {"invoice": {...}}. Felder direkt im Root.
  • Verschachtelte Referenzen sind Objekte, nicht IDs. customer: {company_id: 42} statt customer_id: 42. Gilt auch fuer project, payment_term, task.project, time_entry.task/user.
  • Archive/Cancel/Deliver/Pay sind eigene Actions, nicht Status-Felder.
  • Generalumkehr unterscheiden: cancel_voucher = einfache Stornierung. cancel_voucher_with_reverse_entry = DATEV-konforme Stornierung mit Gegenbuchung.
  • PDFs als base64 im Feld pdf_base64. Content-Type immer application/pdf.
  • BETA-Status — Breaking Changes laut Papierkram moeglich.

Typische Flows

Bestandsaufnahme (read-only):

get_info → list_companies → list_invoices(document_date_range_start, document_date_range_end)
  → list_vouchers(document_date_range_start, document_date_range_end)
  → list_projects → list_time_entries(start_time_range_start, start_time_range_end)
  → list_bank_transactions(bank_connection_id)

Filter-Notiz: Es gibt kein q (Freitextsuche), kein status und kein archived als Query-Param — Status clientseitig filtern. Zeiteintraege ueber billing_state (billed|unbilled|billable|unbillable|archived). Banking-Transactions haben NUR bank_connection_id als Filter, keinen Datumsbereich.

Beleg-Workflow:

create_voucher(data) → upload_voucher_document(voucher_id, filename, content_base64) → pay_voucher(voucher_id, {payment_date, amount})

Rechnung erstellen + versenden + PDF:

create_invoice(data) → deliver_invoice(invoice_id) → get_invoice_pdf(invoice_id)