Spaces:
Running
Running
| """ | |
| MCP SSE Transport for HuggingFace Spaces | |
| Manual SSE setup that works behind HF's proxy | |
| """ | |
| from mcp.server.fastmcp import FastMCP | |
| from mcp.server.sse import SseServerTransport | |
| from starlette.applications import Starlette | |
| from starlette.routing import Route, Mount | |
| from starlette.requests import Request | |
| from starlette.responses import Response | |
| import sys | |
| import os | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from src.servers.weather import WeatherServer | |
| from src.servers.soil import SoilPropertiesServer | |
| from src.servers.water import WaterServer | |
| from src.servers.elevation import ElevationServer | |
| from src.servers.pests import PestsServer | |
| # Create MCP server | |
| mcp = FastMCP("farmer-chat") | |
| # Initialize data servers | |
| weather = WeatherServer() | |
| soil = SoilPropertiesServer() | |
| water = WaterServer() | |
| elevation = ElevationServer() | |
| pests = PestsServer() | |
| # ============================================================================ | |
| # MCP TOOLS | |
| # ============================================================================ | |
| async def get_weather(latitude: float, longitude: float) -> dict: | |
| """Get current weather and 7-day forecast from Open-Meteo API.""" | |
| return await weather.get_data(latitude, longitude) | |
| async def get_soil_properties(latitude: float, longitude: float) -> dict: | |
| """Get soil composition (clay, sand, silt, pH, organic carbon) from Google Earth Engine.""" | |
| return await soil.get_data(latitude, longitude) | |
| async def get_groundwater(latitude: float, longitude: float) -> dict: | |
| """Get groundwater levels and drought status from NASA GRACE satellite data.""" | |
| return await water.get_data(latitude, longitude) | |
| async def get_elevation(latitude: float, longitude: float) -> dict: | |
| """Get terrain elevation from OpenElevation API.""" | |
| return await elevation.get_data(latitude, longitude) | |
| async def get_pest_observations(latitude: float, longitude: float) -> dict: | |
| """Get recent pest/insect observations within 50km from iNaturalist.""" | |
| return await pests.get_data(latitude, longitude) | |
| async def get_all_farm_data(latitude: float, longitude: float) -> dict: | |
| """Get data from all sources in parallel (weather, soil, water, elevation, pests).""" | |
| import asyncio | |
| results = await asyncio.gather( | |
| weather.get_data(latitude, longitude), | |
| soil.get_data(latitude, longitude), | |
| water.get_data(latitude, longitude), | |
| elevation.get_data(latitude, longitude), | |
| pests.get_data(latitude, longitude), | |
| return_exceptions=True | |
| ) | |
| def safe(r): | |
| return r if not isinstance(r, Exception) else {"status": "error", "error": str(r)} | |
| return { | |
| "weather": safe(results[0]), | |
| "soil": safe(results[1]), | |
| "groundwater": safe(results[2]), | |
| "elevation": safe(results[3]), | |
| "pests": safe(results[4]) | |
| } | |
| # ============================================================================ | |
| # SSE TRANSPORT SETUP (Works with HF Spaces) | |
| # ============================================================================ | |
| def create_sse_app(): | |
| """Create Starlette app with proper SSE transport for remote hosting.""" | |
| sse_transport = SseServerTransport("/messages/") | |
| async def handle_sse(request: Request): | |
| async with sse_transport.connect_sse( | |
| request.scope, | |
| request.receive, | |
| request._send, | |
| ) as (read_stream, write_stream): | |
| await mcp._mcp_server.run( | |
| read_stream, | |
| write_stream, | |
| mcp._mcp_server.create_initialization_options(), | |
| ) | |
| return Response() | |
| return Starlette( | |
| debug=True, | |
| routes=[ | |
| Route("/sse", endpoint=handle_sse), | |
| Mount("/messages/", app=sse_transport.handle_post_message), | |
| ], | |
| ) | |
| # Create the SSE app for mounting | |
| sse_app = create_sse_app() | |
| # ============================================================================ | |
| # STANDALONE RUN (for local testing) | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| mcp.run(transport="stdio") |