Sớm hay muộn cũng sẽ có người thả ảnh một cuốn hộ chiếu vào cuộc trò chuyện với trợ lý và bảo nó “điền giúp cái form”. Một mô hình thị giác đa dụng sẽ thử làm. Có khi nó còn đọc đúng cả tên. Điều nó sẽ không làm là cho bạn biết chữ số kiểm tra của vùng đọc máy (MRZ) có khớp hay không, trả về ngày tháng theo cùng một định dạng mọi lúc, hay nói “tôi không đọc được cái này” thay vì đoán.

Khoảng trống đó rất hợp để giao cho một công cụ. Model Context Protocol cho phép agent gọi công cụ, và bài viết này hướng dẫn cách trang bị cho Claude Desktop, Claude Code, Cursor và các client khác một công cụ nhận dạng giấy tờ, agent nhận lại được gì, làm sao giữ chi phí trong giới hạn, và cần cân nhắc những gì trước khi để một agent xử lý giấy tờ tùy thân.

Đây là blog của doc.cheap, API đứng sau MCP server được dùng trong bài. Server này dùng giấy phép MIT, và các câu hỏi về thiết lập áp dụng cho mọi công cụ thuộc loại này.

Agent nhận được gì

Server là @doc-cheap/mcp trên npm (MIT, Node 20 trở lên), và nó cung cấp ba công cụ:

Công cụ Chức năng Có tốn credit không?
scan_document Nhận dạng hộ chiếu, thẻ căn cước hoặc giấy phép lái xe từ ảnh chụp hay bản quét và trả về kết quả có cấu trúc Có, chỉ khi nhận dạng được giấy tờ
check_balance Đọc số credit còn lại và các bộ đếm của tháng này Không (chỉ đọc)
search_docs Tìm trong tài liệu API đi kèm server, offline Không (chỉ đọc)

Mỗi công cụ có tiêu đề, mô tả (hai công cụ chạm vào trạng thái tài khoản có ghi giá), và các gợi ý hành vi MCP mà client đọc trước khi quyết định có hỏi bạn trước hay không: scan_document được đánh dấu là không chỉ đọc, hai công cụ còn lại là chỉ đọc. Server cũng gửi hướng dẫn mà mô hình đọc trước mọi lần gọi, nói rõ nó nhận dạng được gì và một lần gọi tốn bao nhiêu. Ngoài các công cụ còn có bốn prompt (scan_document_to_json, check_document_expiry, batch_scan, explain_error), và mọi trang tài liệu được cung cấp dưới dạng resource chỉ đọc, chẳng hạn doccheap://docs/reference/fields.

scan_document trả về toàn bộ kết quả dưới dạng JSON có cấu trúc kèm một dòng tóm tắt, ví dụ:

Scan 01a0af18-cd8d-7a61-9f2d-4c7b8e105da3: recognized · passport (GRC) · PARADEIGMA ELENI SOFIA · billed · 684 ms

(Người mang giấy tờ là một mẫu hư cấu lấy từ tài liệu.) JSON phía sau chứa thông tin người mang giấy tờ, số giấy tờ và ngày tháng theo ISO 8601, mọi trường kèm mức độ tin cậy, các dòng MRZ với kết luận passed / failed / absent, và cờ billed. Cấu trúc đó mới là điểm mấu chốt: agent không phải diễn giải điểm ảnh, nó đọc các trường.

Cài đặt: server chạy cục bộ

Mọi client bên dưới đều khởi chạy server bằng npx. Khi chưa đặt khóa, server dùng khóa sandbox công khai, cho tổng cộng 10 giấy tờ được nhận dạng miễn phí trên mỗi địa chỉ IP và tối đa 10 request mỗi giờ. Như vậy là đủ để dùng thử. Đăng ký sẽ được 20 credit miễn phí; sau đó mỗi giấy tờ được nhận dạng có giá $0.01.

Claude Desktop, Cursor và Windsurf đọc cùng một khối cấu hình, lần lượt trong 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" }
    }
  }
}

