Skip to main content
Der Fynn MCP-Server setzt das Model Context Protocol um und macht die wichtigsten Fynn-Funktionen für KI-Assistenten wie Claude direkt zugänglich. Statt eine Benutzeroberfläche zu öffnen, beschreibst du, was du brauchst, und der Assistent ruft die passenden Tools auf. Typische Einsatzfälle:
  • Kunden anlegen, suchen und Stammdaten pflegen
  • Abonnements und ihre Positionen einsehen, Preise anpassen
  • Angebote vollständig erstellen, mit Inhalten befüllen und an Empfänger zustellen
  • Rechnungen erstellen, bearbeiten und zur Freigabe einreichen
  • DATEV-Cloud-Export per Bestätigungsdialog starten

Verbindung einrichten

Endpunkt

Für die Sandbox-Umgebung ersetze den Host durch preview.coreapi.io.

Transport

Der Server verwendet Streamable HTTP (MCP-Spezifikation 2025-03-26). MCP-Clients, die dieses Transport-Protokoll unterstützen, verbinden sich direkt mit dem Endpunkt oben.

Authentifizierung

Jede Anfrage benötigt zwei Header: Den API-Schlüssel erstellst du unter Einstellungen > API-Schlüssel. Die Organisations-ID findest du in den Einstellungen deiner Organisation.

Konfigurationsbeispiel

Die meisten MCP-Clients (z. B. Claude Desktop) nehmen eine JSON-Konfiguration. Hier ein Beispiel:

Berechtigungen

Alle Tools respektieren die Rollen-Berechtigungen des verbundenen Nutzers. Wer in Fynn keine Rechnung lesen darf, bekommt über den MCP-Server auch keine. Die Anfrage schlägt in dem Fall mit einer klaren Fehlermeldung fehl, statt leere Daten zu liefern.

Konventionen

Einige Konventionen gelten für alle schreibenden Tools: Geldbeträge werden immer als Dezimalstring mit Währung übergeben, nie als Cent-Integer:
Datumsangaben sind ISO-8601 mit Zeitzonen-Offset, z. B. 2026-09-01T00:00:00+02:00. Reine Kalenderdaten (2026-09-01) sind bei Datumsfeldern erlaubt; das Tool interpretiert sie als Mitternacht in der Organisations-Zeitzone. Menschliche Bezeichner (Kundennummer, Abonummer, Belegnummer) werden neben UUIDs akzeptiert. Du musst IDs nicht vorab nachschlagen, wenn du die lesbare Nummer kennst. nextSteps in Schreibantworten: Jede erfolgreiche Schreibantwort enthält einen nextSteps-Array mit konkreten Hinweisen, welche Tools als nächstes sinnvoll wären.

Tool-Referenz

Die folgenden Tools stehen zur Verfügung, gruppiert nach Funktionsbereich.

Allgemein

fynn_ping

Prüft die Verbindung, Authentifizierung und den Organisations-Kontext. Gibt ok, die Organisations-ID und einen Zeitstempel zurück. Nimmt keine Parameter entgegen.

Kunden

Sucht nach Kunden anhand eines Suchbegriffs oder einer Abonummer. Gibt Identitätsfelder zurück (ID, Kundennummer, Anzeigename, Typ, Status), keine Adressen oder Kontaktdaten.

customer_create

Legt einen neuen Kunden an. Typ Firma: companyName ist Pflicht. Typ Privatperson: firstName und lastName sind Pflicht. Ohne customerNumber wird die Nummer automatisch vergeben (Normalfall). Vor der Anlage wird geprüft, ob ein Kunde mit gleicher E-Mail oder gleichem Firmennamen bereits existiert. Wenn ja, gibt das Tool den vorhandenen Kunden zurück (status: "duplicate_warning") und legt keinen zweiten an. Mit allowDuplicate: true lässt sich die Anlage trotzdem erzwingen. Fehlen Pflichtfelder, kommen die Fehlermeldungen feldbezogen zurück (status: "validation_error"). Gibt { status, customer, nextSteps } zurück. Berechtigung: customer:write.

customer_update

Aktualisiert die Stammdaten eines bestehenden Kunden. Nur mitgesendete Felder werden geändert; fehlende Felder bleiben unverändert. Kundennummer, DATEV-Debitorenkonto und Vertriebskanal-Bezug können nicht geändert werden. Adresse, Rechnungs-E-Mail und Kundenstatus (Archivierung) sind nicht Teil dieses Stammdaten-Updates. Gibt { status, customer, nextSteps } zurück. Berechtigung: customer:write.

customer_balance

Liefert den Debitorensaldo eines Kunden. Ohne Zeitraum den aktuellen Gesamtsaldo, mit Zeitraum zusätzlich Soll-/Haben-Summen. Beträge sind in Cent mit Währungsangabe.

customer_payments

