90 lines
2.6 KiB
Python
90 lines
2.6 KiB
Python
"""Standalone Streamable HTTP entry point for ARR MCP."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
from typing import Optional, Sequence
|
|
|
|
from arr_mcp.auth import BearerAuthConfig
|
|
from arr_mcp.database import controlled_connect
|
|
from arr_mcp.gateway import DirectResultGateway
|
|
from arr_mcp.server import create_http_application
|
|
from arr_web.direct_ingestion_runtime import compose_oss_direct_ingestion
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
_LOOPBACK_HOSTS = {"127.0.0.1", "localhost", "::1"}
|
|
|
|
|
|
def _parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(prog="arr-mcp")
|
|
parser.add_argument("--host", default="127.0.0.1")
|
|
parser.add_argument("--port", type=int, default=8890)
|
|
parser.add_argument(
|
|
"--db-config",
|
|
type=Path,
|
|
help="private 0600 ARR_DB_* configuration file",
|
|
)
|
|
parser.add_argument(
|
|
"--allowed-host",
|
|
action="append",
|
|
default=[],
|
|
help="exact HTTP Host value or host:*; repeat for reverse-proxy names",
|
|
)
|
|
parser.add_argument(
|
|
"--max-concurrency",
|
|
type=int,
|
|
default=2,
|
|
help="maximum concurrent deterministic replays (1-8)",
|
|
)
|
|
return parser
|
|
|
|
|
|
def _allowed_hosts(bind_host: str, configured: list[str]) -> list[str]:
|
|
if configured:
|
|
return list(dict.fromkeys(configured))
|
|
if bind_host not in _LOOPBACK_HOSTS:
|
|
raise ValueError(
|
|
"--allowed-host is required when ARR MCP binds beyond loopback"
|
|
)
|
|
return ["localhost:*", "127.0.0.1:*", "[::1]:*"]
|
|
|
|
|
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
args = _parser().parse_args(argv)
|
|
if not 1 <= args.port <= 65535:
|
|
raise ValueError("ARR MCP port is invalid")
|
|
connect = controlled_connect(args.db_config) if args.db_config else None
|
|
runtime = compose_oss_direct_ingestion(
|
|
project_root=PROJECT_ROOT,
|
|
connect=connect,
|
|
)
|
|
try:
|
|
runtime.service.assert_ready()
|
|
gateway = DirectResultGateway(
|
|
runtime.service,
|
|
max_concurrency=args.max_concurrency,
|
|
)
|
|
application = create_http_application(
|
|
gateway,
|
|
BearerAuthConfig.from_environment(),
|
|
allowed_hosts=_allowed_hosts(args.host, args.allowed_host),
|
|
)
|
|
import uvicorn
|
|
|
|
uvicorn.run(
|
|
application,
|
|
host=args.host,
|
|
port=args.port,
|
|
access_log=True,
|
|
log_level="info",
|
|
)
|
|
finally:
|
|
runtime.close()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|