Marco310 commited on
Commit
5bbc6a1
·
1 Parent(s): 529a8bd

feat: 🚀 Evolve to Stateful MCP Architecture with Context Injection Middleware

Browse files

Major architectural overhaul to decouple Agent Logic (Service) from Tool Execution (Server) using the Model Context Protocol (MCP).

Key Highlights:
- **Micro-Architecture**: Migrated local toolkits to a standalone `FastMCP` server process (`mcp_server_lifeflow.py`).
- **Context Engineering**: Implemented an AOP-style interceptor using Python `ContextVars`. This automatically injects `session_id` and `api_key` into MCP tool calls, keeping Agent prompts clean and reducing token usage.
- **Multi-Tenant Safety**: Achieved strict session isolation. User-provided API keys dynamically override system environment variables via the injection layer.
- **Runtime Metaprogramming**: Solved Agno/Phidata internal naming conflicts (`_` prefix) by dynamically patching tool entrypoints at startup.
- **Exclusive Channels**: Configured dedicated MCP pipes for each Agent (`Scout`, `Navigator`, etc.) to prevent tool hallucination.

This commit satisfies all requirements for Hackathon Track 2: MCP in Action.

Files changed (2) hide show
  1. README.md +13 -0
  2. mcp_server_lifeflow.py +176 -0
README.md CHANGED
@@ -21,6 +21,19 @@ tags:
21
 
22
  ---
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  ## 📖 Overview
25
 
26
  **LifeFlow AI** is not just a chatbot; it's a **State Machine for Real-World Operations**. It solves the complexity of daily travel planning—considering traffic, weather, opening hours, and route optimization—by coordinating a team of specialized AI agents.
 
21
 
22
  ---
23
 
