scmcp-shared 0.3.7__tar.gz → 0.4.0__tar.gz

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.
Files changed (34) hide show
  1. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/PKG-INFO +2 -1
  2. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/pyproject.toml +2 -1
  3. scmcp_shared-0.4.0/src/scmcp_shared/__init__.py +3 -0
  4. scmcp_shared-0.4.0/src/scmcp_shared/agent.py +30 -0
  5. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/cli.py +10 -0
  6. scmcp_shared-0.4.0/src/scmcp_shared/schema/tool.py +11 -0
  7. scmcp_shared-0.4.0/src/scmcp_shared/server/auto.py +52 -0
  8. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/base.py +1 -1
  9. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/pp.py +5 -1
  10. scmcp_shared-0.3.7/src/scmcp_shared/__init__.py +0 -3
  11. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/.github/release.yml +0 -0
  12. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/.github/workflows/publish.yml +0 -0
  13. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/.github/workflows/test.yml +0 -0
  14. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/LICENSE +0 -0
  15. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/README.md +0 -0
  16. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/logging_config.py +0 -0
  17. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/__init__.py +0 -0
  18. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/io.py +0 -0
  19. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/pl.py +0 -0
  20. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/pp.py +0 -0
  21. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/tl.py +0 -0
  22. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/util.py +0 -0
  23. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/__init__.py +0 -0
  24. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/io.py +0 -0
  25. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/pl.py +0 -0
  26. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/tl.py +0 -0
  27. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/util.py +0 -0
  28. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/util.py +0 -0
  29. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/conftest.py +0 -0
  30. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/data/hg19/barcodes.tsv +0 -0
  31. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/data/hg19/genes.tsv +0 -0
  32. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/data/hg19/matrix.mtx +0 -0
  33. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/test_io.py +0 -0
  34. {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/test_pp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scmcp_shared
3
- Version: 0.3.7
3
+ Version: 0.4.0
4
4
  Summary: A shared function libray for scmcphub
5
5
  Project-URL: Homepage, http://scmcphub.org/
6
6
  Project-URL: Repository, https://github.com/scmcphub/scmcp-shared
@@ -39,6 +39,7 @@ Keywords: AI,agent,bioinformatics,llm,mcp,model context protocol,scRNA-seq,singl
39
39
  Requires-Python: >=3.10
40
40
  Requires-Dist: fastmcp>=2.7.0
41
41
  Requires-Dist: igraph
42
+ Requires-Dist: instructor>=1.8.3
42
43
  Requires-Dist: leidenalg
43
44
  Requires-Dist: mcp>=1.8.0
44
45
  Requires-Dist: nest-asyncio
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "mcp>=1.8.0",
28
28
  "fastmcp>=2.7.0",
29
29
  "nest_asyncio",
30
+ "instructor>=1.8.3",
30
31
  ]
31
32
 
32
33
  [build-system]
@@ -51,4 +52,4 @@ Documentation = "https://docs.scmcphub.org/"
51
52
 
52
53
  [tool.pytest.ini_options]
53
54
  asyncio_mode = "strict"
54
- asyncio_default_fixture_loop_scope = "function"
55
+ asyncio_default_fixture_loop_scope = "function"
@@ -0,0 +1,3 @@
1
+
2
+ __version__ = "0.4.0"
3
+
@@ -0,0 +1,30 @@
1
+ import instructor
2
+ from openai import OpenAI
3
+ from scmcp_shared.schema.tool import ToolList
4
+ import os
5
+
6
+
7
+
8
+ def select_tool(query):
9
+
10
+ API_KEY = os.environ.get("API_KEY", None)
11
+ BASE_URL = os.environ.get("BASE_URL", None)
12
+ MODEL = os.environ.get("MODEL", None)
13
+
14
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
15
+ client = instructor.from_openai(client)
16
+
17
+ response = client.chat.completions.create(
18
+ model=MODEL,
19
+ messages=[
20
+ {
21
+ "role": "system",
22
+ "content": f"you are a bioinformatician, you are given a task and a list of tools, you need to select the most directly relevant tools to use to solve the task"},
23
+ {
24
+ "role": "user",
25
+ "content": query
26
+ },
27
+ ],
28
+ response_model=ToolList,
29
+ )
30
+ return response.tools
@@ -35,6 +35,7 @@ class MCPCLI:
35
35
  parser.add_argument('--host', default='127.0.0.1', help='transport host')
36
36
  parser.add_argument('-f', '--forward', help='forward request to another server')
37
37
  parser.add_argument('-wd', '--working-dir', default=".", help='working directory')
38
+ parser.add_argument('--tool-mode', choices=["auto", "normal"], default="normal", help='tool selection mode')
38
39
  parser.add_argument('--log-file', help='log file path, use stdout if None')
39
40
 
40
41
  def add_command(self, name: str, help_text: str, handler: Callable) -> argparse.ArgumentParser:
@@ -78,6 +79,15 @@ class MCPCLI:
78
79
  modules = None
