scmcp-shared 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scmcp_shared/__init__.py +1 -1
- scmcp_shared/schema/__init__.py +1 -1
- scmcp_shared/schema/base.py +11 -0
- scmcp_shared/schema/io.py +5 -8
- scmcp_shared/schema/pl.py +3 -3
- scmcp_shared/schema/pp.py +14 -70
- scmcp_shared/schema/tl.py +69 -18
- scmcp_shared/schema/util.py +18 -11
- scmcp_shared/server/__init__.py +51 -1
- scmcp_shared/server/io.py +28 -29
- scmcp_shared/server/pl.py +324 -0
- scmcp_shared/server/pp.py +368 -0
- scmcp_shared/server/tl.py +432 -0
- scmcp_shared/server/util.py +251 -0
- scmcp_shared/util.py +79 -49
- {scmcp_shared-0.1.0.dist-info → scmcp_shared-0.2.1.dist-info}/METADATA +2 -2
- scmcp_shared-0.2.1.dist-info/RECORD +20 -0
- scmcp_shared-0.1.0.dist-info/RECORD +0 -15
- {scmcp_shared-0.1.0.dist-info → scmcp_shared-0.2.1.dist-info}/WHEEL +0 -0
- {scmcp_shared-0.1.0.dist-info → scmcp_shared-0.2.1.dist-info}/licenses/LICENSE +0 -0
scmcp_shared/server/io.py
CHANGED
@@ -3,33 +3,24 @@ import inspect
|
|
3
3
|
from pathlib import Path
|
4
4
|
import scanpy as sc
|
5
5
|
from fastmcp import FastMCP , Context
|
6
|
+
from fastmcp.exceptions import ToolError
|
6
7
|
from ..schema.io import *
|
7
|
-
from ..util import filter_args, forward_request
|
8
|
+
from ..util import filter_args, forward_request, get_ads, generate_msg
|
8
9
|
|
9
10
|
|
10
11
|
io_mcp = FastMCP("SCMCP-IO-Server")
|
11
12
|
|
12
13
|
|
13
14
|
@io_mcp.tool()
|
14
|
-
async def read(
|
15
|
-
request: ReadModel,
|
16
|
-
ctx: Context,
|
17
|
-
sampleid: str = Field(default=None, description="adata sampleid"),
|
18
|
-
dtype: str = Field(default="exp", description="adata.X data type")
|
19
|
-
):
|
15
|
+
async def read(request: ReadModel):
|
20
16
|
"""
|
21
|
-
Read data from various file formats (h5ad, 10x, text files, etc.)
|
17
|
+
Read data from 10X directory or various file formats (h5ad, 10x, text files, etc.).
|
22
18
|
"""
|
23
19
|
try:
|
24
|
-
result = await forward_request("io_read", request
|
20
|
+
result = await forward_request("io_read", request)
|
25
21
|
if result is not None:
|
26
22
|
return result
|
27
23
|
kwargs = request.model_dump()
|
28
|
-
ads = ctx.request_context.lifespan_context
|
29
|
-
if sampleid is not None:
|
30
|
-
ads.active_id = sampleid
|
31
|
-
else:
|
32
|
-
ads.active_id = f"adata{len(ads.adata_dic[dtype])}"
|
33
24
|
|
34
25
|
file = Path(kwargs.get("filename", None))
|
35
26
|
if file.is_dir():
|
@@ -43,38 +34,46 @@ async def read(
|
|
43
34
|
adata = adata.T
|
44
35
|
else:
|
45
36
|
raise FileNotFoundError(f"{kwargs['filename']} does not exist")
|
37
|
+
|
38
|
+
sampleid = kwargs.get("sampleid", None)
|
39
|
+
adtype = kwargs.get("adtype", "exp")
|
40
|
+
ads = get_ads()
|
41
|
+
if sampleid is not None:
|
42
|
+
ads.active_id = sampleid
|
43
|
+
else:
|
44
|
+
ads.active_id = f"adata{len(ads.adata_dic[adtype])}"
|
45
|
+
|
46
46
|
adata.layers["counts"] = adata.X
|
47
47
|
adata.var_names_make_unique()
|
48
48
|
adata.obs_names_make_unique()
|
49
|
-
ads.set_adata(adata,
|
50
|
-
return
|
49
|
+
ads.set_adata(adata, request=request)
|
50
|
+
return generate_msg(request, adata, ads)
|
51
|
+
except ToolError as e:
|
52
|
+
raise ToolError(e)
|
51
53
|
except Exception as e:
|
52
54
|
if hasattr(e, '__context__') and e.__context__:
|
53
|
-
raise
|
55
|
+
raise ToolError(e.__context__)
|
54
56
|
else:
|
55
|
-
raise e
|
57
|
+
raise ToolError(e)
|
56
58
|
|
57
59
|
|
58
60
|
@io_mcp.tool()
|
59
|
-
async def write(
|
60
|
-
request: WriteModel,
|
61
|
-
ctx: Context,
|
62
|
-
sampleid: str = Field(default=None, description="adata sampleid"),
|
63
|
-
dtype: str = Field(default="exp", description="adata.X data type")
|
64
|
-
):
|
61
|
+
async def write(request: WriteModel):
|
65
62
|
"""save adata into a file.
|
66
63
|
"""
|
67
64
|
try:
|
68
|
-
result = await forward_request("io_write", request
|
65
|
+
result = await forward_request("io_write", request)
|
69
66
|
if result is not None:
|
70
67
|
return result
|
71
|
-
ads =
|
72
|
-
adata = ads.get_adata(
|
68
|
+
ads = get_ads()
|
69
|
+
adata = ads.get_adata(request=request)
|
73
70
|
kwargs = request.model_dump()
|
74
71
|
sc.write(kwargs["filename"], adata)
|
75
72
|
return {"filename": kwargs["filename"], "msg": "success to save file"}
|
73
|
+
except ToolError as e:
|
74
|
+
raise ToolError(e)
|
76
75
|
except Exception as e:
|
77
76
|
if hasattr(e, '__context__') and e.__context__:
|
78
|
-
raise
|
77
|
+
raise ToolError(e.__context__)
|
79
78
|
else:
|
80
|
-
raise e
|
79
|
+
raise ToolError(e)
|
@@ -0,0 +1,324 @@
|
|
1
|
+
import os
|
2
|
+
import inspect
|
3
|
+
from functools import partial
|
4
|
+
import scanpy as sc
|
5
|
+
from fastmcp import FastMCP, Context
|
6
|
+
from fastmcp.exceptions import ToolError
|
7
|
+
from ..schema.pl import *
|
8
|
+
from pathlib import Path
|
9
|
+
from ..logging_config import setup_logger
|
10
|
+
from ..util import forward_request, sc_like_plot, get_ads
|
11
|
+
|
12
|
+
|
13
|
+
logger = setup_logger()
|
14
|
+
|
15
|
+
pl_mcp = FastMCP("ScanpyMCP-PL-Server")
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
@pl_mcp.tool()
|
20
|
+
async def pca(request: PCAModel = PCAModel()):
|
21
|
+
"""Scatter plot in PCA coordinates. default figure for PCA plot"""
|
22
|
+
try:
|
23
|
+
result = await forward_request("pl_pca", request)
|
24
|
+
if result is not None:
|
25
|
+
return result
|
26
|
+
adata = get_ads().get_adata(request=request)
|
27
|
+
fig_path = sc_like_plot(sc.pl.pca, adata, request)
|
28
|
+
return {"figpath": fig_path}
|
29
|
+
except ToolError as e:
|
30
|
+
raise ToolError(e)
|
31
|
+
except Exception as e:
|
32
|
+
if hasattr(e, '__context__') and e.__context__:
|
33
|
+
raise ToolError(e.__context__)
|
34
|
+
else:
|
35
|
+
raise ToolError(e)
|
36
|
+
|
37
|
+
@pl_mcp.tool()
|
38
|
+
async def diffmap(request: DiffusionMapModel = DiffusionMapModel()):
|
39
|
+
"""Plot diffusion map embedding of cells."""
|
40
|
+
try:
|
41
|
+
result = await forward_request("pl_diffmap", request)
|
42
|
+
if result is not None:
|
43
|
+
return result
|
44
|
+
adata = get_ads().get_adata(request=request)
|
45
|
+
fig_path = sc_like_plot(sc.pl.diffmap, adata, request)
|
46
|
+
return {"figpath": fig_path}
|
47
|
+
except ToolError as e:
|
48
|
+
raise ToolError(e)
|
49
|
+
except Exception as e:
|
50
|
+
if hasattr(e, '__context__') and e.__context__:
|
51
|
+
raise ToolError(e.__context__)
|
52
|
+
else:
|
53
|
+
raise ToolError(e)
|
54
|
+
|
55
|
+
@pl_mcp.tool()
|
56
|
+
async def violin(request: ViolinModel,):
|
57
|
+
"""Plot violin plot of one or more variables."""
|
58
|
+
try:
|
59
|
+
result = await forward_request("pl_violin", request)
|
60
|
+
if result is not None:
|
61
|
+
return result
|
62
|
+
adata = get_ads().get_adata(request=request)
|
63
|
+
fig_path = sc_like_plot(sc.pl.violin, adata, request)
|
64
|
+
return {"figpath": fig_path}
|
65
|
+
except KeyError as e:
|
66
|
+
raise ToolError(f"doest found {e} in current sampleid with adtype {request.adtype}")
|
67
|
+
except ToolError as e:
|
68
|
+
raise ToolError(e)
|
69
|
+
except Exception as e:
|
70
|
+
if hasattr(e, '__context__') and e.__context__:
|
71
|
+
raise ToolError(e.__context__)
|
72
|
+
else:
|
73
|
+
raise ToolError(e)
|
74
|
+
|
75
|
+
|
76
|
+
@pl_mcp.tool()
|
77
|
+
async def stacked_violin(request: StackedViolinModel = StackedViolinModel()):
|
78
|
+
"""Plot stacked violin plots. Makes a compact image composed of individual violin plots stacked on top of each other."""
|
79
|
+
try:
|
80
|
+
result = await forward_request("pl_stacked_violin", request)
|
81
|
+
if result is not None:
|
82
|
+
return result
|
83
|
+
adata = get_ads().get_adata(request=request)
|
84
|
+
fig_path = sc_like_plot(sc.pl.stacked_violin, adata, request)
|
85
|
+
return {"figpath": fig_path}
|
86
|
+
except ToolError as e:
|
87
|
+
raise ToolError(e)
|
88
|
+
except Exception as e:
|
89
|
+
if hasattr(e, '__context__') and e.__context__:
|
90
|
+
raise ToolError(e.__context__)
|
91
|
+
else:
|
92
|
+
raise ToolError(e)
|
93
|
+
|
94
|
+
|
95
|
+
@pl_mcp.tool()
|
96
|
+
async def heatmap(request: HeatmapModel):
|
97
|
+
"""Heatmap of the expression values of genes."""
|
98
|
+
try:
|
99
|
+
result = await forward_request("pl_heatmap", request)
|
100
|
+
if result is not None:
|
101
|
+
return result
|
102
|
+
adata = get_ads().get_adata(request=request)
|
103
|
+
fig_path = sc_like_plot(sc.pl.heatmap, adata, request)
|
104
|
+
return {"figpath": fig_path}
|
105
|
+
except ToolError as e:
|
106
|
+
raise ToolError(e)
|
107
|
+
except Exception as e:
|
108
|
+
if hasattr(e, '__context__') and e.__context__:
|
109
|
+
raise ToolError(e.__context__)
|
110
|
+
else:
|
111
|
+
raise ToolError(e)
|
112
|
+
|
113
|
+
|
114
|
+
@pl_mcp.tool()
|
115
|
+
async def dotplot(request: DotplotModel):
|
116
|
+
"""Plot dot plot of expression values per gene for each group."""
|
117
|
+
try:
|
118
|
+
result = await forward_request("pl_dotplot", request)
|
119
|
+
if result is not None:
|
120
|
+
return result
|
121
|
+
adata = get_ads().get_adata(request=request)
|
122
|
+
fig_path = sc_like_plot(sc.pl.dotplot, adata, request)
|
123
|
+
return {"figpath": fig_path}
|
124
|
+
except ToolError as e:
|
125
|
+
raise ToolError(e)
|
126
|
+
except Exception as e:
|
127
|
+
if hasattr(e, '__context__') and e.__context__:
|
128
|
+
raise ToolError(e.__context__)
|
129
|
+
else:
|
130
|
+
raise ToolError(e)
|
131
|
+
|
132
|
+
@pl_mcp.tool()
|
133
|
+
async def matrixplot(request: MatrixplotModel):
|
134
|
+
"""matrixplot, Create a heatmap of the mean expression values per group of each var_names."""
|
135
|
+
try:
|
136
|
+
result = await forward_request("pl_matrixplot", request)
|
137
|
+
if result is not None:
|
138
|
+
return result
|
139
|
+
adata = get_ads().get_adata(request=request)
|
140
|
+
fig_path = sc_like_plot(sc.pl.matrixplot, adata, request)
|
141
|
+
return {"figpath": fig_path}
|
142
|
+
except ToolError as e:
|
143
|
+
raise ToolError(e)
|
144
|
+
except Exception as e:
|
145
|
+
if hasattr(e, '__context__') and e.__context__:
|
146
|
+
raise ToolError(e.__context__)
|
147
|
+
else:
|
148
|
+
raise ToolError(e)
|
149
|
+
|
150
|
+
|
151
|
+
@pl_mcp.tool()
|
152
|
+
async def tracksplot(request: TracksplotModel):
|
153
|
+
"""tracksplot, compact plot of expression of a list of genes."""
|
154
|
+
try:
|
155
|
+
result = await forward_request("pl_tracksplot", request)
|
156
|
+
if result is not None:
|
157
|
+
return result
|
158
|
+
adata = get_ads().get_adata(request=request)
|
159
|
+
fig_path = sc_like_plot(sc.pl.tracksplot, adata, request)
|
160
|
+
return {"figpath": fig_path}
|
161
|
+
except ToolError as e:
|
162
|
+
raise ToolError(e)
|
163
|
+
except Exception as e:
|
164
|
+
if hasattr(e, '__context__') and e.__context__:
|
165
|
+
raise ToolError(e.__context__)
|
166
|
+
else:
|
167
|
+
raise ToolError(e)
|
168
|
+
|
169
|
+
@pl_mcp.tool()
|
170
|
+
async def scatter(request: EnhancedScatterModel = EnhancedScatterModel()):
|
171
|
+
"""Plot a scatter plot of two variables, Scatter plot along observations or variables axes."""
|
172
|
+
try:
|
173
|
+
result = await forward_request("pl_scatter", request)
|
174
|
+
if result is not None:
|
175
|
+
return result
|
176
|
+
adata = get_ads().get_adata(request=request)
|
177
|
+
fig_path = sc_like_plot(sc.pl.scatter, adata, request)
|
178
|
+
return {"figpath": fig_path}
|
179
|
+
except ToolError as e:
|
180
|
+
raise ToolError(e)
|
181
|
+
except Exception as e:
|
182
|
+
if hasattr(e, '__context__') and e.__context__:
|
183
|
+
raise ToolError(e.__context__)
|
184
|
+
else:
|
185
|
+
raise ToolError(e)
|
186
|
+
|
187
|
+
@pl_mcp.tool()
|
188
|
+
async def embedding(request: EmbeddingModel):
|
189
|
+
"""Scatter plot for user specified embedding basis (e.g. umap, tsne, etc)."""
|
190
|
+
try:
|
191
|
+
result = await forward_request("pl_embedding", request)
|
192
|
+
if result is not None:
|
193
|
+
return result
|
194
|
+
adata = get_ads().get_adata(request=request)
|
195
|
+
fig_path = sc_like_plot(sc.pl.embedding, adata, request)
|
196
|
+
return {"figpath": fig_path}
|
197
|
+
except KeyError as e:
|
198
|
+
raise ToolError(f"doest found {e} in current sampleid with adtype {request.adtype}")
|
199
|
+
except Exception as e:
|
200
|
+
if hasattr(e, '__context__') and e.__context__:
|
201
|
+
raise ToolError(e.__context__)
|
202
|
+
else:
|
203
|
+
raise ToolError(e)
|
204
|
+
|
205
|
+
|
206
|
+
@pl_mcp.tool()
|
207
|
+
async def embedding_density(request: EmbeddingDensityModel):
|
208
|
+
"""Plot the density of cells in an embedding."""
|
209
|
+
try:
|
210
|
+
result = await forward_request("pl_embedding_density", request)
|
211
|
+
if result is not None:
|
212
|
+
return result
|
213
|
+
adata = get_ads().get_adata(request=request)
|
214
|
+
fig_path = sc_like_plot(sc.pl.embedding_density, adata, request)
|
215
|
+
return {"figpath": fig_path}
|
216
|
+
except ToolError as e:
|
217
|
+
raise ToolError(e)
|
218
|
+
except Exception as e:
|
219
|
+
if hasattr(e, '__context__') and e.__context__:
|
220
|
+
raise ToolError(e.__context__)
|
221
|
+
else:
|
222
|
+
raise ToolError(e)
|
223
|
+
|
224
|
+
@pl_mcp.tool()
|
225
|
+
async def rank_genes_groups(request: RankGenesGroupsModel):
|
226
|
+
"""Plot ranking of genes based on differential expression."""
|
227
|
+
try:
|
228
|
+
result = await forward_request("pl_rank_genes_groups", request)
|
229
|
+
if result is not None:
|
230
|
+
return result
|
231
|
+
adata = get_ads().get_adata(request=request)
|
232
|
+
fig_path = sc_like_plot(sc.pl.rank_genes_groups, adata, request)
|
233
|
+
return {"figpath": fig_path}
|
234
|
+
except ToolError as e:
|
235
|
+
raise ToolError(e)
|
236
|
+
except Exception as e:
|
237
|
+
if hasattr(e, '__context__') and e.__context__:
|
238
|
+
raise ToolError(e.__context__)
|
239
|
+
else:
|
240
|
+
raise ToolError(e)
|
241
|
+
|
242
|
+
|
243
|
+
@pl_mcp.tool()
|
244
|
+
async def rank_genes_groups_dotplot(
|
245
|
+
request: RankGenesGroupsDotplotModel,
|
246
|
+
):
|
247
|
+
"""Plot ranking of genes(DEGs) using dotplot visualization. Defualt plot DEGs for rank_genes_groups tool"""
|
248
|
+
from fastmcp.exceptions import ClientError
|
249
|
+
try:
|
250
|
+
result = await forward_request("pl_rank_genes_groups_dotplot", request)
|
251
|
+
if result is not None:
|
252
|
+
return result
|
253
|
+
adata = get_ads().get_adata(request=request)
|
254
|
+
fig_path = sc_like_plot(sc.pl.rank_genes_groups_dotplot, adata, request)
|
255
|
+
return {"figpath": fig_path}
|
256
|
+
except ToolError as e:
|
257
|
+
raise ToolError(e)
|
258
|
+
except Exception as e:
|
259
|
+
if hasattr(e, '__context__') and e.__context__:
|
260
|
+
raise ToolError(e.__context__)
|
261
|
+
else:
|
262
|
+
raise ToolError(e)
|
263
|
+
|
264
|
+
|
265
|
+
@pl_mcp.tool()
|
266
|
+
async def clustermap(
|
267
|
+
request: ClusterMapModel = ClusterMapModel()
|
268
|
+
):
|
269
|
+
"""Plot hierarchical clustering of cells and genes."""
|
270
|
+
try:
|
271
|
+
result = await forward_request("pl_clustermap", request)
|
272
|
+
if result is not None:
|
273
|
+
return result
|
274
|
+
adata = get_ads().get_adata(request=request)
|
275
|
+
fig_path = sc_like_plot(sc.pl.clustermap, adata, request)
|
276
|
+
return {"figpath": fig_path}
|
277
|
+
except ToolError as e:
|
278
|
+
raise ToolError(e)
|
279
|
+
except Exception as e:
|
280
|
+
if hasattr(e, '__context__') and e.__context__:
|
281
|
+
raise ToolError(e.__context__)
|
282
|
+
else:
|
283
|
+
raise ToolError(e)
|
284
|
+
|
285
|
+
@pl_mcp.tool()
|
286
|
+
async def highly_variable_genes(
|
287
|
+
request: HighlyVariableGenesModel = HighlyVariableGenesModel()
|
288
|
+
):
|
289
|
+
"""plot highly variable genes; Plot dispersions or normalized variance versus means for genes."""
|
290
|
+
try:
|
291
|
+
result = await forward_request("pl_highly_variable_genes", request)
|
292
|
+
if result is not None:
|
293
|
+
return result
|
294
|
+
adata = get_ads().get_adata(request=request)
|
295
|
+
fig_path = sc_like_plot(sc.pl.highly_variable_genes, adata, request)
|
296
|
+
return {"figpath": fig_path}
|
297
|
+
except ToolError as e:
|
298
|
+
raise ToolError(e)
|
299
|
+
except Exception as e:
|
300
|
+
if hasattr(e, '__context__') and e.__context__:
|
301
|
+
raise ToolError(e.__context__)
|
302
|
+
else:
|
303
|
+
raise ToolError(e)
|
304
|
+
|
305
|
+
|
306
|
+
@pl_mcp.tool()
|
307
|
+
async def pca_variance_ratio(
|
308
|
+
request: PCAVarianceRatioModel = PCAVarianceRatioModel()
|
309
|
+
):
|
310
|
+
"""Plot the PCA variance ratio to visualize explained variance."""
|
311
|
+
try:
|
312
|
+
result = await forward_request("pl_pca_variance_ratio", request)
|
313
|
+
if result is not None:
|
314
|
+
return result
|
315
|
+
adata = get_ads().get_adata(request=request)
|
316
|
+
fig_path = sc_like_plot(sc.pl.pca_variance_ratio, adata, request)
|
317
|
+
return {"figpath": fig_path}
|
318
|
+
except ToolError as e:
|
319
|
+
raise ToolError(e)
|
320
|
+
except Exception as e:
|
321
|
+
if hasattr(e, '__context__') and e.__context__:
|
322
|
+
raise ToolError(e.__context__)
|
323
|
+
else:
|
324
|
+
raise ToolError(e)
|