Tracy André commited on
Commit
e55413f
·
1 Parent(s): 6df7dd5
Files changed (3) hide show
  1. MCP_RESOURCES.md +130 -0
  2. mcp_server.py +268 -0
  3. test_mcp_resources.py +47 -0
MCP_RESOURCES.md ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MCP Resources Documentation
2
+
3
+ ## Overview
4
+ This MCP server provides agricultural data analysis resources for weed pressure prediction and crop recommendations. The resources are designed to be consumed by LLMs through the Model Context Protocol.
5
+
6
+ ## Available Resources
7
+
8
+ ### Static Resources
9
+
10
+ #### `agricultural://plots`
11
+ - **Description**: List of all available agricultural plots
12
+ - **Returns**: String with plot names and count
13
+ - **Example**: "Available plots (106): Charbonnerie Entrée, Grand champ 5 (MH), ..."
14
+
15
+ #### `agricultural://crops`
16
+ - **Description**: List of all crop types in the dataset
17
+ - **Returns**: String with crop names and count
18
+ - **Example**: "Available crops (42): blé tendre hiver, pois de conserve, ..."
19
+
20
+ #### `agricultural://years`
21
+ - **Description**: Range of years available in the dataset
22
+ - **Returns**: String with year range and total count
23
+ - **Example**: "Available years: 2014-2025 (12 years total)"
24
+
25
+ #### `agricultural://dataset-info`
26
+ - **Description**: Comprehensive dataset information
27
+ - **Returns**: String with statistics about the agricultural dataset
28
+ - **Includes**: Record counts, plot/crop/intervention counts, herbicide usage stats
29
+
30
+ #### `agricultural://herbicide-usage`
31
+ - **Description**: Summary of herbicide usage patterns
32
+ - **Returns**: String with IFT statistics and risk distribution
33
+ - **Includes**: Total applications, average IFT, risk levels, most used herbicides
34
+
35
+ #### `agricultural://predictions/2025-2027`
36
+ - **Description**: Summary of weed pressure predictions for 2025-2027
37
+ - **Returns**: String with prediction statistics and risk distribution
38
+ - **Includes**: Total predictions, average IFT, risk levels, best plots
39
+
40
+ #### `agricultural://recommendations/sensitive-crops`
41
+ - **Description**: Summary of plot recommendations for sensitive crops
42
+ - **Returns**: String with recommendation statistics and top plots
43
+ - **Includes**: Suitable plots count, average score, top 5 recommendations
44
+
45
+ ### Parameterized Resources
46
+
47
+ #### `agricultural://plot/{plot_name}`
48
+ - **Description**: Detailed information about a specific agricultural plot
49
+ - **Parameters**: `plot_name` (string) - Name of the plot
50
+ - **Returns**: String with plot statistics and characteristics
51
+ - **Includes**: Interventions count, years active, herbicide usage, surface, main crops
52
+
53
+ #### `agricultural://crop/{crop_type}`
54
+ - **Description**: Information about a specific crop type
55
+ - **Parameters**: `crop_type` (string) - Name of the crop
56
+ - **Returns**: String with crop cultivation patterns
57
+ - **Includes**: Total interventions, years cultivated, plots count, herbicide usage
58
+
59
+ #### `agricultural://year/{year}`
60
+ - **Description**: Summary of agricultural activities for a specific year
61
+ - **Parameters**: `year` (int) - Year to analyze
62
+ - **Returns**: String with year statistics
63
+ - **Includes**: Total interventions, active plots, crop types, most active plot/crop
64
+
65
+ #### `agricultural://plot/{plot_name}/predictions`
66
+ - **Description**: Weed pressure predictions for a specific plot
67
+ - **Parameters**: `plot_name` (string) - Name of the plot
68
+ - **Returns**: String with predictions for 2025-2027
69
+ - **Includes**: IFT predictions by year, risk levels, historical average, recent crops
70
+
71
+ ## Usage Examples
72
+
73
+ ### For LLMs
74
+ ```python
75
+ # Get dataset overview
76
+ dataset_info = get_dataset_info()
77
+
78
+ # Get specific plot information
79
+ plot_info = get_plot_info("Champ ferme W du sol")
80
+
81
+ # Get crop cultivation patterns
82
+ crop_info = get_crop_info("blé tendre hiver")
83
+
84
+ # Get year summary
85
+ year_summary = get_year_summary(2023)
86
+
87
+ # Get herbicide usage patterns
88
+ herbicide_usage = get_herbicide_usage_summary()
89
+
90
+ # Get predictions summary
91
+ predictions = get_predictions_summary()
92
+
93
+ # Get recommendations for sensitive crops
94
+ recommendations = get_recommendations_summary()
95
+
96
+ # Get specific plot predictions
97
+ plot_predictions = get_plot_predictions("Etang 5")
98
+ ```
99
+
100
+ ## Activation Instructions
101
+
102
+ To activate MCP resources, uncomment the `@gr.mcp.resource` decorators in `mcp_server.py`:
103
+
104
+ ```python
105
+ # Change from:
106
+ # @gr.mcp.resource("agricultural://plots")
107
+ def get_available_plots_resource() -> str:
108
+
109
+ # To:
110
+ @gr.mcp.resource("agricultural://plots")
111
+ def get_available_plots_resource() -> str:
112
+ ```
113
+
114
+ ## Data Sources
115
+
116
+ - **Station**: Station Expérimentale de Kerguéhennec
117
+ - **Period**: 2014-2025
118
+ - **Records**: 5,756 agricultural interventions
119
+ - **Plots**: 106 agricultural plots
120
+ - **Crops**: 42 different crop types
121
+ - **Herbicide Applications**: 985 applications
122
+
123
+ ## Key Metrics
124
+
125
+ - **IFT (Indice de Fréquence de Traitement)**: Number of herbicide applications per hectare
126
+ - **Risk Levels**:
127
+ - Low (IFT < 1.0): Suitable for sensitive crops
128
+ - Moderate (1.0 ≤ IFT < 2.0): Requires monitoring
129
+ - High (IFT ≥ 2.0): Not recommended for sensitive crops
130
+ - **Recommendation Score**: 100 - (predicted_ift × 30)
mcp_server.py CHANGED
@@ -89,6 +89,274 @@ class WeedPressureAnalyzer:
89
  # Initialize analyzer
