From 82709ac479d5c1be828ceb8c3e88b9b8fc187dc6 Mon Sep 17 00:00:00 2001 From: Tony Date: Sun, 10 May 2026 13:53:54 +0800 Subject: [PATCH] feat: add submit_image_for_ocr tool for image OCR support --- .../__pycache__/server.cpython-311.pyc | Bin 13476 -> 36683 bytes src/procleaning_mcp/server.py | 175 ++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/src/procleaning_mcp/__pycache__/server.cpython-311.pyc b/src/procleaning_mcp/__pycache__/server.cpython-311.pyc index 19409b23f938efa9139d2b9d7c633448e19e1f56..dbb03d0697aa77769d2c69b3a67352d5202d3c18 100644 GIT binary patch literal 36683 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 G@c##c-hJl) diff --git a/src/procleaning_mcp/server.py b/src/procleaning_mcp/server.py index 70e43b2..3532129 100644 --- a/src/procleaning_mcp/server.py +++ b/src/procleaning_mcp/server.py @@ -117,6 +117,12 @@ async def _save_ocr_result( return output_md +# ============================================================ +# 📦 Image extensions list +# ============================================================ + +IMAGE_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".webp") + # ============================================================ # 🛠 Tool 1: Submit PDF for OCR (returns immediately) # ============================================================ @@ -286,6 +292,175 @@ async def submit_pdf_for_ocr( return json.dumps(final_result, ensure_ascii=False, indent=2) +# ============================================================ +# 🛠 Tool 2: Submit Image for OCR (returns immediately) +# ============================================================ + +@mcp.tool() +async def submit_image_for_ocr( + input_file: str = None, + input_dir: str = None, + output_dir: str = None, + api_key: str = None, + base_url: str = "https://agent.imqimacau.com", +) -> str: + """ + Submit Image file(s) (jpg, png, bmp, tiff) for OCR processing. Returns task_id(s) immediately. + Then use get_ocr_result(task_id) to check status and retrieve results. + + ⚠️ This tool does NOT wait for OCR completion. It submits and returns. + Use get_ocr_result() to poll for completion. + + 此工具只提交圖片,立馬回傳 task_id。然後用 get_ocr_result() 查詢結果。 + + Args: + input_file: Path to a single image file + input_dir: Directory containing image files + output_dir: Directory to save OCR results (auto-created) + api_key: API key for the OCR service. Uses env var 'OCR_API_KEY' if not provided. + base_url: Base URL of the OCR API. + + Returns: + JSON string with task_id(s) and metadata. + """ + if not api_key: + api_key = os.environ.get("OCR_API_KEY", "") + + if input_file and input_dir: + return json.dumps( + {"error": "input_file and input_dir are mutually exclusive."}, + ensure_ascii=False, indent=2 + ) + + if not input_file and not input_dir: + return json.dumps( + {"error": "Either input_file or input_dir must be provided."}, + ensure_ascii=False, indent=2 + ) + + if input_file: + if not os.path.isfile(input_file): + return json.dumps( + {"error": f"Input file does not exist: {input_file}"}, + ensure_ascii=False, indent=2 + ) + if not output_dir: + output_dir = os.path.join(os.path.dirname(input_file) or ".", "ocr_output") + image_files = [input_file] + else: + if not os.path.isdir(input_dir): + return json.dumps( + {"error": f"Input directory does not exist: {input_dir}"}, + ensure_ascii=False, indent=2 + ) + if not output_dir: + output_dir = os.path.join(input_dir, "ocr_output") + image_files = [ + os.path.join(input_dir, f) + for f in os.listdir(input_dir) + if f.lower().endswith(IMAGE_EXTENSIONS) + ] + + if not image_files: + return json.dumps( + {"message": f"No image files found in {input_dir}", "files_processed": 0}, + ensure_ascii=False, indent=2 + ) + + tasks = _load_tasks() + results = [] + success_count = 0 + fail_count = 0 + + async with aiohttp.ClientSession() as session: + for image_file in image_files: + filename = os.path.basename(image_file) + base_name = os.path.splitext(filename)[0] + + try: + # Read and encode image + with open(image_file, "rb") as f: + file_base64 = base64.b64encode(f.read()).decode() + + # Submit task + async with session.post( + f"{base_url}/task/submit", + headers={ + "X-API-Key": api_key, + "Content-Type": "application/json", + }, + json={ + "file_base64": file_base64, + "file_type": "image", + "enable_ai_description": False, + "output_type": "ocr_only", + }, + timeout=aiohttp.ClientTimeout(total=30), + ) as response: + if response.status != 200: + fail_count += 1 + results.append({ + "file": filename, + "status": "failed", + "error": f"API error: {response.status}", + }) + continue + + data = await response.json() + task_id = data.get("task_id") + if not task_id: + fail_count += 1 + results.append({ + "file": filename, + "status": "failed", + "error": "No task_id returned", + }) + continue + + # Register task for later polling + tasks[task_id] = { + "filename": filename, + "base_name": base_name, + "output_dir": output_dir, + "base_url": base_url, + "api_key": api_key, + "status": "pending", + "submitted_at": datetime.now().isoformat(), + } + _save_tasks(tasks) + + success_count += 1 + results.append({ + "file": filename, + "status": "submitted", + "task_id": task_id, + }) + + except Exception as e: + fail_count += 1 + results.append({ + "file": filename, + "status": "failed", + "error": str(e), + }) + + # Delay between files + if len(image_files) > 1: + await asyncio.sleep(2) + + final_result = { + "summary": { + "total": len(image_files), + "submitted": success_count, + "failed": fail_count, + "note": "Use get_ocr_result(task_id) to poll for results", + }, + "files": results, + } + + return json.dumps(final_result, ensure_ascii=False, indent=2) + + # ============================================================ # 🛠 Tool 2: Get OCR Result (polls and returns) # ============================================================