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:
| Ressource | Operationen |
|---|---|
| Info, Users | Read-only |
| Contact/Companies + Persons | Full CRUD + Archive |
| Income/Invoices | CRUD + Archive + Cancel + Deliver + PDF |
| Income/Estimates | CRUD + Archive + Cancel + Deliver + PDF |
| Income/Propositions (Produkte) | CRUD + Archive |
| Income/PaymentTerms | Read-only |
| Expense/Vouchers | CRUD + Archive + Cancel + Generalumkehr + Pay + PDF + Document-Upload/Delete |
| Projects | CRUD + Archive |
| Tracker/Tasks | CRUD + Archive |
| Tracker/TimeEntries | CRUD + Archive (Filter nach Projekt/User/Zeitraum) |
| Banking/Connections + Transactions | Read-only |
| Settings/CustomAttributes | CRUD + Archive |
| Raw | raw_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
vibefactoryaus Firmennamen fuehrte 30 min lang zu 401, obwohl Token korrekt — echte Subdomain warvibefactorygmbh(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}stattcustomer_id: 42. Gilt auch fuerproject,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 immerapplication/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)