guohanghui commited on
Commit
af1fc4c
·
verified ·
1 Parent(s): e11bf70

Update causalml/mcp_output/mcp_plugin/mcp_service.py

Browse files
causalml/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -7,158 +7,501 @@ if source_path not in sys.path:
7
  sys.path.insert(0, source_path)
8
 
9
  from fastmcp import FastMCP
10
- from causalml.inference.meta import BaseSLearner, BaseTLearner, BaseXLearner, BaseRLearner, BaseDRLearner, TMLELearner
11
- from causalml.inference.tree import UpliftTreeClassifier, CausalTreeRegressor
12
- from causalml.metrics import AUUC, Qini
13
- from causalml.optimize import CounterfactualUnitSelector
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  mcp = FastMCP("causalml_service")
16
 
17
- @mcp.tool(name="s_learner", description="Estimate treatment effect using S-Learner")
18
- def s_learner(X: list, treatment: list, y: list) -> dict:
19
- """
20
- Estimate treatment effect using S-Learner.
 
21
 
22
- Parameters:
23
- - X: list of features
24
- - treatment: list of treatment indicators
25
- - y: list of outcomes
26
 
27
- Returns:
28
- - dict: containing success, result, or error
29
- """
30
  try:
31
- model = BaseSLearner()
32
- model.fit(X, treatment, y)
33
- result = model.estimate_ate(X, treatment, y)
34
- return {"success": True, "result": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  except Exception as e:
36
  return {"success": False, "error": str(e)}
37
 
38
- @mcp.tool(name="t_learner", description="Estimate treatment effect using T-Learner")
39
- def t_learner(X: list, treatment: list, y: list) -> dict:
40
- """
41
- Estimate treatment effect using T-Learner.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- Parameters:
44
- - X: list of features
45
- - treatment: list of treatment indicators
46
- - y: list of outcomes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- Returns:
49
- - dict: containing success, result, or error
50
- """
51
  try:
52
- model = BaseTLearner()
53
- model.fit(X, treatment, y)
54
- result = model.estimate_ate(X, treatment, y)
55
- return {"success": True, "result": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  except Exception as e:
57
  return {"success": False, "error": str(e)}
58
 
59
- @mcp.tool(name="uplift_tree", description="Classify using Uplift Tree")
60
- def uplift_tree(X: list, treatment: list, y: list) -> dict:
61
- """
62
- Classify using Uplift Tree.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- Parameters:
65
- - X: list of features
66
- - treatment: list of treatment indicators
67
- - y: list of outcomes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- Returns:
70
- - dict: containing success, result, or error
71
- """
 
 
72
  try:
73
- model = UpliftTreeClassifier()
74
- model.fit(X, treatment, y)
75
- result = model.predict(X)
76
- return {"success": True, "result": result}
 
 
 
 
 
 
 
77
  except Exception as e:
78
  return {"success": False, "error": str(e)}
79
 
80
- @mcp.tool(name="causal_tree", description="Regress using Causal Tree")
81
- def causal_tree(X: list, treatment: list, y: list) -> dict:
82
- """
83
- Regress using Causal Tree.
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- Parameters:
86
- - X: list of features
87
- - treatment: list of treatment indicators
88
- - y: list of outcomes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- Returns:
91
- - dict: containing success, result, or error
92
- """
93
  try:
94
- model = CausalTreeRegressor()
95
- model.fit(X, treatment, y)
96
- result = model.predict(X)
97
- return {"success": True, "result": result}
 
 
 
 
 
 
 
 
 
 
98
  except Exception as e:
99
  return {"success": False, "error": str(e)}
100
 
101
- @mcp.tool(name="auuc_metric", description="Calculate AUUC metric")
102
- def auuc_metric(y_true: list, uplift: list) -> dict:
103
- """
104
- Calculate AUUC metric.
105
 
106
- Parameters:
107
- - y_true: list of true outcomes
108
- - uplift: list of uplift predictions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- Returns:
111
- - dict: containing success, result, or error
112
- """
113
  try:
114
- result = AUUC(y_true, uplift)
115
- return {"success": True, "result": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  except Exception as e:
117
  return {"success": False, "error": str(e)}
118
 
119
- @mcp.tool(name="qini_metric", description="Calculate Qini metric")
120
- def qini_metric(y_true: list, uplift: list) -> dict:
121
- """
122
- Calculate Qini metric.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- Parameters:
125
- - y_true: list of true outcomes
126
- - uplift: list of uplift predictions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- Returns:
129
- - dict: containing success, result, or error
130
- """
131
  try:
132
- result = Qini(y_true, uplift)
133
- return {"success": True, "result": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  except Exception as e:
135
  return {"success": False, "error": str(e)}
136
 
137
- @mcp.tool(name="counterfactual_selector", description="Select counterfactual units")
138
- def counterfactual_selector(X: list, treatment: list, y: list) -> dict:
139
- """
140
- Select counterfactual units.
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- Parameters:
143
- - X: list of features
144
- - treatment: list of treatment indicators
145
- - y: list of outcomes
146
 
147
- Returns:
148
- - dict: containing success, result, or error
149
- """
150
  try:
151
- selector = CounterfactualUnitSelector()
152
- result = selector.select(X, treatment, y)
153
- return {"success": True, "result": result}
 
 
 
 
 
 
 
154
  except Exception as e:
155
  return {"success": False, "error": str(e)}
156
 
157
- def create_app() -> FastMCP:
158
- """
159
- Create and return the FastMCP application instance.
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
- Returns:
162
- - FastMCP: the application instance
163
- """
164
- return mcp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  sys.path.insert(0, source_path)
8
 
9
  from fastmcp import FastMCP
10
+ import numpy as np
11
+ import pandas as pd
12
+ from typing import Optional
13
+
14
+ # Import causalml components
15
+ from causalml.inference.meta import (
16
+ BaseSLearner, BaseTLearner, BaseXLearner, BaseRLearner,
17
+ BaseDRLearner, TMLELearner
18
+ )
19
+ from causalml.inference.tree import (
20
+ UpliftTreeClassifier, UpliftRandomForestClassifier,
21
+ CausalTreeRegressor, CausalRandomForestRegressor
22
+ )
23
+ from causalml.propensity import (
24
+ LogisticRegressionPropensityModel,
25
+ GradientBoostedPropensityModel
26
+ )
27
+ from causalml.match import NearestNeighborMatch, smd
28
+ from causalml.metrics import (
29
+ auuc_score, qini_score,
30
+ get_cumgain, get_cumlift, get_qini
31
+ )
32
 
33
  mcp = FastMCP("causalml_service")
34
 
35
+ # Session storage for models
36
+ _meta_learners = {}
37
+ _tree_models = {}
38
+ _propensity_models = {}
39
+ _matches = {}
40
 
41
+ # ==================== Meta-Learner Tools ====================
 
 
 
42
 
43
+ @mcp.tool()
44
+ def create_s_learner(learner_id: str, learner_type: str = "dummy", control_name: int = 0) -> dict:
45
+ """Create an S-Learner for treatment effect estimation."""
46
  try:
47
+ from sklearn.ensemble import RandomForestRegressor
48
+ from sklearn.linear_model import Ridge
49
+
50
+ if learner_type == "lr":
51
+ base_learner = Ridge()
52
+ elif learner_type == "rf":
53
+ base_learner = RandomForestRegressor(n_estimators=100, random_state=42)
54
+ else:
55
+ base_learner = None
56
+
57
+ learner = BaseSLearner(learner=base_learner, control_name=control_name)
58
+ _meta_learners[learner_id] = learner
59
+
60
+ return {
61
+ "success": True,
62
+ "learner_id": learner_id,
63
+ "type": "S-Learner",
64
+ "base_learner": learner_type
65
+ }
66
  except Exception as e:
67
  return {"success": False, "error": str(e)}
68
 
69
+ @mcp.tool()
70
+ def create_t_learner(learner_id: str, learner_type: str = "dummy", control_name: int = 0) -> dict:
71
+ """Create a T-Learner for treatment effect estimation."""
72
+ try:
73
+ from sklearn.ensemble import RandomForestRegressor
74
+ from sklearn.linear_model import Ridge
75
+
76
+ if learner_type == "lr":
77
+ base_learner = Ridge()
78
+ elif learner_type == "rf":
79
+ base_learner = RandomForestRegressor(n_estimators=100, random_state=42)
80
+ else:
81
+ base_learner = None
82
+
83
+ learner = BaseTLearner(learner=base_learner, control_name=control_name)
84
+ _meta_learners[learner_id] = learner
85
+
86
+ return {
87
+ "success": True,
88
+ "learner_id": learner_id,
89
+ "type": "T-Learner"
90
+ }
91
+ except Exception as e:
92
+ return {"success": False, "error": str(e)}
93
 
94
+ @mcp.tool()
95
+ def create_x_learner(learner_id: str, learner_type: str = "dummy", control_name: int = 0) -> dict:
96
+ """Create an X-Learner for treatment effect estimation."""
97
+ try:
98
+ from sklearn.ensemble import RandomForestRegressor
99
+ from sklearn.linear_model import Ridge
100
+
101
+ if learner_type == "lr":
102
+ base_learner = Ridge()
103
+ elif learner_type == "rf":
104
+ base_learner = RandomForestRegressor(n_estimators=100, random_state=42)
105
+ else:
106
+ base_learner = None
107
+
108
+ learner = BaseXLearner(learner=base_learner, control_name=control_name)
109
+ _meta_learners[learner_id] = learner
110
+
111
+ return {
112
+ "success": True,
113
+ "learner_id": learner_id,
114
+ "type": "X-Learner"
115
+ }
116
+ except Exception as e:
117
+ return {"success": False, "error": str(e)}
118
 
119
+ @mcp.tool()
120
+ def fit_meta_learner(learner_id: str, X: list, treatment: list, y: list, p: Optional[list] = None) -> dict:
121
+ """Fit a meta-learner on training data."""
122
  try:
123
+ if learner_id not in _meta_learners:
124
+ return {"success": False, "error": f"Learner {learner_id} not found"}
125
+
126
+ learner = _meta_learners[learner_id]
127
+ X_arr = np.array(X)
128
+ treatment_arr = np.array(treatment)
129
+ y_arr = np.array(y)
130
+ p_arr = np.array(p) if p is not None else None
131
+
132
+ learner.fit(X_arr, treatment_arr, y_arr, p=p_arr)
133
+
134
+ return {
135
+ "success": True,
136
+ "learner_id": learner_id,
137
+ "n_samples": len(y),
138
+ "fitted": True
139
+ }
140
  except Exception as e:
141
  return {"success": False, "error": str(e)}
142
 
143
+ @mcp.tool()
144
+ def predict_treatment_effects(learner_id: str, X: list) -> dict:
145
+ """Predict treatment effects for new samples."""
146
+ try:
147
+ if learner_id not in _meta_learners:
148
+ return {"success": False, "error": f"Learner {learner_id} not found"}
149
+
150
+ learner = _meta_learners[learner_id]
151
+ X_arr = np.array(X)
152
+
153
+ te = learner.predict(X_arr, verbose=False)
154
+
155
+ return {
156
+ "success": True,
157
+ "learner_id": learner_id,
158
+ "treatment_effects": te.tolist(),
159
+ "n_samples": len(te)
160
+ }
161
+ except Exception as e:
162
+ return {"success": False, "error": str(e)}
163
 
164
+ @mcp.tool()
165
+ def estimate_ate(learner_id: str, X: list, treatment: list, y: list) -> dict:
166
+ """Estimate Average Treatment Effect (ATE)."""
167
+ try:
168
+ if learner_id not in _meta_learners:
169
+ return {"success": False, "error": f"Learner {learner_id} not found"}
170
+
171
+ learner = _meta_learners[learner_id]
172
+ X_arr = np.array(X)
173
+ treatment_arr = np.array(treatment)
174
+ y_arr = np.array(y)
175
+
176
+ ate = learner.estimate_ate(X_arr, treatment_arr, y_arr)
177
+
178
+ if isinstance(ate, np.ndarray):
179
+ ate = ate.tolist()
180
+ elif isinstance(ate, (np.float32, np.float64, np.int32, np.int64)):
181
+ ate = float(ate)
182
+
183
+ return {
184
+ "success": True,
185
+ "learner_id": learner_id,
186
+ "ate": ate
187
+ }
188
+ except Exception as e:
189
+ return {"success": False, "error": str(e)}
190
 
191
+ # ==================== Uplift Tree Tools ====================
192
+
193
+ @mcp.tool()
194
+ def create_uplift_tree(model_id: str, max_depth: int = 3, min_samples_leaf: int = 100) -> dict:
195
+ """Create an Uplift Tree classifier."""
196
  try:
197
+ model = UpliftTreeClassifier(
198
+ max_depth=max_depth,
199
+ min_samples_leaf=min_samples_leaf
200
+ )
201
+ _tree_models[model_id] = model
202
+
203
+ return {
204
+ "success": True,
205
+ "model_id": model_id,
206
+ "type": "UpliftTree"
207
+ }
208
  except Exception as e:
209
  return {"success": False, "error": str(e)}
210
 
211
+ @mcp.tool()
212
+ def create_causal_tree(model_id: str, max_depth: int = 3, min_samples_leaf: int = 100) -> dict:
213
+ """Create a Causal Tree regressor."""
214
+ try:
215
+ model = CausalTreeRegressor(
216
+ max_depth=max_depth,
217
+ min_samples_leaf=min_samples_leaf
218
+ )
219
+ _tree_models[model_id] = model
220
+
221
+ return {
222
+ "success": True,
223
+ "model_id": model_id,
224
+ "type": "CausalTree"
225
+ }
226
+ except Exception as e:
227
+ return {"success": False, "error": str(e)}
228
 
229
+ @mcp.tool()
230
+ def fit_tree_model(model_id: str, X: list, treatment: list, y: list) -> dict:
231
+ """Fit an uplift/causal tree model."""
232
+ try:
233
+ if model_id not in _tree_models:
234
+ return {"success": False, "error": f"Model {model_id} not found"}
235
+
236
+ model = _tree_models[model_id]
237
+ X_arr = np.array(X)
238
+ treatment_arr = np.array(treatment)
239
+ y_arr = np.array(y)
240
+
241
+ model.fit(X_arr, treatment_arr, y_arr)
242
+
243
+ return {
244
+ "success": True,
245
+ "model_id": model_id,
246
+ "n_samples": len(y),
247
+ "fitted": True
248
+ }
249
+ except Exception as e:
250
+ return {"success": False, "error": str(e)}
251
 
252
+ @mcp.tool()
253
+ def predict_uplift(model_id: str, X: list) -> dict:
254
+ """Predict uplift scores."""
255
  try:
256
+ if model_id not in _tree_models:
257
+ return {"success": False, "error": f"Model {model_id} not found"}
258
+
259
+ model = _tree_models[model_id]
260
+ X_arr = np.array(X)
261
+
262
+ uplift = model.predict(X_arr)
263
+
264
+ return {
265
+ "success": True,
266
+ "model_id": model_id,
267
+ "uplift_scores": uplift.tolist(),
268
+ "n_samples": len(uplift)
269
+ }
270
  except Exception as e:
271
  return {"success": False, "error": str(e)}
272
 
273
+ # ==================== Propensity Score Tools ====================
 
 
 
274
 
275
+ @mcp.tool()
276
+ def create_propensity_model(model_id: str, model_type: str = "lr", calibrate: bool = True) -> dict:
277
+ """Create a propensity score model."""
278
+ try:
279
+ if model_type == "gbm":
280
+ model = GradientBoostedPropensityModel(calibrate=calibrate)
281
+ else:
282
+ model = LogisticRegressionPropensityModel(calibrate=calibrate)
283
+
284
+ _propensity_models[model_id] = model
285
+
286
+ return {
287
+ "success": True,
288
+ "model_id": model_id,
289
+ "type": model_type
290
+ }
291
+ except Exception as e:
292
+ return {"success": False, "error": str(e)}
293
 
294
+ @mcp.tool()
295
+ def fit_propensity_model(model_id: str, X: list, treatment: list) -> dict:
296
+ """Fit a propensity score model."""
297
  try:
298
+ if model_id not in _propensity_models:
299
+ return {"success": False, "error": f"Model {model_id} not found"}
300
+
301
+ model = _propensity_models[model_id]
302
+ X_arr = np.array(X)
303
+ treatment_arr = np.array(treatment)
304
+
305
+ model.fit(X_arr, treatment_arr)
306
+
307
+ return {
308
+ "success": True,
309
+ "model_id": model_id,
310
+ "n_samples": len(treatment),
311
+ "fitted": True
312
+ }
313
  except Exception as e:
314
  return {"success": False, "error": str(e)}
315
 
316
+ @mcp.tool()
317
+ def predict_propensity(model_id: str, X: list) -> dict:
318
+ """Predict propensity scores."""
319
+ try:
320
+ if model_id not in _propensity_models:
321
+ return {"success": False, "error": f"Model {model_id} not found"}
322
+
323
+ model = _propensity_models[model_id]
324
+ X_arr = np.array(X)
325
+
326
+ p_scores = model.predict(X_arr)
327
+
328
+ return {
329
+ "success": True,
330
+ "model_id": model_id,
331
+ "propensity_scores": p_scores.tolist()
332
+ }
333
+ except Exception as e:
334
+ return {"success": False, "error": str(e)}
335
+
336
+ # ==================== Matching Tools ====================
337
 
338
+ @mcp.tool()
339
+ def create_nearest_neighbor_match(match_id: str, caliper: float = 0.2, replace: bool = False, ratio: int = 1) -> dict:
340
+ """Create a nearest neighbor matching object."""
341
+ try:
342
+ matcher = NearestNeighborMatch(
343
+ caliper=caliper,
344
+ replace=replace,
345
+ ratio=ratio
346
+ )
347
+ _matches[match_id] = {"matcher": matcher, "matched_data": None}
348
+
349
+ return {
350
+ "success": True,
351
+ "match_id": match_id
352
+ }
353
+ except Exception as e:
354
+ return {"success": False, "error": str(e)}
355
 
356
+ @mcp.tool()
357
+ def perform_matching(match_id: str, data_dict: dict, treatment_col: str, score_cols: list) -> dict:
358
+ """Perform propensity score matching."""
359
  try:
360
+ if match_id not in _matches:
361
+ return {"success": False, "error": f"Matcher {match_id} not found"}
362
+
363
+ matcher = _matches[match_id]["matcher"]
364
+ data = pd.DataFrame(data_dict)
365
+
366
+ matched_data = matcher.match(data, treatment_col, score_cols)
367
+ _matches[match_id]["matched_data"] = matched_data
368
+
369
+ n_treated = len(matched_data[matched_data[treatment_col] == 1])
370
+ n_control = len(matched_data[matched_data[treatment_col] == 0])
371
+
372
+ return {
373
+ "success": True,
374
+ "match_id": match_id,
375
+ "n_matched_treated": int(n_treated),
376
+ "n_matched_control": int(n_control)
377
+ }
378
  except Exception as e:
379
  return {"success": False, "error": str(e)}
380
 
381
+ @mcp.tool()
382
+ def calculate_smd(feature: list, treatment: list) -> dict:
383
+ """Calculate Standardized Mean Difference (SMD)."""
384
+ try:
385
+ feature_series = pd.Series(feature)
386
+ treatment_series = pd.Series(treatment)
387
+
388
+ smd_value = smd(feature_series, treatment_series)
389
+
390
+ return {
391
+ "success": True,
392
+ "smd": float(smd_value)
393
+ }
394
+ except Exception as e:
395
+ return {"success": False, "error": str(e)}
396
 
397
+ # ==================== Evaluation Metrics ====================
 
 
 
398
 
399
+ @mcp.tool()
400
+ def calculate_auuc(y_true: list, uplift: list, treatment: list) -> dict:
401
+ """Calculate Area Under the Uplift Curve (AUUC)."""
402
  try:
403
+ y_arr = np.array(y_true)
404
+ uplift_arr = np.array(uplift)
405
+ treatment_arr = np.array(treatment)
406
+
407
+ auuc = auuc_score(y_arr, uplift_arr, treatment_arr)
408
+
409
+ return {
410
+ "success": True,
411
+ "auuc": float(auuc)
412
+ }
413
  except Exception as e:
414
  return {"success": False, "error": str(e)}
415
 
416
+ @mcp.tool()
417
+ def calculate_qini_score(y_true: list, uplift: list, treatment: list) -> dict:
418
+ """Calculate Qini coefficient."""
419
+ try:
420
+ y_arr = np.array(y_true)
421
+ uplift_arr = np.array(uplift)
422
+ treatment_arr = np.array(treatment)
423
+
424
+ qini = qini_score(y_arr, uplift_arr, treatment_arr)
425
+
426
+ return {
427
+ "success": True,
428
+ "qini_score": float(qini)
429
+ }
430
+ except Exception as e:
431
+ return {"success": False, "error": str(e)}
432
 
433
+ @mcp.tool()
434
+ def calculate_cumulative_gain(y_true: list, uplift: list, treatment: list) -> dict:
435
+ """Calculate cumulative gain curve data."""
436
+ try:
437
+ y_arr = np.array(y_true)
438
+ uplift_arr = np.array(uplift)
439
+ treatment_arr = np.array(treatment)
440
+
441
+ cum_gain = get_cumgain(y_arr, uplift_arr, treatment_arr)
442
+
443
+ return {
444
+ "success": True,
445
+ "cumulative_gain": cum_gain.tolist() if isinstance(cum_gain, np.ndarray) else cum_gain
446
+ }
447
+ except Exception as e:
448
+ return {"success": False, "error": str(e)}
449
+
450
+ # ==================== Utility Tools ====================
451
+
452
+ @mcp.tool()
453
+ def list_meta_learners() -> dict:
454
+ """List all created meta-learners."""
455
+ learners = []
456
+ for learner_id, learner in _meta_learners.items():
457
+ learners.append({
458
+ "id": learner_id,
459
+ "type": learner.__class__.__name__
460
+ })
461
+
462
+ return {
463
+ "success": True,
464
+ "learners": learners,
465
+ "count": len(learners)
466
+ }
467
+
468
+ @mcp.tool()
469
+ def list_tree_models() -> dict:
470
+ """List all created tree models."""
471
+ models = []
472
+ for model_id, model in _tree_models.items():
473
+ models.append({
474
+ "id": model_id,
475
+ "type": model.__class__.__name__
476
+ })
477
+
478
+ return {
479
+ "success": True,
480
+ "models": models,
481
+ "count": len(models)
482
+ }
483
+
484
+ @mcp.tool()
485
+ def get_causalml_info() -> dict:
486
+ """Get information about causalml library."""
487
+ try:
488
+ import causalml
489
+
490
+ return {
491
+ "success": True,
492
+ "version": causalml.__version__ if hasattr(causalml, '__version__') else "unknown",
493
+ "available_meta_learners": [
494
+ "BaseSLearner", "BaseTLearner", "BaseXLearner",
495
+ "BaseRLearner", "BaseDRLearner", "TMLELearner"
496
+ ],
497
+ "available_tree_models": [
498
+ "UpliftTreeClassifier", "UpliftRandomForestClassifier",
499
+ "CausalTreeRegressor", "CausalRandomForestRegressor"
500
+ ]
501
+ }
502
+ except Exception as e:
503
+ return {"success": False, "error": str(e)}
504
+
505
+ def create_app() -> FastMCP:
506
+ """Create and return the FastMCP application instance."""
507
+ return mcp