Skip to main content
Glama

doc.cheap MCP server – passport, ID card and MRZ OCR for MCP clients

MCP Registry Smithery cursor.directory npm

Give your assistant a passport, national ID card or driver's licence and get the printed fields back as structured JSON – $0.01 per recognised document, and free to try before you register. Without a key, the public sandbox key is used. It gives 10 free recognised documents per address in all, and at most 10 requests per address an hour, whatever their answer. Registering gives 100 free documents every month.

An MCP server over stdio, for Claude Desktop, Claude Code, Cursor, VS Code, Gemini CLI, Windsurf, Kiro and any other MCP client – and the same server hosted at https://mcp.doc.cheap/mcp for clients that connect to a URL instead. It is a thin client of the public doc.cheap HTTP API and a copy of the documentation: it holds no data of its own.

npx -y @doc-cheap/mcp

Hosted – nothing to install

https://mcp.doc.cheap/mcp serves the same six tools over Streamable HTTP. No login: send your key as X-Doc-Cheap-Api-Key: sk_live_your_key or Authorization: Bearer sk_live_your_key – the named header wins when both are sent, and an Authorization that is not a doc.cheap key is ignored rather than passed on. Without a key, the public sandbox key is used. It gives 10 free recognised documents per address in all, and at most 10 requests per address an hour, whatever their answer. Registering gives 100 free documents every month. The hosted server cannot read files on your machine, so scan_document takes the image as image_base64 or image_url; image_path is for the local server only.

Claude Code:

claude mcp add --transport http doc-cheap https://mcp.doc.cheap/mcp --header "Authorization: Bearer sk_live_your_key"

Claude Desktop and claude.ai: Settings → Connectors → Add custom connector, URL https://mcp.doc.cheap/mcp (no key: the sandbox key is used).

Cursor, ~/.cursor/mcp.json:

{
  "mcpServers": {
    "doc-cheap": {
      "url": "https://mcp.doc.cheap/mcp",
      "headers": { "Authorization": "Bearer sk_live_your_key" }
    }
  }
}

VS Code, .vscode/mcp.json (the key is asked for once and stored by VS Code):

{
  "servers": {
    "doc-cheap": {
      "type": "http",
      "url": "https://mcp.doc.cheap/mcp",
      "headers": { "Authorization": "Bearer ${input:doc-cheap-key}" }
    }
  },
  "inputs": [
    {
      "type": "promptString",
      "id": "doc-cheap-key",
      "description": "doc.cheap API key",
      "password": true
    }
  ]
}

Related MCP server: document-intelligence-server

Install locally

Set DOC_CHEAP_API_KEY to your key. Without a key, the public sandbox key is used. It gives 10 free recognised documents per address in all, and at most 10 requests per address an hour, whatever their answer. Registering gives 100 free documents every month. The sandbox key has no balance.

Claude Desktop – one-click extension

Download doc-cheap-<version>.mcpb from the latest release and open it, or drag it into the Claude Desktop window. The install screen asks for two optional settings: your API key (stored as a secret; leave it empty for the sandbox key) and the one folder image_path may read images from (leave it empty and no local file is read). Node.js ships with Claude Desktop, so nothing else needs installing.

Claude Desktop – by hand

claude_desktop_config.json:

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

Claude Code

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

Cursor

~/.cursor/mcp.json (or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "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"]}'

Or .vscode/mcp.json, which nests servers under servers rather than mcpServers:

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

Gemini CLI

~/.gemini/settings.json:

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

The repository also carries gemini-extension.json, so it installs as a Gemini CLI extension without writing settings by hand.

Windsurf

~/.codeium/windsurf/mcp_config.json:

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

Kiro

.kiro/settings/mcp.json in the workspace, or ~/.kiro/settings/mcp.json:

{
  "mcpServers": {
    "doc-cheap": {
      "command": "npx",
      "args": ["-y", "@doc-cheap/mcp"],
      "env": { "DOC_CHEAP_API_KEY": "${DOC_CHEAP_API_KEY}" },
      "disabled": false,
      "autoApprove": ["check_balance", "search_docs"]
    }
  }
}