Listet Zahlungstransaktionen eines Kunden, neueste zuerst. Enthält Datum, Betrag (Cent), Status, Zahlungsart und Belegnummer, jedoch keine Bankdaten oder IBAN.

Abonnements

Die Abo-Tools sind rein lesend und dienen dazu, den aktuellen Bestand einzusehen, bevor mit update_subscription_prices Preise angepasst werden. Alle Ergebnisse sind auf die Organisation beschränkt. Berechtigung: subscription:read. Sucht Abonnements der Organisation. Menschliche Bezeichner werden aufgelöst: statt der Kunden-UUID kann die Kundennummer angegeben werden. Gibt pro Treffer zurück: ID, Nummer, Kunde (ID, Kundennummer, Anzeigename), Status, Vertragsbeginn/-ende, Abrechnungsintervall, Anzahl Positionen und den wiederkehrenden Netto-Monatsbetrag. Beträge als { amount, currency }.

subscription_get

Liest ein Abonnement im Detail. Entweder subscriptionId (UUID) oder subscriptionNumber (Abonummer) angeben. Gibt zurück: Kopfdaten (ID, Nummer, Status, Kunde, Vertragsbeginn/-ende, Abrechnungsintervall, PO-Nummer, Probelaufende), Vertragsperioden (Anzahl, Laufzeit, Kündigungsfrist), Summen (Anzahl Positionen, wiederkehrender Netto-Monatsbetrag) und eine kompakte Positionsübersicht. Vollständige Positionsdetails liefert subscription_items.

subscription_items

Liest die Positionen eines Abonnements im Detail. Gleiche Eingabe wie subscription_get. Gibt je Position zurück: ID, Status, Produkt, Preisplan, Preismodell, Menge, Stückpreis (null bei tiered/volume/percentage), wiederkehrender Netto-Monatsbetrag, Rabatte (Name, Typ, Prozent, Fixbetrag, Frequenz, Befristung, Status) und Laufzeitfenster.

update_subscription_prices

Bereitet eine Preisänderung für alle passenden Abos eines Kunden vor und legt eine Freigabe-Aufgabe in der App-Inbox an. Die Preise werden dabei nicht direkt geändert. Ein typischer Auftrag lautet: „Setze alle Abos von Kunde XY auf 0,89 € monatlich.” Da sich das Abrechnungsintervall eines laufenden Abos nicht ändern lässt, erfolgt die Anpassung als Transfer-Leistung: Das Intervall bleibt bestehen, nur der Betrag wird angepasst. Ein jährlich abgerechnetes Abo wird auf das Zwölffache des Monatspreises gesetzt (0,89 € monatlich ergeben 10,68 € jährlich), ein monatliches auf den Monatspreis selbst. Der monatlich wiederkehrende Umsatz (MRR) wird dabei direkt fortgeschrieben. Geändert werden nur laufende, wiederkehrende Positionen mit festem Preis (Flat Fee) oder Preis pro Einheit (Per Unit) in der Zielwährung; der Preistyp bleibt dabei erhalten. Bei Per-Unit-Positionen wird der Preis pro Einheit gesetzt, die monatliche MRR ergibt sich aus Menge mal Einheitspreis. Alle übrigen Positionen werden mit Begründung als übersprungen ausgewiesen und bleiben unverändert. Der Ablauf ist zweistufig: Der KI-Assistent erstellt die Vorschau und die Freigabe-Aufgabe, ein Mensch prüft und genehmigt sie anschließend in der App-Inbox. Erst nach der manuellen Freigabe werden die Preise tatsächlich geändert. Ändern sich die Abos des Kunden zwischen Anfrage und Freigabe, wird die Freigabe abgelehnt und muss neu angefordert werden. Freigabe-Anfragen verfallen 72 Stunden nach der Erstellung. Gibt { actionItemId, status: "pending_approval", customerId, targetMonthlyNetPrice, currency, summary, hint } zurück. summary enthält die Kundendaten, die Anzahl der zu ändernden und der übersprungenen Positionen sowie je Position das Produkt, das Abrechnungsintervall, den bisherigen und den neuen Betrag, die bisherige und die neue monatliche MRR und das Datum, ab dem die Änderung gilt. Gibt es keine passende Position, kommt eine klare Fehlermeldung und es wird keine Aufgabe angelegt. Mit pending_action_status (Parameter actionItemId) fragst du den Freigabestatus ab. Berechtigung: subscription:write.

Rechnungen

Sucht Belege (Rechnungen, Gutschriften, Stornos). Filterbar nach Status, Belegtyp, Kunde und Finalisierungszeitraum. Beträge in Cent, Daten als ISO-8601.

invoice_reference_chain

Löst die Storno-/Referenzkette eines Belegs auf. Gibt den Beleg selbst, den referenzierten Ursprungsbeleg sowie alle Stornos/Gutschriften zurück, die auf diesen Beleg verweisen.

