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.
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/PKG-INFO +2 -1
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/pyproject.toml +2 -1
- scmcp_shared-0.4.0/src/scmcp_shared/__init__.py +3 -0
- scmcp_shared-0.4.0/src/scmcp_shared/agent.py +30 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/cli.py +10 -0
- scmcp_shared-0.4.0/src/scmcp_shared/schema/tool.py +11 -0
- scmcp_shared-0.4.0/src/scmcp_shared/server/auto.py +52 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/base.py +1 -1
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/pp.py +5 -1
- scmcp_shared-0.3.7/src/scmcp_shared/__init__.py +0 -3
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/.github/release.yml +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/.github/workflows/publish.yml +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/.github/workflows/test.yml +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/LICENSE +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/README.md +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/logging_config.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/__init__.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/io.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/pl.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/pp.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/tl.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/schema/util.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/__init__.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/io.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/pl.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/tl.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/server/util.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/src/scmcp_shared/util.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/conftest.py +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/data/hg19/barcodes.tsv +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/data/hg19/genes.tsv +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/data/hg19/matrix.mtx +0 -0
- {scmcp_shared-0.3.7 → scmcp_shared-0.4.0}/tests/test_io.py +0 -0
- {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
|
+
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,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
|
@@ -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)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|