Kiro also installs from a one-click link, which writes that block for you – it asks for confirmation first and shows the command and argument list it is about to add:

https://kiro.dev/launch/mcp/add?name=doc-cheap&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40doc-cheap%2Fmcp%22%5D%2C%22disabled%22%3Afalse%7D

(config is the URL-encoded JSON of {"command":"npx","args":["-y","@doc-cheap/mcp"],"disabled":false}.)

Claude Code plugin

The repository carries .claude-plugin/plugin.json, so it installs as a Claude Code plugin rather than as a hand-written server entry.

Every client starts the server as a process, so an edited configuration takes effect on the client's next launch.

Tools

Tool

Title

Read-only

Reaches the network

scan_document

Recognise a passport or ID document

no

yes

check_balance

Check remaining credits

yes

yes

search_docs

Search the doc.cheap API documentation

yes

no

list_scans

List stored scans

yes

yes

get_scan

Fetch a stored scan

yes

yes

delete_scan

Delete a stored scan – cannot be undone

no

yes

scan_document

Recognise a passport, national ID card or driver's licence and return what is printed on it. Give it the image as image_base64, image_path or image_url, plus the optional expect_country, return_portrait, retain_hours, reference and idempotency_key.

{
  "image_base64": "/9j/4AAQSkZJRgABAQ…",
  "expect_country": "GRC",
  "idempotency_key": "order-4711-front"
}

It answers with the whole Scan as structured JSON – meta (id, status, billed, confidence, timing), document (kind, issuing country, number, series, date of issue, date of expiry, whether it has expired and how many days are left), holder (given names, surname, date of birth, sex, nationality), fields (every field read off the printed page, each with its own confidence), mrz (whether the machine-readable zone checks out, why not when it does not, and its lines exactly as read), images, quality and authenticity – and a one-line summary of the same result:

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

One recognised document costs one credit, $0.01. An unreadable image, an empty frame or an unsupported type costs nothing, and meta.billed says which happened. Sending the same idempotency_key again returns the first result rather than recognising and charging a second time.

check_balance

No arguments. Returns the balance – this month's free credits (100 every month, drawn first) and the paid credits, each on its own – the credits spent and this period's scan counters by status. With the public sandbox key there is no account behind the call: the balance comes back as null, and the first line says so and how to get a key, instead of reporting zeros that read like a balance.

Balance: 1840 credits · 42 free credits left this month · 1798 paid credits · 63 scans this period (58 billed, 58 credits spent).

search_docs

{ "query": "mrz check digit", "limit": 5 }

Full-text search over the documentation – endpoints, response fields, error codes, MRZ rules, retention, pricing – returning the matching sections with titles, snippets and links. It reads a copy shipped inside this package, so it makes no network call.

Stored scans: list_scans, get_scan, delete_scan

A result is stored only when the scan was made with a live key under a non-zero retention window – retain_hours on the call, or the account's history-retention setting (one year by default) when the call named none – and only until that window ends. A key reaches its own account's scans and no other's. Under a sandbox key nothing is stored, so the list is empty and a lookup or a deletion finds nothing; with the public sandbox key the server says so without calling the API. None of the three charges a credit.

list_scans returns the stored scans, newest first, one page at a time:

{ "limit": 20 }

Each row carries id, status, billed, duration_ms, reference and created_at, and the page carries next_cursor – pass it back as cursor for the next page; it is null on the last one. limit is 1 to 100 and defaults to 20. A row holds no extracted data.

get_scan takes a scan_id – meta.id of a scan_document result, or id of a row – and returns the full Scan as it was first returned, except that the image crops are never stored (every images slot is null) and quality reads not_checked. It never recognises the image again.

delete_scan takes a scan_id and permanently deletes that stored scan – the result, its history row and its thumbnail. It cannot be undone: the scan can no longer be listed, fetched or replayed through its idempotency_key. The credit it drew is not refunded. It answers { "id": "…", "deleted": true }.