24
+ ## 👥 Team
25
+
26
+ ### Team Information:
27
+ - **Man-Ho Li** - [@Marco310](https://huggingface.co/Marco310) - Lead Developer & AI Architect
28
+ - ** ** - [@]() - Technical Consultant & Marketing Director
29
+
30
+
31
+ ## 📺 Demo & Submission
32
+ - Demo Video: [Coming Soon]
33
+ - Social Media Post: [Coming Soon]
34
+ - Submission Date: November 2025
35
+
36
+
37
  ## 📖 Overview
38
 
39
  **LifeFlow AI** is not just a chatbot; it's a **State Machine for Real-World Operations**. It solves the complexity of daily travel planning—considering traffic, weather, opening hours, and route optimization—by coordinating a team of specialized AI agents.
mcp_server_lifeflow.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mcp_server_lifeflow.py
2
+ import os
3
+ import json
4
+ from fastmcp import FastMCP
5
+
6
+ # 引入你原本的工具包 (保持邏輯不變)
7
+ from src.tools.scout_toolkit import ScoutToolkit
8
+ from src.tools.optimizer_toolkit import OptimizationToolkit
9
+ from src.tools.navigation_toolkit import NavigationToolkit
10
+ from src.tools.weather_toolkit import WeatherToolkit
11
+ from src.tools.reader_toolkit import ReaderToolkit
12
+
13
+ from src.infra.context import set_session_id # 記得 import 這個
14
+
15
+ # 定義 Server
16
+ mcp = FastMCP("LifeFlow Core Service")
17
+
18
+ # ================= Helper: 獲取環境變數 =================
19
+ # 注意:這些 Key 是由 app.py 在啟動 subprocess 時透過 env 傳進來的
20
+ def get_env_key(name):
21
+ return os.environ.get(name)
22
+
23
+ # ================= Tool Definitions =================
24
+
25
+ @mcp.tool()
26
+ def search_and_offload(task_list_json: str, session_id: str = None) -> str:
27
+ """
28
+ Performs a proximity search for POIs based on the provided tasks and global context, then offloads results to the DB.
29
+
30
+ 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.
31
+
32
+ Args:
33
+ task_list_json (str): A JSON formatted string. The structure must be:
34
+ {
35
+ "global_info": {
36
+ "language": str,
37
+ "plan_type": str
38
+ "return_to_start": bool,
39
+ "start_location": ...,
40
+ "departure_time": str,
41
+ "deadline": str or null,
42
+
43
+ },
44
+ "tasks": [
45
+ {
46
+ "task_id": 1,
47
+ "category": "MEAL" | "LEISURE" | "ERRAND" | "SHOPPING",
48
+ "description": "Short description",
49
+ "location_hint": "Clean Keyword for Google Maps",
50
+ "priority": "HIGH" | "MEDIUM" | "LOW",
51
+ "service_duration_min": 30,
52
+ "time_window": {
53
+ "earliest_time": "ISO 8601" or null,
54
+ "latest_time": "ISO 8601" or null}
55
+ }
56
+ ...,
57
+ ]
58
+ }
59
+ Returns:
60
+ str: A Ref_id of DB system.
61
+ """
62
+ if session_id: set_session_id(session_id)
63
+
64
+ key = get_env_key("GOOGLE_MAPS_API_KEY")
65
+ # 實例化原本的 Toolkit
66
+ tool = ScoutToolkit(google_maps_api_key=key)
67
+ # 執行業務邏輯
68
+ return tool.search_and_offload(task_list_json)
69
+
70
+ @mcp.tool()
71
+ def optimize_from_ref(ref_id: str, max_wait_time_min: int = 0, return_to_start: bool = None, session_id: str = None) -> str:
72
+ """
73
+ Executes the mathematical route solver (TSPTW) to find the most efficient task sequence.
74
+
75
+ This tool loads the POI data, calculates travel times, and reorders tasks to respect time windows.
76
+ It returns detailed status information, allowing the caller to decide if a retry (with relaxed constraints) is needed.
77
+
78
+ Args:
79
+ ref_id (str): The unique reference ID returned by the Scout Agent.
80
+ max_wait_time_min (int, optional): Max waiting time allowed at a location. Defaults to 0.
81
+ 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'.
82
+
83
+ Returns:
84
+ str: A JSON string containing the result status and reference ID.
85
+ Structure:
86
+ {
87
+ "status": "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "ERROR",
88
+ "opt_ref_id": str,
89
+ "dropped_tasks_count": int,
90
+ "skipped_tasks_id": list,
91
+ "message": str
92
+ }
93
+ """
94
+ if session_id: set_session_id(session_id)
95
+
96
+ tool = OptimizationToolkit()
97
+ return tool.optimize_from_ref(ref_id, max_wait_time_min, return_to_start)
98
+
99
+ @mcp.tool()
100
+ def calculate_traffic_and_timing(optimization_ref_id: str, session_id: str = None) -> str:
101
+ """
102
+ Calculates precise travel times, traffic delays, and arrival timestamps for the optimized route.
103
+ This tool acts as a 'Reality Check' by applying Google Routes data to the sequence generated by the Optimizer.
104
+ It ensures the schedule is physically possible under current traffic conditions and generates the final timeline.
105
+
106
+ Args:
107
+ optimization_ref_id (str): The unique reference ID returned by the Optimizer Agent (e.g., "optimization_result_xyz").
108
+ This ID links to the logically sorted task list.
109
+
110
+ Returns:
111
+ str: A JSON string containing the 'nav_ref_id' (e.g., '{"nav_ref_id": "navigation_result_abc"}').
112
+ """
113
+ if session_id: set_session_id(session_id)
114
+
115
+ key = get_env_key("GOOGLE_MAPS_API_KEY")
116
+ tool = NavigationToolkit(google_maps_api_key=key)
117
+ return tool.calculate_traffic_and_timing(optimization_ref_id)
118
+
119
+ @mcp.tool()
120
+ def check_weather_for_timeline(nav_ref_id: str, session_id: str = None) -> str:
121
+ """
122
+ Enriches the solved navigation route with weather forecasts and Air Quality Index (AQI) to create the final timeline.
123
+
124
+ This tool is the final post-processing step. It loads the solved route data, calculates precise local arrival times for each stop
125
+ (dynamically adjusting for the destination's timezone), and fetches specific weather conditions and AQI for those times.
126
+ It also resolves final location names and saves the complete itinerary for presentation.
127
+
128
+ Args:
129
+ nav_ref_id (str): The unique reference ID returned by the Route Solver (or Navigation) step.
130
+
131
+ Returns:
132
+ str: A JSON string containing the reference ID for the finalized data.
133
+ Structure:
134
+ {
135
+ "final_ref_id": str
136
+ }
137
+ """
138
+ if session_id: set_session_id(session_id)
139
+
140
+ key = get_env_key("OPENWEATHER_API_KEY")
141
+ tool = WeatherToolkit(openweather_api_key=key)
142
+ return tool.check_weather_for_timeline(nav_ref_id)
143
+
144
+ @mcp.tool()
145
+ def read_final_itinerary(ref_id: str, session_id: str = None) -> str:
146
+ """
147
+ Retrieves the complete, enriched itinerary data for final presentation.
148
+
149
+ This tool acts as the 'Data Fetcher' for the Presenter. It loads the fully processed
150
+ trip plan (including Weather, Traffic, and Optimized Route) associated with the `ref_id`.
151
+
152
+ Args:
153
+ ref_id (str): The unique reference ID returned by the Weatherman Agent (e.g., "final_itinerary_xyz").
154
+ This ID links to the completed dataset ready for reporting.
155
+
156
+ Returns:
157
+ str: A structured JSON string containing the full trip details.
158
+ Structure:
159
+ {
160
+ "status": "COMPLETE" | "INCOMPLETE",
161
+ "global_info": { ... },
162
+ "traffic_summary": { "total_distance": ..., "total_drive_time": ... },
163
+ "schedule": [
164
+ { "time": "10:00", "location": "...", "weather": "...", "air_quality": "..." },
165
+ ...,
166
+ ]
167
+ }
168
+ """
169
+ if session_id: set_session_id(session_id)
170
+
171
+ tool = ReaderToolkit()
172
+ return tool.read_final_itinerary(ref_id)
173
+
174
+ if __name__ == "__main__":
175
+ # 啟動 MCP Server
176
+ mcp.run()