LifeFlow-AI / mcp_server_lifeflow.py
Marco310's picture
feat: 🚀 Evolve to Stateful MCP Architecture with Context Injection Middleware
5bbc6a1
# mcp_server_lifeflow.py
import os
import json
from fastmcp import FastMCP
# 引入你原本的工具包 (保持邏輯不變)
from src.tools.scout_toolkit import ScoutToolkit
from src.tools.optimizer_toolkit import OptimizationToolkit
from src.tools.navigation_toolkit import NavigationToolkit
from src.tools.weather_toolkit import WeatherToolkit
from src.tools.reader_toolkit import ReaderToolkit
from src.infra.context import set_session_id # 記得 import 這個
# 定義 Server
mcp = FastMCP("LifeFlow Core Service")
# ================= Helper: 獲取環境變數 =================
# 注意:這些 Key 是由 app.py 在啟動 subprocess 時透過 env 傳進來的
def get_env_key(name):
return os.environ.get(name)
# ================= Tool Definitions =================
@mcp.tool()
def search_and_offload(task_list_json: str, session_id: str = None) -> str:
"""
Performs a proximity search for POIs based on the provided tasks and global context, then offloads results to the DB.
CRITICAL: The input JSON **MUST** include the 'global_info' section containing 'start_location' (lat, lng) to ensure searches are performed nearby the user's starting point, not in a random location.
Args:
task_list_json (str): A JSON formatted string. The structure must be:
{
"global_info": {
"language": str,
"plan_type": str
"return_to_start": bool,
"start_location": ...,
"departure_time": str,
"deadline": str or null,
},
"tasks": [
{
"task_id": 1,
"category": "MEAL" | "LEISURE" | "ERRAND" | "SHOPPING",
"description": "Short description",
"location_hint": "Clean Keyword for Google Maps",
"priority": "HIGH" | "MEDIUM" | "LOW",
"service_duration_min": 30,
"time_window": {
"earliest_time": "ISO 8601" or null,
"latest_time": "ISO 8601" or null}
}
...,
]
}
Returns:
str: A Ref_id of DB system.
"""
if session_id: set_session_id(session_id)
key = get_env_key("GOOGLE_MAPS_API_KEY")
# 實例化原本的 Toolkit
tool = ScoutToolkit(google_maps_api_key=key)
# 執行業務邏輯
return tool.search_and_offload(task_list_json)
@mcp.tool()
def optimize_from_ref(ref_id: str, max_wait_time_min: int = 0, return_to_start: bool = None, session_id: str = None) -> str:
"""
Executes the mathematical route solver (TSPTW) to find the most efficient task sequence.
This tool loads the POI data, calculates travel times, and reorders tasks to respect time windows.
It returns detailed status information, allowing the caller to decide if a retry (with relaxed constraints) is needed.
Args:
ref_id (str): The unique reference ID returned by the Scout Agent.
max_wait_time_min (int, optional): Max waiting time allowed at a location. Defaults to 0.
return_to_start (bool, optional): Whether to force a round trip. Defaults to None. If None, it defaults to the value in 'global_info.return_to_start'.
Returns:
str: A JSON string containing the result status and reference ID.
Structure:
{
"status": "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "ERROR",
"opt_ref_id": str,
"dropped_tasks_count": int,
"skipped_tasks_id": list,
"message": str
}
"""
if session_id: set_session_id(session_id)
tool = OptimizationToolkit()
return tool.optimize_from_ref(ref_id, max_wait_time_min, return_to_start)
@mcp.tool()
def calculate_traffic_and_timing(optimization_ref_id: str, session_id: str = None) -> str:
"""
Calculates precise travel times, traffic delays, and arrival timestamps for the optimized route.
This tool acts as a 'Reality Check' by applying Google Routes data to the sequence generated by the Optimizer.
It ensures the schedule is physically possible under current traffic conditions and generates the final timeline.
Args:
optimization_ref_id (str): The unique reference ID returned by the Optimizer Agent (e.g., "optimization_result_xyz").
This ID links to the logically sorted task list.
Returns:
str: A JSON string containing the 'nav_ref_id' (e.g., '{"nav_ref_id": "navigation_result_abc"}').
"""
if session_id: set_session_id(session_id)
key = get_env_key("GOOGLE_MAPS_API_KEY")
tool = NavigationToolkit(google_maps_api_key=key)
return tool.calculate_traffic_and_timing(optimization_ref_id)
@mcp.tool()
def check_weather_for_timeline(nav_ref_id: str, session_id: str = None) -> str:
"""
Enriches the solved navigation route with weather forecasts and Air Quality Index (AQI) to create the final timeline.
This tool is the final post-processing step. It loads the solved route data, calculates precise local arrival times for each stop
(dynamically adjusting for the destination's timezone), and fetches specific weather conditions and AQI for those times.
It also resolves final location names and saves the complete itinerary for presentation.
Args:
nav_ref_id (str): The unique reference ID returned by the Route Solver (or Navigation) step.
Returns:
str: A JSON string containing the reference ID for the finalized data.
Structure:
{
"final_ref_id": str
}
"""
if session_id: set_session_id(session_id)
key = get_env_key("OPENWEATHER_API_KEY")
tool = WeatherToolkit(openweather_api_key=key)
return tool.check_weather_for_timeline(nav_ref_id)
@mcp.tool()
def read_final_itinerary(ref_id: str, session_id: str = None) -> str:
"""
Retrieves the complete, enriched itinerary data for final presentation.
This tool acts as the 'Data Fetcher' for the Presenter. It loads the fully processed
trip plan (including Weather, Traffic, and Optimized Route) associated with the `ref_id`.
Args:
ref_id (str): The unique reference ID returned by the Weatherman Agent (e.g., "final_itinerary_xyz").
This ID links to the completed dataset ready for reporting.
Returns:
str: A structured JSON string containing the full trip details.
Structure:
{
"status": "COMPLETE" | "INCOMPLETE",
"global_info": { ... },
"traffic_summary": { "total_distance": ..., "total_drive_time": ... },
"schedule": [
{ "time": "10:00", "location": "...", "weather": "...", "air_quality": "..." },
...,
]
}
"""
if session_id: set_session_id(session_id)
tool = ReaderToolkit()
return tool.read_final_itinerary(ref_id)
if __name__ == "__main__":
# 啟動 MCP Server
mcp.run()