Mais cedo ou mais tarde alguém solta a foto de um passaporte no chat com um assistente e pede para ele "só preencher o formulário". Um modelo de visão de uso geral vai tentar. Pode até acertar o nome. O que ele não vai fazer é dizer se os dígitos verificadores da zona de leitura mecânica (MRZ) bateram, devolver as datas sempre no mesmo formato ou dizer "não consegui ler isto" em vez de chutar.

Essa lacuna é um bom caso para uma ferramenta. O Model Context Protocol permite que um agente chame uma, e este post mostra como dar ao Claude Desktop, ao Claude Code, ao Cursor e a outros clientes uma ferramenta de reconhecimento de documentos, o que o agente recebe de volta, como manter o custo sob controle e no que pensar antes de apontar um agente para documentos de identidade.

Este é o blog do doc.cheap, a API por trás do servidor MCP usado aqui. O servidor tem licença MIT, e as questões de configuração valem para qualquer ferramenta desse tipo.

O que o agente recebe

O servidor é o @doc-cheap/mcp no npm (MIT, Node 20 ou superior), e ele expõe três ferramentas:

Ferramenta O que faz Gasta crédito?
scan_document Reconhece um passaporte, carteira de identidade ou carteira de motorista a partir de uma foto ou digitalização e devolve o resultado estruturado Sim, só quando um documento é reconhecido
check_balance Lê os créditos restantes e os contadores deste mês Não (somente leitura)
search_docs Pesquisa a documentação da API que vem junto com o servidor, offline Não (somente leitura)

Cada ferramenta traz um título, uma descrição (as duas que mexem no estado da conta informam o preço) e as dicas de comportamento do MCP que um cliente lê antes de decidir se pergunta a você primeiro: scan_document é marcada como não somente leitura, e as outras duas como somente leitura. O servidor também envia instruções que o modelo lê antes de qualquer chamada, dizendo o que ele reconhece e quanto custa uma chamada. Além das ferramentas há quatro prompts (scan_document_to_json, check_document_expiry, batch_scan, explain_error), e cada página da documentação é exposta como um recurso somente leitura, por exemplo doccheap://docs/reference/fields.

scan_document responde com o resultado completo em JSON estruturado mais um resumo de uma linha, por exemplo:

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

(Um titular de espécime inventado da documentação.) O JSON por trás traz o titular, o número do documento e as datas em ISO 8601, cada campo com uma faixa de confiança, as linhas da MRZ com um veredito passed / failed / absent, e um flag billed. Essa estrutura é o ponto: o agente não precisa interpretar pixels, ele lê campos.

Instalação: o servidor local

Todos os clientes abaixo iniciam o servidor com npx. Sem chave configurada, ele usa a chave pública de sandbox, que dá 10 documentos reconhecidos grátis por endereço IP no total e no máximo 10 requisições por hora. É o suficiente para testar. O cadastro dá 20 créditos grátis; depois disso, um documento reconhecido custa $0.01.

Claude Desktop, Cursor e Windsurf leem o mesmo bloco, em claude_desktop_config.json, ~/.cursor/mcp.json e ~/.codeium/windsurf/mcp_config.json, respectivamente:

{
  "mcpServers": {
    "doc-cheap": {
      "command": "npx",
      "args": ["-y", "@doc-cheap/mcp"],
      "env": { "DOC_CHEAP_API_KEY": "sk_live_your_key" }
    }
  }
}

Deixe de fora a linha env para rodar com a chave de 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"]}'

O Gemini CLI e o Kiro usam o mesmo bloco mcpServers nos seus próprios arquivos de configuração; o guia de MCP da documentação (em inglês) traz o caminho de cada um. Reinicie o cliente depois de editar: o servidor só percebe um ambiente alterado quando é iniciado de novo.

Instalação: o servidor hospedado

Se o seu cliente se conecta a uma URL em vez de iniciar um comando, as mesmas três ferramentas estão hospedadas em https://mcp.doc.cheap/mcp via Streamable HTTP, sem login. A chave vai num cabeçalho, X-Doc-Cheap-Api-Key ou Authorization: Bearer (se os dois forem enviados, vale o cabeçalho nomeado); sem chave, é usada a chave de 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" }
    }
  }
}

No Claude Desktop e no claude.ai, adicione-o em Settings → Connectors como um conector personalizado com essa URL.

O servidor hospedado não enxerga os arquivos da sua máquina, então ali o scan_document recebe a imagem como image_base64 ou como uma image_url pública.

Arquivos locais e URLs têm cerca de propósito

O argumento de uma ferramenta é escolhido por um modelo, e um modelo pode ser convencido de muita coisa. Por isso o servidor local não lê caminhos arbitrários:

  • image_path fica desligado até você apontar DOC_CHEAP_IMAGE_ROOT para um diretório. Os caminhos são resolvidos primeiro através dos links simbólicos, e .. ou um link que aponte para fora do diretório é recusado. Um arquivo inexistente e um arquivo fora dos limites recebem a mesma mensagem, então a ferramenta não pode ser usada para sondar se um arquivo existe.
  • image_url precisa ser https:, precisa resolver apenas para endereços públicos (loopback, faixas privadas, link-local e similares são recusadas), segue no máximo três redirecionamentos, com cada salto verificado de novo, e tem limite de 25 MB.

