Add: Web UI (React 19 + Vite + shadcn/ui) + FastAPI REST layer
Adds a self-contained web interface that wraps the MCP tools so end users
don't need to issue MCP commands. Single Docker container serves:
/ → React SPA (RTL Hebrew, Heebo font, shadcn/ui components)
/api/* → FastAPI REST endpoints used by the SPA
/api/docs → OpenAPI / Swagger
/mcp → existing FastMCP streamable-http endpoint
/health → liveness probe
REST endpoints (in nadlan_mcp/web_app.py):
GET /api/search/address?q=... — autocomplete + best-effort gush/חלקה
via Govmap PARCEL_ALL layer
POST /api/search/deals — Govmap deals around an address
POST /api/search/appraisals — Decisive-appraiser search (gov.il)
GET /api/appraisals/pdf?url=.. — Stream PDFs through this server,
carrying the upstream x-client-id
header (a normal browser fetch fails)
UI flow:
- Address search → resolves gush/חלקה → shows BOTH deals (Govmap) AND
appraiser decisions (gov.il) for the same parcel side-by-side in tabs.
- Block+plot search → goes straight to appraisals.
- Appraiser-name search → all decisions by that appraiser.
Stack chosen for RTL-first usability and longevity: React 19, Vite 6,
TypeScript, Tailwind, shadcn/ui (Radix primitives), TanStack Query,
Lucide icons. ~295KB gzipped JS, no SSR overhead.
Multi-stage Dockerfile: Node 22 builds web/ in stage 1, Python 3.13
runtime serves the dist + the FastAPI app in stage 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-37
@@ -1,11 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
HTTP Server Entry Point for Nadlan-MCP
|
||||
HTTP Server Entry Point for Nadlan-MCP.
|
||||
|
||||
This module provides an HTTP/SSE transport server for the Nadlan-MCP service,
|
||||
enabling deployment to cloud platforms like Render, Railway, or Docker containers.
|
||||
Serves three things on a single port:
|
||||
/ → React SPA (search & browse Israeli real estate data)
|
||||
/api/... → REST endpoints used by the SPA (also documented at /api/docs)
|
||||
/mcp → FastMCP streamable-http endpoint (for LLM clients)
|
||||
|
||||
For local Claude Desktop integration, use run_fastmcp_server.py (stdio transport) instead.
|
||||
For local Claude Desktop integration, use run_fastmcp_server.py (stdio).
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -14,52 +16,30 @@ import sys
|
||||
|
||||
import uvicorn
|
||||
|
||||
from nadlan_mcp.fastmcp_server import mcp
|
||||
from nadlan_mcp.web_app import create_app
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Module-level app for ASGI servers (uvicorn web_app:app, gunicorn, etc.).
|
||||
app = create_app()
|
||||
|
||||
def main():
|
||||
"""Run the FastMCP server with HTTP transport using uvicorn."""
|
||||
# Get port from environment variable (Render sets PORT automatically)
|
||||
|
||||
def main() -> None:
|
||||
port = int(os.environ.get("PORT", 8000))
|
||||
host = "0.0.0.0"
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("Starting Nadlan-MCP HTTP Server")
|
||||
logger.info("=" * 60)
|
||||
logger.info("Transport: HTTP (via uvicorn)")
|
||||
logger.info(f"Host: {host}")
|
||||
logger.info(f"Port: {port}")
|
||||
logger.info(f"MCP Endpoint: http://{host}:{port}/mcp")
|
||||
logger.info(f"Health Check: http://{host}:{port}/health")
|
||||
logger.info(f" UI: http://{host}:{port}/")
|
||||
logger.info(f" API docs: http://{host}:{port}/api/docs")
|
||||
logger.info(f" MCP: http://{host}:{port}/mcp")
|
||||
logger.info(f" Health: http://{host}:{port}/health")
|
||||
logger.info("=" * 60)
|
||||
|
||||
try:
|
||||
# Get the HTTP ASGI app from FastMCP
|
||||
# Try different possible method names based on FastMCP version
|
||||
if hasattr(mcp, "streamable_http_app"):
|
||||
app = mcp.streamable_http_app()
|
||||
elif hasattr(mcp, "http_app"):
|
||||
app = mcp.http_app()
|
||||
elif hasattr(mcp, "get_app"):
|
||||
app = mcp.get_app()
|
||||
else:
|
||||
# Fallback: try to access the app directly
|
||||
app = getattr(mcp, "app", None)
|
||||
if app is None:
|
||||
raise AttributeError(
|
||||
"FastMCP instance has no HTTP app method. "
|
||||
"Available methods: " + ", ".join(dir(mcp))
|
||||
)
|
||||
|
||||
logger.info(f"Using app: {type(app).__name__}")
|
||||
|
||||
# Run with uvicorn
|
||||
uvicorn.run(app, host=host, port=port, log_level="info")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start HTTP server: {e}", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user