web package

This commit is contained in:
John Lancaster
2026-06-18 21:15:54 -05:00
parent 4b1a261b2d
commit b6010775ad
11 changed files with 277 additions and 28 deletions
+3 -27
View File
@@ -1,31 +1,7 @@
from fastmcp import FastMCP
from personal_mcp.mcp import mcp
from personal_mcp.web.app import app
from personal_mcp.catalog.server import catalog_server
from personal_mcp.skills.fastapi_async_sqlalchemy_modernization.server import (
fastapi_async_sqlalchemy_modernization_server,
)
from personal_mcp.skills.fastapi_uv_docker.server import fastapi_uv_docker_server
from personal_mcp.skills.nicegui.server import nicegui_server
from personal_mcp.skills.nicegui_ui_customization.server import (
nicegui_ui_customization_server,
)
from personal_mcp.skills.pytest_scaffolding.server import pytest_scaffolding_server
from personal_mcp.skills.python_logging_dictconfig.server import (
python_logging_dictconfig_server,
)
mcp = FastMCP("personal-mcp")
mcp.mount(catalog_server, namespace="catalog")
mcp.mount(
fastapi_async_sqlalchemy_modernization_server,
namespace="fastapi_async_sqlalchemy_modernization",
)
mcp.mount(nicegui_server, namespace="nicegui")
mcp.mount(nicegui_ui_customization_server, namespace="nicegui_ui_customization")
mcp.mount(pytest_scaffolding_server, namespace="pytest_scaffolding")
mcp.mount(python_logging_dictconfig_server, namespace="python_logging_dictconfig")
mcp.mount(fastapi_uv_docker_server, namespace="fastapi_uv_docker")
__all__ = ["app", "main", "mcp"]
def main() -> None:
+28
View File
@@ -0,0 +1,28 @@
from fastmcp import FastMCP
from personal_mcp.catalog.server import catalog_server
from personal_mcp.skills.fastapi_async_sqlalchemy_modernization.server import (
fastapi_async_sqlalchemy_modernization_server,
)
from personal_mcp.skills.fastapi_uv_docker.server import fastapi_uv_docker_server
from personal_mcp.skills.nicegui.server import nicegui_server
from personal_mcp.skills.nicegui_ui_customization.server import (
nicegui_ui_customization_server,
)
from personal_mcp.skills.pytest_scaffolding.server import pytest_scaffolding_server
from personal_mcp.skills.python_logging_dictconfig.server import (
python_logging_dictconfig_server,
)
mcp = FastMCP("personal-mcp")
mcp.mount(catalog_server, namespace="catalog")
mcp.mount(
fastapi_async_sqlalchemy_modernization_server,
namespace="fastapi_async_sqlalchemy_modernization",
)
mcp.mount(nicegui_server, namespace="nicegui")
mcp.mount(nicegui_ui_customization_server, namespace="nicegui_ui_customization")
mcp.mount(pytest_scaffolding_server, namespace="pytest_scaffolding")
mcp.mount(python_logging_dictconfig_server, namespace="python_logging_dictconfig")
mcp.mount(fastapi_uv_docker_server, namespace="fastapi_uv_docker")
+1
View File
@@ -0,0 +1 @@
"""FastAPI web runtime for personal MCP."""
+36
View File
@@ -0,0 +1,36 @@
from fastapi import FastAPI
from personal_mcp.mcp import mcp
from personal_mcp.web.config import Settings, get_settings
from personal_mcp.web.docs_mount import mount_docs_static
from personal_mcp.web.health import router as health_router
def create_app(settings: Settings | None = None) -> FastAPI:
runtime_settings = settings or get_settings()
mcp_app = mcp.http_app(
path=runtime_settings.mcp_route,
json_response=True,
stateless_http=True,
transport="http",
)
app = FastAPI(
debug=runtime_settings.debug,
docs_url=None,
redoc_url=None,
openapi_url=None,
lifespan=mcp_app.lifespan,
)
app.state.settings = runtime_settings
app.include_router(health_router)
mount_docs_static(
app,
docs_route=runtime_settings.docs_route,
site_dir=runtime_settings.site_dir,
)
app.mount("/", mcp_app, name="mcp")
return app
app = create_app()
+28
View File
@@ -0,0 +1,28 @@
from pathlib import Path
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
_REPO_ROOT = Path(__file__).resolve().parents[3]
class Settings(BaseSettings):
"""Runtime settings for the HTTP MCP and docs server."""
model_config = SettingsConfigDict(
env_file=".env",
env_prefix="PERSONAL_MCP_",
extra="ignore",
)
host: str = "127.0.0.1"
port: int = 8000
debug: bool = False
log_level: str = "info"
docs_route: str = "/docs"
mcp_route: str = "/mcp"
site_dir: Path = Field(default=_REPO_ROOT / "site")
def get_settings() -> Settings:
return Settings()
+40
View File
@@ -0,0 +1,40 @@
from pathlib import Path
from fastapi import FastAPI, Response, status
from fastapi.staticfiles import StaticFiles
def mount_docs_static(app: FastAPI, *, docs_route: str, site_dir: Path) -> None:
"""Mount the pre-built static docs site, or expose a clear missing-build response."""
normalized_route = docs_route.rstrip("/") or "/docs"
if site_dir.is_dir():
app.mount(
normalized_route,
StaticFiles(directory=site_dir, html=True),
name="docs",
)
return
async def docs_not_built() -> Response:
return Response(
content=(
"Static docs have not been built yet. "
"Run `uv run zensical build` before using this route."
),
media_type="text/plain",
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
)
app.add_api_route(
normalized_route,
docs_not_built,
methods=["GET"],
include_in_schema=False,
)
app.add_api_route(
f"{normalized_route}/{{path:path}}",
docs_not_built,
methods=["GET"],
include_in_schema=False,
)
+8
View File
@@ -0,0 +1,8 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/healthz", include_in_schema=False)
def healthz() -> dict[str, str]:
return {"status": "ok"}