किसी एडिटर में दस्तावेज़ पहचानने वाला टूल इंस्टॉल करने में एक मिनट लगता है। उसके बाद उससे क्या करना है, यह समझने में ज़्यादा समय लगता है। यह लेख सेटअप का पूरा दौरा छोड़कर सीधे उन तीन कामों पर आता है जो डेवलपर पासपोर्ट से जुड़े काम में सचमुच Claude Code और Cursor के पास लाते हैं:

  1. पासपोर्ट स्कैन के एक फ़ोल्डर को CSV में बदलना, इस तरह कि बीच में काम रुक जाए तो भी कुछ न बिगड़े।
  2. एक पासपोर्ट को उस ऑनबोर्डिंग फ़ॉर्म से मिलाना जो आप बना रहे हैं।
  3. स्कैन के नतीजे एक बार रिकॉर्ड करना और उन्हें टेस्ट फ़िक्स्चर की तरह इस्तेमाल करना, ताकि आपका टेस्ट सूट कभी किसी लाइव सेवा को कॉल न करे।

तीनों में doc.cheap का MCP सर्वर, यानी पैकेज @doc-cheap/mcp (MIT, Node 20 या उसके बाद का) इस्तेमाल होता है। यह doc.cheap का अपना ब्लॉग है, उस सर्वर के पीछे की पासपोर्ट और ID OCR API, इसलिए प्रोडक्ट से जुड़ी पसंद को इसी हिसाब से परखें। पैटर्न अपने आप में (एक घिरी हुई इमेज डायरेक्टरी, हर फ़ाइल के लिए एक key, एक बार रिकॉर्ड किया गया फ़िक्स्चर) इस तरह के किसी भी टूल पर लागू होते हैं।

सर्वर एडिटर को क्या देता है

छह टूल, हर एक के साथ वे संकेत जिन्हें क्लाइंट यह तय करने से पहले पढ़ता है कि पहले आपसे पूछना है या नहीं:

टूल क्या करता है क्रेडिट लगता है?
scan_document पासपोर्ट, ID कार्ड या ड्राइविंग लाइसेंस पढ़कर संरचित JSON देता है सिर्फ़ तब जब कोई दस्तावेज़ पहचाना जाए
check_balance बचे हुए क्रेडिट और इस महीने के काउंटर नहीं
search_docs सर्वर के साथ आए API दस्तावेज़ों में ऑफ़लाइन खोजता है नहीं
list_scans खाते के सहेजे गए स्कैन दिखाता है, सबसे नए पहले नहीं
get_scan सहेजे गए एक स्कैन को उसकी id से दोबारा पढ़ता है नहीं
delete_scan सहेजे गए एक स्कैन को हमेशा के लिए मिटाता है नहीं

scan_document इमेज को image_base64, image_url या लोकल सर्वर पर image_path के रूप में लेता है, साथ में वैकल्पिक expect_country, return_portrait, retain_hours, reference और idempotency_key। नीचे के ज़्यादातर वर्कफ़्लो इन्हीं पाँच विकल्पों पर टिके हैं।

कोई key सेट न हो तो सर्वर सार्वजनिक sandbox key इस्तेमाल करता है: हर पते के लिए कुल 10 मुफ़्त पहचाने गए दस्तावेज़, और हर पते के लिए एक घंटे में ज़्यादा से ज़्यादा 10 अनुरोध। वर्कफ़्लो 2 और 3 के लिए इतना काफ़ी है। वर्कफ़्लो 1 के लिए आपकी अपनी key चाहिए; रजिस्टर करने पर हर महीने 100 मुफ़्त पहचान मिलती हैं।

अगर आपने पहले कभी किसी एजेंट से MCP सर्वर नहीं जोड़ा है, तो MCP के ज़रिए एजेंट से पहचान दस्तावेज़ पढ़वाना बुनियादी बातें और प्राइवेसी से जुड़े सवाल ज़्यादा विस्तार से समझाता है।