An id that is unknown, belongs to another account, was made with retain_hours: 0 or has passed its window is not_found for both tools; the cases are not told apart.

Every tool declares an output schema, and every successful call returns structuredContent that matches it, beside the text blocks: the API's Scan for scan_document and get_scan, its Usage for check_balance, a page of { "scans": [...], "next_cursor": … } for list_scans, { "id", "deleted" } for delete_scan, and { "results": [...] } for search_docs. A failed call is an error block with no structured content.

Resources

Every page of the documentation this package ships is a read-only resource, text/markdown, with its title and description:

doccheap://docs/reference/fields
doccheap://docs/reference/errors
doccheap://docs/errors/insufficient_credits

resources/list lists them all, and the template doccheap://docs/{+slug} looks one up by its path. Reading one makes no network call.

Prompts

Prompt

Argument

What it asks for

scan_document_to_json

image_url

Scan one image and present its printed fields, with the JSON underneath

check_document_expiry

image_url

Scan one image and report the expiry date, whether it has expired and the days left

batch_scan

image_urls

Check the balance, scan each address in turn, then tabulate the results and the failures

explain_error

error_code

Explain an API error code from its documentation page, which is attached

Configuration

Variable

Default

Meaning

DOC_CHEAP_API_KEY

sk_sandbox_public

Your API key. Unset uses the public sandbox key: 10 free recognised documents per address in all, at most 10 requests per address an hour, no balance.

DOC_CHEAP_API_BASE

https://api.doc.cheap

Base URL of the API. Only set this to reach another deployment.

DOC_CHEAP_DOCS_BASE

https://doc.cheap/docs

Base URL used to build documentation links.

DOC_CHEAP_DOCS_DIR

the copy inside the package

Override the directory search_docs reads.

DOC_CHEAP_IMAGE_ROOT

unset (image_path disabled)

The one directory image_path may read images from.

DOC_CHEAP_SENTRY_DSN

unset (nothing is reported)

Opt in to failure reporting. Without it the tracker library is never loaded.

DO_NOT_TRACK

unset

Set it to 1 and the calls this server makes stop naming which application is driving it. See What this server sends about itself below.

Image sources

The server runs on your machine, with your files and your network, and the arguments to scan_document are chosen by a model. So the two sources that are not the image itself are fenced in:

  • image_path is disabled until you set DOC_CHEAP_IMAGE_ROOT to a directory of your choosing. With it set, only files inside that directory can be read: both the directory and the requested file are resolved to their real locations first, so .. segments and symlinks pointing out of the directory are refused rather than followed. A relative image_path is taken from that directory. Without the variable the tool answers with an error telling the agent to set it or to send image_base64.

  • image_url must be https: and must resolve to a public internet address. Loopback, private, link-local (including the cloud metadata address), carrier-grade NAT, multicast, reserved and IPv6 unique-local and link-local addresses are refused, as are the IPv4-mapped IPv6 spellings of them. Redirects are followed by hand, at most three hops, and every hop is re-checked, so a public URL cannot hand off to a private one. The body is capped at 25 MB – the API refuses more anyway.

  • image_base64 has no such constraints: the caller already holds the bytes. It is the fallback every refusal above points at.

A guard refusal is a normal tool error with a readable message, so the agent can tell you what to change.

What this server sends about itself

When it calls the doc.cheap API it identifies itself in the request's User-Agent, the way any HTTP client does:

doc-cheap-mcp/0.3.10 (claude-code/1.4.2)

The first half is this package and its version. The second half is the name and version your MCP client reports over the protocol – the editor or assistant you launched it from – normalised to a short label, plus the same label on a baggage header. It is used for one thing: counting how much this server is used and from which applications, so that the work goes where people actually are. It is never used to change what the server does, and nothing else about you, your prompts, your files or your images travels with it.

