Files
wehub-resource-sync e904b667c6
Build/Publish Develop Docs / deploy (push) Failing after 1s
PaddleOCR Code Style Check / check-code-style (push) Failing after 1s
PaddleOCR PR Tests GPU / detect-changes (push) Failing after 1s
PaddleOCR PR Tests / detect-changes (push) Failing after 1s
PaddleOCR PR Tests GPU / test-pr-gpu (push) Has been cancelled
PaddleOCR PR Tests / test-pr (push) Has been cancelled
PaddleOCR PR Tests GPU / test-pr-gpu-impl (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.13) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.8) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.9) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:26 +08:00

279 lines
8.8 KiB
Python

#!/usr/bin/env python3
# Copyright (c) 2026 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.
from __future__ import annotations
import argparse
import asyncio
import os
import sys
from fastmcp import FastMCP
from .inference import create_inference
from .selection import DEFAULT_MODEL, resolve_model
from .providers import InferenceProvider, provider_choices
from .tasks import create_task
def _parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description="PaddleOCR MCP server.")
parser.add_argument(
"--model",
default=os.getenv("PADDLEOCR_MCP_MODEL", DEFAULT_MODEL),
help="Model to run. Env: PADDLEOCR_MCP_MODEL.",
)
parser.add_argument(
"--ppocr_source",
choices=provider_choices(),
default=os.getenv("PADDLEOCR_MCP_PPOCR_SOURCE", "local"),
help="Inference provider. Env: PADDLEOCR_MCP_PPOCR_SOURCE.",
)
parser.add_argument(
"--http",
action="store_true",
help="Use Streamable HTTP instead of stdio.",
)
parser.add_argument(
"--host",
default="127.0.0.1",
help="HTTP bind host (with --http). Default: 127.0.0.1.",
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="HTTP bind port (with --http). Default: 8000.",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Enable verbose logging.",
)
parser.add_argument(
"--pipeline_config",
default=os.getenv("PADDLEOCR_MCP_PIPELINE_CONFIG"),
help="Pipeline config file path (local). Env: PADDLEOCR_MCP_PIPELINE_CONFIG.",
)
parser.add_argument(
"--device",
default=os.getenv("PADDLEOCR_MCP_DEVICE"),
help="Inference device (local). Env: PADDLEOCR_MCP_DEVICE.",
)
parser.add_argument(
"--aistudio-base-url",
dest="aistudio_base_url",
default=os.getenv("PADDLEOCR_MCP_AISTUDIO_BASE_URL"),
help="AI Studio API base URL (aistudio). Env: PADDLEOCR_MCP_AISTUDIO_BASE_URL.",
)
parser.add_argument(
"--qianfan-base-url",
dest="qianfan_base_url",
default=os.getenv("PADDLEOCR_MCP_QIANFAN_BASE_URL")
or "https://qianfan.baidubce.com/v2/ocr",
help="Qianfan API base URL (qianfan). Env: PADDLEOCR_MCP_QIANFAN_BASE_URL.",
)
parser.add_argument(
"--self-hosted-base-url",
dest="self_hosted_base_url",
default=os.getenv("PADDLEOCR_MCP_SELF_HOSTED_BASE_URL"),
help="Self-hosted service base URL (self_hosted). Env: PADDLEOCR_MCP_SELF_HOSTED_BASE_URL.",
)
parser.add_argument(
"--aistudio_access_token",
default=os.getenv("PADDLEOCR_MCP_AISTUDIO_ACCESS_TOKEN"),
help="AI Studio access token (aistudio). Env: PADDLEOCR_MCP_AISTUDIO_ACCESS_TOKEN.",
)
parser.add_argument(
"--qianfan_api_key",
default=os.getenv("PADDLEOCR_MCP_QIANFAN_API_KEY"),
help="Qianfan API key (qianfan). Env: PADDLEOCR_MCP_QIANFAN_API_KEY.",
)
parser.add_argument(
"--http-timeout",
dest="http_timeout",
type=int,
default=int(os.getenv("PADDLEOCR_MCP_HTTP_TIMEOUT", "600")),
help=(
"HTTP read timeout in seconds for synchronous APIs. "
"Env: PADDLEOCR_MCP_HTTP_TIMEOUT."
),
)
parser.add_argument(
"--aistudio-request-timeout",
dest="aistudio_request_timeout",
type=int,
default=int(os.getenv("PADDLEOCR_MCP_AISTUDIO_REQUEST_TIMEOUT", "120")),
help=(
"Per-request HTTP timeout in seconds for AI Studio API calls. "
"Env: PADDLEOCR_MCP_AISTUDIO_REQUEST_TIMEOUT."
),
)
parser.add_argument(
"--aistudio-poll-timeout",
dest="aistudio_poll_timeout",
type=int,
default=int(os.getenv("PADDLEOCR_MCP_AISTUDIO_POLL_TIMEOUT", "600")),
help=(
"Total job polling timeout in seconds for AI Studio. "
"Env: PADDLEOCR_MCP_AISTUDIO_POLL_TIMEOUT."
),
)
return parser.parse_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 == InferenceProvider.AISTUDIO.value:
if 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)
elif args.ppocr_source == InferenceProvider.QIANFAN.value:
if not args.qianfan_api_key:
print("Error: The Qianfan API key is required.", file=sys.stderr)
print(
"Please either set `--qianfan_api_key` or set the environment variable "
"`PADDLEOCR_MCP_QIANFAN_API_KEY`.",
file=sys.stderr,
)
sys.exit(2)
elif args.ppocr_source == InferenceProvider.SELF_HOSTED.value:
if not args.self_hosted_base_url:
print(
"Error: The self-hosted service base URL is required.", file=sys.stderr
)
print(
f"Please set `--self-hosted-base-url` or the environment variable "
"`PADDLEOCR_MCP_SELF_HOSTED_BASE_URL`.",
file=sys.stderr,
)
sys.exit(2)
def _create_inference_from_args(args: argparse.Namespace, model: str):
provider = args.ppocr_source
if provider == InferenceProvider.LOCAL.value:
return create_inference(
model=model,
provider=provider,
config=args.pipeline_config,
device=args.device,
)
elif provider == InferenceProvider.AISTUDIO.value:
return create_inference(
model=model,
provider=provider,
token=args.aistudio_access_token,
base_url=args.aistudio_base_url,
request_timeout=float(args.aistudio_request_timeout),
poll_timeout=float(args.aistudio_poll_timeout),
)
elif provider == InferenceProvider.QIANFAN.value:
return create_inference(
model=model,
provider=provider,
base_url=args.qianfan_base_url,
api_key=args.qianfan_api_key,
http_timeout=args.http_timeout,
)
elif provider == InferenceProvider.SELF_HOSTED.value:
return create_inference(
model=model,
provider=provider,
base_url=args.self_hosted_base_url,
http_timeout=args.http_timeout,
)
else:
raise ValueError(f"Unknown provider: {provider}")
async def async_main() -> None:
"""Asynchronous main entry point."""
args = _parse_args()
_validate_args(args)
try:
model = resolve_model(args.model, args.ppocr_source)
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(2)
inference = _create_inference_from_args(args, model)
try:
await inference.start()
task = create_task(model, inference)
server_name = f"PaddleOCR {model} MCP server"
mcp = FastMCP(
name=server_name,
mask_error_details=True,
)
task.register_tools(mcp)
log_level = "INFO" if args.verbose else "WARNING"
if args.http:
await mcp.run_async(
transport="streamable-http",
host=args.host,
port=args.port,
log_level=log_level,
)
else:
await mcp.run_async(log_level=log_level)
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)
finally:
await inference.stop()
def main() -> None:
"""Main entry point."""
asyncio.run(async_main())
if __name__ == "__main__":
main()