Jahresabschluss.io
ENAnmeldenRegistrieren →

Claude Agent SDK

Claude Agent SDK: eigener Buchhaltungsagent in Python

Mit dem Python-Paket claude-agent-sdk bauen Sie einen eigenen Buchhaltungsagenten, indem Sie den gehosteten MCP-Server von jahresabschluss.io als Server vom Typ „http“ mit Ihrem Schlüssel in der Kopfzeile eintragen, zuerst nur lesende Werkzeuge freigeben und jeden schreibenden Aufruf über die Rückfrage can_use_tool bestätigen. Das SDK liefert die Agentenschleife, die Buchhaltung liefert die Werkzeuge und prüft jede Buchung im selben Buchungskern wie die App. Diese Seite zeigt beides als lauffähiges Programm: einen reinen Leselauf und einen Lauf, in dem ein Beleg erst nach Ihrem Ja gebucht wird.

Stand: 2026-10-19

Was Sie vorab brauchen

  • Python ab 3.10 und das Paket aus PyPI: pip install claude-agent-sdk. Laut README bringt das Paket die nötige Kommandozeile von Claude Code bereits mit.
  • Den API-Schlüssel Ihres Claude-Kontos als Umgebungsvariable, wie in der SDK-Dokumentation beschrieben. Das SDK liest keine .env-Datei von selbst.
  • Einen Buchhaltungsschlüssel jab_live_… in der Umgebungsvariable JAB_API_KEY. Anlegen dürfen ihn Administratoren und Ersteller des Arbeitsbereichs unter Buchhaltung → Einstellungen → „KI-Agent & API“, bestätigt mit dem zweiten Faktor.
  • Für den Anfang einen Schlüssel nur mit Leserecht. Der MCP-Server listet in tools/list ohnehin nur die Werkzeuge, die der Schlüssel darf — ein Leseschlüssel zeigt dem Agenten gar kein schreibendes Werkzeug.
  • Ein Buchhaltungs-Abo (Solo oder Gesamtpaket) oder ein EÜR-Abo; Schlüssel und Werkzeuge kosten dort nichts extra.

Die wichtigste Regel: allowed_tools gibt frei, beschränkt aber nicht

In der Python-Referenz des SDK steht ausdrücklich: allowed_tools genehmigt die genannten Werkzeuge automatisch, schränkt Claude aber nicht auf sie ein. Nicht genannte Werkzeuge fallen an permission_mode und an Ihre Rückfrage can_use_tool weiter. Wer nur allowed_tools setzt und den Modus offen lässt, hat also keine Leseschranke gebaut.

Eine echte Schranke entsteht mit permission_mode="dontAsk": Dann wird alles abgelehnt, was nicht vorab freigegeben ist, statt nachzufragen. Werkzeuge vom MCP-Server heißen im SDK mcp__<Servername>__<Werkzeug>, bei uns also etwa mcp__buchhaltung__get_saldenliste. Platzhalter wie mcp__buchhaltung__list_* sind erlaubt, aber nur hinter dem festen Präfix; ein nacktes "*" ignoriert das SDK mit einer Warnung.

Für schreibende Werkzeuge gilt umgekehrt: Nehmen Sie sie nie in allowed_tools auf. Laut Dokumentation erreichen automatisch genehmigte Aufrufe can_use_tool gar nicht mehr — Ihre Bestätigung würde übersprungen.

Schritt 1: ein reiner Leselauf

Ein Auftrag, der nur liest. Mit strict_mcp_config=True und setting_sources=[] lädt das SDK keine fremden MCP-Server oder lokalen Einstellungen dazu:

import asyncio, os
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

SERVER = {
    "type": "http",
    "url": "https://api.jahresabschluss.io/functions/v1/bookkeeping-mcp",
    "headers": {"Authorization": f"Bearer {os.environ['JAB_API_KEY']}"},  # jab_live_…
}
LESEN = [f"mcp__buchhaltung__{n}" for n in (
    "get_entity", "list_fiscal_years", "list_receipts",
    "get_receipt", "get_receipt_proposal", "list_accounts",
)]

async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"buchhaltung": SERVER},
        allowed_tools=LESEN,
        permission_mode="dontAsk",   # alles andere wird abgelehnt
        strict_mcp_config=True,
        setting_sources=[],
    )
    async for message in query(
        prompt="Welche Belege warten auf Prüfung? Nenne je Beleg Lieferant, "
               "Betrag und den Buchungsvorschlag mit Sicherheitsstufe.",
        options=options,
    ):
        if isinstance(message, ResultMessage):
            print(message.result)

asyncio.run(main())

