File size: 6,663 Bytes
bad6218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67fee40
bad6218
 
 
67fee40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bad6218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""Gradio MCP Server for Indicateurs Territoriaux de Transition Écologique.

This application exposes 4 MCP tools for querying French territorial
ecological indicators via the Cube.js API.

Tools:
    - list_indicators: List all indicators with optional filters
    - get_indicator_details: Get detailed info about a specific indicator
    - query_indicator_data: Query data values for a territory
    - search_indicators: Search indicators by keywords

Usage:
    Run locally:
        python app.py
    
    Deploy on HuggingFace Spaces:
        Push to a Space with Gradio SDK configured.
    
    Connect as MCP Server:
        URL: http://your-server:7860/gradio_api/mcp/
"""

import os
import logging
import gradio as gr
from dotenv import load_dotenv


# =============================================================================
# Logging Configuration - Filter out non-critical ASGI errors
# =============================================================================
class ASGIErrorFilter(logging.Filter):
    """Filter to suppress known non-critical ASGI/MCP errors.
    
    These errors occur when external clients send malformed requests
    or when health checks hit MCP endpoints. They don't affect functionality.
    """
    
    SUPPRESSED_MESSAGES = [
        "Exception in ASGI application",
        "'NoneType' object is not callable",
        "Exception Group Traceback",
    ]
    
    def filter(self, record: logging.LogRecord) -> bool:
        """Return False to suppress the log record."""
        message = record.getMessage()
        for suppressed in self.SUPPRESSED_MESSAGES:
            if suppressed in message:
                return False
        return True


# Apply filter to uvicorn error logger
uvicorn_error_logger = logging.getLogger("uvicorn.error")
uvicorn_error_logger.addFilter(ASGIErrorFilter())

# Also filter the root logger for starlette errors
logging.getLogger("starlette").addFilter(ASGIErrorFilter())

# Load environment variables
load_dotenv()

# Import tools
from src.tools import (
    list_indicators,
    get_indicator_details,
    query_indicator_data,
    search_indicators,
)
from src.models import GEOGRAPHIC_LEVELS

# Check if token is configured
if not os.getenv("INDICATEURS_TE_TOKEN"):
    print("WARNING: INDICATEURS_TE_TOKEN not set. API calls will fail.")
    print("Set the token in .env file or as environment variable.")


# Create individual interfaces for each tool
list_interface = gr.Interface(
    fn=list_indicators,
    inputs=[
        gr.Textbox(
            label="Thématique FNV",
            placeholder="Ex: mieux se déplacer, mieux se loger...",
            info="Filtre par thématique France Nation Verte (recherche partielle)",
        ),
        gr.Dropdown(
            choices=[""] + GEOGRAPHIC_LEVELS,
            label="Maille géographique",
            info="Filtre par niveau géographique disponible",
        ),
    ],
    outputs=gr.JSON(label="Indicateurs"),
    title="Lister les indicateurs",
    description="Liste tous les indicateurs disponibles avec filtres optionnels.",
    api_name="list_indicators",
)

details_interface = gr.Interface(
    fn=get_indicator_details,
    inputs=[
        gr.Textbox(
            label="ID de l'indicateur",
            placeholder="Ex: 611",
            info="Identifiant numérique de l'indicateur",
        ),
    ],
    outputs=gr.JSON(label="Détails"),
    title="Détails d'un indicateur",
    description="Retourne les métadonnées complètes et les sources d'un indicateur.",
    api_name="get_indicator_details",
)

query_interface = gr.Interface(
    fn=query_indicator_data,
    inputs=[
        gr.Textbox(
            label="ID de l'indicateur",
            placeholder="Ex: 611",
            info="Identifiant numérique de l'indicateur",
        ),
        gr.Dropdown(
            choices=GEOGRAPHIC_LEVELS,
            label="Niveau géographique",
            value="region",
            info="Maille territoriale à interroger",
        ),
        gr.Textbox(
            label="Code INSEE",
            placeholder="Ex: 93 (PACA), 13 (Bouches-du-Rhône)...",
            info="Code du territoire (optionnel)",
        ),
        gr.Textbox(
            label="Année",
            placeholder="Ex: 2020",
            info="Année des données (optionnel)",
        ),
    ],
    outputs=gr.JSON(label="Données"),
    title="Interroger les données",
    description="Récupère les valeurs d'un indicateur pour un territoire donné.",
    api_name="query_indicator_data",
)

search_interface = gr.Interface(
    fn=search_indicators,
    inputs=[
        gr.Textbox(
            label="Recherche",
            placeholder="Ex: consommation espace, surface bio, émissions CO2...",
            info="Mots-clés à rechercher dans le nom et la description",
        ),
    ],
    outputs=gr.JSON(label="Résultats"),
    title="Rechercher des indicateurs",
    description="Recherche des indicateurs par mots-clés.",
    api_name="search_indicators",
)

# Combine all interfaces into a tabbed interface
demo = gr.TabbedInterface(
    interface_list=[
        list_interface,
        search_interface,
        details_interface,
        query_interface,
    ],
    tab_names=[
        "Lister",
        "Rechercher",
        "Détails",
        "Données",
    ],
    title="MCP Server - Indicateurs Territoriaux de Transition Écologique",
)

# Add a description block
with demo:
    gr.Markdown(
        """
        ---
        ### Connexion MCP
        
        Pour utiliser ce serveur comme outil MCP dans Claude Desktop, Cursor ou autre client MCP :
        
        ```json
        {
          "mcpServers": {
            "indicateurs-te": {
              "url": "https://YOUR-SPACE.hf.space/gradio_api/mcp/"
            }
          }
        }
        ```
        
        ### Structure des données
        
        Les cubes de données suivent le format `{thematique}_{maille}` :
        - `conso_enaf_com` → Consommation ENAF, maille commune
        - `surface_bio_dpt` → Surface bio, maille département
        
        Les measures contiennent l'ID de l'indicateur : `{cube}.id_{indicator_id}`
        
        ### API Cube.js
        
        Ce serveur interroge l'API du Hub d'Indicateurs Territoriaux du Ministère de la Transition Écologique.
        
        - Documentation : [ecologie.data.gouv.fr/indicators](https://ecologie.data.gouv.fr/indicators)
        - API : `https://api.indicateurs.ecologie.gouv.fr`
        """
    )


if __name__ == "__main__":
    demo.launch(
        mcp_server=True,
        server_name="0.0.0.0",
        server_port=7860,
    )