guohanghui commited on
Commit
c77a646
·
verified ·
1 Parent(s): 251f274

Update scvelo/mcp_output/mcp_plugin/mcp_service.py

Browse files
scvelo/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,103 +1,428 @@
 
 
 
 
 
 
 
 
 
 
1
  from fastmcp import FastMCP
2
 
 
 
 
 
 
 
 
3
  # Create the FastMCP service application
4
  mcp = FastMCP("scvelo_service")
5
 
6
- @mcp.tool(name="read_data", description="Read and load data into scvelo")
7
- def read_data(file_path: str) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
- Read and load data into scvelo.
10
 
11
  Parameters:
12
- - file_path: Path to the data file.
 
 
 
 
 
13
 
14
  Returns:
15
- - dict: Success status and loaded data information.
16
  """
17
  try:
18
- from scvelo.read_load import read
19
- data = read(file_path)
20
- return {
21
- "success": True,
22
- "data_summary": str(data)
 
 
 
 
 
 
 
 
 
23
  }
 
24
  except Exception as e:
25
- return {"success": False, "error": str(e)}
26
 
27
- @mcp.tool(name="compute_velocity", description="Compute RNA velocity")
28
- def compute_velocity(data: dict) -> dict:
 
29
  """
30
- Compute RNA velocity using scvelo.
31
 
32
  Parameters:
33
- - data: The loaded data object.
 
 
34
 
35
  Returns:
36
- - dict: Success status and velocity computation results.
37
  """
38
  try:
39
- from scvelo.core import velocity
40
- velocity(data)
41
- return {
42
- "success": True,
43
- "message": "Velocity computed successfully."
 
 
44
  }
 
45
  except Exception as e:
46
- return {"success": False, "error": str(e)}
 
 
 
47
 
48
- @mcp.tool(name="plot_velocity", description="Plot RNA velocity")
49
- def plot_velocity(data: dict, output_path: str) -> dict:
50
  """
51
- Plot RNA velocity results.
52
 
53
  Parameters:
54
- - data: The data object with computed velocity.
55
- - output_path: Path to save the plot.
 
 
56
 
57
  Returns:
58
- - dict: Success status and plot information.
59
  """
60
  try:
61
- from scvelo.plotting import velocity_embedding_stream
62
- velocity_embedding_stream(data, save=output_path)
63
- return {
64
- "success": True,
65
- "message": f"Velocity plot saved to {output_path}."
 
 
 
 
 
 
 
66
  }
 
67
  except Exception as e:
68
- return {"success": False, "error": str(e)}
69
 
70
- @mcp.tool(name="run_pipeline", description="Run a full scvelo pipeline")
71
- def run_pipeline(file_path: str, output_path: str) -> dict:
 
 
 
72
  """
73
- Run a full scvelo pipeline from data loading to plotting.
74
 
75
  Parameters:
76
- - file_path: Path to the data file.
77
- - output_path: Path to save the final plot.
78
 
79
  Returns:
80
- - dict: Success status and pipeline results.
81
  """
82
  try:
83
- from scvelo.read_load import read
84
- from scvelo.core import velocity
85
- from scvelo.plotting import velocity_embedding_stream
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- data = read(file_path)
88
- velocity(data)
89
- velocity_embedding_stream(data, save=output_path)
90
 
91
- return {
92
- "success": True,
93
- "message": f"Pipeline completed successfully. Plot saved to {output_path}."
 
 
 
 
 
 
 
 
 
94
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  except Exception as e:
96
- return {"success": False, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  def create_app() -> FastMCP:
99
  """
100
- Create and return the FastMCP application instance.
101
 
102
  Returns:
103
  - FastMCP: The FastMCP application instance.
 
1
+ import os
2
+ import sys
3
+ from typing import Any, List, Optional, Dict
4
+
5
+ # Add the local source directory to sys.path
6
+ source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
7
+ if source_path not in sys.path:
8
+ sys.path.insert(0, source_path)
9
+
10
+ import numpy as np
11
  from fastmcp import FastMCP
12
 