invoice_pdf_download

Erstellt einen zeitlich begrenzten Download-Link für die PDF-Datei eines Belegs. Gibt url, filename und expiresAt zurück, keine Binärdaten.

invoices_zip_download

Bündelt die PDFs mehrerer Belege in ein ZIP-Archiv und gibt einen signierten Download-Link zurück. Belege ohne PDF werden übersprungen.

Rechnungen erstellen und bearbeiten

Die schreibenden Rechnungs-Tools erfordern die Berechtigung invoice:write. Eine Rechnung durchläuft dabei einen klaren Lebenszyklus: Anlegen als Entwurf, Positionen hinzufügen und bearbeiten, und schließlich Finalisieren oder Schließen. Eine finalisierte Rechnung ist unveränderlich und rechtlich bindend; für die Finalisierung ist daher eine manuelle Freigabe durch einen Menschen erforderlich.

invoice_create

Legt eine neue Rechnung im Status Entwurf an. Positionen werden nicht hier, sondern anschließend mit invoice_add_position oder invoice_add_product_position hinzugefügt. Gibt die neue Entwurfs-Rechnung mit Kopfdaten, Positionen und Summen (in Cent) zurück.

invoice_get

Liest eine Rechnung mit Kopfdaten, Positionen und Summen. Die Rechnungs-ID kann eine UUID oder eine Belegnummer sein. Gibt zurück: Kopfdaten (id, number, status, type, customer, currencyCode, dueDate, finalizedAt, title, introduction, closing, internalNote), Summen (totals.net/tax/gross in Cent), Positionen und ein editable-Kennzeichen.

invoice_update

Aktualisiert die Kopf-Felder einer noch nicht finalisierten Rechnung. Nur mitgesendete Felder werden geändert; fehlende Felder bleiben unverändert. Gibt die aktualisierte Rechnung zurück (gleiche Struktur wie invoice_get).

invoice_add_position

Fügt einer Entwurfs-Rechnung eine frei definierte Position hinzu. Preis und Steuergruppe sind immer explizit anzugeben. Gibt { positionId, invoice } zurück, wobei invoice die aktualisierte Rechnung mit Summen in Cent enthält.

invoice_add_product_position

Leitet eine Rechnungsposition aus einem Produkt im Katalog ab. Bezeichnung, Einheit und Steuergruppe stammen aus dem Produkt; der Preis kommt entweder aus einem Flat-Fee-Preisplan oder aus einem expliziten Preis-Override.
Ein Preis wird nie geraten. Fehlt sowohl ein passender Preisplan als auch ein unitPrice-Override, lehnt das Tool die Anfrage mit einer klaren Fehlermeldung ab.
Gibt { positionId, invoice } zurück (Summen in Cent).

invoice_update_position

Aktualisiert eine Position einer bearbeitbaren Rechnung. Nur mitgesendete Felder werden geändert. Gibt { positions[], totals } zurück (Beträge in Cent).

invoice_remove_position

Entfernt eine Position von einer bearbeitbaren Rechnung. Die verbleibenden Positionen werden neu nummeriert und die Summen neu berechnet. Gibt { positions[], totals } zurück (Beträge in Cent).

invoice_close

Schließt eine Rechnung endgültig, ohne sie zu finalisieren. Der Beleg erhält den Status STATUS_CLOSED und wird verworfen: keine Belegnummer, kein PDF, keine Zahlung. Nur aus den Status Entwurf (STATUS_DRAFT) oder STATUS_NEW heraus möglich. Gibt { id, status } zurück.

invoice_finalize

Bereitet die Finalisierung einer Rechnung vor und legt eine Freigabe-Aufgabe in der App-Inbox an. Die Rechnung wird dabei nicht direkt finalisiert. Da eine finalisierte Rechnung rechtlich bindend und unveränderlich ist, läuft die Finalisierung zweistufig ab: Der KI-Assistent prüft, ob die Rechnung finalisierbar ist (gültige Zahlungsart, aktiver Kunde), und legt dann eine Freigabe-Aufgabe an. Ein Mensch prüft und genehmigt diese anschließend in der App-Inbox. Erst nach der manuellen Freigabe wird die Rechnung tatsächlich finalisiert. Ein zweiter Aufruf für dieselbe Rechnung liefert die bereits vorhandene offene Aufgabe zurück, sofern die Rechnung seit der ersten Anfrage nicht verändert wurde; die Parameter der ursprünglichen Anfrage bleiben dabei bestehen. Wurde die Rechnung geändert, wird die alte Aufgabe verworfen und eine neue mit neuer actionItemId angelegt. Freigabe-Anfragen verfallen 72 Stunden nach der Erstellung und müssen dann erneut gestellt werden. Gibt { actionItemId, status: "pending_approval", invoiceId, summary, hint } zurück, wobei summary Gesamtbeträge (Netto, Steuer, Brutto in Cent), Anzahl Positionen und Kundendaten enthält, und hint den Hinweis auf die notwendige manuelle Freigabe in der Inbox. Mit pending_action_status (Parameter actionItemId) kannst du den Freigabestatus und das Ablaufdatum abfragen. Berechtigung: invoice:finalize.