Schritt 2: Buchen nur nach Ihrer Bestätigung

Jetzt darf der Agent einen Beleg buchen — aber jeder Aufruf von confirm_receipt hält an und zeigt Ihnen die Argumente. Die SDK-Dokumentation zeigt can_use_tool zusammen mit ClaudeSDKClient; so sieht das für die Buchhaltung aus:

import asyncio, json, os
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, ResultMessage
from claude_agent_sdk.types import (
    PermissionResultAllow, PermissionResultDeny, ToolPermissionContext,
)

SERVER = {
    "type": "http",
    "url": "https://api.jahresabschluss.io/functions/v1/bookkeeping-mcp",
    "headers": {"Authorization": f"Bearer {os.environ['JAB_API_KEY']}"},
}
LESEN = [f"mcp__buchhaltung__{n}" for n in (
    "get_entity", "list_fiscal_years", "list_receipts",
    "get_receipt", "get_receipt_proposal", "list_accounts",
)]
MIT_FREIGABE = {"mcp__buchhaltung__confirm_receipt"}

async def freigabe(tool_name: str, input_data: dict, context: ToolPermissionContext):
    if tool_name in MIT_FREIGABE:
        print(f"\n{tool_name}\n{json.dumps(input_data, indent=2, ensure_ascii=False)}")
        antwort = await asyncio.to_thread(input, "Buchen? (j/n) ")
        if antwort.strip().lower() == "j":
            return PermissionResultAllow(updated_input=input_data)
        return PermissionResultDeny(message="Vom Menschen abgelehnt — nicht erneut versuchen.")
    return PermissionResultDeny(message="Dieses Werkzeug ist in diesem Lauf nicht freigegeben.")

async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"buchhaltung": SERVER},
        allowed_tools=LESEN,          # Schreibwerkzeuge bewusst NICHT hier
        can_use_tool=freigabe,
        permission_mode="default",
        strict_mcp_config=True,
        setting_sources=[],
    )
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Buche den ältesten Beleg, dessen Vorschlag nichts mehr "
                           "zu prüfen hat. Zeige mir vorher Konten, Betrag und Datum.")
        async for message in client.receive_response():
            if isinstance(message, ResultMessage):
                print(message.result)

asyncio.run(main())

Was der Agent dabei tut

  • get_entity: Stammdaten, Kontenrahmen und ob die Gesellschaft bilanziert oder eine EÜR führt. Steht dort sandbox=true, arbeitet der Schlüssel in der Sandbox der Gesellschaft.
  • list_receipts: Belege mit Buchungsstand (processing, received, needs_review, booked, dismissed), neueste zuerst.
  • get_receipt_proposal: der Buchungsvorschlag wie in der App — Konten, Steuerschlüssel, Betrag, Sicherheitsstufe, ob noch etwas zu prüfen ist, dazu state_version und correction_template.
  • confirm_receipt: erst nach Ihrem „j“ in der Rückfrage. Der Aufruf braucht id, eine neue UUID als idempotency_key und die state_version aus dem Vorschlag; bei einer Wiederholung nach Netzwerkfehler bleibt die UUID gleich, damit nichts doppelt entsteht.
  • Der Server prüft dann Geschäftsjahr, Festschreibung, Dubletten, Umsatzsteuer und Kreditor. Einen Dublettenhinweis kann der Agent per API nicht übergehen; ist es wirklich eine Dublette, legt dismiss_receipt den Vorschlag mit Begründung ab.

Hooks für Regeln, die immer gelten

can_use_tool ist die Rückfrage an Sie; ein PreToolUse-Hook ist eine feste Regel. Laut SDK-Dokumentation laufen Hooks vor allen anderen Prüfungen, und ein Nein aus einem Hook gilt sogar im Modus bypassPermissions. Beispiel: Eingangsrechnungen dürfen in diesem Programm nur als Probelauf laufen:

from claude_agent_sdk.types import HookMatcher

async def nur_probelauf(input_data, tool_use_id, context):
    if (input_data["tool_name"] == "mcp__buchhaltung__book_incoming_invoice"
            and input_data["tool_input"].get("dry_run") is not True):
        return {"hookSpecificOutput": {
            "hookEventName": input_data["hook_event_name"],
            "permissionDecision": "deny",
            "permissionDecisionReason": "Eingangsrechnungen hier nur mit dry_run=true.",
        }}
    return {}

# in ClaudeAgentOptions ergänzen:
# hooks={"PreToolUse": [HookMatcher(matcher="^mcp__buchhaltung__", hooks=[nur_probelauf])]}

