scmcp-shared 0.3.0__py3-none-any.whl → 0.3.5__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 CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- __version__ = "0.3.0"
2
+ __version__ = "0.3.5"
3
3
 
scmcp_shared/cli.py CHANGED
@@ -1,77 +1,89 @@
1
- import typer
2
- from typing import Optional, Union, Type
1
+ import argparse
2
+ from typing import Optional, Union, Type, Dict, Callable
3
3
  from enum import Enum
4
- from .util import add_figure_route, set_env,setup_mcp
5
-
6
-
7
- class TransportEnum(str, Enum):
8
- STDIO = "stdio"
9
- SSE = "sse"
10
- SHTTP = "shttp"
11
-
12
- @property
13
- def transport_value(self) -> str:
14
- """Get the actual transport value to use."""
15
- if self == TransportEnum.SHTTP:
16
- return "streamable-http"
17
- return self.value
18
-
19
-
20
- class ModuleEnum(str, Enum):
21
- """Base class for module types."""
22
- ALL = "all"
23
-
4
+ from .util import add_figure_route, set_env
5
+ import os
24
6
 
25
7
 
26
8
  class MCPCLI:
27
9
  """Base class for CLI applications with support for dynamic modules and parameters."""
28
10
 
29
- def __init__(self, name: str, help_text: str, manager=None, modules=ModuleEnum):
11
+ def __init__(self, name: str, help_text: str, mcp=None, manager=None):
30
12
  self.name = name
31
- self.modules = modules
13
+ self.mcp = mcp
32
14
  self.manager = manager
33
- self.app = typer.Typer(
34
- name=name,
35
- help=help_text,
36
- add_completion=False,
37
- no_args_is_help=True,
15
+ self.parser = argparse.ArgumentParser(
16
+ description=help_text,
17
+ prog=name
38
18
  )
19
+ self.subcommands: Dict[str, tuple[argparse.ArgumentParser, Callable]] = {}
39
20
  self._setup_commands()
40
21
 
41
22
  def _setup_commands(self):
42
23
  """Setup the main commands for the CLI."""
43
- self.app.command(name="run", help="Start the server with the specified configuration")(self.run_command())
44
- self.app.callback()(self._callback)
45
-
46
- def run_command(self):
47
- def _run_command(
48
- log_file: Optional[str] = typer.Option(None, "--log-file", help="log file path, use stdout if None"),
49
- transport: TransportEnum = typer.Option(TransportEnum.STDIO, "-t", "--transport", help="specify transport type",
50
- case_sensitive=False),
51
- port: int = typer.Option(8000, "-p", "--port", help="transport port"),
52
- host: str = typer.Option("127.0.0.1", "--host", help="transport host"),
53
- forward: str = typer.Option(None, "-f", "--forward", help="forward request to another server"),
54
- module: list[self.modules] = typer.Option(
55
- [self.modules.ALL],
56
- "-m",
57
- "--module",
58
- help="specify module to run"
59
- ),
60
- ):
61
- """Start the server with the specified configuration."""
62
- if "all" in module:
63
- modules = None
64
- elif isinstance(module, list) and bool(module):
65
- modules = [m.value for m in module]
66
- self.mcp = self.manager(self.name, include_modules=modules).mcp
67
-
68
- self.run_mcp(log_file, forward, transport, host, port)
69
- return _run_command
24
+ subparsers = self.parser.add_subparsers(dest='command', help='Available commands')
25
+ run_parser = subparsers.add_parser('run', help='Start the server with the specified configuration')
26
+ self._setup_run_command(run_parser)
27
+ self.subcommands['run'] = (run_parser, self._run_command)
70
28
 
71
-
72
- def _callback(self):
73
- """Liana MCP CLI root command."""
74
- pass
29
+ def _setup_run_command(self, parser: argparse.ArgumentParser):
30
+ """Setup run command arguments."""
31
+ parser.add_argument('-t', '--transport', default="stdio",
32
+ choices=["stdio", "shttp", "sse"],
33
+ help='specify transport type')
34
+ parser.add_argument('-p', '--port', type=int, default=8000, help='transport port')
35
+ parser.add_argument('--host', default='127.0.0.1', help='transport host')
36
+ parser.add_argument('-f', '--forward', help='forward request to another server')
37
+ parser.add_argument('-wd', '--working-dir', default=".", help='working directory')
38
+ parser.add_argument('--log-file', help='log file path, use stdout if None')
39
+
40
+ def add_command(self, name: str, help_text: str, handler: Callable) -> argparse.ArgumentParser:
41
+ """add new subcommand
42
+
43
+ Args:
44
+ name: subcommand name
45
+ help_text: help text
46
+ handler: handler function
47
+
48
+ Returns:
49
+ ArgumentParser: parser for the subcommand
50
+ """
51
+ subparsers = self.parser._subparsers._group_actions[0]
52
+ parser = subparsers.add_parser(name, help=help_text)
53
+ self.subcommands[name] = (parser, handler)
54
+ return parser
55
+
56
+ def get_command_parser(self, name: str) -> Optional[argparse.ArgumentParser]:
57
+ """get the parser for the subcommand
58
+
59
+ Args:
60
+ name: subcommand name
61
+
62
+ Returns:
63
+ ArgumentParser: parser for the subcommand, return None if the subcommand does not exist
64
+ """
65
+ if name in self.subcommands:
66
+ return self.subcommands[name][0]
67
+ return None
68
+
69
+ def _run_command(self, args):
70
+ """Start the server with the specified configuration."""
71
+ os.chdir(args.working_dir)
72
+ if hasattr(args, 'module'):
73
+ if "all" in args.module:
74
+ modules = None
75
+ elif isinstance(args.module, list) and bool(args.module):
76
+ modules = args.module
77
+ else:
78
+ modules = None
79
+ if self.manager is not None:
80
+ self.mcp = self.manager(self.name, include_modules=modules).mcp
81
+ elif self.mcp is not None:
82
+ pass
83
+ else:
84
+ raise ValueError("No manager or mcp provided")
85
+ transport = args.transport
86
+ self.run_mcp(args.log_file, args.forward, transport, args.host, args.port)
75
87
 
76
88
  def run_mcp(self, log_file, forward, transport, host, port):
77
89
  set_env(log_file, forward, transport, host, port)
@@ -80,8 +92,8 @@ class MCPCLI:
80
92
  if transport == "stdio":
81
93
  self.mcp.run()
82
94
  elif transport in ["sse", "shttp"]:
95
+ transport = "streamable-http" if transport == "shttp" else transport
83
96
  add_figure_route(self.mcp)
84
- transport = transport.transport_value
85
97
  self.mcp.run(
86
98
  transport=transport,
87
99
  host=host,
@@ -89,8 +101,11 @@ class MCPCLI:
89
101
  log_level="info"
90
102
  )
91
103
 
92
- def run_cli(self, mcp=None, module_dic=None):
104
+ def run(self):
93
105
  """Run the CLI application."""
94
- self.mcp = mcp
95
- self.module_dic = module_dic
96
- self.app()
106
+ args = self.parser.parse_args()
107
+ if args.command in self.subcommands:
108
+ handler = self.subcommands[args.command][1]
109
+ handler(args)
110
+ else:
111
+ self.parser.print_help()
scmcp_shared/schema/tl.py CHANGED
@@ -938,6 +938,11 @@ class PCAModel(BaseModel):
938
938
  gt=0
939
939
  )