pending_action_status

Fragt den Status einer Freigabe-Aufgabe ab, zum Beispiel einer per invoice_finalize angelegten Rechnungsfreigabe. Gibt { id, type, status, createdAt, resolvedAt, expiresAt, invoiceId, executions } zurück. expiresAt ist der Zeitpunkt (UTC), zu dem die Freigabe-Anfrage abläuft; danach muss sie per invoice_finalize neu angefordert werden. Mögliche Werte für status: open, snoozed, in_progress, resolved, dismissed. Jeder Eintrag in executions enthält actionKey (z. B. approve oder reject), resultStatus (success oder failure), resultMessage und executedAt. Bei erfolgreicher Freigabe steht in resultMessage die vergebene Belegnummer. Schlägt die Freigabe fehl, weil die Rechnung nach der Anfrage geändert wurde oder die Anfrage abgelaufen ist, enthält resultMessage den Grund; die Rechnung bleibt in dem Fall unfinalisiert. Berechtigung: action-item:read.

invoice_write_off_small_difference

Bucht einen geringfügigen Restbetrag (Kleinstbetragsdifferenz) auf einer finalisierten, noch nicht bezahlten Rechnung aus, sodass die Rechnung als vollständig bezahlt gilt. Es wird keine E-Mail an den Kunden verschickt. Voraussetzungen: Die Rechnung muss finalisiert und unbezahlt oder gemahnt sein. Der offene Betrag muss kleiner als der konfigurierte Kleinstbetrag-Schwellenwert sein und größer als 0. Berechtigung: invoice:write.

Rechnung mit KI erstellen und finalisieren: ein typischer Ablauf

So erstellst du mit einem KI-Assistenten eine vollständige Rechnung und sendest sie zur Freigabe:
1

Rechnung anlegen

Lege eine neue Rechnung für den gewünschten Kunden an:
Die Antwort enthält die invoiceId, die du für alle weiteren Aufrufe benötigst.
2

Positionen hinzufügen

Füge Positionen aus dem Katalog oder frei definierte Positionen hinzu:
Beachte: unitPrice kommt immer aus dem Preisplan oder einem expliziten Override, nie aus einer Schätzung.
3

Rechnung prüfen

Lies die aktuelle Rechnung mit allen Positionen und Summen, bevor du die Finalisierung anforderst:
4

Freigabe anfordern

Erstelle die Freigabe-Aufgabe. Die Rechnung wird noch nicht finalisiert:
Die Antwort enthält actionItemId, status: "pending_approval", eine Zusammenfassung der Rechnung und einen hint-Text. Informiere die Nutzerin oder den Nutzer, dass die Freigabe in der App-Inbox wartet.
5

Mensch prüft und gibt frei

Ein Mitarbeiter öffnet die Inbox, prüft die Rechnung und gibt sie frei oder lehnt sie ab. Dieser Schritt findet ausschließlich in der App statt und kann nicht durch den KI-Assistenten übersprungen werden.
6

Status abfragen

Frage den Freigabestatus ab, bis die Aufgabe bearbeitet wurde:
Bei status: "resolved" und executions[0].resultStatus: "success" ist die Rechnung finalisiert; resultMessage enthält die Belegnummer. Bei resultStatus: "failure" wurde die Rechnung nicht finalisiert (z. B. weil sie nach der Anfrage geändert wurde); erstelle in dem Fall eine neue Freigabe-Anfrage.
Eine genehmigte Rechnung ist rechtlich bindend und unveränderlich. Die manuelle Freigabe kann nicht aus dem MCP-Server heraus umgangen werden.

Audit

Jeder Tool-Aufruf über den MCP-Server wird automatisch protokolliert: Nutzerkonto, Organisation, Tool-Name, Parameter (ohne sensible Inhalte), Ergebnis und Ausführungsdauer. Der Audit-Log ist unveränderlich und dient der Nachvollziehbarkeit für Compliance-Anforderungen. Auch fehlgeschlagene oder abgelehnte Aufrufe werden erfasst.

DATEV

datev_export_status

Zeigt, ob ein DATEV-Cloud-Konto verbunden ist, und liefert für jeden Monat eine Zusammenfassung der exportierten und offenen Buchungen sowie den letzten Export-Status.

datev_cloud_export_start

Startet einen DATEV-Cloud-Export für einen oder mehrere Monate. Dieses Tool verändert Daten. Ohne confirm: true kommt eine Rückfrage zur Bestätigung zurück, es wird nichts ausgeführt.