Se você já conectou uma ferramenta de leitura de arquivos a um agente, compare-a com esta lista. "O modelo só vai passar caminhos sensatos" não é uma fronteira de segurança.

Mantendo o custo sob controle

Duas propriedades tornam previsível o uso por agentes:

  1. Só documentos reconhecidos são cobrados. Uma chamada é cobrada quando o tipo do documento foi determinado e dados foram de fato extraídos: uma MRZ cujos dígitos verificadores batem, pelo menos cinco campos impressos ou um código de barras decodificado corretamente. Documento não encontrado, imagem ilegível, tipo não suportado, erro interno ou timeout: nada disso custa. O flag billed do resultado diz o que aconteceu, sempre. Na chave de sandbox nada é cobrado, e o flag então diz se a mesma leitura teria sido cobrada numa chave de produção.
  2. Retentativas podem sair de graça. scan_document aceita um idempotency_key; numa chave de produção, uma repetição com a mesma chave devolve o primeiro resultado armazenado em vez de cobrar de novo. Uma retentativa sem chave é uma segunda leitura. Uma repetição devolve o resultado armazenado, então é retain_hours: 0 ou retentativas repetíveis, não os dois.

Na prática:

  • Faça o agente chamar check_balance antes de um lote. As próprias instruções do servidor mandam o modelo fazer isso, e o prompt batch_scan faz isso primeiro. Com a chave de sandbox o saldo é null, e a ferramenta diz que não há saldo em vez de mostrar zeros.
  • Aprove automaticamente só as ferramentas somente leitura. A configuração do Kiro, por exemplo, aceita "autoApprove": ["check_balance", "search_docs"]. Deixe o scan_document atrás de uma confirmação, porque é ele que gasta.
  • Deixe o agente consultar a documentação. search_docs funciona offline sobre a documentação embutida, então "o que significa unsupported_document" não custa nada e não depende da memória do modelo.

Privacidade: perguntas a fazer antes de fazer isso

Documentos de identidade são dos dados mais sensíveis que existem, e um agente acrescenta participantes ao fluxo. Veja o que é verdade do lado da API e o que depende da sua configuração.

Do lado da API (conforme a documentação):

  • A imagem enviada fica na memória durante a requisição e nunca é gravada em armazenamento persistente.
  • O resultado do reconhecimento é guardado para poder ser lido depois, por um período definido na conta (24 horas, 7 dias, 30 dias ou um ano). O padrão de uma conta nova é um ano. Por chamada, retain_hours: 0 não grava linha nenhuma, e o scan_document também aceita retain_hours. Se o agente só precisa da resposta uma vez, configure isso.
  • O processamento acontece na União Europeia. Os dados não são usados para treinar modelos.
  • return_portrait: false deixa de fora images.main_photo, o recorte da foto do titular. O recorte da página inteira continua vindo, e com ele a segunda cópia esmaecida do rosto que alguns documentos imprimem na página.

Do seu lado:

  • O resultado entra no contexto do modelo. O que o scan_document devolve (nomes, números, datas) passa a estar na conversa e é processado pelo provedor de LLM que roda o seu cliente, nos termos desse provedor. Isso é inerente a qualquer ferramenta MCP, não algo específico desta.
  • O caminho da imagem importa. Com image_path no servidor local, o servidor lê o arquivo e o envia direto para a API. Com image_base64, os bytes da imagem ficam nos argumentos da chamada de ferramenta do modelo. Se você quer que os pixels fiquem fora do contexto do modelo, use um diretório local cercado.
  • Isto é reconhecimento, não verificação. O grupo authenticity do resultado diz not_checked. Uma MRZ aprovada significa que a zona foi lida e é coerente consigo mesma, não que o documento seja autêntico. Não há prova de vida (liveness) nem comparação facial. Se o seu caso de uso é KYC, isto é um insumo, não a decisão.
  • Use documentos sintéticos enquanto desenvolve. Espécimes e MRZs geradas bastam para ligar tudo.

Uma sessão curta

Com o servidor instalado, basta um prompt como "Leia este passaporte digitalizado e me diga se ele vence nos próximos seis meses" com um espécime sintético anexado. Um agente bem-comportado chama scan_document, lê document.expiry_date e document.days_remaining no resultado e responde com base nesses campos, não na impressão que teve da imagem. Se a leitura voltar unreadable, ele deve dizer isso e pedir uma foto melhor, e você não foi cobrado por ela.

Esse último comportamento é o verdadeiro motivo para usar uma ferramenta aqui: o agente recebe um "não consegui ler" explícito em vez da tentação de preencher a lacuna.

Links

Se você construir algo com ele, ou encontrar um cliente em que a configuração acima não funcione, escreva para admin@doc.cheap.

Cada afirmação sobre o doc.cheap e o seu servidor MCP foi conferida com o código deles.