Switching it off: set DO_NOT_TRACK=1 in the server's environment. The request then carries doc-cheap-mcp/0.3.10 and nothing more – no client name, no client version, no baggage header – and everything else works identically.

Your API key already identifies your account to the API; that is what a key is for, and it is unaffected by the setting above.

Privacy Policy

The full policy is https://doc.cheap/privacy. What it says about this server:

  • What is collected. The image you ask it to read, sent to the doc.cheap API (https://api.doc.cheap) – nowhere else. Its calls also name this package and version, and the client you run it in (see the section above; DO_NOT_TRACK=1 removes the client). Your key identifies your account to the API.

  • How it is used and stored. The image is read and never stored: it lives in memory for the length of the request. The result – the fields read off the document – is kept for the window the call asked for in retain_hours (0 stores nothing) or, when it asked for none, for the account's history-retention setting, which defaults to one year; expiry deletes it, and delete_scan deletes one result sooner.

  • Who else sees it. Nobody the policy does not name: the hosting provider and the network provider that carry the traffic. Nothing is sold or shared for advertising. Nothing this server does is reported anywhere unless you set DOC_CHEAP_SENTRY_DSN yourself.

  • On your machine. The server reads no file unless you name a folder for image_path, and then only images inside it.

  • Contact. admin@doc.cheap.

The hosted server keeps nothing either. Its log records which method and which tool a request called, how long it took and whether a key of your own was used – never the image, the result, the key or your address.

Run it from source

{
  "mcpServers": {
    "doc-cheap": {
      "command": "node",
      "args": ["/absolute/path/to/the/checkout/apps/mcp/src/index.ts"],
      "env": { "DOC_CHEAP_API_BASE": "http://127.0.0.1:3000" }
    }
  }
}

pnpm --filter @doc-cheap/mcp build bundles the server to build/index.js with a shebang and copies the documentation content next to it, so the bin (doc-cheap-mcp) runs standalone.

Licence

MIT – see LICENSE. The monorepo this server is developed in is UNLICENSED; this package alone is published, and it is published under MIT.

In the MCP registry this server is cheap.doc/mcp.

Available Tools

6 tools
check_balanceCheck remaining creditsA
Read-only

Return how many credits are left on the account and what the current period has used: the balance, split into this month's free credits (100 every month, drawn first) and the paid credits, the credits spent, and the scan counters broken down by status (recognized, unreadable, no document found, unsupported document, rejected). Takes no arguments and calls GET /v1/usage; under the public sandbox key it answers without calling anything. One recognised document draws one credit, at $0.01; scans that recognised nothing are counted and never charged. Needs a real API key – under the public sandbox key there is no account behind the call, and the answer says so instead of reporting zeros that read like a balance. Use it before working through a batch of documents, or when a scan is refused for lack of credit.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
scansYes
periodYesBounds of the current usage period (UTC calendar month).
credits_spentYesCredits charged within the period.
free_allowanceYesThis month's free credits, drawn before paid credits; null when the account may not draw free credits (its email address is not confirmed, or its free credits were withdrawn) and for a key with no account.
balance_creditsYesCredits currently available to the account: this month's free credits plus the paid credits; null for a key with no account (the public sandbox key).
paid_balance_creditsYesPaid credits: bought or granted to the account, drawn once this month's free credits are used up, and never reset; null only for a key with no account (the public sandbox key).
credits_spent_by_kindYesCredits charged within the period, by the kind that paid.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnlyHint and openWorldHint annotations. It discloses the sandbox behavior (no account, answer says so), the need for a real API key, and the credit/charge logic (one recognized doc costs $0.01, unrecognized scans free), providing rich behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but efficient; every sentence adds value, from the core result to pricing, sandbox notes, and usage guidance. It is front-loaded with the main purpose, though slightly long for a no-parameter tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, the description doesn't need to detail the return structure. It covers the essential semantics: credit split, spending, scan counters, costs, sandbox behavior, and when to call it. Nothing an agent needs to use it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters, the baseline is 4. The description adds meaning by explaining what the output represents, but since there are no parameters, there is nothing to elaborate on beyond the schema's empty object.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: returning credit balance and usage details. It names the specific resource (GET /v1/usage) and enumerates the data returned, making it unmistakable from siblings like scan_document and search_docs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use it ('before working through a batch of documents, or when a scan is refused for lack of credit') and even covers the sandbox key exception, guiding the agent on appropriate invocation contexts.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_scanDelete a stored scanA
DestructiveIdempotent

Permanently delete one stored scan now, before its retention window would end. This cannot be undone: the stored result, its history row and its thumbnail are removed, and the scan can no longer be listed, fetched or replayed through its idempotency_key. The credit it drew is not refunded, and this period's usage counters still count it. Input: scan_id – meta.id of a scan_document result, or id of a list_scans row. Output: { id, deleted: true }. Calls DELETE /v1/scans/{id}. Only scans made with a live key under a non-zero retention window are stored, and only until that window ends (retain_hours on the scan, or the account's history-retention setting, one year by default); a scan made with retain_hours 0 was never stored. A key reaches its own account's scans and no other account's. Under a sandbox key – the public one or an account's own sk_sandbox_ key – nothing is stored. Errors: an id that is unknown, belongs to another account, has passed its window or was already deleted is refused as not_found, and nothing is deleted. Use it when someone asks for a document's data to be removed; confirm the id with list_scans or get_scan first, because the deletion is final.

