遅かれ早かれ、誰かがアシスタントとのチャットにパスポートの写真を放り込んで、「とりあえずフォームを埋めておいて」と頼むことになります。汎用の画像認識モデルは挑戦してみるでしょう。名前は正しく読めるかもしれません。しかし、機械読取領域(MRZ)のチェックディジットが一致したかどうかを教えてくれたり、毎回同じ形式で日付を返したり、推測する代わりに「これは読み取れませんでした」と言ったりはしません。

この隙間を埋めるのにツールは打ってつけです。Model Context Protocolを使えばエージェントがツールを呼び出せます。この記事では、Claude Desktop、Claude Code、Cursorなどのクライアントに書類認識ツールを追加する方法、エージェントが受け取る内容、コストを一定の範囲に収める方法、そしてそもそもエージェントに本人確認書類を扱わせる前に考えておくべきことを説明します。

これは、ここで使うMCPサーバーの背後にあるAPI、doc.cheapのブログです。サーバーはMITライセンスで、セットアップに関する論点はこの種のどのツールにも当てはまります。

エージェントが受け取るもの

サーバーはnpmの @doc-cheap/mcp(MIT、Node 20以降)で、3つのツールを提供します。

ツール 機能 クレジットを消費するか
scan_document 写真やスキャン画像からパスポート、国民IDカード、運転免許証を認識し、構造化された結果を返す はい。書類を認識できたときだけ
check_balance 残りのクレジットと今月のカウンターを読み取る いいえ(読み取り専用)
search_docs サーバーに同梱されたAPIドキュメントをオフラインで検索する いいえ(読み取り専用)

各ツールにはタイトルと説明(アカウントの状態に関わる2つのツールでは料金を明記)、そしてクライアントが先にユーザーへ確認するかどうかを決める前に読むMCPの動作ヒントが付いています。scan_document は読み取り専用ではないと、残りの2つは読み取り専用とマークされています。サーバーは、モデルがどの呼び出しよりも先に読む指示も送り、何を認識できるか、1回の呼び出しにいくらかかるかを伝えます。ツールに加えて4つのプロンプト(scan_document_to_jsoncheck_document_expirybatch_scanexplain_error)があり、ドキュメントの各ページは doccheap://docs/reference/fields のような読み取り専用のリソースとして公開されています。

scan_document は、構造化JSONの完全な結果と1行の要約を返します。例:

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件まで、リクエストは1時間に最大10件です。試すにはこれで十分です。登録すると20クレジットが無料でもらえ、その後は認識できた書類1件につき $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" }
    }
  }
}

sandboxキーで動かすなら 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に接続する方式なら、同じ3つのツールが https://mcp.doc.cheap/mcp でStreamable HTTP経由で提供されており、ログインは不要です。キーはヘッダーで渡します。X-Doc-Cheap-Api-Key または Authorization: Bearer で、両方送った場合は名前付きのヘッダーが優先されます。キーがなければ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" }
    }
  }
}

Claude Desktopとclaude.aiでは、Settings → Connectorsでカスタムコネクタとして、このURLを追加します。

ホスティング型サーバーはあなたのマシン上のファイルを見られないので、そこでは scan_document は画像を image_base64 か公開された image_url として受け取ります。

ローカルファイルとURLは意図的に制限している

ツールの引数を選ぶのはモデルであり、モデルは言いくるめられることがあります。そのため、ローカルサーバーは任意のパスを読み取りません。

  • image_path は、DOC_CHEAP_IMAGE_ROOT に1つのディレクトリを設定するまで無効です。パスはまずシンボリックリンクをたどって解決され、.. やディレクトリの外を指すリンクは拒否されます。存在しないファイルと範囲外のファイルには同じメッセージが返るので、このツールでファイルの有無を探ることはできません。
  • image_urlhttps: でなければならず、公開アドレスにだけ解決される必要があります(ループバック、プライベート、リンクローカルなどの範囲は拒否)。リダイレクトは最大3回までで各ホップを再チェックし、サイズの上限は25 MBです。

以前にエージェント向けのファイル読み取りツールを組んだことがあるなら、このリストと比べてみてください。「モデルはまともなパスしか渡さないはず」はセキュリティ境界ではありません。

コストを一定の範囲に収める