Die Reihenfolge der Prüfung ist dokumentiert: Hooks, dann Verbotsregeln, Rückfrageregeln, der Modus, Freigaberegeln und zuletzt can_use_tool. Der Probelauf von book_incoming_invoice bucht nichts, nennt aber die Buchungszeilen und ihre Wirkung auf Konten und Umsatzsteuer-Kennzahlen — eine gute Vorlage für Ihre Entscheidung.

Prüfen Sie außerdem die Systemnachricht zu Beginn des Laufs: Sie meldet je MCP-Server den Status, etwa connected oder failed. Ist der Server nicht verbunden, sollte Ihr Programm abbrechen, statt den Agenten ohne Buchhaltung weiterlaufen zu lassen.

Grenzen: was in der App bleibt

  • Steuern übermitteln: Der Agent kann amtlich prüfen (check_tax_filing) und vorbereiten (prepare_tax_filing) und Ihnen mit request_tax_filing_approval einen Freigabe-Link geben. Übermittelt wird in der App mit Ihrer PIN; ein Recht für die Direktübermittlung über die API wird derzeit nicht vergeben.
  • Jahresabschluss: Lesen geht (get_annual_report_status, get_annual_report), Erstellen, Prüfen und Ausstellen geschieht in der App.
  • Bankanbindung, Einstellungen der Gesellschaft und Bankverbindungen der Geschäftspartner gibt es über die API nicht.
  • Belegdateien gehen nicht durch das Modell: get_receipt_file nennt nur einen download_path, die Datei holt Ihr Code per REST mit dem Schlüssel.
  • Je Schlüssel gelten 120 Anfragen pro Minute; darüber antwortet die API mit 429 und Retry-After.

Rechtlicher Rahmen beim Buchen per Agent

Nach § 239 Abs. 3 HGB und § 146 Abs. 4 AO darf eine Buchung nicht so verändert werden, dass der ursprüngliche Inhalt nicht mehr feststellbar ist. Deshalb kennt die API kein Ändern oder Löschen gebuchter Sätze, sondern nur das Storno (storno_journal_entry) und die Neubuchung — auch wenn der Agent einen Fehler korrigieren soll.

Die Buchführungspflicht nach § 238 Abs. 1 HGB bleibt beim Kaufmann. Was der Agent vorschlägt, ist ein Vorschlag; die Rückfrage vor confirm_receipt ist der Punkt, an dem Sie oder Ihre Kanzlei die Verantwortung bewusst übernehmen. Die Software ersetzt keine Steuerberatung.

Häufige Fragen

Welches Paket brauche ich für das Claude Agent SDK in Python?

Das Paket claude-agent-sdk aus PyPI, installiert mit pip install claude-agent-sdk, und Python ab 3.10. Den MCP-Server tragen Sie in ClaudeAgentOptions unter mcp_servers mit type "http", url und headers ein.

Beschränkt allowed_tools den Agenten auf lesende Werkzeuge?

Nein. allowed_tools genehmigt nur automatisch. Eine Leseschranke entsteht erst mit permission_mode="dontAsk", der alles nicht Freigegebene ablehnt, oder mit einem Schlüssel, der nur das Leserecht hat.

Wie heißen die Werkzeuge im SDK?

mcp__<Servername>__<Werkzeug>. Registrieren Sie den Server als buchhaltung, heißt das Lesen der Saldenliste mcp__buchhaltung__get_saldenliste und das Buchen eines Belegs mcp__buchhaltung__confirm_receipt.

Sieht das Modell meinen Buchhaltungsschlüssel?

Nein. Der Schlüssel steht in der Kopfzeile der Serverkonfiguration und wird aus JAB_API_KEY gelesen. Das Modell sieht Werkzeugnamen, Argumente und Antworten, aber keine Kopfzeilen.

Kann der Agent etwas doppelt buchen, wenn die Verbindung abbricht?

Nicht, wenn er bei der Wiederholung dieselbe UUID als idempotency_key sendet; dann liefert der Server dasselbe Ergebnis. Eine neue UUID erzeugt einen zweiten Vorgang.

Kann ich den Agenten zuerst ohne echte Bücher testen?

Ja, derzeit für Gesellschaften ohne Jahresabschluss-Daten wie Saldenlisten, Abschlussbuchungen oder Rückstellungen: Richten Sie in den Einstellungen die Sandbox ein und legen Sie darin einen eigenen Schlüssel an. get_entity meldet dann sandbox=true, und nichts verlässt den Zwilling.

Vom selben Anbieter: Jahresabschluss nach HGB – der komplette Leitfaden auf jahresabschluss.io.