Bỏ dòng env để chạy bằng khóa sandbox.

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 và Kiro dùng cùng khối mcpServers trong file cài đặt riêng của chúng; hướng dẫn MCP (bằng tiếng Anh) có đường dẫn cho từng client. Khởi động lại client sau khi sửa: server chỉ nhận biến môi trường đã thay đổi khi được khởi chạy lại từ đầu.

Cài đặt: server được host sẵn

Nếu client của bạn kết nối tới một URL thay vì chạy một lệnh, ba công cụ đó cũng được host tại https://mcp.doc.cheap/mcp qua Streamable HTTP, không cần đăng nhập. Khóa được đặt trong header, X-Doc-Cheap-Api-Key hoặc Authorization: Bearer (header có tên riêng được ưu tiên nếu gửi cả hai); không có khóa thì dùng khóa sandbox.

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

Trong Claude Desktop và claude.ai, hãy thêm nó ở Settings → Connectors dưới dạng custom connector với URL đó.

Server được host không nhìn thấy file trên máy bạn, nên ở đó scan_document nhận ảnh qua image_base64 hoặc qua một image_url công khai.

File cục bộ và URL được rào chắn có chủ đích

Tham số của công cụ do mô hình chọn, và mô hình thì có thể bị dụ dỗ. Vì vậy server cục bộ không đọc đường dẫn tùy ý:

  • image_path bị tắt cho đến khi bạn đặt DOC_CHEAP_IMAGE_ROOT trỏ vào một thư mục. Đường dẫn được phân giải qua symlink trước, và .. hoặc một liên kết trỏ ra ngoài thư mục đều bị từ chối. File không tồn tại và file nằm ngoài phạm vi nhận cùng một thông báo, nên không thể dùng công cụ để dò xem một file có tồn tại hay không.
  • image_url phải là https:, chỉ được phân giải tới địa chỉ công khai (loopback, private, link-local và các dải tương tự bị từ chối), đi theo tối đa ba lần chuyển hướng với mỗi bước đều được kiểm tra lại, và giới hạn ở 25 MB.

Nếu trước đây bạn từng nối một công cụ đọc file cho agent, hãy so sánh nó với danh sách này. “Mô hình sẽ chỉ truyền đường dẫn hợp lý” không phải là ranh giới bảo mật.

Giữ chi phí trong giới hạn

Hai đặc tính giúp việc dùng agent trở nên dễ đoán:

  1. Chỉ giấy tờ được nhận dạng mới bị tính phí. Một lần gọi bị tính phí khi xác định được loại giấy tờ và thực sự trích xuất được dữ liệu: một MRZ có chữ số kiểm tra hợp lệ, ít nhất năm trường in, hoặc một mã vạch được giải mã đúng. Không tìm thấy giấy tờ, ảnh không đọc được, loại không hỗ trợ, lỗi nội bộ hay timeout đều không mất phí. Cờ billed của kết quả luôn cho biết điều gì đã xảy ra. Với khóa sandbox thì không có gì bị tính phí, và khi đó cờ này cho biết cùng lượt quét đó có bị tính phí trên khóa live hay không.
  2. Thử lại có thể được miễn phí. scan_document nhận một idempotency_key; với khóa live, lần lặp lại với cùng key sẽ trả về kết quả đầu tiên đã lưu thay vì tính phí lần nữa. Thử lại không có key là một lượt quét thứ hai. Lần lặp lại trả về kết quả đã lưu, nên retain_hours: 0 và thử lại có phát lại chỉ chọn được một trong hai.

Trong thực tế:

  • Cho agent gọi check_balance trước một lô. Chính hướng dẫn của server bảo mô hình làm vậy, và prompt batch_scan làm việc này đầu tiên. Với khóa sandbox, số dư là null, và công cụ nói rằng không có số dư thay vì hiển thị số 0.
  • Chỉ tự động phê duyệt các công cụ chỉ đọc. Ví dụ, cấu hình của Kiro hỗ trợ "autoApprove": ["check_balance", "search_docs"]. Hãy để scan_document sau một bước xác nhận, vì đó là công cụ tiêu tiền.
  • Để agent tự tra cứu. search_docs hoạt động offline trên bộ tài liệu đi kèm, nên câu hỏi “unsupported_document nghĩa là gì” không tốn gì và không phụ thuộc vào trí nhớ của mô hình.

