prekontoDokumentationAPIMCPFAQBlog

MCP-Server

prekonto lässt sich als Werkzeugkasten in einen KI-Assistenten einhängen. Dann laden Sie eine Rechnung im Chat hoch und bekommen den Buchungsvorschlag samt Begründung zurück — ohne Oberfläche, ohne Skript, in Sätzen.

Was MCP ist — kurz und ohne Fachjargon

Ein Sprachmodell kann von sich aus nur reden. Es kennt Ihre Kreditorenliste nicht und kann keine Datei bei uns hochladen. Das Model Context Protocol (MCP) ist die vereinbarte Art, wie ein Programm einem KI-Assistenten Werkzeuge anbietet: eine Liste von Handlungen mit Namen, Beschreibung und Parametern. Der Assistent liest diese Liste und ruft die passende Handlung auf, wenn Sie ihn darum bitten.

Der Vergleich, der in der Buchhaltung trägt: MCP ist für einen KI-Assistenten das, was eine Schnittstelle für eine Software ist — nur dass die Beschreibung in normaler Sprache steht, damit das Modell sie versteht. Sie sagen „lade diese Rechnung hoch“, und der Assistent weiß, dass dafür upload_invoice zuständig ist und dass er vorher die Mandanten-ID braucht.

Wichtig für die Einordnung: Der MCP-Server macht nichts, was die REST-API nicht auch macht. Er benutzt dieselbe Fachschicht, dieselbe Authentifizierung und dieselben Prüfungen. Ein Werkzeug ist eine dünne Hülle um denselben Code — es kann nicht großzügiger sein als die REST-Route, weil es gar keine eigene Datenbankabfrage schreibt.

prekonto einbinden

Der Server spricht Streamable HTTP und ist zustandslos — es gibt nichts zu starten und nichts zu installieren. Zu konfigurieren sind im Kern zwei Dinge: die URL und ein Credential.

WasWert
Endpunkt-URLhttps://prekonto.de/api/v1/mcp
TransportStreamable HTTP (JSON-RPC 2.0 über POST)
AnmeldungAuthorization: Bearer <API-Key oder OAuth-Token>

Weg 1: OAuth — der bequeme

Clients, die OAuth beherrschen (Claude und ChatGPT gehören dazu), tragen nur die URL ein. Beim ersten Werkzeugaufruf antwortet prekonto mit 401 und nennt im WWW-Authenticate-Header seine Protected-Resource-Metadaten. Daran erkennt der Client, dass er den Anmelde-Ablauf starten kann: Er schickt Sie auf eine prekonto-Seite, dort sehen Sie, welche Rechte die Anwendung erbittet, und stimmen zu oder nicht. Ein Passwort geben Sie dabei nie im Client ein.

Weg 2: API-Key — für alles andere

Clients ohne OAuth-Unterstützung bekommen einen API-Key. Legen Sie ihn im angemeldeten Bereich unter API-Keys an und haken dort genau die Scopes an, die der Assistent haben soll. Der Key beginnt mit pk_live_ und ist nur einmal sichtbar.

// Konfiguration eines MCP-Clients, der HTTP-Header setzen kann
{
  "mcpServers": {
    "prekonto": {
      "type": "http",
      "url": "https://prekonto.de/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer pk_live_…"
      }
    }
  }
}

Die genauen Feldnamen unterscheiden sich je Client — maßgeblich ist die Anleitung Ihres Clients. Die drei Angaben oben sind alles, was von prekonto gebraucht wird.

Was ohne Anmeldung geht. initialize, ping und tools/list antworten auch ohne Credential — damit ein Client im Verbinden-Dialog anzeigen kann, worum er bittet. Erst tools/call verlangt einen Bearer-Wert mit dem Scope des jeweiligen Werkzeugs.

Wählen Sie die Scopes so knapp, wie es die Aufgabe erlaubt. Ein Assistent, der Belege einliefern und Vorschläge lesen soll, braucht invoices:write und buchungssaetze:read — aber nicht buchungssaetze:write. Ohne diesen Scope kann er nichts freigeben, und die Freigabe bleibt beim Menschen. Das ist der wirksamste Schalter auf dieser Seite.

Die elf Werkzeuge

In der Reihenfolge, in der ein Client sie sinnvoll benutzt. Die Reihenfolge ist nicht Kosmetik: Modelle wählen erfahrungsgemäß eher aus dem Anfang einer Liste, und list_mandanten ist tatsächlich der richtige erste Aufruf. Parameternamen sind snake_case (mandant_id), weil Modelle bei snake_case-Schemas zuverlässiger snake_case liefern.

