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.
@@ -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 AdataModel, AdataInfo
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: QueryOpLogModel, adinfo: self.AdataInfo=self.AdataInfo()):
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: MarkVarModel, adinfo: self.AdataInfo=self.AdataInfo()):
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: ListVarModel, adinfo: self.AdataInfo=self.AdataInfo()):
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: ListObsModel, adinfo: self.AdataInfo=self.AdataInfo()):
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: VarNamesModel, adinfo: self.AdataInfo=self.AdataInfo()):
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
- result = {v: v in adata.var_names for v in var_names}
139
- add_op_log(adata, check_var, {"var_names": var_names}, adinfo)
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: ConcatBaseModel, adinfo: self.AdataInfo=self.AdataInfo()):
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(list(ads.adata_dic[dtype].values()), **kwargs)
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: DPTIROOTModel, adinfo: self.AdataInfo=self.AdataInfo()):
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: AddLayerModel, adinfo: self.AdataInfo=self.AdataInfo()):
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, "dtype": adinfo.adtype, "adata": adata}
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.5
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.3.4
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,,