79
80
  if self.manager is not None:
80
81
  self.mcp = self.manager(self.name, include_modules=modules).mcp
82
+ all_tools = self.mcp._tool_manager._tools
83
+ auto_tools = {tool: all_tools[tool] for tool in all_tools if all_tools[tool].name in ["search_tool", "run_tool"]}
84
+ if args.tool_mode == "auto":
85
+ all_tools = self.mcp._tool_manager._tools
86
+ self.mcp._tool_manager._all_tools = all_tools
87
+ self.mcp._tool_manager._tools = auto_tools
88
+ else:
89
+ for name in auto_tools:
90
+ self.mcp._tool_manager.remove_tool(name)
81
91
  elif self.mcp is not None:
82
92
  pass
83
93
  else:
@@ -0,0 +1,11 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import List
3
+
4
+
5
+ class Tool(BaseModel):
6
+ name: str = Field(description="The name of the tool")
7
+ description: str = Field(description="The description of the tool")
8
+
9
+
10
+ class ToolList(BaseModel):
11
+ tools: List[Tool]
@@ -0,0 +1,52 @@
1
+ from fastmcp import FastMCP
2
+ from fastmcp.server.dependencies import get_context
3
+ from ..agent import select_tool
4
+ from pydantic import Field
5
+
6
+ auto_mcp = FastMCP("SmartMCP-select-Server")
7
+
8
+
9
+ @auto_mcp.tool()
10
+ def search_tool(
11
+ task: str= Field(description="The tasks or questions that needs to be solved using available tools")
12
+ ):
13
+ """search the tools that can be used to solve the user's tasks or questions"""
14
+ ctx = get_context()
15
+ fastmcp = ctx.fastmcp
16
+ all_tools = fastmcp._tool_manager._all_tools
17
+ auto_tools = fastmcp._tool_manager._tools
18
+ fastmcp._tool_manager._tools = all_tools
19
+ query = f"<task>{task}</task>\n"
20
+ for name in all_tools:
21
+ tool = all_tools[name]
22
+ query += f"<Tool>\n<name>{name}</name>\n<description>{tool.description}</description>\n</Tool>\n"
23
+
24
+ results = select_tool(query)
25
+ tool_list = []
26
+ for tool in results:
27
+ tool = tool.model_dump()
28
+ tool["parameters"] = all_tools[tool["name"]].parameters
29
+ tool_list.append(tool)
30
+ fastmcp._tool_manager._tools = auto_tools
31
+ return tool_list
32
+
33
+
34
+ @auto_mcp.tool()
35
+ async def run_tool(
36
+ name: str= Field(description="The name of the tool to run"),
37
+ parameter: dict = Field(description="The parameters to pass to the tool")
38
+ ):
39
+ """run the tool with the given name and parameters. Only start call the tool when last tool is finished."""
40
+ ctx = get_context()
41
+ fastmcp = ctx.fastmcp
42
+ all_tools = fastmcp._tool_manager._all_tools
43
+ auto_tools = fastmcp._tool_manager._tools
44
+ fastmcp._tool_manager._tools = all_tools
45
+
46
+ try:
47
+ result = await fastmcp._tool_manager.call_tool(name, parameter)
48
+ except Exception as e:
49
+ fastmcp._tool_manager._tools = auto_tools
50
+ result = {"error": str(e)}
51
+
52
+ return result
@@ -6,7 +6,7 @@ from collections.abc import AsyncIterator
6
6
  from contextlib import asynccontextmanager
7
7
  import asyncio
8
8
  from typing import Optional, List, Any, Iterable
9
-
9
+ from .auto import auto_mcp
10
10
 
11
11
  class BaseMCP:
12
12
  """Base class for all Scanpy MCP classes."""
@@ -120,12 +120,16 @@ class ScanpyPreprocessingMCP(BaseMCP):
120
120
  func_kwargs = filter_args(request, sc.pp.calculate_qc_metrics)
121
121
  ads = get_ads()
122
122
  adata = ads.get_adata(adinfo=adinfo)
123
+ if request.qc_vars:
124
+ for var in request.qc_vars:
125
+ if var not in adata.var.columns:
126
+ return f"Cound find {var} in adata.var, consider to use mark_var tool to mark the variable"
123
127
  func_kwargs["inplace"] = True
124
128
  try:
125
129
  sc.pp.calculate_qc_metrics(adata, **func_kwargs)
126
130
  add_op_log(adata, sc.pp.calculate_qc_metrics, func_kwargs, adinfo)
127
131
  except KeyError as e:
128
- raise KeyError(f"Cound find {e} in adata.var")
132
+ raise KeyError(f"Cound find {e} in adata.var, consider to use mark_var tool to mark the variable")
129
133
  return [generate_msg(adinfo, adata, ads)]
130
134
  except ToolError as e:
131
135
  raise ToolError(e)
@@ -1,3 +0,0 @@
1
-
2
- __version__ = "0.3.7"
3
-
File without changes
File without changes