13
+ # Import scvelo core modules
14
+ from scvelo.core._arithmetic import clipped_log, invert, multiply, prod_sum, sum as scv_sum
15
+ from scvelo.core._metrics import l2_norm
16
+ from scvelo.core._models import SplicingDynamics
17
+ from scvelo.core._linear_models import LinearRegression
18
+ from scvelo.core._parallelize import get_n_jobs
19
+
20
  # Create the FastMCP service application
21
  mcp = FastMCP("scvelo_service")
22
 
23
+
24
+ # ===================== Arithmetic Tools =====================
25
+
26
+ @mcp.tool(name="arithmetic_tool", description="Tool for arithmetic operations")
27
+ def arithmetic_tool(a: float, b: float) -> dict:
28
+ """
29
+ Performs basic arithmetic operations on two numbers.
30
+
31
+ Parameters:
32
+ - a (float): First number.
33
+ - b (float): Second number.
34
+
35
+ Returns:
36
+ - dict: Result containing sum, product, and other calculations.
37
+ """
38
+ try:
39
+ result = {
40
+ "sum": float(a + b),
41
+ "product": float(a * b),
42
+ "difference": float(a - b),
43
+ "division": float(a / b) if b != 0 else None
44
+ }
45
+ return {"success": True, "result": result, "error": None}
46
+ except Exception as e:
47
+ return {"success": False, "result": None, "error": str(e)}
48
+
49
+
50
+ @mcp.tool(name="clipped_log_tool", description="Calculate clipped logarithm of values")
51
+ def clipped_log_tool(values: List[float], lb: float = 0.0, ub: float = 1.0, eps: float = 1e-6) -> dict:
52
+ """
53
+ Calculate logarithm of values clipped to [lb + eps, ub - eps].
54
+
55
+ Parameters:
56
+ - values (List[float]): Input values to calculate clipped log.
57
+ - lb (float): Lower bound for clipping. Default 0.0.
58
+ - ub (float): Upper bound for clipping. Default 1.0.
59
+ - eps (float): Epsilon offset. Default 1e-6.
60
+
61
+ Returns:
62
+ - dict: Clipped logarithm values.
63
+ """
64
+ try:
65
+ arr = np.array(values)
66
+ result = clipped_log(arr, lb=lb, ub=ub, eps=eps)
67
+ return {"success": True, "result": result.tolist(), "error": None}
68
+ except Exception as e:
69
+ return {"success": False, "result": None, "error": str(e)}
70
+
71
+
72
+ # ===================== Metrics Tools =====================
73
+
74
+ @mcp.tool(name="metrics_tool", description="Tool for metrics calculations")
75
+ def metrics_tool(values: List[float]) -> dict:
76
+ """
77
+ Calculates various metrics including l2 norm, mean, std, etc.
78
+
79
+ Parameters:
80
+ - values (List[float]): List of values for metrics calculation.
81
+
82
+ Returns:
83
+ - dict: Result of the metrics calculation with success status.
84
+ """
85
+ try:
86
+ arr = np.array(values)
87
+ result = {
88
+ "l2_norm": float(np.sqrt(np.sum(arr ** 2))),
89
+ "mean": float(np.mean(arr)),
90
+ "std": float(np.std(arr)),
91
+ "min": float(np.min(arr)),
92
+ "max": float(np.max(arr)),
93
+ "sum": float(np.sum(arr))
94
+ }
95
+ return {"success": True, "result": result, "error": None}
96
+ except Exception as e:
97
+ return {"success": False, "result": None, "error": str(e)}
98
+
99
+
100
+ @mcp.tool(name="l2_norm_tool", description="Calculate L2 norm of a matrix along specified axis")
101
+ def l2_norm_tool(matrix: List[List[float]], axis: int = 1) -> dict:
102
+ """
103
+ Calculate L2 norm along a given axis.
104
+
105
+ Parameters:
106
+ - matrix (List[List[float]]): 2D matrix of values.
107
+ - axis (int): Axis along which to calculate l2 norm (0 or 1). Default 1.
108
+
109
+ Returns:
110
+ - dict: L2 norm values along the specified axis.
111
+ """
112
+ try:
113
+ arr = np.array(matrix)
114
+ result = l2_norm(arr, axis=axis)
115
+ return {"success": True, "result": result.tolist(), "error": None}
116
+ except Exception as e:
117
+ return {"success": False, "result": None, "error": str(e)}
118
+
119
+
120
+ # ===================== Models Tools =====================
121
+
122
+ @mcp.tool(name="models_tool", description="Tool for model operations")
123
+ def models_tool(model_data: dict) -> dict:
124
+ """
125
+ Performs operations related to SplicingDynamics model.
126
+
127
+ Parameters:
128
+ - model_data (dict): Dictionary containing model parameters:
129
+ - alpha (float): Transcription rate
130
+ - beta (float): Translation rate
131
+ - gamma (float): Splicing degradation rate
132
+ - initial_state (list, optional): Initial [u0, s0] state
133
+ - time_points (list, optional): Time points for solution
134
+
135
+ Returns:
136
+ - dict: Result of the model operation with success status.
137
+ """
138
+ try:
139
+ alpha = model_data.get("alpha", 1.0)
140
+ beta = model_data.get("beta", 1.0)
141
+ gamma = model_data.get("gamma", 0.5)
142
+ initial_state = model_data.get("initial_state", [0, 0])
143
+ time_points = model_data.get("time_points", [0, 1, 2, 3, 4, 5])
144
+
145
+ dynamics = SplicingDynamics(
146
+ alpha=alpha,
147
+ beta=beta,
148
+ gamma=gamma,
149
+ initial_state=initial_state
150
+ )
151
+
152
+ t = np.array(time_points)
153
+ solution = dynamics.get_solution(t, with_keys=True)
154
+ steady_states = dynamics.get_steady_states(with_keys=True)
155
+
156
+ result = {
157
+ "solution": {
158
+ "unspliced": solution["u"].tolist() if isinstance(solution["u"], np.ndarray) else [solution["u"]],
159
+ "spliced": solution["s"].tolist() if isinstance(solution["s"], np.ndarray) else [solution["s"]]
160
+ },
161
+ "steady_states": {
162
+ "unspliced": float(steady_states["u"]),
163
+ "spliced": float(steady_states["s"])
164
+ },
165
+ "parameters": {
166
+ "alpha": alpha,
167
+ "beta": beta,
168
+ "gamma": gamma
169
+ }
170
+ }
171
+ return {"success": True, "result": result, "error": None}
172
+ except Exception as e:
173
+ return {"success": False, "result": None, "error": str(e)}
174
+
175
+
176
+ @mcp.tool(name="splicing_dynamics_solution", description="Calculate RNA splicing dynamics solution over time")
177
+ def splicing_dynamics_solution(
178
+ alpha: float,
179
+ beta: float,
180
+ gamma: float,
181
+ time_points: List[float],
182
+ u0: float = 0.0,
183
+ s0: float = 0.0
184
+ ) -> dict:
185
  """
186
+ Calculate RNA splicing dynamics solution.
187
 
188
  Parameters:
189
+ - alpha (float): Transcription rate.
190
+ - beta (float): Translation/splicing rate.
191
+ - gamma (float): Degradation rate.
192
+ - time_points (List[float]): Time points to evaluate.
193
+ - u0 (float): Initial unspliced RNA abundance. Default 0.0.
194
+ - s0 (float): Initial spliced RNA abundance. Default 0.0.
195
 
196
  Returns:
197
+ - dict: Time-course solution of unspliced and spliced RNA.
198
  """
