#!/usr/bin/env python3 # Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import contextlib import os import sys from typing import AsyncIterator, Dict from fastmcp import FastMCP from .pipelines import create_pipeline_handler def _parse_args() -> argparse.Namespace: """Parse command line arguments.""" parser = argparse.ArgumentParser( description="PaddleOCR MCP server - Supports local library, AI Studio service, and self-hosted servers." ) parser.add_argument( "--pipeline", choices=["OCR", "PP-StructureV3"], default=os.getenv("PADDLEOCR_MCP_PIPELINE", "OCR"), help="Pipeline name.", ) parser.add_argument( "--ppocr_source", choices=["local", "aistudio", "self_hosted"], default=os.getenv("PADDLEOCR_MCP_PPOCR_SOURCE", "local"), help="Source of PaddleOCR functionality: local (local library), aistudio (AI Studio service), self_hosted (self-hosted server).", ) parser.add_argument( "--http", action="store_true", help="Use HTTP transport instead of STDIO (suitable for remote deployment and multiple clients).", ) parser.add_argument( "--host", default="127.0.0.1", help="Host address for HTTP mode (default: 127.0.0.1).", ) parser.add_argument( "--port", type=int, default=8000, help="Port for HTTP mode (default: 8000).", ) parser.add_argument( "--verbose", action="store_true", help="Enable verbose logging for debugging." ) # Local mode configuration parser.add_argument( "--pipeline_config", default=os.getenv("PADDLEOCR_MCP_PIPELINE_CONFIG"), help="PaddleOCR pipeline configuration file path (for local mode).", ) parser.add_argument( "--device", default=os.getenv("PADDLEOCR_MCP_DEVICE"), help="Device to run inference on.", ) # Service mode configuration parser.add_argument( "--server_url", default=os.getenv("PADDLEOCR_MCP_SERVER_URL"), help="Base URL of the underlying server (required in service mode).", ) parser.add_argument( "--aistudio_access_token", default=os.getenv("PADDLEOCR_MCP_AISTUDIO_ACCESS_TOKEN"), help="AI Studio access token (required for AI Studio).", ) parser.add_argument( "--timeout", type=int, default=int(os.getenv("PADDLEOCR_MCP_TIMEOUT", "60")), help="HTTP read timeout in seconds for API requests to the underlying server.", ) args = parser.parse_args() return args def _validate_args(args: argparse.Namespace) -> None: """Validate command line arguments.""" if not args.http and (args.host != "127.0.0.1" or args.port != 8000): print( "Host and port arguments are only valid when using HTTP transport (see: `--http`).", file=sys.stderr, ) sys.exit(2) if args.ppocr_source in ["aistudio", "self_hosted"]: if not args.server_url: print("Error: The server base URL is required.", file=sys.stderr) print( "Please either set `--server_url` or set the environment variable " "`PADDLEOCR_MCP_SERVER_URL`.", file=sys.stderr, ) sys.exit(2) if args.ppocr_source == "aistudio" and not args.aistudio_access_token: print("Error: The AI Studio access token is required.", file=sys.stderr) print( "Please either set `--aistudio_access_token` or set the environment variable " "`PADDLEOCR_MCP_AISTUDIO_ACCESS_TOKEN`.", file=sys.stderr, ) sys.exit(2) def main() -> None: """Main entry point.""" args = _parse_args() _validate_args(args) try: pipeline_handler = create_pipeline_handler( args.pipeline, args.ppocr_source, pipeline_config=args.pipeline_config, device=args.device, server_url=args.server_url, aistudio_access_token=args.aistudio_access_token, timeout=args.timeout, ) except Exception as e: print(f"Failed to create the pipeline handler: {e}", file=sys.stderr) if args.verbose: import traceback traceback.print_exc(file=sys.stderr) sys.exit(1) @contextlib.asynccontextmanager async def _lifespan(mcp: FastMCP) -> AsyncIterator[Dict]: async with pipeline_handler: yield {} try: server_name = f"PaddleOCR {args.pipeline} MCP server" mcp = FastMCP( name=server_name, lifespan=_lifespan, log_level="INFO" if args.verbose else "WARNING", mask_error_details=True, ) pipeline_handler.register_tools(mcp) if args.http: mcp.run( transport="streamable-http", host=args.host, port=args.port, ) else: mcp.run() except Exception as e: print(f"Failed to start the server: {e}", file=sys.stderr) if args.verbose: import traceback traceback.print_exc(file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()