Bir editöre belge tanıma aracı kurmak bir dakika sürer. Sonrasında onunla ne yapacağınızı bilmek daha uzun sürer. Bu yazı kurulum turunu atlıyor ve işin içine pasaportlar girdiğinde geliştiricilerin Claude Code ve Cursor'a gerçekten getirdiği üç işe doğrudan geçiyor:

  1. Bir pasaport taraması klasörünü, yarıda kesilmeye dayanacak şekilde bir CSV'ye dönüştürmek.
  2. Bir pasaportu, geliştirdiğiniz kayıt formuyla karşılaştırmak.
  3. Tarama sonuçlarını bir kez kaydedip test fixture'ı olarak kullanmak; böylece test paketiniz hiçbir zaman canlı bir servisi çağırmaz.

Üçü de doc.cheap MCP sunucusunu, yani @doc-cheap/mcp paketini (MIT, Node 20 veya üstü) kullanıyor. Bu, o sunucunun arkasındaki pasaport ve kimlik OCR API'si doc.cheap'in kendi blogu; ürün tercihlerini buna göre değerlendirin. Kalıpların kendisi (sınırları çizilmiş bir görsel dizini, dosya başına bir anahtar, bir kez kaydedilen bir fixture) bu türden her araç için geçerli.

Sunucu editöre ne veriyor

Altı araç; her birinin, istemcinin önce size sorup sormayacağına karar vermeden okuduğu ipuçları var:

Araç Ne yapar Kredi düşer mi?
scan_document Bir pasaportu, kimlik kartını veya ehliyeti yapılandırılmış JSON olarak okur Yalnızca bir belge tanındığında
check_balance Kalan krediler ve bu ayın sayaçları Hayır
search_docs Sunucuyla birlikte gelen API belgelerinde çevrimdışı arama yapar Hayır
list_scans Hesabın saklanan taramalarını en yeniden başlayarak listeler Hayır
get_scan Saklanan bir taramayı kimliğiyle geri okur Hayır
delete_scan Saklanan bir taramayı kalıcı olarak siler Hayır

scan_document görseli image_base64, image_url ya da yerel sunucuda image_path olarak alır; bunlara ek olarak isteğe bağlı expect_country, return_portrait, retain_hours, reference ve idempotency_key seçeneklerini kabul eder. Aşağıdaki iş akışlarının çoğunu bu beş seçenek taşıyor.

Anahtar ayarlanmamışsa sunucu herkese açık sandbox anahtarını kullanır: adres başına toplamda 10 ücretsiz tanınan belge ve adres başına saatte en fazla 10 istek. Bu, 2. ve 3. iş akışları için yeterli. 1. iş akışı için kendi anahtarınız gerekir; kayıt olduğunuzda her ay 100 ücretsiz tanıma alırsınız.

Daha önce bir ajana MCP sunucusu bağlamadıysanız, Bir ajana MCP üzerinden kimlik belgesi okutmak yazısı temelleri ve gizlilik sorularını daha ayrıntılı ele alıyor.

1. iş akışı: Claude Code'da bir tarama klasörünü CSV'ye dökmek

İş. Biri size bir pasaport taraması klasörü veriyor (bir otelin birikmiş misafir kayıtları, bir etkinliğin katılımcı listesi, kâğıt arşivden bir taşıma) ve bir tablo istiyor: dosya başına bir satır, önemli alanlar ve okunamayan her dosya için net bir not.

Kurulum. Projenin kök dizinine bir .mcp.json koyun. Claude Code bunu projenin kendi sunucu listesi olarak okur, yani yapılandırma depoyla birlikte taşınır:

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

Claude Code o klasörde ilk kez başladığında .mcp.json içindeki sunucuyu onaylamanızı ister.

İşi iki satır yapıyor:

  • DOC_CHEAP_API_KEY kabuğunuzdan alınır, böylece hiçbir anahtar commit'e girmez. Değişken ayarlanmamışsa değer boş olur ve sunucu sandbox anahtarına geri döner.
  • DOC_CHEAP_IMAGE_ROOT sunucunun dosya okuyabileceği tek dizindir. Bu olmadan image_path doğrudan reddedilir. Bununla birlikte bir yol önce sembolik bağlantılar üzerinden çözülür ve dizinin dışına çıkan her şey (bir .., dışarıyı gösteren bir bağlantı) reddedilir. Göreli bir image_path bu dizine göre alınır, bu yüzden ajan yalnızca dosya adlarını verebilir.

Bu sınır burada her yerden daha önemli. Dosya adları ajandan geliyor ve ajan sizin oluşturmadığınız bir klasörü okuyor. Ayrıca görseller hiçbir zaman sohbetten geçmez: sunucu dosyayı okur ve doğrudan API'ye gönderir.

İstem.

Önce check_balance çağır. Sonra tarama dizinindeki her JPEG ve PNG için scan_document çağır; image_path dosya adı, reference dosya adı, idempotency_key "backlog-" artı dosyanın SHA-256 değeri, return_portrait false ve retain_hours 24 olsun. backlog.csv dosyasını şu sütunlarla yaz: file, status, billed, kind, country, number, surname, given_names, birth_date, expiry_date, is_expired, mrz_status. status değeri recognized dışında bir şeyse veri sütunlarını boş bırak. Sonunda tanınmayan dosyaları ve nedenlerini listele.