199
  try:
200
+ dynamics = SplicingDynamics(
201
+ alpha=alpha,
202
+ beta=beta,
203
+ gamma=gamma,
204
+ initial_state=[u0, s0]
205
+ )
206
+
207
+ t = np.array(time_points)
208
+ solution = dynamics.get_solution(t, with_keys=True)
209
+
210
+ result = {
211
+ "time": time_points,
212
+ "unspliced": solution["u"].tolist() if isinstance(solution["u"], np.ndarray) else [solution["u"]],
213
+ "spliced": solution["s"].tolist() if isinstance(solution["s"], np.ndarray) else [solution["s"]]
214
  }
215
+ return {"success": True, "result": result, "error": None}
216
  except Exception as e:
217
+ return {"success": False, "result": None, "error": str(e)}
218
 
219
+
220
+ @mcp.tool(name="splicing_steady_state", description="Calculate steady state of RNA splicing dynamics")
221
+ def splicing_steady_state(alpha: float, beta: float, gamma: float) -> dict:
222
  """
223
+ Calculate steady state of RNA splicing system.
224
 
225
  Parameters:
226
+ - alpha (float): Transcription rate.
227
+ - beta (float): Translation/splicing rate.
228
+ - gamma (float): Degradation rate.
229
 
230
  Returns:
231
+ - dict: Steady state values for unspliced and spliced RNA.
232
  """