Elf Werkzeuge. „Jeder Scope“ bei list_mandanten heißt: jedes gültige Credential genügt — ohne Mandanten-ID geht kein anderer Aufruf.
WerkzeugZweckNötiger ScopeArt
list_mandantenDie Firmen dieses Kontos mit ihren DATEV-Kopfdaten. Der erste Aufruf jeder Sitzung — alle anderen Werkzeuge brauchen eine mandant_id.jeder Scopenur lesen
upload_stammdatenÜbernimmt eine Kreditoren- oder Gegenkontenliste: DATEV-KRexport-CSV (deterministisch, ohne KI), beliebige CSV/Excel/PDF-Liste (per KI auf die Zielfelder normalisiert) oder ein strukturiertes Array.stammdaten:writeverändernd
list_kreditorenDie Lieferanten-Personenkonten des Mandanten, optional gefiltert. Um vor einer Korrektur das richtige Kreditorenkonto zu finden.buchungssaetze:readnur lesen
list_gegenkontenDie abschließende Liste der erlaubten Sachkonten. Ein Buchungssatz mit einem Konto, das hier nicht steht, wird beim Bestätigen abgewiesen.buchungssaetze:readnur lesen
upload_invoiceNimmt eine Eingangsrechnung an (PDF, Scan, XRechnung-XML, ZUGFeRD) und startet die Verarbeitung. Kehrt sofort zurück, bevor das Ergebnis vorliegt.invoices:writeverändernd
get_invoiceStand und Ergebnis einer Rechnung: Kopfdaten, deterministische Prüfungen und der entstandene Buchungssatz.buchungssaetze:readnur lesen
list_buchungssaetzeDie Kontierungsvorschläge, filterbar über den Status (auto, review, bestaetigt) und über das Belegdatum.buchungssaetze:readnur lesen
get_buchungssatzEin einzelner Satz mit allen Prüfungen und Feld-Confidences — die Begründung, warum er im Review liegt.buchungssaetze:readnur lesen
update_buchungssatzKorrigiert Kontierungsfelder und gibt den Satz optional gleich frei (bestaetigen=true). Umsatz und Belegdatum sind gesperrt.buchungssaetze:writeverändernd
bestaetige_buchungssatzGibt einen Satz unverändert frei und macht ihn exportierbar. Der Aufruf für den Fall, dass der Vorschlag stimmt — eine Korrektur läuft über update_buchungssatz.buchungssaetze:writeverändernd
export_extfErzeugt aus den exportierbaren Sätzen eines Zeitraums eine EXTF-Datei und liefert einen Download-Link — nicht den Dateiinhalt.export:readverändernd

Jedes Werkzeug antwortet zweifach: mit einem kurzen deutschen Text für das Modell und mit einem Strukturteil für den Client. Der Text ist nicht die JSON-Serialisierung des Strukturteils — ein Modell, das 40 Buchungssätze als JSON bekommt, liest sie nicht. Es braucht die Zusammenfassung und greift für Einzelheiten in den Strukturteil.

Dateien nehmen upload_invoice und upload_stammdaten auf zwei Wegen an: datei_base64 (bevorzugt) oder datei_url. Beides gibt es, weil MCP-Clients hier unterschiedlich zuverlässig sind — manche reichen eine angehängte Datei sauber durch, andere haben nur eine URL. Der URL-Weg lädt nur von öffentlich erreichbaren https-Adressen und folgt keinen Weiterleitungen.

Bei DATEV-KRexport-Dateien lohnt es, den dateiname mitzugeben (KRExport_KRED_…, KRExport_KTO_…): Er liefert Mandantennummer und Wirtschaftsjahresbeginn und ist der Rückfall, falls die Kopfzeile nicht wiedererkannt wird. Nötig ist er nicht — die Art wird zuerst aus der Kopfzeile gelesen, eine umbenannte KRexport-CSV läuft deshalb genauso ohne Sprachmodell durch.

So sieht das in der Praxis aus

Ein Beispieldialog. Was der Assistent im Hintergrund aufruft, steht in eckigen Klammern.

Sie: Lade diese Rechnung hoch und zeig mir den Buchungsvorschlag. (Rechnung als Anhang)

[list_mandanten] → ein Mandant: Muster GmbH, SKR03, Sachkontenlänge 4
[upload_invoice] → angenommen, Verarbeitung läuft