ParametersJSON Schema
NameRequiredDescriptionDefault
scan_idYesThe scan's id: meta.id of the scan_document result, or id of a list_scans row (a lower-case UUID).

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
deletedYesAlways true: the stored result is gone and cannot be read back.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the destructiveHint annotation by disclosing exact side effects (result, history row, thumbnail removed), non-refund of credit, continued usage-counter counting, and the not_found error semantics for refused ids. Retention-window and key-scoping rules add operational context the annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The irreversible consequence is front-loaded in the first sentence, and every clause carries operational value. It is long, but the retention and sandbox rules are genuinely non-obvious rather than filler, so trimming would lose meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description still usefully states the return shape ({ id, deleted: true }), and covers side effects, error conditions, retention/sandbox scoping, and pre-call confirmation. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds provenance beyond the schema by mapping scan_id to 'meta.id of a scan_document result, or id of a list_scans row' and noting the id can no longer be replayed via its idempotency_key. The UUID format itself is left to the schema pattern.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb+resource ('Permanently delete one stored scan') and immediately frames it against siblings by describing the removal of the stored result, history row, and thumbnail. An agent can distinguish this from get_scan/list_scans/scan_document without opening another schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states the triggering condition ('Use it when someone asks for a document's data to be removed') and names the confirmation alternatives ('confirm the id with list_scans or get_scan first'). It also gives the when-not boundary: unknown, other-account, expired, or already-deleted ids are refused.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_scanFetch a stored scanA
Read-only

Fetch the full result of one stored scan by its id, as it was returned when the document was recognised. Input: scan_id – meta.id of a scan_document result, or id of a list_scans row. Output: the same Scan object scan_document returns (meta, document, holder, fields, mrz and the rest) plus a one-line summary, with two differences: the image crops are never stored, so every images slot is null, and quality reads not_checked. Calls GET /v1/scans/{id}; it never re-runs recognition and never charges a credit. Only scans made with a live key under a non-zero retention window are stored, and only until that window ends (retain_hours on the scan, or the account's history-retention setting, one year by default); a scan made with retain_hours 0 was never stored. A key reaches its own account's scans and no other account's. Under a sandbox key – the public one or an account's own sk_sandbox_ key – nothing is stored. Errors: an id that is unknown, belongs to another account, was made with retain_hours 0 or has passed its window is refused as not_found – the cases are not told apart. Use it to read back a document recognised earlier instead of scanning the image again.

ParametersJSON Schema
NameRequiredDescriptionDefault
scan_idYesThe scan's id: meta.id of the scan_document result, or id of a list_scans row (a lower-case UUID).

Output Schema

ParametersJSON Schema
NameRequiredDescription
mrzYes
metaYes
fieldsYesEvery field the engine extracted off the printed document, re-keyed to our vocabulary – the open set. Always present; empty when nothing was extracted. A field read in more than one language appears once per language, so `name` repeats and only `id` is unique.
holderYes
imagesYes
qualityYes
documentYes
authenticityYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnlyHint/openWorldHint annotations: no re-recognition, no credit charge, per-account isolation, sandbox non-storage, retention-window semantics with a one-year default, and the deliberate not_found collapsing of four distinct error cases. It even discloses two output deviations (images slots null, quality not_checked) that an agent would otherwise misread as data errors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose, input, and output before the retention and error caveats, and nearly every sentence carries load-bearing information. It is somewhat dense and run-on, with mild redundancy between 'as it was returned when the document was recognised' and 'never re-runs recognition,' which keeps it from a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the full lifecycle an agent needs: id provenance, output shape and its deviations, storage/retention preconditions, key-scoping rules, and error semantics. Since an output schema exists, the description is not obligated to enumerate return fields, and it still flags the two fields that differ.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the UUID pattern and meaning of scan_id are already fully documented in the schema, and the description largely repeats the same sourcing (meta.id of scan_document, id of list_scans row). Baseline 3 is appropriate since the description adds no format or edge-case detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with scope: 'Fetch the full result of one stored scan by its id, as it was returned when the document was recognised.' It implicitly distinguishes itself from scan_document (no re-recognition) and list_scans (single row vs. list), and names the exact id source for each.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: 'Use it to read back a document recognised earlier instead of scanning the image again,' which names the alternative (scan_document) and the condition selecting this tool. It also states when the tool is NOT applicable – scans made with retain_hours 0, sandbox keys, or past the retention window – so the agent can predict failures before calling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_scansList stored scansA
Read-only

List the account's stored scans, most recent first, one page at a time. Inputs: the optional limit (1 to 100 rows, default 20) and cursor (the next_cursor of the previous page; omit it for the first page). Output: scans – one row per scan with id, status (recognized, unreadable, no_document_found, unsupported_document or rejected), billed, duration_ms, reference (your own string from the scan) and created_at – and next_cursor, which is null on the last page. A row holds no extracted data; call get_scan with its id for the full result. Calls GET /v1/scans; it never charges a credit. Only scans made with a live key under a non-zero retention window are stored, and only until that window ends (retain_hours on the scan, or the account's history-retention setting, one year by default); a scan made with retain_hours 0 was never stored. A key reaches its own account's scans and no other account's. Under a sandbox key – the public one or an account's own sk_sandbox_ key – nothing is stored. Under the public sandbox key the answer is an empty list, given without calling the API. Errors: a cursor this API did not issue is refused (validation_failed); a key the API does not know is refused (unauthorized). Use it to find a scan made earlier – by its reference or its time – before reading or deleting it.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoRows per page, 1 to 100 (default 20).
cursorNonext_cursor from the previous page; omit it for the first page.

Output Schema

ParametersJSON Schema
NameRequiredDescription
scansYesThe account's scans, most recent first.
next_cursorYesOpaque cursor for the next page: send it back as `cursor` to continue after the last row of this one. Null on the last page.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover read-only/open-world, but the description discloses what matters here: no credit charged, storage requires a live key under a non-zero retention window, retain_hours 0 means never stored, sandbox keys store nothing (public sandbox returns an empty list without an API call), key isolation per account, and the two error cases (validation_failed, unauthorized). This is far beyond what the annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and organized into clear Inputs/Output/Errors segments, so it scans well. It is long and packs retention and key-mode details into run-on sentences, but nearly every clause carries decision-relevant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite an output schema existing, the description covers the non-obvious completeness gaps for this tool: retention expiry, key scoping, sandbox behavior, and error semantics. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the description largely restates it (limit 1–100 default 20, cursor as previous next_cursor). It adds only the paging model, not new format or edge-case semantics beyond the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the account's stored scans'), adds ordering and pagination scope, and explicitly distinguishes itself from get_scan for the full result. An agent can tell this apart from its siblings without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ('Use it to find a scan made earlier – by its reference or its time – before reading or deleting it') and names the alternative verb (call get_scan with its id). The routing condition is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scan_documentRecognise a passport or ID documentA
Idempotent

