Ein Werkzeug zur Dokumentenerkennung im Editor zu installieren dauert eine Minute. Zu wissen, was man danach damit anfängt, dauert länger. Dieser Beitrag überspringt die Einrichtungstour und geht direkt zu drei Aufgaben, mit denen Entwickler tatsächlich zu Claude Code und Cursor kommen, wenn Reisepässe im Spiel sind:
- Einen Ordner mit Pass-Scans in eine CSV-Datei verwandeln, und zwar so, dass ein Absturz auf halber Strecke nichts kaputt macht.
- Einen Pass mit dem Onboarding-Formular abgleichen, das Sie gerade bauen.
- Scan-Ergebnisse einmal aufzeichnen und als Test-Fixtures verwenden, damit Ihre Testsuite nie einen echten Dienst aufruft.
Alle drei nutzen den MCP-Server von doc.cheap, das Paket @doc-cheap/mcp (MIT, Node 20 oder neuer). Dies ist der Blog von doc.cheap, der Passport- und ID-OCR-API hinter diesem Server, also gewichten Sie die Produktentscheidungen entsprechend. Die Muster selbst (ein abgegrenztes Bildverzeichnis, ein Schlüssel pro Datei, ein einmal aufgezeichnetes Fixture) gelten für jedes Werkzeug dieser Art.
Was der Server dem Editor gibt
Sechs Werkzeuge, jedes mit den Hinweisen, die ein Client liest, bevor er entscheidet, ob er Sie zuerst fragt:
| Werkzeug | Was es tut | Kostet es ein Guthaben? |
|---|---|---|
scan_document |
Liest einen Reisepass, Personalausweis oder Führerschein als strukturiertes JSON aus | Nur wenn ein Dokument erkannt wird |
check_balance |
Verbleibendes Guthaben und die Zähler dieses Monats | Nein |
search_docs |
Durchsucht die mit dem Server gebündelte API-Dokumentation, offline | Nein |
list_scans |
Listet die gespeicherten Scans des Kontos auf, die neuesten zuerst | Nein |
get_scan |
Liest einen gespeicherten Scan anhand seiner ID zurück | Nein |
delete_scan |
Löscht einen gespeicherten Scan endgültig | Nein |
scan_document nimmt das Bild als image_base64, image_url oder, beim lokalen Server, als image_path entgegen, dazu die optionalen Parameter expect_country, return_portrait, retain_hours, reference und idempotency_key. Diese fünf Optionen tragen den Großteil der folgenden Abläufe.
Ohne gesetzten Schlüssel verwendet der Server den öffentlichen Sandbox-Schlüssel: insgesamt 10 kostenlos erkannte Dokumente pro Adresse und höchstens 10 Anfragen pro Adresse und Stunde. Das reicht für die Abläufe 2 und 3. Ablauf 1 braucht einen eigenen Schlüssel; mit einer Registrierung erhalten Sie jeden Monat 100 kostenlose Erkennungen.
Wenn Sie noch nie einen MCP-Server mit einem Agenten verbunden haben, erklärt Einen Agenten Ausweisdokumente über MCP lesen lassen die Grundlagen und die Datenschutzfragen ausführlicher.
Ablauf 1: ein Ordner mit Scans als CSV, in Claude Code
Die Aufgabe. Jemand gibt Ihnen einen Ordner mit Pass-Scans (der Gästerückstand eines Hotels, die Teilnehmerliste einer Veranstaltung, die Übernahme aus einem Papierarchiv) und möchte eine Tabelle: eine Zeile pro Datei, die wichtigen Felder und einen klaren Vermerk für jede Datei, die nicht gelesen werden konnte.
Die Einrichtung. Legen Sie eine .mcp.json ins Wurzelverzeichnis des Projekts. Claude Code liest sie als die eigene Serverliste des Projekts, sodass die Konfiguration mit dem Repository wandert:
{
"mcpServers": {
"doc-cheap": {
"command": "npx",
"args": ["-y", "@doc-cheap/mcp"],
"env": {
"DOC_CHEAP_API_KEY": "${DOC_CHEAP_API_KEY:-}",
"DOC_CHEAP_IMAGE_ROOT": "/Users/you/guest-backlog/scans"
}
}
}
}
Beim ersten Start in diesem Ordner bittet Claude Code Sie, den Server aus .mcp.json freizugeben.
Zwei Zeilen erledigen die Arbeit:
DOC_CHEAP_API_KEYwird aus Ihrer Shell übernommen, sodass kein Schlüssel im Commit landet. Ist die Variable nicht gesetzt, bleibt der Wert leer, und der Server greift auf den Sandbox-Schlüssel zurück.DOC_CHEAP_IMAGE_ROOTist das einzige Verzeichnis, aus dem der Server Dateien lesen darf. Ohne diese Angabe wirdimage_pathgrundsätzlich abgelehnt. Mit ihr wird ein Pfad zuerst über symbolische Links aufgelöst, und alles, was aus dem Verzeichnis ausbricht (ein.., ein Link nach draußen), wird abgelehnt. Ein relativerimage_pathwird von diesem Verzeichnis aus gelesen, sodass der Agent bloße Dateinamen übergeben kann.
Diese Abgrenzung zählt hier mehr als irgendwo sonst. Die Dateinamen kommen vom Agenten, und der Agent liest einen Ordner, den Sie nicht angelegt haben. Außerdem laufen die Bilder nie durch die Unterhaltung: Der Server liest die Datei und schickt sie direkt an die API.
Der Prompt.
Rufe zuerst check_balance auf. Rufe dann für jedes JPEG und PNG im Scan-Verzeichnis scan_document auf, mit image_path gleich dem Dateinamen, reference gleich dem Dateinamen, idempotency_key gleich "backlog-" plus dem SHA-256 der Datei, return_portrait false und retain_hours 24. Schreibe backlog.csv mit den Spalten file, status, billed, kind, country, number, surname, given_names, birth_date, expiry_date, is_expired, mrz_status. Lass bei jedem status außer recognized die Datenspalten leer. Liste am Ende die Dateien auf, die nicht erkannt wurden, und den Grund.
Warum jede Option dabei ist:
referencekommt im Feldmeta.referencedes Ergebnisses zurück und erscheint in jeder Zeile vonlist_scans, sodass sich jeder gespeicherte Scan seiner Datei zuordnen lässt, ohne ihn zu öffnen.idempotency_keymacht den Stapel sicher fortsetzbar. Mit einem Live-Schlüssel liefert eine Wiederholung mit demselben Schlüssel das erste Ergebnis, statt erneut zu erkennen und erneut abzurechnen. Bricht die Sitzung bei Datei 140 von 300 ab, starten Sie denselben Prompt noch einmal: Die ersten 139 kommen kostenlos als Wiederholungen zurück, und die Arbeit geht von dort aus weiter. Ein Schlüssel aus dem Hash der Datei statt einer zufälligen UUID ist das, was eine neue Sitzung ihn reproduzieren lässt. Claude Code kann ihn mitshasum -a 256berechnen.retain_hours: 24ist der Kompromiss, den der Leitfaden zur Idempotenz erläutert. Eine Wiederholung gibt das gespeicherte Ergebnis zurück, also lässt sich ein Scan mitretain_hours: 0nicht wiederholen: Ein erneuter Versuch unter seinem Schlüssel innerhalb von 24 Stunden wird mitidempotency_replay_unavailableabgelehnt. Ein Fenster von einem Tag hält Wiederholungen kostenlos und lässt am Tag danach trotzdem nichts gespeichert.return_portrait: falselässt den Ausschnitt des Inhaberfotos weg, mit dem eine CSV nichts anfangen kann.
Was zurückkommt. Der status jeder Zeile ist recognized, no_document_found, unreadable, unsupported_document oder rejected, und billed sagt, ob diese Datei ein Guthaben verbraucht hat. Nur recognized wird berechnet, also kostet ein Ordner mit zwanzig verwackelten Fotos zwanzig Guthaben weniger, als er Dateien enthält. Die Spalte billed ist zugleich Ihre eigene Aufstellung dessen, was der Stapel verbraucht hat, ohne Rechnung, die abgeglichen werden müsste.
Noch eine Gewohnheit. Die CSV enthält jetzt personenbezogene Daten. Tragen Sie sie in .gitignore ein, bevor der Agent sie schreibt, nicht danach.
Ablauf 2: einen Pass prüfen, während Sie ein Formular bauen, in Cursor
Die Aufgabe. Sie bauen ein Onboarding- oder Check-in-Formular und möchten sehen, was ein echter Scan zurückgibt, bevor Sie Feldnamen, Formate und Validierung festlegen.
Die Einrichtung. Für einen schnellen Blick kann Cursor den gehosteten Server verwenden, der keine Installation braucht. In der .cursor/mcp.json des Projekts:
{
"mcpServers": {
"doc-cheap": {
"url": "https://mcp.doc.cheap/mcp"
}
}
}
Ohne Schlüssel-Header nutzt der gehostete Server den Sandbox-Schlüssel. Er sieht keine Dateien auf Ihrem Rechner, deshalb nimmt scan_document dort das Bild als image_base64 oder als öffentliche https:-image_url entgegen. Liegen Ihre Muster als lokale Dateien vor, starten Sie den Server stattdessen lokal. Cursor schreibt Variablen in eigener Syntax, ${workspaceFolder} für den Projektordner und ${env:NAME} für eine Shell-Variable, deshalb wird der Eintrag aus Ablauf 1 nicht unverändert übernommen:
{
"mcpServers": {
"doc-cheap": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@doc-cheap/mcp"],
"env": {
"DOC_CHEAP_IMAGE_ROOT": "${workspaceFolder}/specimens"
}
}
}
}
Ohne Zeile DOC_CHEAP_API_KEY nutzt auch dieser Eintrag den Sandbox-Schlüssel, und der Server liest Bilder nur aus dem Ordner specimens des Projekts. Verwenden Sie hier synthetische oder Musterdokumente, niemals Ihren eigenen Pass.
Der Prompt.
Scanne td3-specimen.jpg mit scan_document. Vergleiche das Ergebnis mit den Feldern in components/OnboardingForm.tsx und schlage Änderungen vor, damit jedes Formularfeld aus dem Scan befüllt wird. Nutze search_docs für jedes Feld, bei dem du unsicher bist.
Was der Vergleich meist zutage fördert, und was die Form des Ergebnisses darauf antwortet:
- Namen kommen getrennt.
holder.surnameundholder.given_namessind getrennte Felder, undholder.full_namefügt sie zusammen. Ein einzelnes Feld "Vollständiger Name" verliert die Trennung, die Ihr Backoffice später haben möchte. - Datumsangaben sind bereits ISO
YYYY-MM-DD.holder.birth_dateunddocument.expiry_datepassen direkt in ein<input type="date">, dessen Wert dasselbe Format verwendet. Kein Parsen vonDD.MM.YYYYoder der sechsstelligen Datumsangaben der MRZ. - Länder kommen als Codes mit Details.
holder.nationalityist der dreistellige Code des Dokuments, undholder.nationality_detailergänzt die ISO-Formen Alpha-3 und Alpha-2. Wenn Ihre Länderauswahl mit zweistelligen Codes arbeitet, übernehmen Sie die Zuordnung aus dem Detailobjekt, statt eine eigene Tabelle zu schreiben. - Der Ablauf der Gültigkeit wird für Sie berechnet.
document.is_expiredunddocument.days_remaining(negativ nach Ablauf) lassen das Formular vor einem abgelaufenen Pass warnen, ohne Datumsrechnung. - Jeder Wert kann null sein. Jeder Schlüssel ist immer vorhanden, und ein unbekannter Wert ist
null, nie ein fehlender Schlüssel. Ein Formular, das davon ausgeht, dass jedes Feld ankommt, bricht beim ersten abgenutzten Pass. - Das MRZ-Urteil steht getrennt von den Feldern.
mrz.statusistpassed,failedoderabsent. Es sagt, ob die maschinenlesbare Zone gelesen wurde und ihre Prüfziffern stimmen; es sagt nicht, dass das Dokument echt ist. Die Gruppeauthenticitydes Ergebnisses zeigtnot_checked.
search_docs lohnt sich in dieser Schleife. Es liest die mit dem Server gebündelte Dokumentation, funktioniert offline und kostet nichts, sodass "was bedeutet unsupported_document" oder "welche Felder hat ein Personalausweis" die dokumentierte Antwort bekommen statt einer Vermutung.
Ablauf 3: Test-Fixtures einmal aufzeichnen
Die Aufgabe. Ihr eigener Code ruft die API auf, seine Tests sollten das nicht tun. Eine Testsuite, die bei jedem Lauf einen Pass an einen echten Dienst schickt, ist langsam, braucht einen Schlüssel in der CI und stößt in der Sandbox bald an ihre Grenzen: 10 Anfragen pro Stunde und eine Ablehnung document_repeated für dasselbe Dokument, wenn es zu oft eingereicht wird.
Die Lösung: jedes Ergebnis, das Ihr Code behandeln muss, einmal aufzeichnen, als JSON speichern und die Tests gegen die Dateien laufen lassen.
Der Prompt, in Claude Code oder Cursor:
Scanne td3-specimen.jpg und blank-page.png mit scan_document und speichere jedes strukturierte Ergebnis als test/fixtures/scan-
.json. Ersetze jeden persönlichen Wert (Namen, Nummern, Geburtsdaten, MRZ-Zeilen) durch einen Platzhalter desselben Typs und entferne die Data-URLs aus dem Objekt images.
Das strukturierte Ergebnis des Werkzeugs ist dasselbe Scan-Objekt, das die HTTP-API zurückgibt, also entspricht das Fixture dem, was Ihr Code in der Produktion erhält. Ein maskiertes Fixture sieht so aus:
{
"meta": { "status": "recognized", "billed": false, "reference": null },
"document": { "kind": "passport", "country": "<ICAO3>", "number": "<NUMBER>", "expiry_date": "<YYYY-MM-DD>", "is_expired": false, "days_remaining": 0 },
"holder": { "surname": "<SURNAME>", "given_names": "<GIVEN NAMES>", "birth_date": "<YYYY-MM-DD>", "nationality": "<ICAO3>" },
"mrz": { "status": "passed", "reason": null, "lines": ["P<…", "…"], "text": "P<…" }
}
(Hier gekürzt; behalten Sie jeden Schlüssel, den das echte Ergebnis hat, damit ein Test fehlschlägt, wenn Ihr Code einen liest, der fehlt.)
Drei Details machen die Fixtures ehrlich:
- Zeichnen Sie auch die Fehlschläge auf. Eine leere Seite liefert
no_document_foundmitbilled: falseund die Datengruppen alsnull. Genau diese Datei fängt Code ab, der annimmt, dass ein Scan immer einen Inhaber hat. - In der Sandbox ist
billedeine Vorhersage. Dort wird nichts berechnet, und das Kennzeichen sagt, ob derselbe Scan mit einem Live-Schlüssel berechnet worden wäre. Genau das muss ein Abrechnungstest prüfen. - Mit einem Live-Schlüssel zurücklesen statt neu scannen.
list_scansfindet einen früheren Scan über seine Referenz oder seinen Zeitpunkt, undget_scangibt ihn so zurück, wie er beim ersten Mal geliefert wurde, ohne das Bild erneut zu erkennen und ohne Abrechnung. Zwei Unterschiede: Jeder Platz inimagesistnull, undqualityzeigtnot_checked, weil Bildausschnitte nie gespeichert werden.
Wenn Sie lieber aus einem Skript statt aus dem Editor aufzeichnen, lautet derselbe Aufruf über einfaches HTTP:
curl -X POST https://api.doc.cheap/v1/scans \
-H "Authorization: Bearer sk_sandbox_public" \
-H "Content-Type: application/json" \
-d "{\"image\": \"$(base64 < specimens/td3-specimen.jpg | tr -d '\n')\"}" \
-o test/fixtures/scan-recognized.json
Maskieren Sie die Datei, bevor Sie sie committen.
Welcher Ablauf was braucht
| Schlüssel | Server | Bilder erreichen die API aus | |
|---|---|---|---|
| Ordner als CSV | Ein eigener (ab mehr als 10 Dokumenten) | Lokal, npx |
Dem abgegrenzten Verzeichnis |
| Formularprüfung | Sandbox reicht | Gehostet oder lokal | Einer URL oder dem abgegrenzten Verzeichnis |
| Test-Fixtures | Sandbox reicht | Beides | Dem abgegrenzten Verzeichnis, einmal |
Die Konfiguration für jeden Editor, darunter Windsurf, VS Code, Gemini CLI und Kiro, steht auf der Seite OCR-MCP-Server für Claude und Cursor und im Leitfaden zum MCP-Server. Wenn Sie noch einen Dienst auswählen, stellt der Vergleich von Passport-OCR-APIs doc.cheap neben die anderen, anhand dessen, was jeder veröffentlicht.
Wenn einer dieser Abläufe in Ihrem Editor nicht funktioniert oder Sie einen vierten haben, der einen Beitrag wert ist, schreiben Sie an admin@doc.cheap.
Eine Frage an Sie: Was geben Sie dem Agenten in Ihrem Editor am häufigsten – ein Dokument nach dem anderen oder einen ganzen Ordner? Erzählen Sie es uns unten in den Kommentaren.