Her seçeneğin neden orada olduğu:

  • reference sonucun meta.reference alanında geri döner ve her list_scans satırında görünür; böylece saklanan her tarama, açılmadan kendi dosyasına bağlanabilir.
  • idempotency_key toplu işi kaldığı yerden sürdürülebilir kılan şeydir. Canlı bir anahtarda aynı anahtarla yapılan tekrar, yeniden tanıyıp yeniden ücretlendirmek yerine ilk sonucu döndürür. Oturum 300 dosyanın 140'ıncısında kapanırsa aynı istemi yeniden çalıştırın: ilk 139 dosya ücretsiz tekrar olarak geri gelir ve iş oradan devam eder. Yeni bir oturumun anahtarı yeniden üretebilmesini sağlayan, rastgele bir UUID değil, dosyanın hash'inden üretilmiş bir anahtardır. Claude Code bunu shasum -a 256 ile hesaplayabilir.
  • retain_hours: 24 idempotency rehberinin açıkladığı ödünleşimdir. Bir tekrar saklanan sonucu geri verir, dolayısıyla retain_hours: 0 ile yapılmış bir tarama tekrarlanamaz: onun anahtarıyla 24 saat içinde yapılan yeni bir deneme idempotency_replay_unavailable ile reddedilir. Bir günlük pencere yeniden denemeleri ücretsiz tutar ve ertesi gün yine hiçbir şey saklı kalmaz.
  • return_portrait: false belge sahibinin fotoğrafından kesilen görüntüyü dışarıda bırakır; bir CSV'nin buna ihtiyacı yok.

Ne geri geliyor. Her satırın status değeri recognized, no_document_found, unreadable, unsupported_document veya rejected değerlerinden biridir ve billed o dosyanın kredi düşürüp düşürmediğini söyler. Yalnızca recognized ücretlendirilir; içinde yirmi bulanık fotoğraf olan bir klasör, dosya sayısından yirmi kredi daha az tutar. billed sütunu aynı zamanda toplu işin ne harcadığının sizin kendi sayımınızdır; mutabakat yapılacak bir fatura yoktur.

Bir alışkanlık daha. CSV artık kişisel veri içeriyor. Ajan onu yazmadan önce .gitignore dosyasına ekleyin, sonra değil.

2. iş akışı: Cursor'da form geliştirirken bir pasaportu kontrol etmek

İş. Bir kayıt ya da check-in formu geliştiriyorsunuz ve alan adlarına, biçimlere ve doğrulamaya karar vermeden önce gerçek bir taramanın ne döndürdüğünü görmek istiyorsunuz.

Kurulum. Hızlı bir bakış için Cursor, kurulum gerektirmeyen barındırılan sunucuyu kullanabilir. Projenin .cursor/mcp.json dosyasında:

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

Anahtar başlığı olmadan barındırılan sunucu sandbox anahtarını kullanır. Makinenizdeki dosyaları göremez; bu yüzden orada scan_document görseli image_base64 ya da herkese açık bir https: image_url olarak alır. Örnekleriniz yerel dosyalarsa bunun yerine sunucuyu yerelde çalıştırın. Cursor değişkenleri kendi sözdizimiyle yazar: proje klasörü için ${workspaceFolder}, kabuk değişkeni için ${env:NAME}; bu yüzden 1. iş akışındaki girdi olduğu gibi kopyalanmaz:

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

DOC_CHEAP_API_KEY satırı olmadan bu girdi de sandbox anahtarını kullanır ve sunucu görselleri yalnızca projenin specimens klasöründen okur. Burada sentetik ya da örnek belgeler kullanın, asla kendi pasaportunuzu değil.

İstem.

td3-specimen.jpg dosyasını scan_document ile tara. Sonucu components/OnboardingForm.tsx içindeki alanlarla karşılaştır ve her form alanının taramadan doldurulması için değişiklikler öner. Emin olmadığın her alan için search_docs kullan.

Karşılaştırmanın genellikle ortaya çıkardıkları ve sonucun yapısının verdiği yanıtlar:

  • Adlar ayrı gelir. holder.surname ve holder.given_names ayrı alanlardır, holder.full_name ise ikisini birleştirir. Tek bir "Ad soyad" alanı, arka ofisinizin ileride isteyeceği ayrımı kaybeder.
  • Tarihler zaten ISO YYYY-MM-DD biçimindedir. holder.birth_date ve document.expiry_date doğrudan bir <input type="date"> alanına girer; onun değeri de aynı biçimi kullanır. DD.MM.YYYY ya da MRZ'nin altı haneli tarihlerini ayrıştırmaya gerek yok.
  • Ülkeler ayrıntılarıyla birlikte kod olarak gelir. holder.nationality belgedeki üç harfli koddur, holder.nationality_detail ise ISO alpha-3 ve alpha-2 biçimlerini ekler. Ülke seçiciniz iki harfli kodlara göre çalışıyorsa kendi tablonuzu yazmak yerine ayrıntı nesnesinden eşleyin.
  • Son kullanma sizin için hesaplanır. document.is_expired ve document.days_remaining (süre dolduğunda negatif) formun tarih hesabı yapmadan süresi dolmuş bir pasaport için uyarı vermesini sağlar.
  • Her şey null olabilir. Her anahtar her zaman bulunur ve bilinmeyen bir değer null olur, asla eksik bir anahtar olmaz. Her alanın geleceğini varsayan bir form, ilk yıpranmış pasaportta bozulur.
  • MRZ kararı alanlardan ayrıdır. mrz.status değeri passed, failed veya absent olur. Makinece okunabilir bölgenin okunup okunmadığını ve kontrol hanelerinin tutup tutmadığını söyler; belgenin gerçek olduğunu söylemez. Sonucun authenticity grubu not_checked gösterir.

