feat: prepare ARR for controlled public deployment
This commit is contained in:
89
arr_mcp/run.py
Normal file
89
arr_mcp/run.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user