File size: 4,250 Bytes
ab37e84
0314ff4
 
ab37e84
 
 
0314ff4
 
 
 
09a6947
0314ff4
ab37e84
 
 
 
 
3d2c52e
 
 
 
 
ab37e84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0314ff4
ab37e84
0314ff4
 
ab37e84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0314ff4
 
 
 
 
 
 
 
 
 
 
 
e9a0985
0314ff4
 
 
 
 
 
a455076
0314ff4
 
 
 
 
09a6947
0314ff4
 
 
 
 
 
 
 
 
 
ab37e84
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""
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")