Recognise a passport, national ID card or driver's licence from a photo or scan and return what is printed on it as structured JSON. Inputs: the image as image_base64 (always available), image_path (a local file, and only inside the directory DOC_CHEAP_IMAGE_ROOT names) or image_url (https, on a public address); plus the optional expect_country, return_portrait, retain_hours, reference and idempotency_key. Output: a Scan object – meta (id, status, billed, confidence, timing), document (kind, issuing country, number, series, date of issue, date of expiry, whether it has expired and how many days are left), holder (given names, surname, date of birth, sex, nationality), fields (every field read off the printed page, each with its own confidence), mrz (whether the machine-readable zone checks out, why not when it does not, and its lines exactly as read), images, quality and authenticity – plus a one-line summary of the same result. Calls POST /v1/scans. Cost: it bills one credit ($0.01) only when a document is recognised; an unreadable image, an empty frame or an unsupported type costs nothing, and meta.billed says which happened. Without a key, the public sandbox key is used. It gives 10 free recognised documents per address in all, and at most 10 requests per address an hour, whatever their answer. Registering gives 100 free documents every month. Use it whenever someone hands over an identity document and wants it read, transcribed, or checked against what they claim – a name, a document number, a date of birth or an expiry date.

ParametersJSON Schema
NameRequiredDescriptionDefault
image_urlNohttps: URL of an image on a public internet address, which the server fetches (25 MB maximum).
referenceNoYour own correlation string, echoed back in the result.
image_pathNoPath to a local image file, inside the directory named by DOC_CHEAP_IMAGE_ROOT. Disabled unless that variable is set; send image_base64 instead.
image_base64NoThe document image as base64 (a data: URL is also accepted).
retain_hoursNoHours the result stays readable via GET /v1/scans/{id} (0 = store nothing). Omit it to use the account's own history-retention setting.
expect_countryNoISO 3166-1 alpha-3 country you expect, or omit for any.
idempotency_keyNoMakes a retried scan return the first result instead of charging again.
return_portraitNoWhether to include the holder photograph crop, images.main_photo (default true).