90
  analyzer = WeedPressureAnalyzer()
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  def analyze_herbicide_trends(year_start, year_end, plot_filter):
93
  """
94
  Analyze herbicide usage trends over time by calculating IFT (Treatment Frequency Index).
 
89
  # Initialize analyzer
90
  analyzer = WeedPressureAnalyzer()
91
 
92
+ # MCP Resources (to be activated when @gr.mcp.resource is available)
93
+ # These functions provide structured data access for LLMs and can be exposed as MCP resources
94
+ # To activate: uncomment the @gr.mcp.resource decorators below
95
+
96
+ # @gr.mcp.resource("agricultural://plots")
97
+ def get_available_plots_resource() -> str:
98
+ """Get list of all available agricultural plots from the dataset"""
99
+ try:
100
+ plots = get_available_plots()
101
+ return f"Available plots ({len(plots)-1}): " + ", ".join(plots[1:6]) + f" ... and {len(plots)-6} more"
102
+ except Exception as e:
103
+ return f"Error loading plots: {str(e)}"
104
+
105
+ # @gr.mcp.resource("agricultural://crops")
106
+ def get_available_crops_resource() -> str:
107
+ """Get list of all crop types in the dataset"""
108
+ try:
109
+ crops = get_available_crops()
110
+ return f"Available crops ({len(crops)-1}): " + ", ".join(crops[1:6]) + f" ... and {len(crops)-6} more"
111
+ except Exception as e:
112
+ return f"Error loading crops: {str(e)}"
113
+
114
+ # @gr.mcp.resource("agricultural://years")
115
+ def get_available_years_resource() -> str:
116
+ """Get range of years available in the dataset"""
117
+ try:
118
+ df = analyzer.load_data()
119
+ years = sorted(df['year'].dropna().unique())
120
+ return f"Available years: {min(years)}-{max(years)} ({len(years)} years total)"
121
+ except Exception as e:
122
+ return f"Error loading years: {str(e)}"
123
+
124
+ # @gr.mcp.resource("agricultural://dataset-info")
125
+ def get_dataset_info() -> str:
126
+ """Get comprehensive information about the agricultural dataset"""
127
+ try:
128
+ df = analyzer.load_data()
129
+ herbicide_data = df[df['is_herbicide'] == True]
130
+
131
+ info = f"""
132
+ Agricultural Dataset Information:
133
+ - Total records: {len(df):,}
134
+ - Years covered: {df['year'].min()}-{df['year'].max()}
135
+ - Number of plots: {df['plot_name'].nunique()}
136
+ - Number of crop types: {df['crop_type'].nunique()}
137
+ - Number of intervention types: {df['intervention_type'].nunique()}
138
+ - Herbicide applications: {len(herbicide_data):,}
139
+ - Average IFT (herbicides): {herbicide_data.groupby(['plot_name', 'year']).size().mean():.2f}
140
+ - Data source: Station Expérimentale de Kerguéhennec
141
+ - Last updated: 2025
142
+ """
143
+ return info.strip()
144
+ except Exception as e:
145
+ return f"Error loading dataset info: {str(e)}"
146
+
147
+ # @gr.mcp.resource("agricultural://plot/{plot_name}")
148
+ def get_plot_info(plot_name: str) -> str:
149
+ """Get detailed information about a specific agricultural plot"""
150
+ try:
151
+ df = analyzer.load_data()
152
+ plot_data = df[df['plot_name'] == plot_name]
153
+
154
+ if len(plot_data) == 0:
155
+ return f"Plot '{plot_name}' not found in dataset"
156
+
157
+ herbicide_data = plot_data[plot_data['is_herbicide'] == True]
158
+ years = sorted(plot_data['year'].unique())
159
+
160
+ info = f"""
161
+ Plot Information: {plot_name}
162
+ - Total interventions: {len(plot_data):,}
163
+ - Years active: {min(years)}-{max(years)} ({len(years)} years)
164
+ - Herbicide applications: {len(herbicide_data):,}
165
+ - Average IFT: {herbicide_data.groupby('year').size().mean():.2f}
166
+ - Surface: {plot_data['plot_surface'].iloc[0]:.2f} hectares
167
+ - Main crops: {', '.join(plot_data['crop_type'].value_counts().head(3).index.tolist())}
168
+ - Main interventions: {', '.join(plot_data['intervention_type'].value_counts().head(3).index.tolist())}
169
+ """
170
+ return info.strip()
171
+ except Exception as e:
172
+ return f"Error loading plot info: {str(e)}"
173
+
174
+ # @gr.mcp.resource("agricultural://crop/{crop_type}")
175
+ def get_crop_info(crop_type: str) -> str:
176
+ """Get information about a specific crop type and its cultivation patterns"""
177
+ try:
178
+ df = analyzer.load_data()
179
+ crop_data = df[df['crop_type'] == crop_type]
180
+
181
+ if len(crop_data) == 0:
182
+ return f"Crop type '{crop_type}' not found in dataset"
183
+
184
+ herbicide_data = crop_data[crop_data['is_herbicide'] == True]
185
+ years = sorted(crop_data['year'].unique())
186
+ plots = crop_data['plot_name'].nunique()
187
+
188
+ info = f"""
189
+ Crop Information: {crop_type}
190
+ - Total interventions: {len(crop_data):,}
191
+ - Years cultivated: {min(years)}-{max(years)} ({len(years)} years)
192
+ - Number of plots: {plots}
193
+ - Herbicide applications: {len(herbicide_data):,}
194
+ - Average IFT: {herbicide_data.groupby(['plot_name', 'year']).size().mean():.2f}
195
+ - Main plots: {', '.join(crop_data['plot_name'].value_counts().head(3).index.tolist())}
196
+ - Main interventions: {', '.join(crop_data['intervention_type'].value_counts().head(3).index.tolist())}
197
+ """
198
+ return info.strip()
199
+ except Exception as e:
200
+ return f"Error loading crop info: {str(e)}"
201
+
202
+ # @gr.mcp.resource("agricultural://year/{year}")
203
+ def get_year_summary(year: int) -> str:
204
+ """Get summary of agricultural activities for a specific year"""
205
+ try:
206
+ df = analyzer.load_data()
207
+ year_data = df[df['year'] == year]
208
+
209
+ if len(year_data) == 0:
210
+ return f"No data available for year {year}"
211
+
212
+ herbicide_data = year_data[year_data['is_herbicide'] == True]
213
+ plots = year_data['plot_name'].nunique()
214
+ crops = year_data['crop_type'].nunique()
215
+
216
+ info = f"""
217
+ Year Summary: {year}
218
+ - Total interventions: {len(year_data):,}
219
+ - Active plots: {plots}
220
+ - Crop types: {crops}
221
+ - Herbicide applications: {len(herbicide_data):,}
222
+ - Average IFT: {herbicide_data.groupby('plot_name').size().mean():.2f}
223
+ - Most active plot: {year_data['plot_name'].value_counts().index[0]} ({year_data['plot_name'].value_counts().iloc[0]} interventions)
224
+ - Most common crop: {year_data['crop_type'].value_counts().index[0]} ({year_data['crop_type'].value_counts().iloc[0]} interventions)
225
+ - Most common intervention: {year_data['intervention_type'].value_counts().index[0]} ({year_data['intervention_type'].value_counts().iloc[0]} interventions)
226
+ """
227
+ return info.strip()
228
+ except Exception as e:
229
+ return f"Error loading year summary: {str(e)}"
230
+
231
+ # @gr.mcp.resource("agricultural://herbicide-usage")
232
+ def get_herbicide_usage_summary() -> str:
233
+ """Get comprehensive summary of herbicide usage patterns"""
234
+ try:
235
+ df = analyzer.load_data()
236
+ herbicide_data = df[df['is_herbicide'] == True]
237
+
238
+ if len(herbicide_data) == 0:
239
+ return "No herbicide data available"
240
+
241
+ # Calculate IFT by plot and year
242
+ ift_data = herbicide_data.groupby(['plot_name', 'year']).size().reset_index(name='applications')
243
+ ift_data['ift'] = ift_data['applications'] / herbicide_data.groupby(['plot_name', 'year'])['plot_surface'].first().values
244
+
245
+ avg_ift = ift_data['ift'].mean()
246
+ max_ift = ift_data['ift'].max()
247
+ min_ift = ift_data['ift'].min()
248
+
249
+ # Risk distribution
250
+ low_risk = len(ift_data[ift_data['ift'] < 1.0])
251
+ moderate_risk = len(ift_data[(ift_data['ift'] >= 1.0) & (ift_data['ift'] < 2.0)])
252
+ high_risk = len(ift_data[ift_data['ift'] >= 2.0])
253
+
254
+ info = f"""
255
+ Herbicide Usage Summary:
256
+ - Total applications: {len(herbicide_data):,}
257
+ - Plots with herbicides: {herbicide_data['plot_name'].nunique()}
258
+ - Years with data: {herbicide_data['year'].nunique()}
259
+ - Average IFT: {avg_ift:.2f}
260
+ - IFT range: {min_ift:.2f} - {max_ift:.2f}
261
+ - Risk distribution:
262
+ * Low risk (IFT < 1.0): {low_risk} plot-years ({low_risk/len(ift_data)*100:.1f}%)
263
+ * Moderate risk (1.0 ≤ IFT < 2.0): {moderate_risk} plot-years ({moderate_risk/len(ift_data)*100:.1f}%)
264
+ * High risk (IFT ≥ 2.0): {high_risk} plot-years ({high_risk/len(ift_data)*100:.1f}%)
265
+ - Most used herbicides: {', '.join(herbicide_data['produit'].value_counts().head(3).index.tolist())}
266
+ """
267
+ return info.strip()
268
+ except Exception as e:
269
+ return f"Error loading herbicide usage summary: {str(e)}"
270
+
271
+ # @gr.mcp.resource("agricultural://predictions/2025-2027")
272
+ def get_predictions_summary() -> str:
273
+ """Get summary of weed pressure predictions for 2025-2027"""
274
+ try:
275
+ predictions = analyzer.predict_weed_pressure()
276
+
277
+ if len(predictions) == 0:
278
+ return "No predictions available - insufficient historical data"
279
+
280
+ low_risk = len(predictions[predictions['risk_level'] == 'Faible'])
281
+ moderate_risk = len(predictions[predictions['risk_level'] == 'Modéré'])
282
+ high_risk = len(predictions[predictions['risk_level'] == 'Élevé'])
283
+
284
+ avg_ift = predictions['predicted_ift'].mean()
285
+
286
+ info = f"""
287
+ Weed Pressure Predictions 2025-2027:
288
+ - Total predictions: {len(predictions)}
289
+ - Average predicted IFT: {avg_ift:.2f}
290
+ - Risk distribution:
291
+ * Low risk (IFT < 1.0): {low_risk} predictions ({low_risk/len(predictions)*100:.1f}%)
292
+ * Moderate risk (1.0 ≤ IFT < 2.0): {moderate_risk} predictions ({moderate_risk/len(predictions)*100:.1f}%)
293
+ * High risk (IFT ≥ 2.0): {high_risk} predictions ({high_risk/len(predictions)*100:.1f}%)
294
+ - Best plots (lowest IFT): {', '.join(predictions.nsmallest(3, 'predicted_ift')['plot_name'].tolist())}
295
+ - Method: Linear regression on historical IFT data
296
+ """
297
+ return info.strip()
298
+ except Exception as e:
299
+ return f"Error loading predictions summary: {str(e)}"
300
+
301
+ # @gr.mcp.resource("agricultural://recommendations/sensitive-crops")
302
+ def get_recommendations_summary() -> str:
303
+ """Get summary of plot recommendations for sensitive crops (pois, haricot)"""
304
+ try:
305
+ predictions = analyzer.predict_weed_pressure()
306
+ suitable_plots = predictions[predictions['risk_level'] == "Faible"].copy()
307
+
308
+ if len(suitable_plots) == 0:
309
+ return "No plots recommended for sensitive crops - all plots have high predicted weed pressure"
310
+
311
+ suitable_plots['recommendation_score'] = 100 - (suitable_plots['predicted_ift'] * 30)
312
+ suitable_plots = suitable_plots.sort_values('recommendation_score', ascending=False)
313
+
314
+ top_plots = suitable_plots.head(5)
315
+ avg_score = suitable_plots['recommendation_score'].mean()
316
+
317
+ info = f"""
318
+ Sensitive Crop Recommendations (Pois, Haricot):
319
+ - Suitable plots: {len(suitable_plots)}
320
+ - Average recommendation score: {avg_score:.1f}/100
321
+ - Top 5 recommended plots:
322
+ """
323
+ for i, (_, plot) in enumerate(top_plots.iterrows(), 1):
324
+ info += f" {i}. {plot['plot_name']} - Score: {plot['recommendation_score']:.1f}, IFT: {plot['predicted_ift']:.2f}\n"
325
+
326
+ info += f"- Criteria: IFT < 1.0 (low weed pressure)\n"
327
+ info += f"- Score formula: 100 - (predicted_ift × 30)\n"
328
+ info += f"- Method: Based on 2025-2027 predictions"
329
+
330
+ return info.strip()
331
+ except Exception as e:
332
+ return f"Error loading recommendations summary: {str(e)}"
333
+
334
+ # @gr.mcp.resource("agricultural://plot/{plot_name}/predictions")
335
+ def get_plot_predictions(plot_name: str) -> str:
336
+ """Get weed pressure predictions for a specific plot"""
337
+ try:
338
+ predictions = analyzer.predict_weed_pressure()
339
+ plot_predictions = predictions[predictions['plot_name'] == plot_name]
340
+
341
+ if len(plot_predictions) == 0:
342
+ return f"No predictions available for plot '{plot_name}' - insufficient historical data"
343
+
344
+ plot_predictions = plot_predictions.sort_values('year')
345
+
346
+ info = f"""
347
+ Predictions for {plot_name}:
348
+ """
349
+ for _, pred in plot_predictions.iterrows():
350
+ info += f"- {pred['year']}: IFT {pred['predicted_ift']:.2f} ({pred['risk_level']} risk)\n"
351
+
352
+ info += f"- Historical average IFT: {plot_predictions['historical_avg_ift'].iloc[0]:.2f}\n"
353
+ info += f"- Recent crops: {plot_predictions['recent_crops'].iloc[0]}\n"
354
+ info += f"- Recommendation: {'Suitable for sensitive crops' if plot_predictions['risk_level'].iloc[0] == 'Faible' else 'Not recommended for sensitive crops'}"
355
+
356
+ return info.strip()
357
+ except Exception as e:
358
+ return f"Error loading plot predictions: {str(e)}"
359
+
360
  def analyze_herbicide_trends(year_start, year_end, plot_filter):
361
  """