Assistent: Die Rechnung ist angenommen und wird verarbeitet. Ich frage in einigen Sekunden den Stand ab.

[get_invoice] → Status kontiert

Assistent: Böttcher AG, Rechnung 2026-4711 vom 12. Juli 2026, brutto 1.428,00 Euro. Vorschlag: Kreditorenkonto 70023 an Gegenkonto 4930 (Bürobedarf), 1.428,00 Euro im Haben, BU-Schlüssel 9, Belegfeld 1 = 2026-4711. Der Satz steht auf review: Der Kreditor wurde nur über die Namensähnlichkeit gefunden, das reicht nicht für eine automatische Buchung. Soll ich das Gegenkonto ändern oder den Vorschlag so freigeben?

Sie: Das Konto stimmt nicht, das ist Fachliteratur. Welche Konten gibt es?

[list_gegenkonten] → die Sachkonten des Mandanten

Assistent: In Ihrer Kontenliste steht 4940 „Zeitschriften, Bücher“. Soll ich darauf umbuchen?

Sie: Ja, und dann bestätigen.

[update_buchungssatz] gegenkonto=4940, bestaetigen=true

Assistent: Gespeichert und bestätigt: 70023 an 4940. Der Satz geht in den nächsten Export. Gelernt: Das Standard-Gegenkonto der Böttcher AG bleibt vorerst unverändert — dafür bräuchte es eine zweite gleiche Entscheidung.

Drei Dinge daran sind kein Zufall. Der Assistent fragt nach der Kontenliste, statt eine Kontonummer zu erfinden — die Server-Instruktionen sagen ihm ausdrücklich, dass nur Konten aus list_gegenkonten gültig sind, und der Server würde ein erfundenes Konto ohnehin abweisen. Er fragt vor dem Bestätigen. Und er erklärt, warum der Satz im Review liegt, weil die Begründung als Prüfergebnis am Buchungssatz hängt und nicht erfunden werden muss.

Grenzen: was kein Werkzeug kann

Die Werkzeugliste ist bewusst kürzer als die REST-API. Was ein Modell versehentlich tun kann, wird es irgendwann versehentlich tun — deshalb gibt es diese Handlungen im MCP-Kanal gar nicht:

Bestätigen ist ein eigener Schritt — mit Absicht. Ein Buchungssatz wird nicht dadurch freigegeben, dass ihn jemand ansieht. bestaetige_buchungssatz und update_buchungssatz mit bestaetigen=true sind die beiden Stellen, an denen das passiert, und beide brauchen buchungssaetze:write. Wer diesen Scope nicht vergibt, hat einen Assistenten, der lesen, erklären und einliefern kann — und nichts freigeben. Für den Anfang ist das die richtige Einstellung.

Und die Regel, die über allem steht: Ein Kontierungsvorschlag, den ein Sprachmodell gebildet hat, geht immer einmal durch den Review — unabhängig davon, welchen Weg der Beleg genommen hat. Ein Modell kann diese Schwelle nicht anheben, weil sie nicht in seiner Antwort steht, sondern in deterministischen Prüfungen danach. Die Einzelheiten stehen in der Dokumentation zu Confidence und Ampel.

Technische Einzelheiten

WasWert
Servername und Versionprekonto · 1.0.0
Protokollversionen2025-06-18, 2025-03-26, 2024-11-05
Methodeninitialize · tools/list · tools/call · ping
Antwortformapplication/json (kein SSE-Strom)
Ohne Credential möglichinitialize · ping · tools/list
Fehlercode „nicht autorisiert“-32001 (JSON-RPC, anwendungsspezifisch)

Kein SSE-Strom, und das ist eine Entscheidung: Es gibt keine server-initiierten Nachrichten — keine Fortschrittsmeldungen, keine Sampling-Rückfragen. Ein Strom, über den nur eine Nachricht geht, wäre bloß eine Verbindung, die offen bleibt. Fragt ein Client eine unbekannte Protokollversion an, antwortet der Server mit seiner neuesten; der Client entscheidet dann, ob er damit arbeiten kann.

Ein GET auf den Endpunkt antwortet 405 mit Allow. Absichtlich kein 404: Den würde ein Client als „falscher Pfad“ deuten und die Verbindung ganz aufgeben, statt zu erkennen, dass dieser Server zustandslos ist.

Der Pfad steht auch im OpenAPI-Dokument unter /api/v1/openapi.json — damit die Beschreibung der Schnittstelle vollständig ist, nicht damit ein generierter REST-Client ihn benutzt.

Weiterlesen