""" 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 # ============================================================================ @mcp.tool() 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) @mcp.tool() 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) @mcp.tool() 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) @mcp.tool() async def get_elevation(latitude: float, longitude: float) -> dict: """Get terrain elevation from OpenElevation API.""" return await elevation.get_data(latitude, longitude) @mcp.tool() 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) @mcp.tool() 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")