Produktkatalog

product_tree

Liefert den Produktkatalog als Kategorie-Baum. Jeder Knoten enthält id, name, path, count (Anzahl Produkte) und children. Mit includeProducts: true zusätzlich eine flache Produktliste.
Sucht Produkte im Katalog. Teiltreffer auf Produktname, internen Namen und Produktnummer.

product_prices

Liefert alle Preispläne eines Produkts in vollständiger Form. Die zurückgegebene Preisplan-ID ist genau die pricePlan-Referenz, die ein Angebots-Produkteintrag (subscriptionProposal) benötigt. Alle Beträge sind ganzzahlige Cent. Je Preisplan wird zurückgegeben: id, name, internalName, status, currency, chargeType, billingInterval, billingFrequency, payInAdvance, proRata, freeUnits, minimumCommitment und ein price-Block mit den Beträgen in Cent.

Angebote

Die Angebots-Tools sind in drei Gruppen aufgeteilt: Lesen, Inhalte aufbauen (Intent-Tools) und Zustellen. Lesende Tools benötigen offer:read, schreibende offer:write. Angenommene oder archivierte Angebote sind unveränderlich. Intent-Tools (offer_set_content, offer_add_section, offer_set_pricing, offer_set_parties, offer_set_signature, offer_set_letterhead) nehmen fachliche Eingaben entgegen und lassen den Server die interne Dokument-Repräsentation aufbauen. Du schreibst dabei niemals Tiptap/ProseMirror-JSON und keine Preis-Snapshots von Hand. Jede Schreibantwort dieser Tools enthält das gerechnete Preis-View-Model, sodass du sofort siehst, ob Preise und Rabatte korrekt greifen. Zustellungs-Tools (offer_publish, offer_add_recipient, offer_list_recipients, offer_remove_recipient, offer_preview_link, offer_download_pdf, offer_activities) verwalten den Versand an Empfänger. offer_add_recipient ist ausgehend: es verschickt eine echte E-Mail. Frage daher vor dem Aufruf ausdrücklich nach, ob an genau diese Person zugestellt werden soll.

offer_block_types

Listet alle unterstützten Block-Typen mit Metadaten auf (ob der Block atomar ist, ob er in PDF und Dokument erlaubt ist, und welche Attribute er akzeptiert). Mit optionalem type kommt zusätzlich ein konkretes Beispiel-Node zurück, das direkt an offer_add_block übergeben werden kann.

offer_get

Liest den Zustand eines Angebots effizient: Einstellungen (ID, Nummer, Name, Status, Anzeigesprache, Kunde, Gültigkeitsdatum, Annahme-Modus, Variablen) und eine kompakte Abschnitts- und Block-Übersicht. Mit sectionIndex (und optional blockIndex) wird zusätzlich der rohe JSON-Body genau dieses Abschnitts oder Blocks zurückgegeben.

offer_create

Legt ein neues Angebot an. Alle Parameter sind optional: ein Angebot ohne Kunde ist ausdrücklich erlaubt.

offer_update

Aktualisiert die Einstellungen eines Angebots. Nur mitgesendete Felder werden geändert; fehlende Felder bleiben unverändert.

offer_add_block

Fügt einen Block in einen Angebots-Abschnitt ein. Den gültigen Block-Node erhältst du über offer_block_types. Hat das Angebot noch keine Abschnitte, wird automatisch einer angelegt.

offer_update_block

Ersetzt einen bestehenden Block vollständig durch den übergebenen Tiptap-Node.

offer_remove_block

Entfernt einen Block aus einem Abschnitt; die nachfolgenden Blöcke rücken auf.

offer_upload_image

Lädt ein Bild für die Verwendung in Angeboten hoch. Erlaubt sind PNG, JPEG, WebP und GIF (max. 5 MB, dekodiert). Der MIME-Typ wird aus den Bilddaten erkannt; die Dateiendung wird entsprechend erzwungen. SVG wird abgelehnt. Die Antwort enthält mediaId, url, filename und einen fertigen imageBlock, der direkt an offer_add_block übergeben werden kann.

offer_add_section

Fügt dem Angebot einen neuen Textabschnitt aus Markdown hinzu. Der Server konvertiert Markdown in das Editor-Format; du schreibst nie Tiptap-JSON. Unterstützt werden Überschriften, Absätze, Fett/Kursiv, Listen, Tabellen (GFM) und Links. Gibt die neue sectionId zurück (für spätere offer_set_content-Aufrufe).

offer_set_content

Setzt den Prosa-Inhalt eines bestehenden Abschnitts aus Markdown (ersetzt den Abschnitts-Body). Gleiche Markdown-Unterstützung wie offer_add_section. Für Positionen und Preise stattdessen offer_set_pricing nutzen.

offer_set_pricing