940
940
 
941
+ key_added: str = Field(
942
+ default="X_pca",
943
+ description="PCA embedding stored key in adata.obsm."
944
+ )
945
+
941
946
  @field_validator('n_comps', 'chunk_size')
942
947
  def validate_positive_integers(cls, v: Optional[int]) -> Optional[int]:
943
948
  """Validate positive integers"""
scmcp_shared/server/tl.py CHANGED
@@ -376,7 +376,7 @@ class ScanpyToolsMCP(BaseMCP):
376
376
  return _score_genes_cell_cycle
377
377
 
378
378
  def _tool_pca(self):
379
- def _pca(request: PCAModel, adinfo: self.AdataInfo=self.AdataInfo()):
379
+ def _pca(request: PCAModel=PCAModel(), adinfo: self.AdataInfo=self.AdataInfo()):
380
380
  """Compute PCA (Principal Component Analysis)."""
381
381
  try:
382
382
  result = forward_request("tl_pca", request, adinfo)
scmcp_shared/util.py CHANGED
@@ -260,14 +260,10 @@ def setup_mcp(mcp, sub_mcp_dic, modules=None):
260
260
  return mcp
261
261
 
262
262
  def _update_args(mcp, func, args_dic : dict):
263
- defs = mcp._tool_manager._tools[func].parameters['$defs']
264
- model_names = list(defs.keys())
265
- args_model = model_names[0] if model_names[0] != "AdataModel" else model_names[1]
266
263
  for args, property_dic in args_dic.items():
267
264
  for pk, v in property_dic.items():
268
- for model in model_names:
269
- if args in mcp._tool_manager._tools[func].parameters['$defs'][model]["properties"]:
270
- mcp._tool_manager._tools[func].parameters['$defs'][model]["properties"][args][pk] = v
265
+ mcp._tool_manager._tools[func].parameters["properties"]["request"].setdefault(pk, {})
266
+ mcp._tool_manager._tools[func].parameters["properties"]["request"][pk][args] = v
271
267
 
272
268
 
