scmcp-shared 0.3.5__py3-none-any.whl → 0.3.7__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/io.py +5 -5
- scmcp_shared/schema/pl.py +42 -42
- scmcp_shared/schema/pp.py +10 -10
- scmcp_shared/schema/tl.py +18 -18
- scmcp_shared/schema/util.py +9 -9
- scmcp_shared/server/base.py +6 -11
- scmcp_shared/server/io.py +17 -6
- scmcp_shared/server/pl.py +33 -32
- scmcp_shared/server/pp.py +29 -25
- scmcp_shared/server/tl.py +35 -34
- scmcp_shared/server/util.py +28 -23
- scmcp_shared/util.py +11 -1
- {scmcp_shared-0.3.5.dist-info → scmcp_shared-0.3.7.dist-info}/METADATA +8 -2
- scmcp_shared-0.3.7.dist-info/RECORD +21 -0
- scmcp_shared-0.3.5.dist-info/RECORD +0 -21
- {scmcp_shared-0.3.5.dist-info → scmcp_shared-0.3.7.dist-info}/WHEEL +0 -0
- {scmcp_shared-0.3.5.dist-info → scmcp_shared-0.3.7.dist-info}/licenses/LICENSE +0 -0
scmcp_shared/server/util.py
CHANGED
@@ -4,8 +4,9 @@ from pathlib import Path
|
|
4
4
|
import scanpy as sc
|
5
5
|
from fastmcp import FastMCP , Context
|
6
6
|
from fastmcp.exceptions import ToolError
|
7
|
+
from fastmcp.tools.tool import Tool
|
7
8
|
from ..schema.util import *
|
8
|
-
from ..schema import
|
9
|
+
from ..schema import AdataInfo
|
9
10
|
from ..util import filter_args, forward_request, get_ads, generate_msg,add_op_log
|
10
11
|
from .base import BaseMCP
|
11
12
|
|
@@ -24,7 +25,7 @@ class ScanpyUtilMCP(BaseMCP):
|
|
24
25
|
|
25
26
|
|
26
27
|
def _tool_query_op_log(self):
|
27
|
-
def _query_op_log(request:
|
28
|
+
def _query_op_log(request: QueryOpLogParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
28
29
|
"""Query the adata operation log"""
|
29
30
|
adata = get_ads().get_adata(adinfo=adinfo)
|
30
31
|
op_dic = adata.uns["operation"]["op"]
|
@@ -33,10 +34,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
33
34
|
for opid in opids:
|
34
35
|
op_list.append(op_dic[opid])
|
35
36
|
return op_list
|
36
|
-
return _query_op_log
|
37
|
+
return Tool.from_function(_query_op_log, name="query_op_log")
|
37
38
|
|
38
39
|
def _tool_mark_var(self):
|
39
|
-
def _mark_var(request:
|
40
|
+
def _mark_var(request: MarkVarParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
40
41
|
"""
|
41
42
|
Determine if each gene meets specific conditions and store results in adata.var as boolean values.
|
42
43
|
For example: mitochondrion genes startswith MT-.
|
@@ -84,10 +85,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
84
85
|
raise ToolError(e.__context__)
|
85
86
|
else:
|
86
87
|
raise ToolError(e)
|
87
|
-
return _mark_var
|
88
|
+
return Tool.from_function(_mark_var, name="mark_var")
|
88
89
|
|
89
90
|
def _tool_list_var(self):
|
90
|
-
def _list_var(request:
|
91
|
+
def _list_var(request: ListVarParams=ListVarParams(), adinfo: self.AdataInfo=self.AdataInfo()):
|
91
92
|
"""List key columns in adata.var. It should be called for checking when other tools need var key column names as input."""
|
92
93
|
try:
|
93
94
|
result = forward_request("ul_list_var", request, adinfo)
|
@@ -95,7 +96,7 @@ class ScanpyUtilMCP(BaseMCP):
|
|
95
96
|
return result
|
96
97
|
adata = get_ads().get_adata(adinfo=adinfo)
|
97
98
|
columns = list(adata.var.columns)
|
98
|
-
add_op_log(adata, list_var, {}, adinfo)
|
99
|
+
add_op_log(adata, "list_var", {}, adinfo)
|
99
100
|
return columns
|
100
101
|
except ToolError as e:
|
101
102
|
raise ToolError(e)
|
@@ -104,10 +105,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
104
105
|
raise ToolError(e.__context__)
|
105
106
|
else:
|
106
107
|
raise ToolError(e)
|
107
|
-
return _list_var
|
108
|
+
return Tool.from_function(_list_var, name="list_var")
|
108
109
|
|
109
110
|
def _tool_list_obs(self):
|
110
|
-
def _list_obs(request:
|
111
|
+
def _list_obs(request: ListObsParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
111
112
|
"""List key columns in adata.obs. It should be called before other tools need obs key column names input."""
|
112
113
|
try:
|
113
114
|
result = forward_request("ul_list_obs", request, adinfo)
|
@@ -115,7 +116,7 @@ class ScanpyUtilMCP(BaseMCP):
|
|
115
116
|
return result
|
116
117
|
adata = get_ads().get_adata(adinfo=adinfo)
|
117
118
|
columns = list(adata.obs.columns)
|
118
|
-
add_op_log(adata, list_obs, {}, adinfo)
|
119
|
+
add_op_log(adata, "list_obs", {}, adinfo)
|
119
120
|
return columns
|
120
121
|
except ToolError as e:
|
121
122
|
raise ToolError(e)
|
@@ -124,10 +125,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
124
125
|
raise ToolError(e.__context__)
|
125
126
|
else:
|
126
127
|
raise ToolError(e)
|
127
|
-
return _list_obs
|
128
|
+
return Tool.from_function(_list_obs, name="list_obs")
|
128
129
|
|
129
130
|
def _tool_check_var(self):
|
130
|
-
def _check_var(request:
|
131
|
+
def _check_var(request: VarNamesParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
131
132
|
"""Check if genes/variables exist in adata.var_names. This tool should be called before gene expression visualizations or color by genes."""
|
132
133
|
try:
|
133
134
|
result = forward_request("ul_check_var", request, adinfo)
|
@@ -135,8 +136,12 @@ class ScanpyUtilMCP(BaseMCP):
|
|
135
136
|
return result
|
136
137
|
adata = get_ads().get_adata(adinfo=adinfo)
|
137
138
|
var_names = request.var_names
|
138
|
-
|
139
|
-
|
139
|
+
if adata.raw is not None:
|
140
|
+
all_var_names = adata.raw.to_adata().var_names
|
141
|
+
else:
|
142
|
+
all_var_names = adata.var_names
|
143
|
+
result = {v: v in all_var_names for v in var_names}
|
144
|
+
add_op_log(adata, "check_var", {"var_names": var_names}, adinfo)
|
140
145
|
return result
|
141
146
|
except ToolError as e:
|
142
147
|
raise ToolError(e)
|
@@ -145,10 +150,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
145
150
|
raise ToolError(e.__context__)
|
146
151
|
else:
|
147
152
|
raise ToolError(e)
|
148
|
-
return _check_var
|
153
|
+
return Tool.from_function(_check_var, name="check_var")
|
149
154
|
|
150
155
|
def _tool_merge_adata(self):
|
151
|
-
def _merge_adata(request:
|
156
|
+
def _merge_adata(request: ConcatBaseParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
152
157
|
"""Merge multiple adata objects."""
|
153
158
|
try:
|
154
159
|
result = forward_request("ul_merge_adata", request, adinfo)
|
@@ -157,7 +162,7 @@ class ScanpyUtilMCP(BaseMCP):
|
|
157
162
|
ads = get_ads()
|
158
163
|
adata = ads.get_adata(adinfo=adinfo)
|
159
164
|
kwargs = {k: v for k, v in request.model_dump().items() if v is not None}
|
160
|
-
merged_adata = adata.concat(
|
165
|
+
merged_adata = adata.concat(ads.adata_dic, **kwargs)
|
161
166
|
ads.adata_dic[dtype] = {}
|
162
167
|
ads.active_id = "merged_adata"
|
163
168
|
add_op_log(merged_adata, ad.concat, kwargs, adinfo)
|
@@ -170,10 +175,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
170
175
|
raise ToolError(e.__context__)
|
171
176
|
else:
|
172
177
|
raise ToolError(e)
|
173
|
-
return _merge_adata
|
178
|
+
return Tool.from_function(_merge_adata, name="merge_adata")
|
174
179
|
|
175
180
|
def _tool_set_dpt_iroot(self):
|
176
|
-
def _set_dpt_iroot(request:
|
181
|
+
def _set_dpt_iroot(request: DPTIROOTParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
177
182
|
"""Set the iroot cell"""
|
178
183
|
try:
|
179
184
|
result = forward_request("ul_set_dpt_iroot", request, adinfo)
|
@@ -201,10 +206,10 @@ class ScanpyUtilMCP(BaseMCP):
|
|
201
206
|
raise ToolError(e.__context__)
|
202
207
|
else:
|
203
208
|
raise ToolError(e)
|
204
|
-
return _set_dpt_iroot
|
209
|
+
return Tool.from_function(_set_dpt_iroot, name="set_dpt_iroot")
|
205
210
|
|
206
211
|
def _tool_add_layer(self):
|
207
|
-
def _add_layer(request:
|
212
|
+
def _add_layer(request: AddLayerParams, adinfo: self.AdataInfo=self.AdataInfo()):
|
208
213
|
"""Add a layer to the AnnData object."""
|
209
214
|
try:
|
210
215
|
result = forward_request("ul_add_layer", request, adinfo)
|
@@ -233,7 +238,7 @@ class ScanpyUtilMCP(BaseMCP):
|
|
233
238
|
raise ToolError(e.__context__)
|
234
239
|
else:
|
235
240
|
raise ToolError(e)
|
236
|
-
return _add_layer
|
241
|
+
return Tool.from_function(_add_layer, name="add_layer")
|
237
242
|
|
238
243
|
def _tool_check_samples(self):
|
239
244
|
def _check_samples(request: None, adinfo: self.AdataInfo=self.AdataInfo()):
|
@@ -248,4 +253,4 @@ class ScanpyUtilMCP(BaseMCP):
|
|
248
253
|
raise ToolError(e.__context__)
|
249
254
|
else:
|
250
255
|
raise ToolError(e)
|
251
|
-
return _check_samples
|
256
|
+
return Tool.from_function(_check_samples, name="check_samples")
|
scmcp_shared/util.py
CHANGED
@@ -104,6 +104,10 @@ def savefig(axes, func=None, **kwargs):
|
|
104
104
|
args = []
|
105
105
|
for k,v in kwargs.items():
|
106
106
|
if isinstance(v, (tuple, list, set)):
|
107
|
+
v = v[:3] ## show first 3 elements
|
108
|
+
args.append(f"{k}-{'-'.join([str(i) for i in v])}")
|
109
|
+
elif isinstance(v, dict):
|
110
|
+
v = list(v.keys())[:3] ## show first 3 elements
|
107
111
|
args.append(f"{k}-{'-'.join([str(i) for i in v])}")
|
108
112
|
else:
|
109
113
|
args.append(f"{k}-{v}")
|
@@ -209,7 +213,7 @@ def get_ads():
|
|
209
213
|
|
210
214
|
|
211
215
|
def generate_msg(adinfo, adata, ads):
|
212
|
-
return {"sampleid": adinfo.sampleid or ads.active_id, "
|
216
|
+
return {"sampleid": adinfo.sampleid or ads.active_id, "adtype": adinfo.adtype, "adata": adata}
|
213
217
|
|
214
218
|
|
215
219
|
def sc_like_plot(plot_func, adata, request, adinfo, **kwargs):
|
@@ -270,3 +274,9 @@ def update_mcp_args(mcp, tool_args : dict):
|
|
270
274
|
tools = mcp._tool_manager._tools.keys()
|
271
275
|
for tool in tool_args:
|
272
276
|
_update_args(mcp, tool, tool_args[tool])
|
277
|
+
|
278
|
+
|
279
|
+
def check_adata(adata, adinfo, ads):
|
280
|
+
sampleid = adinfo.sampleid or ads.active_id
|
281
|
+
if sampleid != adata.uns["scmcp_sampleid"]:
|
282
|
+
raise ValueError(f"sampleid mismatch: {sampleid} != {adata.uns['scmcp_sampleid']}")
|
@@ -1,7 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: scmcp_shared
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.7
|
4
4
|
Summary: A shared function libray for scmcphub
|
5
|
+
Project-URL: Homepage, http://scmcphub.org/
|
6
|
+
Project-URL: Repository, https://github.com/scmcphub/scmcp-shared
|
7
|
+
Project-URL: Documentation, https://docs.scmcphub.org/
|
5
8
|
Author-email: shuang <hsh-me@outlook.com>
|
6
9
|
License: BSD 3-Clause License
|
7
10
|
|
@@ -32,8 +35,11 @@ License: BSD 3-Clause License
|
|
32
35
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
33
36
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
37
|
License-File: LICENSE
|
38
|
+
Keywords: AI,agent,bioinformatics,llm,mcp,model context protocol,scRNA-seq,single cell
|
35
39
|
Requires-Python: >=3.10
|
36
|
-
Requires-Dist: fastmcp>=2.
|
40
|
+
Requires-Dist: fastmcp>=2.7.0
|
41
|
+
Requires-Dist: igraph
|
42
|
+
Requires-Dist: leidenalg
|
37
43
|
Requires-Dist: mcp>=1.8.0
|
38
44
|
Requires-Dist: nest-asyncio
|
39
45
|
Requires-Dist: scanpy
|
@@ -0,0 +1,21 @@
|
|
1
|
+
scmcp_shared/__init__.py,sha256=V6ifYmd7QuwmWt6vfBSl3D0yHXD7wkIfaUdXa0NUADg,24
|
2
|
+
scmcp_shared/cli.py,sha256=KYHp5_VE2JVVT4Su57ELzQa1lI2P8Gw0b4ruBP_Dtxk,4396
|
3
|
+
scmcp_shared/logging_config.py,sha256=eCuLuyxMmbj8A1E0VqYWoKA5JPTSbo6cmjS4LOyd0RQ,872
|
4
|
+
scmcp_shared/util.py,sha256=8_c6WPNpYNoxKpq6tQCC4elCQkWyk3rH-hTe1sqff4A,9535
|
5
|
+
scmcp_shared/schema/__init__.py,sha256=Kwkc7kPLjExOlxt1sWEy_5qa96MvOS8sNCMlZa6yRg8,737
|
6
|
+
scmcp_shared/schema/io.py,sha256=-wgL2NQBXwAcojid8rF2Y46GsKL7H6JiheKE6frMotw,4638
|
7
|
+
scmcp_shared/schema/pl.py,sha256=7AFXgqb2GAlmeXS6m3IdJgThLz6-FTDBmrJqoAi8j98,29612
|
8
|
+
scmcp_shared/schema/pp.py,sha256=WtaugFLP9vfusBZQCXGAYPmYu-5yWHC2wJTSZutS1XM,21674
|
9
|
+
scmcp_shared/schema/tl.py,sha256=FEZpr218eaQ8rUp5EJzbjyw1ejqCX4Shsf9CumaKs8A,34425
|
10
|
+
scmcp_shared/schema/util.py,sha256=fMZxTNf9Bv_xDzrW7YK08q0tCoC3L7ofqPY0K2ykG8k,4824
|
11
|
+
scmcp_shared/server/__init__.py,sha256=4KE2Y_gDenF0ZyTGicQW0fTgJfMIQYZfpRP4hQ4rFYs,416
|
12
|
+
scmcp_shared/server/base.py,sha256=o6NSi39I6xe7p-00-7FMR3gYwRCsM8D2WQhwWvlJcrU,6400
|
13
|
+
scmcp_shared/server/io.py,sha256=kGBbtd3ltj0ypd0kgMy1l2zT2AVf5yXCHAebQR-ZtUA,4033
|
14
|
+
scmcp_shared/server/pl.py,sha256=GVu7GQKW67jz3ig43HLX0d2cjGDPJDXdxMSf6b72kAA,15558
|
15
|
+
scmcp_shared/server/pp.py,sha256=_U2ZSLIo-u_z3psg8Sw2gJEAaxhp3gW_-YMq4q9kCe8,15984
|
16
|
+
scmcp_shared/server/tl.py,sha256=LeKcR6F4PNRHZomlD3-igrF44YBQW6Kg7h75psGl9c0,19112
|
17
|
+
scmcp_shared/server/util.py,sha256=b6gG1dfXe5GdhM4yUtpxBqx1gMRyQVu3dytmzJeD9zs,12590
|
18
|
+
scmcp_shared-0.3.7.dist-info/METADATA,sha256=vK9jmNEbuZCt-DhGbLMyGhFd6_HXZpkxAy2a1XgvH58,2402
|
19
|
+
scmcp_shared-0.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
20
|
+
scmcp_shared-0.3.7.dist-info/licenses/LICENSE,sha256=YNr1hpea195yq-wGtB8j-2dGtt7A5G00WENmxa7JGco,1495
|
21
|
+
scmcp_shared-0.3.7.dist-info/RECORD,,
|
@@ -1,21 +0,0 @@
|
|
1
|
-
scmcp_shared/__init__.py,sha256=6zWLsZ8g5hYGDsXCmJv2JvlO9aeJZjPXG5UykIoIeEA,24
|
2
|
-
scmcp_shared/cli.py,sha256=KYHp5_VE2JVVT4Su57ELzQa1lI2P8Gw0b4ruBP_Dtxk,4396
|
3
|
-
scmcp_shared/logging_config.py,sha256=eCuLuyxMmbj8A1E0VqYWoKA5JPTSbo6cmjS4LOyd0RQ,872
|
4
|
-
scmcp_shared/util.py,sha256=bJmbgYjhY44flRo51XDiH-5w7OztG34XxGL6JSGG9fo,9101
|
5
|
-
scmcp_shared/schema/__init__.py,sha256=Kwkc7kPLjExOlxt1sWEy_5qa96MvOS8sNCMlZa6yRg8,737
|
6
|
-
scmcp_shared/schema/io.py,sha256=ZKJpKkKazDE3_ZX3GtMIT08kSaiNmy0qVaxivhN7Dx4,4744
|
7
|
-
scmcp_shared/schema/pl.py,sha256=rzE09wHMY3JR56HZc-QfIUUM0fGXRKd-7Dh3CrQrFB0,29547
|
8
|
-
scmcp_shared/schema/pp.py,sha256=48F6oKf-I8IZuNQDfq_Lpp3fLLKA4PruqRje_ZrtTyw,21664
|
9
|
-
scmcp_shared/schema/tl.py,sha256=DaZUce33OW67h-caK9BW4sD9zhlehmj3mCq1P-3vllM,34407
|
10
|
-
scmcp_shared/schema/util.py,sha256=x_2GPsmliHabi9V5C6YEv_M8ZHJsinDZJ6ePWrLPmcI,4815
|
11
|
-
scmcp_shared/server/__init__.py,sha256=4KE2Y_gDenF0ZyTGicQW0fTgJfMIQYZfpRP4hQ4rFYs,416
|
12
|
-
scmcp_shared/server/base.py,sha256=kaOfi4yHLWr_AdBdpKzDrJiwRmbHF2jbsfI5g2qqgRA,6576
|
13
|
-
scmcp_shared/server/io.py,sha256=yrQXdkAsPvk_62xQk5-SRmc5-jAvI-d3sE0QqHDXfMM,3424
|
14
|
-
scmcp_shared/server/pl.py,sha256=HdhjrZfEx-IzCkZ03IGCW-mjUItfNvKdSOFbJ2_-2XQ,14854
|
15
|
-
scmcp_shared/server/pp.py,sha256=neyRV3yEZ2a08eO-COI-t-PFFvntRzNmu19SQ77do_4,15406
|
16
|
-
scmcp_shared/server/tl.py,sha256=nneWj9SHNcZulhn3WRQrsoecHNsG2apDctB8H5OtQNI,18331
|
17
|
-
scmcp_shared/server/util.py,sha256=L51fFL7Nb84JCQrFYyZIGsfmNFlA9uGzTV93hvc9mfI,12025
|
18
|
-
scmcp_shared-0.3.5.dist-info/METADATA,sha256=SPZehu8YN559S-rEBcNViW5vf1h0TUjlzXhIAVxuQ3w,2103
|
19
|
-
scmcp_shared-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
20
|
-
scmcp_shared-0.3.5.dist-info/licenses/LICENSE,sha256=YNr1hpea195yq-wGtB8j-2dGtt7A5G00WENmxa7JGco,1495
|
21
|
-
scmcp_shared-0.3.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|