Setzt die Positionen und Preise eines Angebots aus fachlicher Sicht (ersetzt den kompletten Preisblock). Preise als { "amount": "1500.00", "currency": "EUR" } angeben, nie als Cent-Integer. price weglassen bedeutet Listenpreis des Preisplans. Befristete Rabatte über discount.months: der Server baut daraus intern zwei Positionen mit Vertragsfenstern. Jede Schreibantwort enthält das gerechnete Preis-View-Model mit Positionen, Rabatt-Labels, Phasen, Summen und Vertragswert.

offer_set_parties

Setzt den Parteien-Block eines Angebots (Verkäufer, Käufer, Rechnungsempfänger, Referenz, Gültig-bis, PO-Nummer). Der Block wird automatisch angelegt, falls noch nicht vorhanden. Nur übergebene Felder werden geändert (Merge, kein Zurücksetzen). Datumsangaben als ISO-8601 mit Zeitzonen-Offset.

offer_set_signature

Setzt den Signatur-Block eines Angebots (Modus, Partei-Beschriftungen, Annahmetext). Block wird automatisch angelegt. Nur übergebene Felder werden geändert. Den Annahme-Modus des Angebots (click, esignature, print) setzt du über offer_update.

offer_set_letterhead

Setzt den Briefkopf-Block eines Angebots (Logo und Breite). Block wird automatisch angelegt. Nur übergebene Felder werden geändert. Logo-ID erhältst du aus offer_upload_image.

offer_preview

Gibt die gerechnete Angebots-Vorschau zurück (Proposal-View-Model). Rechnet denselben Calculator durch wie die Käuferansicht und der PDF-Druck: Positionen mit Stückpreis, Menge, Rabatt-Label und Betrag; Phasen mit Datumsbereich; Summen; Vertragswert. So lässt sich vor dem Versand prüfen, ob Preise und Rabatte korrekt greifen. Berechtigung: offer:read.
Gibt einen internen Vorschau-Link für ein Angebot zurück, ohne einen Empfänger anzulegen und ohne eine E-Mail zu verschicken. Rein lesend, keine Nebenwirkung. Nutze diesen Link zum Prüfen vor der Zustellung.

offer_publish

Veröffentlicht ein Angebot. Erst nach der Veröffentlichung erhält ein Empfänger einen gültigen Link und kann die Einladungs-E-Mail erhalten. Standardmäßig werden vorhandene Empfänger nicht benachrichtigt (notifyRecipients: false). Mit notifyRecipients: true wird eine E-Mail an bereits angelegte Empfänger verschickt (dann ausgehend). Greift ein Freigabe-Workflow, geht das Angebot nicht sofort live, sondern in den Status „Freigabe ausstehend” (outcome: "pending_approval" + requestId). Veröffentlichen ist kundenwirksam und braucht eine menschliche Freigabe: Der erste Aufruf gibt status: "confirmation_required" mit einer Vorschau zurück und veröffentlicht nichts. Das Bestätigungs-Token steckt nicht in der Antwort, sondern nur in einer Freigabe-Karte, die ein Mensch bestätigt. Unterstützt der Client keine Freigabe-Karten, wird eine Freigabe-Aufgabe im Posteingang angelegt (status: "pending_approval"). Greifen zusätzlich die Freigabe-Regeln des Angebots, geht es auch nach der Bestätigung nicht sofort live, sondern in den bestehenden Freigabeprozess (outcome: "pending_approval" mit requestId), den die berechtigte Person abschließt.

offer_list_recipients

Listet die Empfänger eines Angebots auf (an wen es bereits zugestellt wurde). Rein lesend, verschickt nichts. Gibt pro Empfänger zurück: id, email, firstName, lastName, role (read/sign/countersigner), signingStatus und den persönlichen Link. Berechtigung: offer:read.

offer_add_recipient

Dieses Tool ist ausgehend: Bei einem bereits veröffentlichten Angebot verschickt es eine echte E-Mail an eine echte Person. Frage vor dem Aufruf ausdrücklich nach, ob an genau diese Adresse zugestellt werden soll.
Legt einen Empfänger an und stellt ihm das Angebot zu. Ist das Angebot noch nicht veröffentlicht, wird der Empfänger angelegt, aber keine E-Mail verschickt (emailSent: false); rufe dann zuerst offer_publish auf. Bei einem veröffentlichten Angebot ist der Versand die einzige ausgehende, nicht umkehrbare Aktion und verlangt eine menschliche Freigabe. Der erste Aufruf gibt status: "confirmation_required" mit einer Vorschau zurück und versendet nichts. Das Bestätigungs-Token liegt bewusst nicht in der Modell-Antwort, sondern nur in einer Freigabe-Karte, die ein Mensch bestätigt. Unterstützt der Client keine Freigabe-Karten, wird stattdessen eine Freigabe-Aufgabe im Posteingang angelegt (status: "pending_approval" mit actionItemId); eine zweite Person gibt sie dort frei, woraufhin die Einladung tatsächlich versendet wird. Ein KI-Modell allein kann diesen Versand also nicht auslösen. Gibt zurück: Empfänger-Daten, emailSent (ob eine Mail rausging), hint und nextSteps. Berechtigung: offer:write.