233
  try:
234
+ dynamics = SplicingDynamics(alpha=alpha, beta=beta, gamma=gamma)
235
+ steady_states = dynamics.get_steady_states(with_keys=True)
236
+
237
+ result = {
238
+ "unspliced_steady_state": float(steady_states["u"]),
239
+ "spliced_steady_state": float(steady_states["s"]),
240
+ "ratio_u_to_s": float(steady_states["u"] / steady_states["s"]) if steady_states["s"] != 0 else None
241
  }
242
+ return {"success": True, "result": result, "error": None}
243
  except Exception as e:
244
+ return {"success": False, "result": None, "error": str(e)}
245
+
246
+
247
+ # ===================== Linear Models Tools =====================
248
 
249
+ @mcp.tool(name="linear_models_tool", description="Tool for linear model operations")
250
+ def linear_models_tool(x: List[float], y: List[float], percentile: Optional[float] = None, fit_intercept: bool = False) -> dict:
251
  """
252
+ Performs linear regression fitting.
253
 
254
  Parameters:
255
+ - x (List[float]): Independent variable values.
256
+ - y (List[float]): Dependent variable values.
257
+ - percentile (float, optional): Percentile for extreme quantile regression.
258
+ - fit_intercept (bool): Whether to fit intercept. Default False.
259
 
260
  Returns:
261
+ - dict: Regression coefficients and intercept.
262
  """
263
  try:
264
+ x_arr = np.array(x).reshape(-1, 1)
265
+ y_arr = np.array(y)
266
+
267
+ model = LinearRegression(
268
+ percentile=percentile,
269
+ fit_intercept=fit_intercept
270
+ )
271
+ model.fit(x_arr, y_arr)
272
+
273
+ result = {
274
+ "coefficient": float(model.coef_[0]) if hasattr(model, 'coef_') else None,
275
+ "intercept": float(model.intercept_) if hasattr(model, 'intercept_') else 0.0
276
  }
277
+ return {"success": True, "result": result, "error": None}
278
  except Exception as e:
279
+ return {"success": False, "result": None, "error": str(e)}
280
 
281
+
282
+ # ===================== Utility Tools =====================
283
+
284
+ @mcp.tool(name="base_tool", description="Tool for base operations")
285
+ def base_tool(param: str) -> dict:
286
  """
287
+ Performs base utility operations.
288
 
289
  Parameters:
290
+ - param (str): Input parameter for base operations.
 
291
 
292
  Returns:
293
+ - dict: Result of the base operation.
294
  """
295
  try:
296
+ result = {
297
+ "input": param,
298
+ "length": len(param),
299
+ "upper": param.upper(),
300
+ "lower": param.lower()
301
+ }
302
+ return {"success": True, "result": result, "error": None}
303
+ except Exception as e:
304
+ return {"success": False, "result": None, "error": str(e)}
305
+
306
+
307
+ @mcp.tool(name="parallelize_tool", description="Tool for parallelization operations")
308
+ def parallelize_tool(tasks: List[Any]) -> dict:
309
+ """
310
+ Get information about parallelization capabilities.
311
 
312
+ Parameters:
313
+ - tasks (List): List of tasks (used to determine optimal job count).
 
314
 
315
+ Returns:
316
+ - dict: Parallelization configuration info.
317
+ """
318
+ try:
319
+ n_tasks = len(tasks)
320
+ optimal_jobs = get_n_jobs(None)
321
+
322
+ result = {
323
+ "n_tasks": n_tasks,
324
+ "available_cpus": os.cpu_count(),
325
+ "recommended_n_jobs": min(n_tasks, optimal_jobs),
326
+ "tasks_preview": tasks[:5] if len(tasks) > 5 else tasks
327
  }
