|
15 | 15 | Key insight: Define operation with type hints, get REST + MCP + validation automatically! |
16 | 16 | """ |
17 | 17 |
|
18 | | -from quart import Quart, jsonify, request |
19 | | -from quart_cors import cors |
20 | | -from datetime import datetime |
21 | 18 | import asyncio |
22 | 19 | import json |
23 | | -from pydantic import ValidationError |
24 | | - |
25 | | -# Import Pydantic models and service |
26 | | -from tasks import ( |
27 | | - Task, TaskCreate, TaskUpdate, TaskFilter, TaskStats, |
28 | | - TaskService |
29 | | -) |
| 20 | +import os |
| 21 | +from datetime import datetime |
| 22 | +from pathlib import Path |
30 | 23 |
|
31 | 24 | # Import unified operation system |
32 | | -from api_decorators import operation, get_mcp_tools, get_operation |
33 | | - |
| 25 | +from api_decorators import get_mcp_tools, get_operation, operation |
| 26 | +from pydantic import ValidationError |
| 27 | +from quart import Quart, jsonify, request, send_from_directory |
| 28 | +from quart_cors import cors |
| 29 | +# Import Pydantic models and service |
| 30 | +from tasks import (Task, TaskCreate, TaskFilter, TaskService, TaskStats, |
| 31 | + TaskUpdate) |
34 | 32 |
|
35 | 33 | # ============================================================================ |
36 | 34 | # APPLICATION SETUP |
@@ -282,6 +280,28 @@ async def generate_time_events(): |
282 | 280 | } |
283 | 281 |
|
284 | 282 |
|
| 283 | +frontend_dist_path = Path( |
| 284 | + os.getenv( |
| 285 | + "FRONTEND_DIST", |
| 286 | + Path(__file__).resolve().parents[1] / "frontend" / "dist" |
| 287 | + ) |
| 288 | +) |
| 289 | + |
| 290 | +if frontend_dist_path.exists() and (frontend_dist_path / "index.html").exists(): |
| 291 | + @app.route("/", defaults={"path": ""}) |
| 292 | + @app.route("/<path:path>") |
| 293 | + async def serve_frontend(path: str): |
| 294 | + """Serve the built frontend assets when available.""" |
| 295 | + if path.startswith("api") or path.startswith("mcp"): |
| 296 | + return jsonify({"error": "Not Found"}), 404 |
| 297 | + |
| 298 | + candidate = frontend_dist_path / path |
| 299 | + if path and candidate.exists() and candidate.is_file(): |
| 300 | + return await send_from_directory(frontend_dist_path, path) |
| 301 | + |
| 302 | + return await send_from_directory(frontend_dist_path, "index.html") |
| 303 | + |
| 304 | + |
285 | 305 | # ============================================================================ |
286 | 306 | # MCP JSON-RPC ENDPOINT |
287 | 307 | # ============================================================================ |
@@ -312,6 +332,14 @@ async def mcp_json_rpc(): |
312 | 332 | params = data.get("params", {}) |
313 | 333 | request_id = data.get("id") |
314 | 334 |
|
| 335 | + # Notifications (no response required but we acknowledge for logs) |
| 336 | + if method == "notifications/initialized": |
| 337 | + return jsonify({ |
| 338 | + "jsonrpc": "2.0", |
| 339 | + "result": None, |
| 340 | + "id": request_id |
| 341 | + }), 200 |
| 342 | + |
315 | 343 | # Initialize |
316 | 344 | if method == "initialize": |
317 | 345 | return jsonify({ |
|
0 commit comments