Files
homelable/backend/app/core/config.py
T
Pouzor 7935b671d3 feat: add MCP server with HTTP/SSE transport for AI integration
Exposes homelab topology to MCP-compatible AI clients (Claude Code, etc.)
over LAN via HTTP/SSE on port 8001.

- New mcp/ service: FastAPI + mcp SDK, SSE transport
- Auth: X-API-Key for AI clients, X-MCP-Service-Key for backend (Docker-internal)
- Resources: canvas, nodes, edges, scan/pending, scan/runs
- Tools: create/update/delete nodes+edges, trigger scan, approve/hide devices
- Backend deps.py: accepts JWT or MCP service key (no plain-text password)
- 40 tests (auth, resources, tools) across asyncio + trio
- docker-compose.yml: mcp service on port 8001
- README: MCP setup section with Claude Code/Desktop config examples
2026-03-13 16:41:34 +01:00

57 lines
1.9 KiB
Python

import json
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
secret_key: str # Required — set SECRET_KEY in .env
sqlite_path: str = "./data/homelab.db"
cors_origins: list[str] = ["http://localhost:5173", "http://localhost:3000"]
# JWT
algorithm: str = "HS256"
access_token_expire_minutes: int = 1440 # 24h
# Auth — set AUTH_USERNAME and AUTH_PASSWORD_HASH in .env
auth_username: str = "admin"
auth_password_hash: str = ""
# Scanner
scanner_ranges: list[str] = ["192.168.1.0/24"]
# Status checker
status_checker_interval: int = 60
# MCP service key — set MCP_SERVICE_KEY in .env
# Used by the MCP server to authenticate against the backend without a user password.
# Leave empty to disable MCP service key auth.
mcp_service_key: str = ""
def _override_path(self) -> Path:
return Path(self.sqlite_path).parent / "scan_config.json"
def load_overrides(self) -> None:
"""Load runtime scan config overrides written by the API."""
try:
data = json.loads(self._override_path().read_text())
if "scanner_ranges" in data:
self.scanner_ranges = data["scanner_ranges"]
if "status_checker_interval" in data:
self.status_checker_interval = int(data["status_checker_interval"])
except Exception:
pass
def save_overrides(self) -> None:
"""Persist scan config so it survives container restarts."""
self._override_path().parent.mkdir(parents=True, exist_ok=True)
self._override_path().write_text(json.dumps({
"scanner_ranges": self.scanner_ranges,
"status_checker_interval": self.status_checker_interval,
}))
settings = Settings() # type: ignore[call-arg]