在编辑器里装一个证件识别工具只要一分钟。弄清装好之后用它做什么,则要更久。本文跳过安装介绍,直接进入开发者在涉及护照时真正交给 Claude Code 和 Cursor 的三项工作:
- 把一个文件夹的护照扫描件转成 CSV,而且中途崩溃后还能接着做。
- 将一本护照与你正在开发的注册表单进行核对。
- 只记录一次扫描结果,把它们用作测试夹具,让测试套件永远不调用线上服务。
三者都使用 doc.cheap 的 MCP 服务器,即 @doc-cheap/mcp 包(MIT 许可,Node 20 或更高版本)。这是 doc.cheap 自己的博客,也就是该服务器背后的护照与身份证 OCR API,因此涉及产品选择的部分请自行斟酌。这些模式本身(有边界的图片目录、每个文件一个键、只记录一次的夹具)适用于任何同类工具。
服务器为编辑器提供了什么
六个工具,每个都带有提示信息,客户端在决定是否先征求你的同意之前会读取这些提示:
| 工具 | 作用 | 是否扣除额度? |
|---|---|---|
scan_document |
读取护照、身份证或驾驶证,返回结构化 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。下面的工作流主要就靠这五个选项。
未设置密钥时,服务器使用公共沙盒密钥:每个地址总共可免费识别 10 份证件,且每个地址每小时最多 10 次请求。这足以完成工作流 2 和 3。工作流 1 需要你自己的密钥;注册后每月可获得 100 次免费识别。
如果你从未把 MCP 服务器连接到智能体,让智能体通过 MCP 读取身份证件一文更详细地介绍了基础知识和隐私问题。
工作流 1:把扫描件文件夹转成 CSV(Claude Code)
任务。 有人交给你一个护照扫描件文件夹(酒店积压的住客资料、活动的参会者名单、纸质档案的迁移),想要一张电子表格:每个文件一行,包含需要的字段,并为每个无法读取的文件附上清楚的说明。
配置。 在项目根目录放一个 .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取自你的 shell,因此不会有密钥被提交。变量未设置时,值为空,服务器会退回使用沙盒密钥。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 时,数据列留空。最后列出未被识别的文件及原因。
每个选项的用途:
reference会原样回传到结果的meta.reference中,并显示在list_scans的每一行,因此无需打开就能把每条已保存的扫描追溯到它的文件。idempotency_key让批处理可以安全地续跑。使用正式密钥时,用同一个键重复请求会返回第一次的结果,而不会再次识别和计费。如果会话在 300 个文件中的第 140 个中断,就再次运行同一个提示词:前 139 个会以重放形式免费返回,工作从那里继续。正是因为键由文件哈希生成而不是随机 UUID,新的会话才能重新得到同一个键。Claude Code 可以用shasum -a 256计算它。retain_hours: 24是幂等性指南中说明的权衡。重放返回的是已保存的结果,所以用retain_hours: 0做的扫描无法重放:24 小时内用其键重试会被拒绝,返回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"
}
}
}
没有密钥请求头时,托管服务器使用沙盒密钥。它看不到你机器上的文件,所以在这里 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 这一行时,这个配置同样使用沙盒密钥,服务器也只从项目的 specimens 文件夹读取图片。 这里请使用合成证件或样本证件,绝不要用你自己的护照。
提示词。
用 scan_document 扫描 td3-specimen.jpg。将结果与 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。 每个键始终存在,未知的值是
null,绝不会缺少键。假定每个字段都会有值的表单,遇到第一本磨损的护照就会出错。 - MRZ 判定与字段是分开的。
mrz.status为passed、failed或absent。它说明机读区是否被读取、其校验位是否一致;它并不表示证件是真的。结果中的authenticity组显示为not_checked。
在这个循环里,search_docs 值得一用。它读取随服务器附带的文档,离线可用,而且不花钱,所以像"unsupported_document 是什么意思"或"身份证有哪些字段"这样的问题,得到的是文档中的答案,而不是猜测。
工作流 3:只记录一次测试夹具
任务。 你自己的代码会调用 API,但它的测试不应该调用。每次运行都把护照发送到线上服务的测试套件很慢,需要在 CI 中配置密钥,而且在沙盒上很快就会碰到限制:每小时 10 次请求,以及同一份证件提交次数过多时的 document_repeated 拒绝。
办法是把代码必须处理的每种结果各记录一次,保存为 JSON,再让测试针对这些文件运行。
提示词,在 Claude Code 或 Cursor 中都可以:
用 scan_document 扫描 td3-specimen.jpg 和 blank-page.png,并把每个结构化结果保存为 test/fixtures/scan-
.json。把所有个人信息值(姓名、号码、出生日期、MRZ 行)替换成同类型的占位符,并删除 images 对象中的 data URL。
该工具的结构化结果与 HTTP API 返回的 Scan 对象相同,因此夹具与你的代码在生产环境中得到的内容一致。脱敏后的夹具如下:
{
"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<…" }
}
(此处有删节;请保留真实结果中的每个键,这样当你的代码读取缺失的键时,测试就会失败。)
让夹具如实可靠的三个细节:
- 把失败也记录下来。 一张空白页会得到
no_document_found,billed: false,数据组为null。能抓住那些假定扫描结果总有持证人的代码的,正是这个文件。 - 在沙盒上,
billed是一个预测。 沙盒上不会产生任何费用,这个标志表示同样的扫描在正式密钥下是否会被计费。这正是计费测试需要断言的内容。 - 使用正式密钥时,读回而不是重新扫描。
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
提交之前先对文件脱敏。
各工作流需要什么
| 密钥 | 服务器 | 图片发往 API 的来源 | |
|---|---|---|---|
| 文件夹转 CSV | 你自己的密钥(超过 10 份证件时) | 本地,npx |
有边界的目录 |
| 表单核对 | 沙盒即可 | 托管或本地 | URL 或有边界的目录 |
| 测试夹具 | 沙盒即可 | 均可 | 有边界的目录,一次 |
每个编辑器的配置,包括 Windsurf、VS Code、Gemini CLI 和 Kiro,见面向 Claude 和 Cursor 的 OCR MCP 服务器页面和 MCP 服务器指南。如果你还在挑选服务,护照 OCR API 对比根据各家公开的信息,把 doc.cheap 与其他服务放在一起比较。
如果其中某个工作流在你的编辑器中出了问题,或者你有值得写成文章的第四种工作流,请写信到 admin@doc.cheap。
想问问你: 你最常交给编辑器智能体的是什么 – 一次一份证件,还是整个文件夹?欢迎在下方评论区告诉我们。