Fix UniFi OS authentication and simplify server architecture
All checks were successful
Build and Push Docker Image / build (push) Successful in 15s

- Use /api/auth/login for UniFi OS controllers (UDM, Cloud Gateway)
- Use /api/login for standalone controllers
- Refactor to use FastMCP's native mcp.run() with custom_route for /health
- Switch to network_mode: host for local network access
- Include README.md in Dockerfile for hatchling build
This commit is contained in:
Ben
2026-01-02 02:49:43 +00:00
parent cb57b8f537
commit 487f5355a0
4 changed files with 24 additions and 41 deletions

View File

@@ -11,18 +11,14 @@ Following the BLUEPRINT.md pattern:
- Starlette wrapper with health endpoint
"""
import asyncio
import json
import logging
import os
from contextlib import asynccontextmanager
from typing import Any, Optional
from typing import Optional
from dotenv import load_dotenv
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route
from api_docs import get_api_docs
from unifi_client import UnifiClient, UnifiClientError, UnifiWriteBlockedError
@@ -355,11 +351,14 @@ async def unifi_api_call(
# =============================================================================
# Starlette Wrapper with Health Endpoint
# Health Check Endpoint (using FastMCP custom_route)
# =============================================================================
from starlette.requests import Request
async def health_endpoint(request) -> JSONResponse:
@mcp.custom_route("/health", methods=["GET"])
async def health_endpoint(request: Request) -> JSONResponse:
"""
Health check endpoint for Docker/Kubernetes.
@@ -386,9 +385,8 @@ async def health_endpoint(request) -> JSONResponse:
)
@asynccontextmanager
async def lifespan(app):
"""Application lifespan manager."""
def main():
"""Entry point for the server."""
logger.info("Starting UniFi MCP Light server...")
logger.info(f"Controller: {CONFIG['host']}:{CONFIG['port']}")
logger.info(f"Site: {CONFIG['site']}")
@@ -396,31 +394,8 @@ async def lifespan(app):
f"Write operations: {'enabled' if CONFIG['allow_writes'] else 'disabled'}"
)
yield
logger.info("Shutting down UniFi MCP Light server...")
await close_client()
def create_app() -> Starlette:
"""Create the Starlette ASGI application."""
mcp_app = mcp.http_app()
return Starlette(
routes=[
Route("/health", health_endpoint),
Mount("/", app=mcp_app),
],
lifespan=lifespan,
)
def main():
"""Entry point for the server."""
import uvicorn
app = create_app()
uvicorn.run(app, host="0.0.0.0", port=8000)
# Run with HTTP transport - MCP endpoint at /mcp, health at /health
mcp.run(transport="http", host="0.0.0.0", port=8000)
if __name__ == "__main__":