Multi-engine OCR MCP server (RapidOCR, Tesseract, ABBYY FineReader 16) with accuracy evaluation.
Server has 6 well-structured tools with complete input schemas and descriptions. Naming follows verb_noun convention (list_engines, ocr_image, ocr_pdf, batch_ocr, compare_engines, evaluate_accuracy). Descriptions are detailed (100-250 chars) and explain WHAT, WHEN, and WHAT-RETURNS. All parameters have types and descriptions. However, output schemas are documented only in prose within descriptions, not as formal JSON Schema. Error handling returns JSON with error fields but lacks recovery guidance (e.g., 'Try list_engines() first'). No tool annotations (readOnlyHint, destructiveHint). Parameter descriptions could be more concise per LLM-optimization baseline (50-200 chars ideal; these run 100-250). Path validation via OCR_MCP_ALLOWED_DIRS is good security practice but not exposed in tool descriptions.
OCR many images. `paths_or_glob` is a glob (e.g. 'C:/scans/*.png') or a JSON list of absolute paths. Returns JSON: {count, results:[...]}.
Run ALL available engines on one image and compare them — the core accuracy tool when you have no ground truth. Returns JSON: per-engine {text, mean_confidence, ok}, plus pairwise text similarity, average agreement, and a 'consensus_engine' (the one whose output best agrees with the others).
Score OCR output against a ground-truth text file (CER/WER). Provide EITHER `ocr_text` (already-extracted text) OR `ocr_path` (an image/PDF to OCR now with `engine`). Compares against the UTF-8 text at `ground_truth_path`. Returns JSON: {cer, wer, char_accuracy_pct, word_accuracy_pct, substitutions, deletions, insertions, hits}.
List OCR engines and whether each is currently usable on this machine. Returns JSON: for each engine -> {available, status}. Call this first to see which engines compare_engines will actually run.
OCR a single image file (PNG/JPG/TIFF/BMP). Args: path: absolute path to the image. engine: 'auto' (RapidOCR), 'rapidocr', 'tesseract', or 'finereader'. lang: ISO 639-1 code ('en','de','fr','ro',...). Mapped per-engine. preprocess: if true, apply grayscale/denoise/deskew first (needs opencv). Returns JSON: {engine, ok, text, mean_confidence, line_count, low_confidence_count, lines:[{text,confidence,bbox}], warnings}.
Output schemas documented only in prose descriptions, not as formal JSON Schema. LLMs cannot parse unstructured docstrings to extract field types and structure for downstream tool chaining.
Error responses lack recovery guidance. When path validation fails or engine is unknown, errors return bare messages like 'path outside OCR_MCP_ALLOWED_DIRS' without suggesting next steps (e.g., 'Call list_engines() to see available engines').
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). All tools are read-only but this is not declared in schema, forcing LLMs to infer safety from descriptions.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2026-07-28+ | v2 |
OCR a PDF by rasterizing pages (PyMuPDF) then running an engine per page. Args: path: absolute path to the PDF. engine/lang: see ocr_image. pages: 'all' or a range like '1-3,5'. dpi: rasterization DPI (default 300; higher = slower, more accurate). Returns JSON: {page_count, pages:[{page, ...ocr_image result...}], full_text}.
Parameter descriptions exceed LLM-optimization baseline (50-200 chars). ocr_image 'lang' param description is ~80 chars; ocr_pdf 'pages' is ~60 chars. Descriptions are clear but verbose, wasting tokens.
batch_ocr accepts 'paths_or_glob' as a string that can be either a glob pattern or JSON list. This overloaded parameter invites parsing errors and LLM confusion. Should be two separate parameters or a clearer format constraint.