273
269
  def update_mcp_args(mcp, tool_args : dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scmcp_shared
3
- Version: 0.3.0
3
+ Version: 0.3.5
4
4
  Summary: A shared function libray for scmcphub
5
5
  Author-email: shuang <hsh-me@outlook.com>
6
6
  License: BSD 3-Clause License
@@ -35,7 +35,7 @@ License-File: LICENSE
35
35
  Requires-Python: >=3.10
36
36
  Requires-Dist: fastmcp>=2.3.4
37
37
  Requires-Dist: mcp>=1.8.0
38
- Requires-Dist: pydantic
38
+ Requires-Dist: nest-asyncio
39
39
  Requires-Dist: scanpy
40
40
  Description-Content-Type: text/markdown
41
41
 
@@ -1,21 +1,21 @@
1
- scmcp_shared/__init__.py,sha256=nSn3YFzkE-BAmUSmif33CxdohNxkgmLE9cYFNbjEu_E,24
2
- scmcp_shared/cli.py,sha256=NU31Vv-xev8yGtl1KHZhVswJNbK18x2AVzhhH9MR03M,3325
1
+ scmcp_shared/__init__.py,sha256=6zWLsZ8g5hYGDsXCmJv2JvlO9aeJZjPXG5UykIoIeEA,24
2
+ scmcp_shared/cli.py,sha256=KYHp5_VE2JVVT4Su57ELzQa1lI2P8Gw0b4ruBP_Dtxk,4396
3
3
  scmcp_shared/logging_config.py,sha256=eCuLuyxMmbj8A1E0VqYWoKA5JPTSbo6cmjS4LOyd0RQ,872
4
- scmcp_shared/util.py,sha256=vxmR6_iOBa5jaBNBWl3QBysJ14kXRrqrENDRl3_TChQ,9339
4
+ scmcp_shared/util.py,sha256=bJmbgYjhY44flRo51XDiH-5w7OztG34XxGL6JSGG9fo,9101
5
5
  scmcp_shared/schema/__init__.py,sha256=Kwkc7kPLjExOlxt1sWEy_5qa96MvOS8sNCMlZa6yRg8,737
6
6
  scmcp_shared/schema/io.py,sha256=ZKJpKkKazDE3_ZX3GtMIT08kSaiNmy0qVaxivhN7Dx4,4744
7
7
  scmcp_shared/schema/pl.py,sha256=rzE09wHMY3JR56HZc-QfIUUM0fGXRKd-7Dh3CrQrFB0,29547
8
8
  scmcp_shared/schema/pp.py,sha256=48F6oKf-I8IZuNQDfq_Lpp3fLLKA4PruqRje_ZrtTyw,21664
9
- scmcp_shared/schema/tl.py,sha256=0HlZ_WQkgnc93LfIYapRCijWBBaFAdM5SV3Ioht_1wI,34281
9
+ scmcp_shared/schema/tl.py,sha256=DaZUce33OW67h-caK9BW4sD9zhlehmj3mCq1P-3vllM,34407
10
10
  scmcp_shared/schema/util.py,sha256=x_2GPsmliHabi9V5C6YEv_M8ZHJsinDZJ6ePWrLPmcI,4815
11
11
  scmcp_shared/server/__init__.py,sha256=4KE2Y_gDenF0ZyTGicQW0fTgJfMIQYZfpRP4hQ4rFYs,416
12
12
  scmcp_shared/server/base.py,sha256=kaOfi4yHLWr_AdBdpKzDrJiwRmbHF2jbsfI5g2qqgRA,6576
13
13
  scmcp_shared/server/io.py,sha256=yrQXdkAsPvk_62xQk5-SRmc5-jAvI-d3sE0QqHDXfMM,3424
14
14
  scmcp_shared/server/pl.py,sha256=HdhjrZfEx-IzCkZ03IGCW-mjUItfNvKdSOFbJ2_-2XQ,14854
15
15
  scmcp_shared/server/pp.py,sha256=neyRV3yEZ2a08eO-COI-t-PFFvntRzNmu19SQ77do_4,15406
16
- scmcp_shared/server/tl.py,sha256=2vfl5FrWQgQlPTB4-QLFMuMrtCux6ppmFttKOrryhF0,18320
16
+ scmcp_shared/server/tl.py,sha256=nneWj9SHNcZulhn3WRQrsoecHNsG2apDctB8H5OtQNI,18331
17
17
  scmcp_shared/server/util.py,sha256=L51fFL7Nb84JCQrFYyZIGsfmNFlA9uGzTV93hvc9mfI,12025
18
- scmcp_shared-0.3.0.dist-info/METADATA,sha256=M8NokyJHPjAVnN-JYmprlFhAfDq_XvAAU1l-kiH8LOA,2099
19
- scmcp_shared-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- scmcp_shared-0.3.0.dist-info/licenses/LICENSE,sha256=YNr1hpea195yq-wGtB8j-2dGtt7A5G00WENmxa7JGco,1495
21
- scmcp_shared-0.3.0.dist-info/RECORD,,
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,,