वर्कफ़्लो 1: Claude Code में स्कैन के फ़ोल्डर से CSV

काम। कोई आपको पासपोर्ट स्कैन का एक फ़ोल्डर देता है (किसी होटल के बकाया मेहमान रिकॉर्ड, किसी इवेंट की प्रतिभागी सूची, काग़ज़ी आर्काइव से माइग्रेशन) और एक स्प्रेडशीट चाहता है: हर फ़ाइल की एक पंक्ति, ज़रूरी फ़ील्ड, और हर उस फ़ाइल के लिए साफ़ नोट जो पढ़ी नहीं जा सकी।

सेटअप। प्रोजेक्ट की रूट में एक .mcp.json रखें। Claude Code इसे प्रोजेक्ट की अपनी सर्वर सूची की तरह पढ़ता है, इसलिए कॉन्फ़िगरेशन रिपॉज़िटरी के साथ ही चलता है:

{
  "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 आपसे .mcp.json वाले सर्वर को मंज़ूरी देने के लिए कहता है।

काम दो पंक्तियाँ करती हैं:

  • DOC_CHEAP_API_KEY आपके शेल से ली जाती है, इसलिए कोई key कमिट में नहीं जाती। वेरिएबल सेट न हो तो मान ख़ाली रहता है और सर्वर sandbox key पर लौट आता है।
  • DOC_CHEAP_IMAGE_ROOT वह इकलौती डायरेक्टरी है जिससे सर्वर फ़ाइलें पढ़ सकता है। इसके बिना image_path सीधे अस्वीकार हो जाता है। इसके साथ, किसी पाथ को पहले सिमलिंक के ज़रिए हल किया जाता है, और जो कुछ भी डायरेक्टरी से बाहर निकलता है (कोई .., बाहर की ओर इशारा करता लिंक) वह अस्वीकार हो जाता है। सापेक्ष image_path इसी डायरेक्टरी से लिया जाता है, इसलिए एजेंट सिर्फ़ फ़ाइल के नाम दे सकता है।

यह घेरा यहाँ किसी और जगह से ज़्यादा मायने रखता है। फ़ाइल के नाम एजेंट से आते हैं, और एजेंट एक ऐसा फ़ोल्डर पढ़ रहा है जो आपने नहीं बनाया। इसके अलावा इमेज कभी बातचीत से होकर नहीं गुज़रतीं: सर्वर फ़ाइल पढ़ता है और उसे सीधे API को भेज देता है।

प्रॉम्प्ट।

पहले check_balance कॉल करो। फिर स्कैन डायरेक्टरी की हर JPEG और PNG फ़ाइल के लिए scan_document कॉल करो, जिसमें image_path फ़ाइल का नाम हो, reference फ़ाइल का नाम हो, idempotency_key "backlog-" और उसके बाद फ़ाइल का SHA-256 हो, return_portrait false हो और retain_hours 24 हो। backlog.csv लिखो जिसके कॉलम हों file, status, billed, kind, country, number, surname, given_names, birth_date, expiry_date, is_expired, mrz_status। recognized के अलावा किसी भी status के लिए डेटा कॉलम ख़ाली छोड़ दो। आख़िर में उन फ़ाइलों की सूची दो जो पहचानी नहीं गईं, और कारण भी।

हर विकल्प वहाँ क्यों है:

  • reference नतीजे के meta.reference में वापस आता है और list_scans की हर पंक्ति में दिखता है, इसलिए हर सहेजे गए स्कैन को खोले बिना उसकी फ़ाइल से जोड़ा जा सकता है।
  • idempotency_key ही बैच को दोबारा शुरू करने लायक सुरक्षित बनाता है। लाइव key पर उसी key से दोहराया गया अनुरोध दोबारा पहचानने और चार्ज करने के बजाय पहला नतीजा लौटाता है। अगर सेशन 300 में से 140वीं फ़ाइल पर रुक जाए, तो वही प्रॉम्प्ट फिर से चलाएँ: पहली 139 फ़ाइलें बिना किसी लागत के रीप्ले की तरह लौटती हैं, और काम वहीं से आगे बढ़ता है। फ़ाइल के हैश से बनी key, न कि कोई रैंडम UUID, ही नए सेशन को उसे दोबारा बनाने देती है। Claude Code इसे shasum -a 256 से निकाल सकता है।
  • retain_hours: 24 वह संतुलन है जिसे idempotency गाइड समझाती है। रीप्ले सहेजा गया नतीजा लौटाता है, इसलिए retain_hours: 0 से किया गया स्कैन रीप्ले नहीं हो सकता: 24 घंटे के भीतर उसकी key से दोबारा कोशिश idempotency_replay_unavailable के साथ अस्वीकार होती है। एक दिन की खिड़की दोबारा कोशिशों को मुफ़्त रखती है और अगले दिन कुछ भी सहेजा हुआ नहीं छोड़ती।
  • return_portrait: false धारक की फ़ोटो का कटा हुआ हिस्सा बाहर रखता है, जिसकी CSV को कोई ज़रूरत नहीं।

क्या लौटता है। हर पंक्ति का status इनमें से एक होता है: recognized, no_document_found, unreadable, unsupported_document या rejected, और billed बताता है कि उस फ़ाइल पर क्रेडिट लगा या नहीं। सिर्फ़ recognized चार्ज होता है, इसलिए जिस फ़ोल्डर में बीस धुंधली फ़ोटो हैं, उसका ख़र्च उसकी फ़ाइलों की संख्या से बीस क्रेडिट कम होता है। billed कॉलम बैच के ख़र्च की आपकी अपनी गिनती भी है, किसी इनवॉइस से मिलान की ज़रूरत नहीं।

एक और आदत। अब CSV में निजी डेटा है। एजेंट के लिखने से पहले उसे .gitignore में जोड़ें, बाद में नहीं।

वर्कफ़्लो 2: Cursor में फ़ॉर्म बनाते समय एक पासपोर्ट जाँचना

काम। आप एक ऑनबोर्डिंग या चेक-इन फ़ॉर्म बना रहे हैं, और फ़ील्ड के नाम, फ़ॉर्मेट और वैलिडेशन तय करने से पहले देखना चाहते हैं कि असली स्कैन क्या लौटाता है।

सेटअप। जल्दी से देखने के लिए Cursor होस्टेड सर्वर इस्तेमाल कर सकता है, जिसे इंस्टॉल करने की ज़रूरत नहीं। प्रोजेक्ट की .cursor/mcp.json में:

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

key हेडर के बिना होस्टेड सर्वर sandbox key इस्तेमाल करता है। वह आपकी मशीन की फ़ाइलें नहीं देख सकता, इसलिए वहाँ scan_document इमेज को image_base64 या किसी सार्वजनिक https: image_url के रूप में लेता है। अगर आपके नमूने लोकल फ़ाइलें हैं, तो इसके बजाय सर्वर को लोकल चलाएँ। Cursor वेरिएबल अपने सिंटैक्स में लिखता है, प्रोजेक्ट फ़ोल्डर के लिए ${workspaceFolder} और shell वेरिएबल के लिए ${env:NAME}, इसलिए वर्कफ़्लो 1 वाली एंट्री को जैसी की तैसी कॉपी नहीं किया जाता:

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

DOC_CHEAP_API_KEY वाली लाइन के बिना यह एंट्री भी sandbox key इस्तेमाल करती है, और सर्वर इमेज सिर्फ़ प्रोजेक्ट के specimens फ़ोल्डर से पढ़ता है। यहाँ सिंथेटिक या नमूना दस्तावेज़ इस्तेमाल करें, कभी अपना पासपोर्ट नहीं।

प्रॉम्प्ट।

td3-specimen.jpg को scan_document से स्कैन करो। नतीजे की तुलना components/OnboardingForm.tsx के फ़ील्ड से करो और ऐसे बदलाव सुझाओ कि फ़ॉर्म का हर फ़ील्ड स्कैन से भर जाए। जिस फ़ील्ड को लेकर पक्का न हो, उसके लिए search_docs इस्तेमाल करो।

तुलना में आम तौर पर क्या निकलता है, और नतीजे की संरचना उसका क्या जवाब देती है:

  • नाम अलग-अलग आते हैं। holder.surname और holder.given_names अलग हैं, और holder.full_name दोनों को जोड़ता है। एक अकेला "पूरा नाम" इनपुट वह बँटवारा खो देता है जो आपके बैक ऑफ़िस को बाद में चाहिए होगा।
  • तारीख़ें पहले से ISO YYYY-MM-DD में हैं। holder.birth_date और document.expiry_date सीधे <input type="date"> में जाती हैं, जिसका मान इसी फ़ॉर्मेट में होता है। DD.MM.YYYY या MRZ की छह अंकों वाली तारीख़ें पार्स करने की ज़रूरत नहीं।
  • देश विवरण के साथ कोड के रूप में आते हैं। holder.nationality दस्तावेज़ का तीन अक्षरों वाला कोड है, और holder.nationality_detail उसमें ISO alpha-3 और alpha-2 रूप जोड़ता है। अगर आपका देश चुनने वाला पिकर दो अक्षरों वाले कोड पर चलता है, तो अपनी तालिका लिखने के बजाय विवरण वाले ऑब्जेक्ट से मैप करें।
  • समाप्ति आपके लिए पहले से निकाली हुई है। document.is_expired और document.days_remaining (समाप्ति के बाद ऋणात्मक) से फ़ॉर्म बिना तारीख़ों का हिसाब लगाए समाप्त पासपोर्ट पर चेतावनी दे सकता है।
  • कुछ भी null हो सकता है। हर key हमेशा मौजूद रहती है, और अज्ञात मान null होता है, कभी ग़ायब key नहीं। जो फ़ॉर्म मानकर चलता है कि हर फ़ील्ड आएगा, वह पहले घिसे हुए पासपोर्ट पर टूट जाएगा।
  • MRZ का फ़ैसला फ़ील्ड से अलग है। mrz.status का मान passed, failed या absent होता है। यह बताता है कि मशीन से पढ़ा जाने वाला ज़ोन पढ़ा गया या नहीं और उसके चेक अंक मेल खाते हैं या नहीं; यह नहीं बताता कि दस्तावेज़ असली है। नतीजे का authenticity समूह not_checked दिखाता है।

इस चक्र में search_docs इस्तेमाल करने लायक है। यह सर्वर के साथ आए दस्तावेज़ पढ़ता है, ऑफ़लाइन चलता है और इसकी कोई लागत नहीं, इसलिए "unsupported_document का मतलब क्या है" या "ID कार्ड में कौन-से फ़ील्ड होते हैं" जैसे सवालों को अंदाज़े के बजाय दस्तावेज़ में लिखा जवाब मिलता है।

वर्कफ़्लो 3: टेस्ट फ़िक्स्चर एक बार रिकॉर्ड करना

काम। आपका अपना कोड API को कॉल करता है, पर उसके टेस्ट को नहीं करना चाहिए। जो टेस्ट सूट हर बार चलने पर किसी लाइव सेवा को पासपोर्ट भेजता है, वह धीमा होता है, उसे CI में key चाहिए, और sandbox पर वह जल्दी अपनी सीमाओं से टकराता है: एक घंटे में 10 अनुरोध, और एक ही दस्तावेज़ बहुत बार भेजने पर document_repeated अस्वीकृति।

इसका हल है कि आपके कोड को जिन नतीजों को सँभालना है, उनमें से हर एक को एक बार रिकॉर्ड करें, JSON के रूप में सहेजें, और टेस्ट इन फ़ाइलों पर चलाएँ।

प्रॉम्प्ट, Claude Code या Cursor में:

td3-specimen.jpg और blank-page.png को scan_document से स्कैन करो और हर संरचित नतीजे को test/fixtures/scan-.json के रूप में सहेजो। हर निजी मान (नाम, नंबर, जन्म तिथियाँ, MRZ पंक्तियाँ) को उसी प्रकार के प्लेसहोल्डर से बदल दो, और images ऑब्जेक्ट से data URL हटा दो।

टूल का संरचित नतीजा वही Scan ऑब्जेक्ट है जो HTTP API लौटाती है, इसलिए फ़िक्स्चर उसी से मेल खाता है जो आपके कोड को प्रोडक्शन में मिलेगा। मास्क किया गया फ़िक्स्चर ऐसा दिखता है:

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

(यहाँ छोटा किया गया है; असली नतीजे की हर key रखें, ताकि जब आपका कोड कोई ग़ायब key पढ़े तो टेस्ट फ़ेल हो।)

तीन बातें फ़िक्स्चर को ईमानदार बनाती हैं:

  • विफलताएँ भी रिकॉर्ड करें। एक ख़ाली पन्ना आपको billed: false के साथ no_document_found देता है, और डेटा समूह null होते हैं। यही फ़ाइल उस कोड को पकड़ती है जो मानता है कि हर स्कैन में धारक होता ही है।
  • sandbox पर billed एक अनुमान है। वहाँ कुछ भी चार्ज नहीं होता, और यह फ़्लैग बताता है कि वही स्कैन लाइव key पर चार्ज होता या नहीं। बिलिंग टेस्ट को ठीक इसी की जाँच करनी होती है।
  • लाइव key पर दोबारा स्कैन करने के बजाय वापस पढ़ें। list_scans किसी पुराने स्कैन को उसके reference या समय से ढूँढता है, और get_scan उसे वैसे ही लौटाता है जैसे पहली बार लौटाया था, इमेज को दोबारा पहचाने बिना और बिना चार्ज के। दो अंतर हैं: images का हर स्लॉट null होता है और quality में not_checked दिखता है, क्योंकि इमेज के कटे हिस्से कभी सहेजे नहीं जाते।

अगर आप एडिटर के बजाय किसी स्क्रिप्ट से रिकॉर्ड करना चाहें, तो सादे 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

कमिट करने से पहले फ़ाइल को मास्क करें।

किस वर्कफ़्लो को क्या चाहिए

Key सर्वर इमेज API तक कहाँ से पहुँचती हैं
फ़ोल्डर से CSV आपकी अपनी (10 दस्तावेज़ों के बाद) लोकल, npx घिरी हुई डायरेक्टरी
फ़ॉर्म की जाँच sandbox काफ़ी है होस्टेड या लोकल एक URL या घिरी हुई डायरेक्टरी
टेस्ट फ़िक्स्चर sandbox काफ़ी है कोई भी घिरी हुई डायरेक्टरी, एक बार

Windsurf, VS Code, Gemini CLI और Kiro समेत हर एडिटर का क्लाइंट कॉन्फ़िगरेशन Claude और Cursor के लिए OCR MCP सर्वर पेज पर और MCP सर्वर गाइड में है। अगर आप अभी सेवा चुन रहे हैं, तो पासपोर्ट OCR API तुलना doc.cheap को बाक़ी सेवाओं के साथ, हर एक की प्रकाशित जानकारी के आधार पर रखती है।

अगर इनमें से कोई वर्कफ़्लो आपके एडिटर में टूट जाए, या आपके पास लिखने लायक कोई चौथा वर्कफ़्लो हो, तो admin@doc.cheap पर लिखें।

आपसे एक सवाल: आप अपने एडिटर के एजेंट को सबसे ज़्यादा क्या सौंपते हैं – एक बार में एक दस्तावेज़, या पूरा फ़ोल्डर? नीचे टिप्पणियों में बताएँ।