Output Schema

ParametersJSON Schema
NameRequiredDescription
mrzYes
metaYes
fieldsYesEvery field the engine extracted off the printed document, re-keyed to our vocabulary – the open set. Always present; empty when nothing was extracted. A field read in more than one language appears once per language, so `name` repeats and only `id` is unique.
holderYes
imagesYes
qualityYes
documentYes
authenticityYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: billing is stated precisely (one credit only on successful recognition, free on unreadable/empty/unsupported), rate limits are disclosed (10 free recognised docs per address total, 10 requests/hour), the sandbox fallback is explained, and idempotency behaviour is described alongside its key. This is exactly the extra context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded, but the paragraph then enumerates every output field (meta, document, holder, fields, mrz, images, quality, authenticity) even though an output schema exists, which is redundant bulk. The single dense block would be easier to scan if the usage trigger and billing facts were separated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an open-world, billed, rate-limited mutation tool, the description covers cost, throttling, auth fallback, idempotency and the trigger condition. With an output schema present it need not detail returns further, so nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all eight parameters, giving a baseline of 3. The description enumerates the same parameters and reinforces the image_path root constraint, but adds no syntax, format or default detail the schema lacks.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb (recognise) and resource (passport, national ID card, driver's licence) and states the return form (structured JSON). It is trivially distinguishable from the CRUD-style siblings (list_scans, get_scan, delete_scan, check_balance, search_docs).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The final sentence gives a clear situational trigger: 'whenever someone hands over an identity document and wants it read, transcribed, or checked against what they claim.' It does not, however, explicitly route the agent away from search_docs or contrast with other read tools, so it stops short of full alternative-naming.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_docsSearch the doc.cheap API documentationA
Read-only

Full-text search over the doc.cheap API documentation – endpoints, request options, every response field, the error codes and what to do about each, MRZ rules, retention and pricing. Takes a query and an optional limit (1 to 20, default 5), and answers with the matching sections: title, a snippet, and a link to the page. It reads a copy of the documentation shipped beside this server, so it makes no network call and works offline. Use it before guessing at a field name, an error code or a scan option – what it returns is the published contract rather than a recollection of it.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results (default 5).
queryYesWhat to search the documentation for.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesMatching sections, best first; empty when nothing matched.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=true and openWorldHint=false, and the description adds significant behavioral detail: it makes no network call, works offline, and returns matching sections (title, snippet, link). This goes beyond the annotations without contradicting them, fully informing the agent of the tool's behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is about three sentences but is information-dense with no fluff. It front-loads the purpose, then adds usage and behavior details, and every sentence contributes to the agent's understanding. It is well-structured and appropriately sized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has an output schema, so return format is covered. The description additionally explains offline operation, usage guidance, and what the search covers. For a two-parameter search tool, everything an agent needs to call it correctly is present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the description repeats the limit range and default that the schema already documents. It adds the context of what the query searches over (endpoints, errors, etc.), but this is more about tool scope than parameter semantics. The baseline of 3 applies because the schema handles parameter meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: full-text search over the doc.cheap API documentation, listing specific content types (endpoints, request options, response fields, error codes, MRZ rules, retention, pricing). This distinguishes it from siblings like check_balance and scan_document, which are unrelated operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use guidance: 'Use it before guessing at a field name, an error code or a scan option.' It also notes the tool reads a local copy and works offline, giving practical context for its use. While it doesn't explicitly list alternatives, the siblings are clearly different operations, so no confusion arises.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.3.8
    • Addeddelete_scan
    • Addedget_scan
    • Addedlist_scans
    • Changedscan_document2 fields changed
      • changedOutput schema / definitions / ScanField / properties / language / description
        Previous value: -"The language this reading was made in, e.g. `Greek`; null for the neutral, transliterated Latin reading."New value: +"The language this reading was made in, e.g. `Greek`. The default Latin-script reading – the document's own Latin page and the machine-readable zone, `id` suffix `@0` – is `English`. Null only on `days_to_expire`, a number computed from the expiry date rather than text read in any language."
      • changedOutput schema / definitions / ScanField / properties / value / description
        Previous value: -"The value of this reading: the national-script spelling when `language` names one, the transliterated Latin value otherwise. Null when the field is empty."New value: +"The value of this reading: the national-script spelling on a national-script reading, the transliterated Latin value on the default reading (the one whose `id` carries the identifier `0`). Null when the field is empty."
  2. 3 tool updatesv0.3.7
    • First observedcheck_balance
    • First observedscan_document
    • First observedsearch_docs

TDQS

A4.7/5.0

Scored across 6 tools

Disambiguation5/5

Each tool targets a distinct action: balance check, new scan, list stored scans, fetch a stored scan, delete a scan, and search documentation. There is no functional overlap; scan_document creates a new result while get_scan retrieves an existing one, and the descriptions make this boundary explicit.

Naming Consistency5/5

All tools use a consistent snake_case verb_noun pattern (check_balance, scan_document, list_scans, delete_scan, get_scan, search_docs). The convention is predictable throughout and no mixed styles appear.

Tool Count5/5

Six tools is well-scoped for an identity-document scanning service. Each tool earns its place: the core scan lifecycle (create, list, get, delete), a balance check, and a documentation search cover the necessary operations without bloat.

Completeness5/5

The surface covers the full lifecycle: scan a document, list stored scans, retrieve full results, delete scans, check credits, and search docs for field/error details. No obvious operation is missing; scans are immutable so an update tool would be unnecessary.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers