Memasang alat pengenalan dokumen di editor hanya butuh satu menit. Mengetahui apa yang harus dilakukan dengannya sesudah itu butuh waktu lebih lama. Tulisan ini melewati tur pemasangan dan langsung masuk ke tiga pekerjaan yang benar-benar dibawa pengembang ke Claude Code dan Cursor saat berurusan dengan paspor:

  1. Mengubah satu folder pindaian paspor menjadi CSV, dengan cara yang tetap aman jika proses terhenti di tengah jalan.
  2. Mencocokkan satu paspor dengan formulir pendaftaran yang sedang Anda bangun.
  3. Merekam hasil pindaian sekali saja dan memakainya sebagai fixture uji, sehingga rangkaian tes Anda tidak pernah memanggil layanan sungguhan.

Ketiganya memakai server MCP doc.cheap, paket @doc-cheap/mcp (MIT, Node 20 atau lebih baru). Ini adalah blog milik doc.cheap sendiri, API OCR paspor dan kartu identitas di balik server tersebut, jadi pertimbangkan pilihan produknya dengan mengingat hal itu. Polanya sendiri (direktori gambar yang dipagari, satu kunci per berkas, fixture yang direkam sekali) berlaku untuk alat apa pun yang sejenis.

Apa yang diberikan server kepada editor

Enam alat, masing-masing dengan petunjuk yang dibaca klien sebelum memutuskan apakah perlu bertanya kepada Anda lebih dulu:

Alat Fungsinya Memakai kredit?
scan_document Membaca paspor, kartu identitas, atau SIM menjadi JSON terstruktur Hanya saat dokumen dikenali
check_balance Sisa kredit dan penghitung bulan ini Tidak
search_docs Mencari di dokumentasi API yang disertakan bersama server, secara offline Tidak
list_scans Menampilkan pindaian tersimpan milik akun, dari yang terbaru Tidak
get_scan Membaca kembali satu pindaian tersimpan berdasarkan id-nya Tidak
delete_scan Menghapus satu pindaian tersimpan secara permanen Tidak

scan_document menerima gambar sebagai image_base64, image_url, atau, pada server lokal, image_path, ditambah opsi expect_country, return_portrait, retain_hours, reference, dan idempotency_key. Kelima opsi itulah yang menopang sebagian besar alur kerja di bawah.

Tanpa kunci yang diatur, server memakai kunci sandbox publik: total 10 dokumen dikenali gratis per alamat, dan paling banyak 10 permintaan per alamat setiap jam. Itu cukup untuk alur kerja 2 dan 3. Alur kerja 1 membutuhkan kunci milik Anda sendiri; dengan mendaftar, Anda mendapat 100 pengenalan gratis setiap bulan.

Jika Anda belum pernah menghubungkan server MCP ke agen, Membiarkan agen membaca dokumen identitas lewat MCP membahas dasar-dasarnya serta pertanyaan privasi secara lebih mendalam.

Alur kerja 1: satu folder pindaian menjadi CSV, di Claude Code

Pekerjaannya. Seseorang menyerahkan satu folder pindaian paspor kepada Anda (tumpukan data tamu sebuah hotel, daftar peserta sebuah acara, perpindahan dari arsip kertas) dan menginginkan spreadsheet: satu baris per berkas, kolom-kolom yang penting, dan catatan yang jelas untuk setiap berkas yang tidak bisa dibaca.

Pengaturannya. Taruh .mcp.json di akar proyek. Claude Code membacanya sebagai daftar server milik proyek itu sendiri, sehingga konfigurasinya ikut berpindah bersama repositori:

{
  "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"
      }
    }
  }
}

Saat pertama kali Claude Code berjalan di folder itu, ia meminta Anda menyetujui server dari .mcp.json.

Dua baris yang bekerja:

  • DOC_CHEAP_API_KEY diambil dari shell Anda, jadi tidak ada kunci yang ikut di-commit. Jika variabel itu tidak diatur, nilainya kosong dan server kembali memakai kunci sandbox.
  • DOC_CHEAP_IMAGE_ROOT adalah satu-satunya direktori tempat server boleh membaca berkas. Tanpanya, image_path langsung ditolak. Dengannya, sebuah path lebih dulu diuraikan melalui symlink, dan apa pun yang keluar dari direktori itu (sebuah .., tautan yang mengarah ke luar) ditolak. image_path yang relatif dibaca dari direktori ini, sehingga agen cukup mengirim nama berkas saja.

Pagar itu paling penting di sini. Nama berkas berasal dari agen, dan agen sedang membaca folder yang tidak Anda buat. Gambar juga tidak pernah melewati percakapan: server membaca berkas dan mengirimkannya langsung ke API.

Prompt-nya.

Panggil check_balance lebih dulu. Lalu, untuk setiap JPEG dan PNG di direktori pindaian, panggil scan_document dengan image_path berisi nama berkas, reference berisi nama berkas, idempotency_key berisi "backlog-" ditambah SHA-256 berkas, return_portrait false, dan retain_hours 24. Tulis backlog.csv dengan kolom file, status, billed, kind, country, number, surname, given_names, birth_date, expiry_date, is_expired, mrz_status. Untuk status apa pun selain recognized, kosongkan kolom data. Di akhir, sebutkan berkas yang tidak dikenali beserta alasannya.

Mengapa setiap opsi ada di sana:

  • reference dikembalikan di meta.reference pada hasil dan muncul di setiap baris list_scans, sehingga setiap pindaian tersimpan bisa ditelusuri ke berkasnya tanpa membukanya.
  • idempotency_key yang membuat batch aman dilanjutkan. Dengan kunci live, pengulangan dengan kunci yang sama mengembalikan hasil pertama alih-alih mengenali dan menagih lagi. Jika sesi mati di berkas ke-140 dari 300, jalankan prompt yang sama sekali lagi: 139 berkas pertama kembali sebagai pengulangan tanpa biaya, dan pekerjaan berlanjut dari sana. Kunci yang dibangun dari hash berkas, bukan UUID acak, itulah yang memungkinkan sesi baru membuatnya ulang. Claude Code bisa menghitungnya dengan shasum -a 256.
  • retain_hours: 24 adalah kompromi yang dijelaskan dalam panduan idempotensi. Pengulangan mengembalikan hasil yang tersimpan, jadi pindaian yang dibuat dengan retain_hours: 0 tidak bisa diulang: percobaan ulang dengan kuncinya dalam 24 jam ditolak dengan idempotency_replay_unavailable. Jendela satu hari membuat percobaan ulang tetap gratis dan tetap tidak menyisakan apa pun sehari sesudahnya.
  • return_portrait: false tidak menyertakan potongan foto pemegang, yang tidak berguna bagi CSV.

Apa yang dikembalikan. status setiap baris adalah salah satu dari recognized, no_document_found, unreadable, unsupported_document, atau rejected, dan billed menunjukkan apakah berkas itu memakai kredit. Hanya recognized yang ditagih, jadi folder yang berisi dua puluh foto buram memakai dua puluh kredit lebih sedikit daripada jumlah berkasnya. Kolom billed juga menjadi catatan Anda sendiri tentang apa yang dihabiskan batch itu, tanpa tagihan yang perlu dicocokkan.

Satu kebiasaan lagi. CSV itu kini berisi data pribadi. Tambahkan ke .gitignore sebelum agen menulisnya, bukan sesudahnya.

Alur kerja 2: memeriksa satu paspor sambil membangun formulir, di Cursor

Pekerjaannya. Anda sedang membangun formulir pendaftaran atau check-in, dan ingin melihat apa yang dikembalikan pindaian sungguhan sebelum menentukan nama kolom, format, dan validasi.

Pengaturannya. Untuk melihat sekilas, Cursor bisa memakai server yang di-hosting, yang tidak perlu dipasang. Di .cursor/mcp.json proyek:

{
  "mcpServers": {
    "doc-cheap": {
      "url": "https://mcp.doc.cheap/mcp"
    }
  }
}

Tanpa header kunci, server yang di-hosting memakai kunci sandbox. Server itu tidak bisa melihat berkas di komputer Anda, jadi di sana scan_document menerima gambar sebagai image_base64 atau image_url publik https:. Jika spesimen Anda berupa berkas lokal, jalankan server secara lokal. Cursor menulis variabel dengan sintaksnya sendiri, ${workspaceFolder} untuk folder proyek dan ${env:NAME} untuk variabel shell, jadi entri dari alur kerja 1 tidak disalin apa adanya:

{
  "mcpServers": {
    "doc-cheap": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@doc-cheap/mcp"],
      "env": {
        "DOC_CHEAP_IMAGE_ROOT": "${workspaceFolder}/specimens"
      }
    }
  }
}

Tanpa baris DOC_CHEAP_API_KEY, entri ini juga memakai kunci sandbox, dan server hanya membaca gambar dari folder specimens proyek. Gunakan dokumen sintetis atau spesimen di sini, jangan pernah paspor Anda sendiri.

Prompt-nya.

Pindai td3-specimen.jpg dengan scan_document. Bandingkan hasilnya dengan kolom-kolom di components/OnboardingForm.tsx dan usulkan perubahan agar setiap kolom formulir terisi dari pindaian. Gunakan search_docs untuk kolom apa pun yang kamu ragukan.

Apa yang biasanya ditemukan dari perbandingan itu, dan jawaban yang diberikan oleh bentuk hasilnya:

  • Nama datang terpisah. holder.surname dan holder.given_names terpisah, dan holder.full_name menggabungkan keduanya. Satu isian "Nama lengkap" saja akan menghilangkan pemisahan yang nanti dibutuhkan back office Anda.
  • Tanggal sudah dalam ISO YYYY-MM-DD. holder.birth_date dan document.expiry_date langsung masuk ke <input type="date">, yang nilainya memakai format yang sama. Tidak perlu mengurai DD.MM.YYYY atau tanggal enam digit milik MRZ.
  • Negara datang sebagai kode beserta detailnya. holder.nationality adalah kode tiga huruf dari dokumen, dan holder.nationality_detail menambahkan bentuk ISO alpha-3 dan alpha-2. Jika pemilih negara Anda memakai kode dua huruf, petakan dari objek detail itu alih-alih menulis tabel sendiri.
  • Masa berlaku dihitungkan untuk Anda. document.is_expired dan document.days_remaining (negatif setelah kedaluwarsa) memungkinkan formulir memberi peringatan untuk paspor kedaluwarsa tanpa hitungan tanggal.
  • Nilai apa pun bisa null. Setiap kunci selalu ada, dan nilai yang tidak diketahui adalah null, tidak pernah kunci yang hilang. Formulir yang menganggap semua kolom pasti datang akan rusak pada paspor usang pertama.
  • Putusan MRZ terpisah dari kolom-kolomnya. mrz.status bernilai passed, failed, atau absent. Nilai ini menunjukkan apakah zona baca mesin terbaca dan digit pemeriksanya cocok; nilai ini tidak menyatakan bahwa dokumen itu asli. Grup authenticity pada hasil bernilai not_checked.

search_docs layak dipakai dalam siklus ini. Alat ini membaca dokumentasi yang disertakan bersama server, bekerja offline, dan tidak memakan biaya, jadi pertanyaan "apa arti unsupported_document" atau "kolom apa saja yang dimiliki kartu identitas" mendapat jawaban yang terdokumentasi, bukan tebakan.

Alur kerja 3: merekam fixture uji sekali saja

Pekerjaannya. Kode Anda sendiri memanggil API, tetapi tes-tesnya tidak seharusnya begitu. Rangkaian tes yang mengirim paspor ke layanan sungguhan di setiap putaran itu lambat, butuh kunci di CI, dan di sandbox akan segera menemui batasnya: 10 permintaan per jam, dan penolakan document_repeated untuk dokumen yang sama yang dikirim terlalu sering.

Jawabannya adalah merekam sekali setiap hasil yang harus ditangani kode Anda, menyimpannya sebagai JSON, dan membiarkan tes berjalan terhadap berkas-berkas itu.

Prompt-nya, di Claude Code maupun Cursor:

Pindai td3-specimen.jpg dan blank-page.png dengan scan_document lalu simpan setiap hasil terstruktur sebagai test/fixtures/scan-.json. Ganti setiap nilai pribadi (nama, nomor, tanggal lahir, baris MRZ) dengan placeholder bertipe sama, dan hapus data URL dari objek images.

Hasil terstruktur dari alat ini adalah objek Scan yang sama dengan yang dikembalikan API HTTP, jadi fixture-nya sesuai dengan apa yang akan diterima kode Anda di produksi. Fixture yang sudah disamarkan terlihat seperti ini:

{
  "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<…" }
}

(Dipangkas di sini; pertahankan setiap kunci yang ada di hasil sungguhan, agar tes gagal saat kode Anda membaca kunci yang tidak ada.)

Tiga detail membuat fixture-nya jujur:

  • Rekam juga kegagalannya. Halaman kosong menghasilkan no_document_found dengan billed: false dan grup-grup data bernilai null. Berkas itulah yang menangkap kode yang menganggap setiap pindaian selalu punya pemegang.
  • Di sandbox, billed adalah perkiraan. Tidak ada yang ditagih di sana, dan penanda itu menunjukkan apakah pindaian yang sama akan ditagih dengan kunci live. Itulah tepatnya yang perlu diperiksa oleh tes penagihan.
  • Dengan kunci live, baca kembali alih-alih memindai ulang. list_scans menemukan pindaian sebelumnya berdasarkan referensi atau waktunya, dan get_scan mengembalikannya persis seperti saat pertama kali dikembalikan, tanpa mengenali gambar lagi dan tanpa menagih. Dua perbedaannya: setiap slot images bernilai null dan quality bernilai not_checked, karena potongan gambar tidak pernah disimpan.

Jika Anda lebih suka merekam dari skrip daripada dari editor, panggilan yang sama lewat HTTP biasa adalah:

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

Samarkan berkas itu sebelum Anda meng-commit-nya.

Kebutuhan setiap alur kerja

Kunci Server Gambar sampai ke API dari
Folder ke CSV Milik Anda sendiri (lebih dari 10 dokumen) Lokal, npx Direktori yang dipagari
Pemeriksaan formulir Sandbox sudah cukup Di-hosting atau lokal URL atau direktori yang dipagari
Fixture uji Sandbox sudah cukup Yang mana saja Direktori yang dipagari, sekali

Konfigurasi per klien untuk setiap editor, termasuk Windsurf, VS Code, Gemini CLI, dan Kiro, ada di halaman server MCP OCR untuk Claude dan Cursor dan di panduan server MCP. Jika Anda masih memilih layanan, perbandingan API OCR paspor menempatkan doc.cheap di samping layanan lain berdasarkan apa yang dipublikasikan masing-masing.

Jika salah satu alur kerja ini bermasalah di editor Anda, atau Anda punya alur keempat yang layak ditulis, kirim email ke admin@doc.cheap.

Satu pertanyaan untuk Anda: apa yang paling sering Anda serahkan kepada agen di editor Anda – satu dokumen setiap kali, atau satu folder penuh? Ceritakan di kolom komentar di bawah.