迟早会有人把一张护照照片丢进与助手的对话里,让它“帮忙把表填了”。通用的视觉模型会试一试,甚至可能把姓名读对。但它不会告诉您机读区(MRZ)的校验位是否通过,不会每次都用同一种格式返回日期,也不会在读不出来时说“我没能读出这份证件”,而是会去猜。
这个缺口正适合交给一个工具来填补。Model Context Protocol(MCP)让智能体可以调用工具。本文介绍如何为 Claude Desktop、Claude Code、Cursor 等客户端添加一个证件识别工具、智能体会拿到什么、如何把费用控制在可预期的范围内,以及在让智能体处理身份证件之前需要考虑哪些问题。
这里是 doc.cheap 的博客,doc.cheap 就是本文所用 MCP 服务器背后的 API。该服务器采用 MIT 许可证,文中关于配置的问题同样适用于任何同类工具。
智能体能拿到什么
服务器是 npm 上的 @doc-cheap/mcp(MIT,需要 Node 20 或更高版本),它提供三个工具:
| 工具 | 作用 | 是否消耗点数? |
|---|---|---|
scan_document |
从照片或扫描件中识别护照、国民身份证或驾驶证,并返回结构化结果 | 是,仅在识别出证件时 |
check_balance |
读取剩余点数和本月的计数 | 否(只读) |
search_docs |
离线搜索服务器自带的 API 文档 | 否(只读) |
每个工具都带有标题、描述(与账户相关的两个工具会在描述中写明价格),以及客户端在决定是否先征求您同意之前会读取的 MCP 行为提示:scan_document 标记为非只读,另外两个为只读。服务器还会发送一段说明,模型在任何调用之前都会读到,其中写明它能识别什么、一次调用花费多少。除了工具之外,还有四个提示词(scan_document_to_json、check_document_expiry、batch_scan、explain_error),每一页文档也都作为只读资源提供,例如 doccheap://docs/reference/fields。
scan_document 返回完整的结构化 JSON 结果,外加一行摘要,例如:
Scan 01a0af18-cd8d-7a61-9f2d-4c7b8e105da3: recognized · passport (GRC) · PARADEIGMA ELENI SOFIA · billed · 684 ms
(文档中虚构的样本持有人。)其背后的 JSON 包含持有人信息、证件号码、ISO 8601 格式的日期、带置信度等级的每个字段、附带 passed / failed / absent 判定的 MRZ 行,以及一个 billed 标志。关键就在这种结构:智能体不必去解读像素,它读的是字段。
安装:本地服务器
下面的每个客户端都用 npx 启动服务器。不设置密钥时,它会使用公共沙箱(sandbox)密钥:每个 IP 地址总共可免费识别 10 份证件,每小时最多 10 次请求。这足够试用。注册后赠送 20 个免费点数;之后每识别一份证件收费 $0.01。
Claude Desktop、Cursor 和 Windsurf 读取的是同一段配置,分别位于 claude_desktop_config.json、~/.cursor/mcp.json 和 ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"doc-cheap": {
"command": "npx",
"args": ["-y", "@doc-cheap/mcp"],
"env": { "DOC_CHEAP_API_KEY": "sk_live_your_key" }
}
}
}
去掉 env 这一行,就会使用沙箱密钥运行。
Claude Code:
claude mcp add-json doc-cheap '{"command":"npx","args":["-y","@doc-cheap/mcp"],"env":{"DOC_CHEAP_API_KEY":"sk_live_your_key"}}'
VS Code:
code --add-mcp '{"name":"doc-cheap","command":"npx","args":["-y","@doc-cheap/mcp"]}'
Gemini CLI 和 Kiro 在各自的设置文件中使用同样的 mcpServers 配置段;MCP 指南(英文)列出了每个客户端的路径。修改后请重启客户端:服务器只有在重新启动时才会读取变更后的环境变量。
安装:托管服务器
如果您的客户端是连接一个 URL 而不是启动一条命令,同样的三个工具也托管在 https://mcp.doc.cheap/mcp,通过 Streamable HTTP 提供,无需登录。密钥放在请求头中,X-Doc-Cheap-Api-Key 或 Authorization: Bearer 均可(两者都发送时以前者为准);不提供密钥时使用沙箱密钥。
Claude Code:
claude mcp add --transport http doc-cheap https://mcp.doc.cheap/mcp --header "Authorization: Bearer sk_live_your_key"
Cursor:
{
"mcpServers": {
"doc-cheap": {
"url": "https://mcp.doc.cheap/mcp",
"headers": { "Authorization": "Bearer sk_live_your_key" }
}
}
}
在 Claude Desktop 和 claude.ai 中,到 Settings → Connectors(设置 → 连接器)里用这个 URL 添加一个自定义连接器。
托管服务器看不到您机器上的文件,所以在那里 scan_document 通过 image_base64 或公开的 image_url 接收图片。
本地文件和 URL 是有意设限的
工具的参数由模型选择,而模型是可以被说服去做各种事情的。所以本地服务器不会读取任意路径:
image_path默认关闭,直到您把DOC_CHEAP_IMAGE_ROOT设置为某一个目录。路径会先解析符号链接,..或指向目录之外的链接都会被拒绝。文件不存在和文件越界返回的是同一条消息,因此无法利用这个工具探测某个文件是否存在。image_url必须是https:,只能解析到公网地址(回环、私有、链路本地等地址段都会被拒绝),最多跟随三次重定向且每一跳都会重新检查,大小上限为 25 MB。
如果您以前为智能体接入过读取文件的工具,不妨拿它和这份清单对比一下。“模型只会传入合理的路径”不是安全边界。
把费用控制在可预期范围内
有两个特性让智能体的使用变得可预期:
- 只有识别成功的证件才计费。只有在确定了证件类型并真正提取出数据时才收费:MRZ 校验位通过、至少读出五个印刷字段,或者条形码被正确解码。未找到证件、图片无法读取、类型不支持、内部错误或超时都不收费。结果中的
billed标志每次都会说明是哪种情况。使用沙箱密钥时完全不收费,此时这个标志表示同样的扫描在正式(live)密钥上是否会计费。 - 重试可以做到免费。
scan_document接受idempotency_key;使用正式密钥时,用同一个键重复调用会返回已保存的第一次结果,而不会再次扣费。不带键的重试就是第二次扫描。重放返回的是已保存的结果,所以retain_hours: 0和可重放的重试只能二选一。
实践中:
- 让智能体在批量处理前调用
check_balance。服务器自己的说明就要求模型这样做,batch_scan提示词也会先做这一步。使用沙箱密钥时余额为null,工具会直接说明没有余额,而不是显示一串零。 - 只对只读工具开启自动批准。例如 Kiro 的配置支持
"autoApprove": ["check_balance", "search_docs"]。请让scan_document保留确认提示,因为花钱的就是它。 - 让智能体自己查资料。
search_docs离线搜索自带的文档,所以问一句“unsupported_document是什么意思”不花钱,也不依赖模型的记忆。
隐私:动手之前要问的问题
身份证件几乎是最敏感的一类数据,而智能体会给数据流增加更多参与方。下面分别说明 API 一侧的实际情况,以及取决于您自己配置的部分。
API 一侧(依据文档):
- 上传的图片在请求期间保存在内存中,从不写入持久存储。
- 识别结果会被保存,以便之后再次读取,保存时长在账户中设置(24 小时、7 天、30 天或一年)。新账户的默认值是一年。按调用设置时,
retain_hours: 0表示完全不写入任何记录,scan_document同样接受retain_hours。如果智能体只需要用一次结果,请设置它。 - 处理在欧盟境内进行。数据不会用于训练模型。
return_portrait: false会省略images.main_photo,即持有人照片的裁剪图。整页的裁剪图仍会返回,一些证件印在页面上的那张淡淡的第二张人像也同样包含在内。
您这一侧:
- 结果会进入模型的上下文。
scan_document返回的任何内容(姓名、号码、日期)都会出现在对话中,并由运行您客户端的那家 LLM 提供商按其条款处理。这是任何 MCP 工具固有的特点,并非这个工具独有。 - 图片的传递方式很重要。在本地服务器上使用
image_path时,服务器读取文件并直接发送给 API。使用image_base64时,图片的字节就在模型的工具调用参数里。如果您希望像素不进入模型的上下文,请使用设了限制的本地目录。 - 这是识别,不是核验。结果中的
authenticity组显示为not_checked。MRZ 通过只说明这个区域被读取且内部一致,并不说明证件是真的。这里没有活体检测,也没有人脸比对。如果您的场景是 KYC,这只是其中一项输入,而不是最终决定。 - 开发期间请使用合成证件。用样本和生成的 MRZ 就足以把所有环节连通。
一段简短的会话
装好服务器后,只需附上一份合成样本,再发一句类似 “读取这份护照扫描件,告诉我它是否会在未来六个月内过期” 的提示词即可。一个表现良好的智能体会调用 scan_document,从结果中读取 document.expiry_date 和 document.days_remaining,并根据这些字段作答,而不是凭它对图片的印象。如果扫描结果是 unreadable,它应当如实说明并请您提供更清晰的照片,而这次扫描不会向您收费。
最后这一点,才是在这里使用工具的真正理由:智能体拿到的是明确的“无法读取”,而不是忍不住去填补空白的诱惑。
链接
- 包含所有客户端配置片段的 MCP 页面:https://doc.cheap/mcp
- 完整指南:https://doc.cheap/docs/guides/use-the-mcp-server
- 源代码(MIT):https://gitlab.com/doccheap/ocr-mcp
- npm:https://www.npmjs.com/package/@doc-cheap/mcp
如果您用它做出了什么,或者发现某个客户端上面的配置不起作用,请写信至 admin@doc.cheap。
关于 doc.cheap 及其 MCP 服务器的每一项说法都已对照其代码核实。