offer_remove_recipient

Entfernt einen Empfänger von einem Angebot (verändernd, aber nicht ausgehend). Der Empfänger kann das Angebot danach nicht mehr über seinen Link aufrufen. Empfänger, die bereits unterschreiben oder unterschrieben haben, können nicht entfernt werden.

offer_download_pdf

Rendert den aktuellen Stand eines Angebots als PDF und gibt einen zeitlich begrenzten Download-Link zurück. Keine Binärdaten in der Antwort, nur der Link. Das Angebot wird nicht verändert. Gibt url, filename und expiresAt (ISO-8601) zurück. Berechtigung: offer:read.

offer_activities

Liest das Aktivitätsprotokoll eines Angebots (geöffnet, angesehen, angenommen usw.). Rein lesend. Gibt eine chronologische Liste zurück (älteste zuerst): id, type, occurredAt (ISO-8601), actor, metadata und links. Das Protokoll ist nie leer (enthält mindestens „Angebot erstellt”). Berechtigung: offer:read.
offer_add_block und offer_update_block bleiben als erweiterter Zugriff erhalten, wenn du genaue Kontrolle über den Tiptap-Node-Baum benötigst. Bevorzuge aber immer die Intent-Tools: sie schützen vor ungültigen Dokumentstrukturen und 0,00-Euro-Positionen.

Angebote mit KI erstellen: ein typischer Ablauf

So erstellst du mit einem KI-Assistenten ein vollständiges Angebot und stellst es zu:
1

Kunden und Katalog auflösen

Suche den Kunden mit customer_search, um die UUID zu bestätigen. Suche dann das gewünschte Produkt mit product_search und lade die Preispläne mit product_prices.IDs nie raten. Die zurückgegebenen productId und pricePlanId benötigst du im Schritt „Positionen setzen”.
2

Angebot anlegen

Lege ein leeres Angebot für den Kunden an:
Die Antwort enthält die offerId für alle weiteren Aufrufe.
3

Textabschnitte hinzufügen

Füge Abschnitte als Markdown hinzu. Der Server konvertiert Markdown in das Editor-Format:
Die Antwort enthält die sectionId für spätere Änderungen per offer_set_content.
4

Positionen und Preise setzen

Setze die Positionen über offer_set_pricing. Preise immer als Dezimalstring, nie als Cent-Integer. price weglassen bedeutet Listenpreis:
Die Antwort enthält direkt das gerechnete View-Model. Prüfe dort, ob alle Positionen erwartete Beträge zeigen und keine 0,00-Euro-Zeilen vorkommen.
5

Vorschau prüfen

Rufe offer_preview_link auf, um einen internen Vorschau-Link zu erhalten, ohne eine E-Mail zu verschicken. Alternativ liefert offer_preview das gerechnete View-Model direkt.Besprich das Ergebnis mit der Person und frage, ob das Angebot so versendet werden soll.
6

Angebot veröffentlichen

Veröffentliche das Angebot. Erst danach erhält ein Empfänger einen gültigen Link:
Greift ein Freigabe-Workflow, gibt die Antwort outcome: "pending_approval" zurück. Ein Mensch muss die Freigabe in der App-Inbox bestätigen, bevor das Angebot live geht.
7

Empfänger einladen (nach ausdrücklicher Bestätigung)

Frage zuerst ausdrücklich, an welche Person zugestellt werden soll. Erst nach Bestätigung den Empfänger anlegen:
Die Antwort enthält emailSent: true, wenn die Einladungs-E-Mail rausgegangen ist, sowie den persönlichen Link des Empfängers.
Alle Angebots-Tools respektieren den Lebenszyklus: ein angenommenes oder archiviertes Angebot kann nicht mehr verändert werden. Prüfe den Status vorab mit offer_get.

Ressourcen

Neben Tools stellt der MCP-Server Ressourcen bereit. Ressourcen sind lesbare Datenobjekte mit einer festen URI: Ein KI-Client kann sie in seinen Kontext laden, ohne dafür einen Tool-Aufruf zu verbrauchen. Alle Ressourcen sind organisations-beschränkt. Die dynamischen Ressourcen liefern dieselben Zahlen wie die passenden Tools, kosten aber keinen Tool-Aufruf. Geldbeträge folgen in fynn://catalog/price-plans/{productId} der einheitlichen Form {"amount": "149.00", "currency": "EUR"} (Dezimalstring in der Hauptwährungseinheit), damit ein aus der Ressource übernommener Preis ohne Umrechnung in offer_set_pricing passt.

Autovervollständigung