328
+ return {"success": True, "result": result, "error": None}
329
+ except Exception as e:
330
+ return {"success": False, "result": None, "error": str(e)}
331
+
332
+
333
+ @mcp.tool(name="utils_tool", description="Tool for utility operations")
334
+ def utils_tool(input_data: Any) -> dict:
335
+ """
336
+ Performs utility operations on input data.
337
+
338
+ Parameters:
339
+ - input_data: Input data for utility operations (can be any type).
340
+
341
+ Returns:
342
+ - dict: Information about the input data.
343
+ """
344
+ try:
345
+ if isinstance(input_data, list):
346
+ result = {
347
+ "type": "list",
348
+ "length": len(input_data),
349
+ "preview": input_data[:10] if len(input_data) > 10 else input_data
350
+ }
351
+ elif isinstance(input_data, dict):
352
+ result = {
353
+ "type": "dict",
354
+ "keys": list(input_data.keys()),
355
+ "n_keys": len(input_data)
356
+ }
357
+ elif isinstance(input_data, (int, float)):
358
+ result = {
359
+ "type": "number",
360
+ "value": input_data,
361
+ "is_integer": isinstance(input_data, int)
362
+ }
363
+ else:
364
+ result = {
365
+ "type": str(type(input_data).__name__),
366
+ "value": str(input_data)
367
+ }
368
+ return {"success": True, "result": result, "error": None}
369
  except Exception as e:
370
+ return {"success": False, "result": None, "error": str(e)}
371
+
372
+
373
+ # ===================== AnnData Tools =====================
374
+
375
+ @mcp.tool(name="anndata_tool", description="Tool for handling AnnData operations")
376
+ def anndata_tool(data: dict) -> dict:
377
+ """
378
+ Provides information about AnnData operations available in scvelo.
379
+
380
+ Parameters:
381
+ - data (dict): Configuration for AnnData operations.
382
+ - operation (str): Operation type ('info', 'functions')
383
+
384
+ Returns:
385
+ - dict: Information about available AnnData operations.
386
+ """
387
+ try:
388
+ operation = data.get("operation", "info")
389
+
390
+ if operation == "functions":
391
+ result = {
392
+ "available_functions": [
393
+ "clean_obs_names - Clean up observation names",
394
+ "cleanup - Delete not needed attributes",
395
+ "get_df - Get DataFrame from AnnData",
396
+ "get_initial_size - Get initial size",
397
+ "get_modality - Get modality",
398
+ "get_size - Get size",
399
+ "make_dense - Convert sparse to dense",
400
+ "make_sparse - Convert dense to sparse",
401
+ "merge - Merge AnnData objects",
402
+ "set_initial_size - Set initial size",
403
+ "set_modality - Set modality",
404
+ "show_proportions - Show proportions"
405
+ ]
406
+ }
407
+ else:
408
+ result = {
409
+ "description": "AnnData is the primary data structure in scvelo for storing single-cell RNA velocity data",
410
+ "main_components": [
411
+ "X - Expression matrix",
412
+ "layers - Additional matrices (spliced, unspliced)",
413
+ "obs - Cell annotations",
414
+ "var - Gene annotations",
415
+ "uns - Unstructured data"
416
+ ]
417
+ }
418
+ return {"success": True, "result": result, "error": None}
419
+ except Exception as e:
420
+ return {"success": False, "result": None, "error": str(e)}
421
+
422
 
423
  def create_app() -> FastMCP:
424
  """
425
+ Creates and returns the FastMCP application instance.
426
 
427
  Returns:
428
  - FastMCP: The FastMCP application instance.