109 lines
3.2 KiB
Python
109 lines
3.2 KiB
Python
"""Local secure launcher that loads routes and Keychain-backed MCP secrets."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import stat
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Dict, Optional, Sequence
|
|
|
|
from arr_mcp.run import main as run_mcp
|
|
|
|
|
|
KEYCHAIN_ACCOUNT = "arr-web"
|
|
KEYCHAIN_SERVICES = {
|
|
"OSS_ACCESS_KEY_ID": "com.chillishark.arr.oss-access-key-id",
|
|
"OSS_ACCESS_KEY_SECRET": "com.chillishark.arr.oss-access-key-secret",
|
|
"ARR_MCP_BEARER_TOKEN": "com.chillishark.arr.mcp-bearer",
|
|
}
|
|
ROUTE_KEYS = frozenset(
|
|
{
|
|
"ARR_OSS_REGION",
|
|
"ARR_OSS_ENDPOINT",
|
|
"ARR_OSS_BUCKET",
|
|
"ARR_OBJECT_PREFIX",
|
|
}
|
|
)
|
|
|
|
|
|
def _private_route_values(path: Path) -> Dict[str, str]:
|
|
candidate = path.expanduser()
|
|
if candidate.is_symlink():
|
|
raise ValueError("ARR MCP route configuration is invalid")
|
|
resolved = candidate.resolve(strict=True)
|
|
metadata = resolved.stat()
|
|
if (
|
|
not stat.S_ISREG(metadata.st_mode)
|
|
or metadata.st_uid != os.getuid()
|
|
or stat.S_IMODE(metadata.st_mode) != 0o600
|
|
):
|
|
raise ValueError("ARR MCP route configuration is invalid")
|
|
values: Dict[str, str] = {}
|
|
for raw in resolved.read_text(encoding="utf-8").splitlines():
|
|
line = raw.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
key = key.strip()
|
|
if key in ROUTE_KEYS:
|
|
if key in values:
|
|
raise ValueError("ARR MCP route configuration is invalid")
|
|
values[key] = value.strip().strip('"').strip("'")
|
|
if any(not values.get(key) for key in ("ARR_OSS_REGION", "ARR_OSS_BUCKET")):
|
|
raise ValueError("ARR MCP route configuration is incomplete")
|
|
return values
|
|
|
|
|
|
def _keychain_secret(service: str) -> str:
|
|
try:
|
|
completed = subprocess.run(
|
|
[
|
|
"security",
|
|
"find-generic-password",
|
|
"-s",
|
|
service,
|
|
"-a",
|
|
KEYCHAIN_ACCOUNT,
|
|
"-w",
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
except (OSError, subprocess.SubprocessError):
|
|
raise ValueError("required ARR MCP Keychain item is unavailable") from None
|
|
value = completed.stdout.strip()
|
|
if not value:
|
|
raise ValueError("required ARR MCP Keychain item is unavailable")
|
|
return value
|
|
|
|
|
|
def load_runtime_environment(route_config: Path) -> None:
|
|
for key, value in _private_route_values(route_config).items():
|
|
os.environ[key] = value
|
|
for environment_name, service in KEYCHAIN_SERVICES.items():
|
|
os.environ[environment_name] = _keychain_secret(service)
|
|
|
|
|
|
def _parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(add_help=False)
|
|
parser.add_argument(
|
|
"--route-config",
|
|
type=Path,
|
|
default=Path("~/.config/arr/agent-writeback.env"),
|
|
)
|
|
return parser
|
|
|
|
|
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
args, remaining = _parser().parse_known_args(argv)
|
|
load_runtime_environment(args.route_config)
|
|
return run_mcp(remaining)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|