Bu döngüde search_docs kullanmaya değer. Sunucuyla birlikte gelen belgeleri okur, çevrimdışı çalışır ve hiçbir ücreti yoktur; böylece "unsupported_document ne demek" ya da "bir kimlik kartında hangi alanlar var" gibi sorular tahmin yerine belgelenmiş yanıtı alır.

3. iş akışı: test fixture'larını bir kez kaydetmek

İş. Kendi kodunuz API'yi çağırıyor, testleri ise çağırmamalı. Her çalıştırmada canlı bir servise pasaport gönderen bir test paketi yavaştır, CI'da bir anahtar ister ve sandbox'ta kısa sürede sınırlarına takılır: saatte 10 istek ve aynı belge çok kez gönderildiğinde document_repeated reddi.

Çözüm, kodunuzun ele alması gereken her sonucu bir kez kaydetmek, JSON olarak saklamak ve testleri bu dosyalar üzerinde çalıştırmaktır.

İstem, Claude Code'da da Cursor'da da:

td3-specimen.jpg ve blank-page.png dosyalarını scan_document ile tara ve her yapılandırılmış sonucu test/fixtures/scan-.json olarak kaydet. Her kişisel değeri (adlar, numaralar, doğum tarihleri, MRZ satırları) aynı türde bir yer tutucuyla değiştir ve images nesnesindeki data URL'lerini kaldır.

Aracın yapılandırılmış sonucu, HTTP API'nin döndürdüğü aynı Scan nesnesidir; dolayısıyla fixture, kodunuzun üretimde alacağıyla eşleşir. Maskelenmiş bir fixture şöyle görünür:

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

(Burada kısaltıldı; gerçek sonuçtaki her anahtarı koruyun ki kodunuz eksik bir anahtarı okuduğunda test başarısız olsun.)

Fixture'ları dürüst kılan üç ayrıntı:

  • Başarısızlıkları da kaydedin. Boş bir sayfa size billed: false ile no_document_found ve null veri grupları verir. Bir taramanın her zaman bir belge sahibi olduğunu varsayan kodu yakalayan dosya budur.
  • Sandbox'ta billed bir öngörüdür. Orada hiçbir şey ücretlendirilmez ve bu bayrak, aynı taramanın canlı bir anahtarda ücretlendirilip ücretlendirilmeyeceğini söyler. Bir faturalama testinin doğrulaması gereken tam olarak budur.
  • Canlı bir anahtarda yeniden taramak yerine geri okuyun. list_scans önceki bir taramayı referansına ya da zamanına göre bulur, get_scan ise onu ilk döndürüldüğü haliyle, görseli yeniden tanımadan ve ücret almadan döndürür. İki fark var: her images alanı null olur ve quality değeri not_checked gösterir, çünkü görsel kesitleri hiçbir zaman saklanmaz.

Editör yerine bir betikten kaydetmeyi tercih ederseniz, aynı çağrı düz HTTP ile şöyledir:

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

Dosyayı commit etmeden önce maskeleyin.

Hangi iş akışı neye ihtiyaç duyar

Anahtar Sunucu Görseller API'ye nereden ulaşır
Klasörden CSV'ye Kendi anahtarınız (10 belgenin ötesinde) Yerel, npx Sınırları çizilmiş dizin
Form kontrolü Sandbox yeterli Barındırılan veya yerel Bir URL veya sınırları çizilmiş dizin
Test fixture'ları Sandbox yeterli İkisinden biri Sınırları çizilmiş dizin, bir kez

Windsurf, VS Code, Gemini CLI ve Kiro dahil her editör için istemci yapılandırması, Claude ve Cursor için OCR MCP sunucusu sayfasında ve MCP sunucusu rehberinde yer alıyor. Hâlâ bir servis seçiyorsanız, pasaport OCR API karşılaştırması doc.cheap'i, her birinin yayımladığı bilgilere göre diğerlerinin yanına koyuyor.

Bu iş akışlarından biri editörünüzde bozulursa ya da yazmaya değer dördüncü bir iş akışınız varsa admin@doc.cheap adresine yazın.

Size bir soru: editörünüzün ajanına en sık ne veriyorsunuz – her seferinde tek bir belge mi, yoksa bütün bir klasör mü? Aşağıdaki yorumlarda bize anlatın.