362
  Analyze herbicide usage trends over time by calculating IFT (Treatment Frequency Index).
test_mcp_resources.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test des resources MCP
3
+ """
4
+
5
+ from mcp_server import (
6
+ get_available_plots_resource,
7
+ get_available_crops_resource,
8
+ get_available_years_resource,
9
+ get_dataset_info,
10
+ get_plot_info,
11
+ get_crop_info,
12
+ get_year_summary,
13
+ get_herbicide_usage_summary,
14
+ get_predictions_summary,
15
+ get_recommendations_summary,
16
+ get_plot_predictions
17
+ )
18
+
19
+ def test_mcp_resources():
20
+ """Test des resources MCP"""
21
+ print("🧪 Test des resources MCP...")
22
+
23
+ # Test resources statiques
24
+ print("\n📋 Test resources statiques:")
25
+ print("Plots:", get_available_plots_resource())
26
+ print("Crops:", get_available_crops_resource())
27
+ print("Years:", get_available_years_resource())
28
+ print("Dataset info:", get_dataset_info()[:200] + "...")
29
+
30
+ # Test resources avec paramètres
31
+ print("\n🏞️ Test resources avec paramètres:")
32
+ print("Plot info:", get_plot_info("Champ ferme W du sol")[:200] + "...")
33
+ print("Crop info:", get_crop_info("blé tendre hiver")[:200] + "...")
34
+ print("Year summary:", get_year_summary(2023)[:200] + "...")
35
+
36
+ # Test resources d'analyse
37
+ print("\n📊 Test resources d'analyse:")
38
+ print("Herbicide usage:", get_herbicide_usage_summary()[:200] + "...")
39
+ print("Predictions:", get_predictions_summary()[:200] + "...")
40
+ print("Recommendations:", get_recommendations_summary()[:200] + "...")
41
+
42
+ # Test resource prédictions par parcelle
43
+ print("\n🔮 Test prédictions par parcelle:")
44
+ print("Plot predictions:", get_plot_predictions("Champ ferme W du sol")[:200] + "...")
45
+
46
+ if __name__ == "__main__":
47
+ test_mcp_resources()