迟早会有人把一张护照照片丢进与助手的对话里,让它“帮忙把表填了”。通用的视觉模型会试一试,甚至可能把姓名读对。但它不会告诉您机读区(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_jsoncheck_document_expirybatch_scanexplain_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-KeyAuthorization: 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。

如果您以前为智能体接入过读取文件的工具,不妨拿它和这份清单对比一下。“模型只会传入合理的路径”不是安全边界。

把费用控制在可预期范围内

有两个特性让智能体的使用变得可预期:

  1. 只有识别成功的证件才计费。只有在确定了证件类型并真正提取出数据时才收费:MRZ 校验位通过、至少读出五个印刷字段,或者条形码被正确解码。未找到证件、图片无法读取、类型不支持、内部错误或超时都不收费。结果中的 billed 标志每次都会说明是哪种情况。使用沙箱密钥时完全不收费,此时这个标志表示同样的扫描在正式(live)密钥上是否会计费。
  2. 重试可以做到免费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_datedocument.days_remaining,并根据这些字段作答,而不是凭它对图片的印象。如果扫描结果是 unreadable,它应当如实说明并请您提供更清晰的照片,而这次扫描不会向您收费。

最后这一点,才是在这里使用工具的真正理由:智能体拿到的是明确的“无法读取”,而不是忍不住去填补空白的诱惑。

链接

如果您用它做出了什么,或者发现某个客户端上面的配置不起作用,请写信至 admin@doc.cheap

关于 doc.cheap 及其 MCP 服务器的每一项说法都已对照其代码核实。