2つの性質によって、エージェントでの利用が予測可能になります。

  1. 課金されるのは認識できた書類だけです。呼び出しが課金されるのは、書類の種類が特定され、実際にデータが抽出されたとき、つまりチェックディジットが一致するMRZ、印字された5つ以上のフィールド、または正しくデコードされたバーコードのいずれかが得られたときです。書類が見つからない、画像が読み取れない、未対応の種類、内部エラー、タイムアウトの場合は無料です。どれに当たったかは、結果の billed フラグが毎回示します。sandboxキーでは一切課金されず、その場合フラグは、同じスキャンが本番キーなら課金されたかどうかを示します。
  2. リトライを無料にできますscan_documentidempotency_key を受け付けます。本番キーでは、同じキーで繰り返すと、再び課金する代わりに保存済みの最初の結果を返します。キーなしのリトライは2回目のスキャンです。繰り返しは保存済みの結果を返すものなので、retain_hours: 0 とリプレイ可能なリトライはどちらか一方しか選べません。

実際の運用では:

  • バッチ処理の前に、エージェントに check_balance を呼ばせてください。サーバー自身の指示がモデルにそうするよう伝えており、batch_scan プロンプトは最初にそれを行います。sandboxキーでは残高は null で、ツールはゼロを表示する代わりに残高がないことを伝えます。
  • 自動承認するのは読み取り専用のツールだけにしてください。たとえばKiroの設定は "autoApprove": ["check_balance", "search_docs"] に対応しています。クレジットを消費するのは scan_document なので、これは確認プロンプトの後ろに残しておいてください。
  • 調べものはエージェントに任せましょうsearch_docs は同梱のドキュメントに対してオフラインで動くので、「unsupported_document はどういう意味か」と調べても費用はかからず、モデルの記憶にも頼りません。

プライバシー:始める前に確認すべきこと

本人確認書類は最も機密性の高いデータの1つであり、エージェントを使うとデータの流れに関わる当事者が増えます。API側で成り立っていることと、あなたの構成次第で決まることを整理します。

API側(ドキュメントに記載のとおり)

  • アップロードされた画像はリクエストの間だけメモリ上に保持され、永続ストレージには書き込まれません。
  • 認識の結果は後から読み出せるように、アカウントで設定した期間(24時間、7日、30日、1年のいずれか)保存されます。新規アカウントの既定値は1年です。呼び出しごとに retain_hours: 0 を指定すると行は一切書き込まれず、scan_documentretain_hours を受け付けます。エージェントが答えを1回だけ必要とするなら、指定してください。
  • 処理は欧州連合(EU)域内で行われます。データはモデルの学習には使われません。
  • return_portrait: false を指定すると、所持人の顔写真の切り抜きである images.main_photo が省かれます。ページ全体の切り抜きは引き続き返され、一部の書類がページに印刷している薄い2つ目の顔画像もそこに含まれます。

あなたの側

  • 結果はモデルのコンテキストに入りますscan_document が返すもの(氏名、番号、日付)はすべて会話の中に入り、クライアントを動かしているLLMプロバイダーが、そのプロバイダーの規約のもとで処理します。これはどのMCPツールにも本質的に伴うことで、このツールに固有の話ではありません。
  • 画像の渡し方が重要です。ローカルサーバーで image_path を使うと、サーバーがファイルを読み、そのままAPIに送ります。image_base64 を使うと、画像のバイトがモデルのツール呼び出しの引数に含まれます。ピクセルをモデルのコンテキストに入れたくないなら、制限付きのローカルディレクトリを使ってください。
  • これは認識であって、検証ではありません。結果の authenticity グループは not_checked です。MRZが一致したということは、領域が読み取られ、中身に矛盾がないということであって、書類が本物だということではありません。生体検知(ライブネス)や顔照合のステップもありません。ユースケースがKYCなら、これは判断材料の1つであって、判断そのものではありません。
  • 開発中は合成の書類を使ってください。見本と生成したMRZがあれば、すべてをつなぎ込むには十分です。

短いセッション例

サーバーをインストールしたら、合成の見本を添付して「このパスポートのスキャンを読み取って、今後6か月以内に有効期限が切れるかどうか教えて」のようなプロンプトを送るだけです。行儀のよいエージェントは scan_document を呼び出し、結果から document.expiry_datedocument.days_remaining を読み取り、画像から受けた印象ではなくそれらのフィールドをもとに答えます。スキャン結果が unreadable なら、そう伝えてもっとよい写真を求めるはずで、その分の料金はかかっていません。

この最後の振る舞いこそ、ここでツールを使う本当の理由です。エージェントは隙間を埋めたくなる誘惑の代わりに、明示的な「読み取れませんでした」を受け取れます。

リンク

これを使って何かを作ったり、上の設定が動かないクライアントを見つけたりしたら、admin@doc.cheap までお知らせください。

doc.cheapとそのMCPサーバーに関する記述はすべて、それぞれのコードと照合して確認済みです。