Für die Platzhalter parametrisierter Ressourcen bietet der Server Vorschläge über completion/complete an. Ein Client kann so einen Wert vervollständigen, statt ihn zu raten: Die Vorschläge sind organisations-beschränkt und respektieren die Leserechte: Ohne die passende Berechtigung liefert die Vervollständigung eine leere Liste.

Prompts

Der MCP-Server stellt zwei geführte Prompts bereit. Ein Prompt ist eine vorbereitete Anweisung, die ein KI-Client in seine Unterhaltung einbetten kann.

angebot_erstellen

Geführter Ablauf zum Erstellen eines Angebots: Kunde auflösen, Katalog prüfen, Positionen schemasicher aufbauen, Konditionen setzen, Vorschau prüfen und Rückfrage vor dem Versand. Optionale Argumente: kunde, produkte, laufzeit, rabatt.

angebot_pruefen

Prüft ein Angebot vor dem Versand anhand einer Checkliste: Summen plausibel, keine 0,00-Euro-Positionen, validUntil gesetzt, Empfänger vorhanden, Freigabe nötig? Pflichtargument: offerId.

Fortschritt und Rückfragen

Fortschrittsmeldungen

Länger laufende Tools (DATEV-Cloud-Export, PDF- und ZIP-Download) senden Fortschrittsmeldungen mit einem beschreibenden Text. Das geschieht nur, wenn der Client den Aufruf mit einem progressToken im _meta-Feld anfordert. Ohne progressToken bleibt die Antwort ein einfaches JSON-Objekt, es ändert sich nichts am Verhalten. Ein Client, der den Fortschritt sehen möchte, sendet im tools/call also zum Beispiel "_meta": { "progressToken": "abc" } und erhält die Antwort als Ereignis-Stream mit den Fortschrittsmeldungen vor dem Endergebnis.

Bestätigung und Freigabe

Aktionen mit echter Außenwirkung sind durch eine serverseitige Rückfrage abgesichert. Statt einer interaktiven Nachfrage über das Protokoll (die über die zustandslose HTTP-Anbindung nicht sicher zurücklaufen kann) gibt es drei Stufen, je nach Fallhöhe der Aktion:
  • Einfache Bestätigung (sichtbares Token): Der erste Aufruf gibt status: "confirmation_required" mit einem confirmToken und einer Vorschau zurück und führt nichts aus. Derselbe Aufruf mit unveränderten Argumenten und dem confirmToken löst die Aktion aus. Das schützt vor einer versehentlichen Ausführung bei geringer Fallhöhe.
  • Menschliche Freigabe (zum Beispiel offer_publish und offer_add_recipient bei einem veröffentlichten Angebot): Der erste Aufruf gibt ebenfalls status: "confirmation_required" zurück, aber das Token liegt bewusst nicht in der Antwort, sondern nur in einer Freigabe-Karte, die im Client als eigenständige, abgeschottete Ansicht erscheint. Nur ein Mensch kann dort bestätigen; ein KI-Modell kann das Token nicht selbst erzeugen. Unterstützt der Client keine Freigabe-Karten, wird automatisch eine Freigabe-Aufgabe im Posteingang angelegt (status: "pending_approval"). Greifen beim Veröffentlichen zusätzlich die Freigabe-Regeln des Angebots, übernimmt der bestehende Freigabeprozess und die berechtigte Person gibt frei.
  • Vier-Augen-Freigabe im Posteingang (zum Beispiel invoice_finalize, update_subscription_prices): Es entsteht immer eine Freigabe-Aufgabe (status: "pending_approval" mit actionItemId), die eine zweite Person im Posteingang der App freigibt oder ablehnt. Mit pending_action_status fragst du den Stand ab.
In allen Fällen wird ein Bestätigungs-Token abgelehnt und eine frische Vorschau ausgestellt, wenn sich der zugrunde liegende Zustand zwischen Rückfrage und Bestätigung geändert hat. So bleibt die Kontrolle beim Menschen, ohne dass der KI-Client eine Aktion mit Außenwirkung ungefragt auslöst. datev_cloud_export_start nutzt weiterhin eine einfache confirm: true-Bestätigung.

Listen und Änderungen

Die Listen-Methoden (tools/list, resources/list, resources/templates/list, prompts/list) sind paginiert: Der Server liefert bis zu 50 Einträge pro Seite und, falls mehr existieren, einen nextCursor. Ein Client sollte dem nextCursor folgen, bis keiner mehr zurückkommt. Der aktuelle Umfang liegt unter dieser Grenze, sodass in der Regel eine Seite genügt. Der Server meldet keine nachträglichen Listen-Änderungen (listChanged). Der Satz an Tools, Ressourcen und Prompts steht beim Verbindungsaufbau fest und bleibt für die Dauer der Sitzung unverändert, daher gibt es nichts nachzumelden.