Quyền riêng tư: những câu cần hỏi trước khi làm việc này

Giấy tờ tùy thân thuộc loại dữ liệu nhạy cảm bậc nhất, và agent thêm các bên mới vào luồng xử lý. Dưới đây là những gì đúng ở phía API, và những gì phụ thuộc vào cách bạn thiết lập.

Ở phía API (theo tài liệu):

  • Ảnh tải lên được giữ trong bộ nhớ trong suốt request và không bao giờ được ghi vào bộ lưu trữ lâu dài.
  • Kết quả nhận dạng được giữ lại để có thể đọc lại sau, trong một khoảng thời gian đặt ở tài khoản (24 giờ, 7 ngày, 30 ngày hoặc một năm). Mặc định cho tài khoản mới là một năm. Theo từng lần gọi, retain_hours: 0 không ghi dòng nào cả, và scan_document cũng nhận retain_hours. Nếu agent chỉ cần câu trả lời một lần, hãy đặt tham số này.
  • Việc xử lý diễn ra tại Liên minh châu Âu. Dữ liệu không được dùng để huấn luyện mô hình.
  • return_portrait: false bỏ images.main_photo, tức ảnh cắt chân dung của người mang giấy tờ. Ảnh cắt của toàn bộ trang vẫn được trả về, cùng với bản sao mờ thứ hai của khuôn mặt mà một số giấy tờ in trên trang.

Ở phía bạn:

  • Kết quả đi vào ngữ cảnh của mô hình. Bất cứ thứ gì scan_document trả về (tên, số, ngày tháng) giờ đã nằm trong cuộc trò chuyện, và được xử lý bởi nhà cung cấp LLM đang chạy client của bạn, theo điều khoản của nhà cung cấp đó. Điều này vốn có ở mọi công cụ MCP, không riêng công cụ này.
  • Cách ảnh được truyền đi rất quan trọng. Với image_path trên server cục bộ, server đọc file và gửi thẳng tới API. Với image_base64, các byte của ảnh nằm trong tham số lời gọi công cụ của mô hình. Nếu bạn muốn điểm ảnh nằm ngoài ngữ cảnh của mô hình, hãy dùng một thư mục cục bộ đã được rào chắn.
  • Đây là nhận dạng, không phải xác minh. Nhóm authenticity của kết quả ghi not_checked. MRZ khớp nghĩa là vùng này đã được đọc và nhất quán nội bộ, không có nghĩa giấy tờ là thật. Không có bước kiểm tra liveness hay so khớp khuôn mặt. Nếu trường hợp sử dụng của bạn là KYC, đây là một dữ liệu đầu vào, không phải quyết định.
  • Dùng giấy tờ tổng hợp trong lúc phát triển. Các mẫu và MRZ được tạo tự động là đủ để nối mọi thứ lại với nhau.

Một phiên làm việc ngắn

Khi đã cài server, một prompt như “Đọc bản quét hộ chiếu này và cho tôi biết nó có hết hạn trong sáu tháng tới không” kèm một mẫu tổng hợp là đủ. Một agent hoạt động đúng sẽ gọi scan_document, đọc document.expiry_datedocument.days_remaining từ kết quả, rồi trả lời dựa trên các trường đó chứ không dựa trên ấn tượng của nó về bức ảnh. Nếu lượt quét trả về unreadable, agent nên nói rõ và xin một bức ảnh tốt hơn, và bạn không bị tính phí cho lượt đó.

Hành vi cuối cùng đó mới là lý do thực sự để dùng một công cụ ở đây: agent nhận được câu “không đọc được” rõ ràng thay vì bị cám dỗ tự lấp chỗ trống.

Liên kết

Nếu bạn xây dựng thứ gì đó với nó, hoặc gặp một client mà cấu hình ở trên không chạy được, hãy viết cho admin@doc.cheap.

Mọi nhận định về doc.cheap và MCP server của nó đều đã được đối chiếu với code của chúng.