scmcp-shared 0.3.7__py3-none-any.whl → 0.5.0__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 -3
- scmcp_shared/agent.py +80 -0
- scmcp_shared/backend.py +44 -0
- scmcp_shared/cli.py +76 -37
- scmcp_shared/kb.py +38 -0
- scmcp_shared/logging_config.py +6 -8
- scmcp_shared/mcp_base.py +184 -0
- scmcp_shared/schema/io.py +101 -59
- scmcp_shared/schema/pl.py +386 -490
- scmcp_shared/schema/pp.py +514 -265
- scmcp_shared/schema/preset/__init__.py +15 -0
- scmcp_shared/schema/preset/io.py +103 -0
- scmcp_shared/schema/preset/pl.py +843 -0
- scmcp_shared/schema/preset/pp.py +616 -0
- scmcp_shared/schema/preset/tl.py +917 -0
- scmcp_shared/schema/preset/util.py +123 -0
- scmcp_shared/schema/tl.py +355 -407
- scmcp_shared/schema/tool.py +11 -0
- scmcp_shared/schema/util.py +57 -72
- scmcp_shared/server/__init__.py +0 -12
- scmcp_shared/server/auto.py +54 -0
- scmcp_shared/server/code.py +3 -0
- scmcp_shared/server/preset/__init__.py +14 -0
- scmcp_shared/server/{io.py → preset/io.py} +26 -22
- scmcp_shared/server/{pl.py → preset/pl.py} +162 -78
- scmcp_shared/server/{pp.py → preset/pp.py} +127 -65
- scmcp_shared/server/{tl.py → preset/tl.py} +142 -79
- scmcp_shared/server/{util.py → preset/util.py} +123 -66
- scmcp_shared/util.py +109 -38
- scmcp_shared/vector_db/decoupler.lance/_transactions/0-9499b1b5-85d4-44c2-8f05-1bcce87fe4ef.txn +3 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/1-1632e7a3-4427-4077-8d03-57437144443d.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/10-dcf66479-eafb-4193-9358-198154aea1c1.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/11-2c6ddc17-49f5-47b5-8764-753297cf9e1b.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/12-f079d0a2-9c1c-4e7a-abf6-3e3c4cddac11.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/13-5bda9382-a06e-493d-85cb-e066172778ce.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/14-6f9c669f-25e2-4096-b7ea-9b421a37e110.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/15-2068cca9-31e8-45a3-86d1-bcb8924c72b9.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/16-72356bb2-5c98-424e-97aa-a92fa3453da6.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/17-9baa67ce-f6d3-478c-9511-20d01988d4d5.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/18-b9f2e28e-c4c5-4ce7-bf58-402d53a39558.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/19-d199d9f9-7990-4ec0-adde-e98c1c927202.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/2-3827747c-095e-41cb-af69-3814bab3a588.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/20-31c197a3-7a23-472f-a7e3-056c0ff11e3a.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/21-066e3024-36f8-4557-83ae-d2f338ec045c.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/22-1d1ccbe5-7c4c-4882-986a-c61ec2f7925e.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/23-b2c41628-b2d6-49f1-81b5-3cd9100894b4.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/24-e43e2e8d-9dd8-479f-b63c-d147e737c49f.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/25-188708e9-5ee1-4b5c-8e13-45dec8efd60d.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/26-4f4b6316-5680-4b31-8dd6-215cce66d7b5.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/27-c1855f86-abab-44ef-9e61-1312dffc9b06.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/28-650d2be0-e977-4177-b457-b733eea8b6ea.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/29-e47751d8-0e44-46da-82bd-4836b00a8431.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/3-518fc9b8-67de-48f7-94c8-f2398c63dbc5.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/30-0db3ea0f-deca-4b5b-9d70-9fb5f741f90c.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/31-ef76f0bb-bcfa-4031-b3ad-94b51ee818a9.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/32-a763e0f1-5c8d-460f-a455-0c6f6c4f1450.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/33-30bce2aa-8e6f-4f42-a323-c420133aa20f.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/34-f91ec2f9-d34b-4fcf-8bc7-564c28e50538.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/35-7f36a75c-96a7-4868-a4db-5d5f78ecf850.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/36-f2df7f99-a37c-458a-958d-7eb5e81eee18.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/37-e0af7415-955b-4167-8705-fa9ddb643e9a.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/38-6c24a9f0-ce71-4dda-8193-479aa23c5456.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/39-72b8a20c-3112-4cf1-8b05-b74a2c02c3f2.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/4-42904942-5a79-4f28-90ce-35a4ae8c40d1.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/40-95ac7526-6a2e-4914-9654-288c41bff370.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/41-13430464-5e62-4aa8-9709-513693a75095.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/42-cbb1ad83-a906-4540-bc37-4db158eac618.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/43-3c246401-d742-49a9-b24d-cfc457685461.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/44-11315c14-ecf4-4e3d-8690-46d57cb3e8c0.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/45-7bbf4bc6-96d4-425a-afa2-34aec7121c85.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/5-82dda0b4-7838-4f04-90f3-4b1e932c6891.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/6-c78352d8-16ba-4814-bd7f-74b8c0c5efe7.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/7-bb882b35-63f3-4c52-870f-6a64e3ac7f3c.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/8-fc84ad1b-1b59-4822-8fc7-70f4eb18f6d9.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_transactions/9-3404dcf1-bb17-40e6-9e5d-d9942dff90b2.txn +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/1.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/10.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/11.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/12.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/13.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/14.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/15.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/16.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/17.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/18.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/19.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/2.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/20.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/21.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/22.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/23.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/24.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/25.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/26.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/27.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/28.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/29.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/3.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/30.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/31.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/32.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/33.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/34.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/35.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/36.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/37.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/38.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/39.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/4.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/40.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/41.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/42.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/43.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/44.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/45.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/46.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/5.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/6.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/7.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/8.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/_versions/9.manifest +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/0cc94dc3-2ca3-403c-a9df-59753f24b3f2.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/254a1a71-cdd2-4cca-b7ec-a50540fed62c.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/2574d216-5f05-4794-9e1c-986fafde6ab4.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/26c7bbe7-5f97-4453-b065-123b764448b3.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/286595a6-88f6-4b05-861e-e4f607a6fcdb.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/33219b1a-7575-46ef-bb64-2bdbde1e692b.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/344ad7dd-98bb-41de-b347-2f17bf5735cb.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/395b1ecb-68fe-4dd3-a770-4b3ed393ae0c.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/3db97f4c-9c35-44b7-9042-82b4006c5a22.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/4540246e-b0bb-4f4d-8f98-60a64f1c42ed.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/4643dbc0-9e45-4b63-81e1-e06cf9bddcec.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/4f28eb52-d409-43f2-ae17-d1826f209006.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/4fda4a22-35f0-4897-b4c5-6cedefba66e6.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/51ee77b6-ab24-44c0-916e-b81ff3912731.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/539d15c9-e88b-400c-9ab7-08f5e8dca10d.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/54007132-91f5-4909-8678-c471ef4d100f.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/546d88ec-0d5c-42bb-bccc-3271f3f183a4.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/6912ad8a-343c-4ca1-b1b7-4300125e688e.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/6b72e4f7-3051-40a4-a492-6d9eff1c647d.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/6d7ee320-ae1c-4e49-8ce5-c85971b11ce6.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/6ea47b70-69d4-43e9-b2c6-442fe50e4dd1.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/72adb5ed-bf11-4d06-b146-0272d333db08.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/745907df-e261-4f4b-a757-2c01c058bf27.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/78988307-4c66-4ad7-b295-7463ecd53609.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/798ce305-6d60-4f28-b82a-e1f647907abf.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/7c159cce-2741-442b-9c63-f44bf2996718.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/7cd7a818-e68b-4fa8-bad3-23b60c386670.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/7ec553ed-0c7a-4bac-99b2-2ad976b40466.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/83efdc85-d990-4762-b69a-fb1c3938f60f.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/8c1613b3-8d69-49c5-bfd0-a021bb516faf.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/95cd1b22-1f93-4133-9841-de21549ad8c5.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/9a2ea5d4-087e-4d3a-b64b-6c568b8a0010.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/b1c47dc6-450a-4dca-b465-badb6a3619c2.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/b21d183a-c51d-463b-932e-9beb2e0da9aa.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/b2598625-8683-4c33-be40-6f1a7555dc2c.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/b7297b39-1fc5-4886-be21-708b369bef59.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/b7d90085-5bd6-48f9-8a73-17eb7cf556fa.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/b988ceee-57fc-423b-8e67-5729086c6946.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/bfd68ea6-ddf4-44bb-aa87-2669f0462f7d.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/cb8ac58e-0e59-4391-95ab-6195e71b9625.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/d0f10434-afb5-49c5-9e77-d39a4f8cca94.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/d3f89fec-5795-4e11-8c15-bc2206a7fae2.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/e063c8a4-5ef1-4933-a049-b41049d9be5f.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/e2348b24-f290-4fe2-a6c8-e98009ae4c1b.lance +0 -0
- scmcp_shared/vector_db/decoupler.lance/data/e5d3f893-6763-40dc-9d02-04e2f56a4883.lance +0 -0
- {scmcp_shared-0.3.7.dist-info → scmcp_shared-0.5.0.dist-info}/METADATA +3 -1
- scmcp_shared-0.5.0.dist-info/RECORD +171 -0
- scmcp_shared/server/base.py +0 -148
- scmcp_shared-0.3.7.dist-info/RECORD +0 -21
- {scmcp_shared-0.3.7.dist-info → scmcp_shared-0.5.0.dist-info}/WHEEL +0 -0
- {scmcp_shared-0.3.7.dist-info → scmcp_shared-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,21 @@
|
|
1
|
-
import os
|
2
|
-
import inspect
|
3
|
-
from pathlib import Path
|
4
|
-
import scanpy as sc
|
5
|
-
from fastmcp import FastMCP , Context
|
6
1
|
from fastmcp.exceptions import ToolError
|
7
2
|
from fastmcp.tools.tool import Tool
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from .
|
3
|
+
from scmcp_shared.schema.preset.util import *
|
4
|
+
from scmcp_shared.schema.preset import AdataInfo
|
5
|
+
from scmcp_shared.util import forward_request, get_ads, add_op_log
|
6
|
+
from scmcp_shared.mcp_base import BaseMCP
|
12
7
|
|
13
8
|
|
14
9
|
class ScanpyUtilMCP(BaseMCP):
|
15
|
-
def __init__(
|
10
|
+
def __init__(
|
11
|
+
self,
|
12
|
+
include_tools: list = None,
|
13
|
+
exclude_tools: list = None,
|
14
|
+
AdataInfo=AdataInfo,
|
15
|
+
):
|
16
16
|
"""
|
17
17
|
Initialize ScanpyUtilMCP with optional tool filtering.
|
18
|
-
|
18
|
+
|
19
19
|
Args:
|
20
20
|
include_tools (list, optional): List of tool names to include. If None, all tools are included.
|
21
21
|
exclude_tools (list, optional): List of tool names to exclude. If None, no tools are excluded.
|
@@ -23,21 +23,23 @@ class ScanpyUtilMCP(BaseMCP):
|
|
23
23
|
"""
|
24
24
|
super().__init__("SCMCP-Util-Server", include_tools, exclude_tools, AdataInfo)
|
25
25
|
|
26
|
-
|
27
26
|
def _tool_query_op_log(self):
|
28
|
-
def _query_op_log(
|
27
|
+
def _query_op_log(
|
28
|
+
request: QueryOpLogParam, adinfo: self.AdataInfo = self.AdataInfo()
|
29
|
+
):
|
29
30
|
"""Query the adata operation log"""
|
30
31
|
adata = get_ads().get_adata(adinfo=adinfo)
|
31
32
|
op_dic = adata.uns["operation"]["op"]
|
32
|
-
opids = adata.uns["operation"]["opid"][-request.n:]
|
33
|
+
opids = adata.uns["operation"]["opid"][-request.n :]
|
33
34
|
op_list = []
|
34
35
|
for opid in opids:
|
35
36
|
op_list.append(op_dic[opid])
|
36
37
|
return op_list
|
37
|
-
|
38
|
+
|
39
|
+
return Tool.from_function(_query_op_log, name="query_op_log", enabled=True)
|
38
40
|
|
39
41
|
def _tool_mark_var(self):
|
40
|
-
def _mark_var(request:
|
42
|
+
def _mark_var(request: MarkVarParam, adinfo: self.AdataInfo = self.AdataInfo()):
|
41
43
|
"""
|
42
44
|
Determine if each gene meets specific conditions and store results in adata.var as boolean values.
|
43
45
|
For example: mitochondrion genes startswith MT-.
|
@@ -54,14 +56,20 @@ class ScanpyUtilMCP(BaseMCP):
|
|
54
56
|
patterns = request.patterns
|
55
57
|
if gene_class is not None:
|
56
58
|
if gene_class == "mitochondrion":
|
57
|
-
adata.var["mt"] = adata.var_names.str.startswith(
|
59
|
+
adata.var["mt"] = adata.var_names.str.startswith(
|
60
|
+
("MT-", "Mt", "mt-")
|
61
|
+
)
|
58
62
|
var_name = "mt"
|
59
63
|
elif gene_class == "ribosomal":
|
60
|
-
adata.var["ribo"] = adata.var_names.str.startswith(
|
64
|
+
adata.var["ribo"] = adata.var_names.str.startswith(
|
65
|
+
("RPS", "RPL", "Rps", "Rpl")
|
66
|
+
)
|
61
67
|
var_name = "ribo"
|
62
68
|
elif gene_class == "hemoglobin":
|
63
|
-
adata.var["hb"] = adata.var_names.str.contains(
|
64
|
-
|
69
|
+
adata.var["hb"] = adata.var_names.str.contains(
|
70
|
+
"^HB[^(P)]", case=False
|
71
|
+
)
|
72
|
+
var_name = "hb"
|
65
73
|
elif pattern_type is not None and patterns is not None:
|
66
74
|
if pattern_type == "startswith":
|
67
75
|
adata.var[var_name] = adata.var_names.str.startswith(patterns)
|
@@ -70,25 +78,39 @@ class ScanpyUtilMCP(BaseMCP):
|
|
70
78
|
elif pattern_type == "contains":
|
71
79
|
adata.var[var_name] = adata.var_names.str.contains(patterns)
|
72
80
|
else:
|
73
|
-
raise ValueError(
|
81
|
+
raise ValueError(
|
82
|
+
f"Did not support pattern_type: {pattern_type}"
|
83
|
+
)
|
74
84
|
else:
|
75
|
-
raise ValueError(
|
76
|
-
|
77
|
-
res = {
|
78
|
-
|
85
|
+
raise ValueError("Please provide validated parameter")
|
86
|
+
|
87
|
+
res = {
|
88
|
+
var_name: adata.var[var_name].value_counts().to_dict(),
|
89
|
+
"msg": f"add '{var_name}' column in adata.var",
|
90
|
+
}
|
91
|
+
func_kwargs = {
|
92
|
+
"var_name": var_name,
|
93
|
+
"gene_class": gene_class,
|
94
|
+
"pattern_type": pattern_type,
|
95
|
+
"patterns": patterns,
|
96
|
+
}
|
79
97
|
add_op_log(adata, "mark_var", func_kwargs, adinfo)
|
80
98
|
return res
|
81
99
|
except ToolError as e:
|
82
100
|
raise ToolError(e)
|
83
101
|
except Exception as e:
|
84
|
-
if hasattr(e,
|
102
|
+
if hasattr(e, "__context__") and e.__context__:
|
85
103
|
raise ToolError(e.__context__)
|
86
104
|
else:
|
87
105
|
raise ToolError(e)
|
88
|
-
|
106
|
+
|
107
|
+
return Tool.from_function(_mark_var, name="mark_var", enabled=True)
|
89
108
|
|
90
109
|
def _tool_list_var(self):
|
91
|
-
def _list_var(
|
110
|
+
def _list_var(
|
111
|
+
request: ListVarParam = ListVarParam(),
|
112
|
+
adinfo: self.AdataInfo = self.AdataInfo(),
|
113
|
+
):
|
92
114
|
"""List key columns in adata.var. It should be called for checking when other tools need var key column names as input."""
|
93
115
|
try:
|
94
116
|
result = forward_request("ul_list_var", request, adinfo)
|
@@ -101,19 +123,20 @@ class ScanpyUtilMCP(BaseMCP):
|
|
101
123
|
except ToolError as e:
|
102
124
|
raise ToolError(e)
|
103
125
|
except Exception as e:
|
104
|
-
if hasattr(e,
|
126
|
+
if hasattr(e, "__context__") and e.__context__:
|
105
127
|
raise ToolError(e.__context__)
|
106
128
|
else:
|
107
129
|
raise ToolError(e)
|
108
|
-
|
130
|
+
|
131
|
+
return Tool.from_function(_list_var, name="list_var", enabled=True)
|
109
132
|
|
110
133
|
def _tool_list_obs(self):
|
111
|
-
def _list_obs(request:
|
134
|
+
def _list_obs(request: ListObsParam, adinfo: self.AdataInfo = self.AdataInfo()):
|
112
135
|
"""List key columns in adata.obs. It should be called before other tools need obs key column names input."""
|
113
136
|
try:
|
114
137
|
result = forward_request("ul_list_obs", request, adinfo)
|
115
138
|
if result is not None:
|
116
|
-
return result
|
139
|
+
return result
|
117
140
|
adata = get_ads().get_adata(adinfo=adinfo)
|
118
141
|
columns = list(adata.obs.columns)
|
119
142
|
add_op_log(adata, "list_obs", {}, adinfo)
|
@@ -121,19 +144,22 @@ class ScanpyUtilMCP(BaseMCP):
|
|
121
144
|
except ToolError as e:
|
122
145
|
raise ToolError(e)
|
123
146
|
except Exception as e:
|
124
|
-
if hasattr(e,
|
147
|
+
if hasattr(e, "__context__") and e.__context__:
|
125
148
|
raise ToolError(e.__context__)
|
126
149
|
else:
|
127
150
|
raise ToolError(e)
|
128
|
-
|
151
|
+
|
152
|
+
return Tool.from_function(_list_obs, name="list_obs", enabled=True)
|
129
153
|
|
130
154
|
def _tool_check_var(self):
|
131
|
-
def _check_var(
|
155
|
+
def _check_var(
|
156
|
+
request: VarNamesParam, adinfo: self.AdataInfo = self.AdataInfo()
|
157
|
+
):
|
132
158
|
"""Check if genes/variables exist in adata.var_names. This tool should be called before gene expression visualizations or color by genes."""
|
133
159
|
try:
|
134
160
|
result = forward_request("ul_check_var", request, adinfo)
|
135
161
|
if result is not None:
|
136
|
-
return result
|
162
|
+
return result
|
137
163
|
adata = get_ads().get_adata(adinfo=adinfo)
|
138
164
|
var_names = request.var_names
|
139
165
|
if adata.raw is not None:
|
@@ -146,14 +172,17 @@ class ScanpyUtilMCP(BaseMCP):
|
|
146
172
|
except ToolError as e:
|
147
173
|
raise ToolError(e)
|
148
174
|
except Exception as e:
|
149
|
-
if hasattr(e,
|
175
|
+
if hasattr(e, "__context__") and e.__context__:
|
150
176
|
raise ToolError(e.__context__)
|
151
177
|
else:
|
152
178
|
raise ToolError(e)
|
153
|
-
|
179
|
+
|
180
|
+
return Tool.from_function(_check_var, name="check_var", enabled=True)
|
154
181
|
|
155
182
|
def _tool_merge_adata(self):
|
156
|
-
def _merge_adata(
|
183
|
+
def _merge_adata(
|
184
|
+
request: ConcatBaseParam, adinfo: self.AdataInfo = self.AdataInfo()
|
185
|
+
):
|
157
186
|
"""Merge multiple adata objects."""
|
158
187
|
try:
|
159
188
|
result = forward_request("ul_merge_adata", request, adinfo)
|
@@ -161,96 +190,124 @@ class ScanpyUtilMCP(BaseMCP):
|
|
161
190
|
return result
|
162
191
|
ads = get_ads()
|
163
192
|
adata = ads.get_adata(adinfo=adinfo)
|
164
|
-
kwargs = {
|
193
|
+
kwargs = {
|
194
|
+
k: v for k, v in request.model_dump().items() if v is not None
|
195
|
+
}
|
165
196
|
merged_adata = adata.concat(ads.adata_dic, **kwargs)
|
166
197
|
ads.adata_dic[dtype] = {}
|
167
198
|
ads.active_id = "merged_adata"
|
168
199
|
add_op_log(merged_adata, ad.concat, kwargs, adinfo)
|
169
200
|
ads.adata_dic[ads.active_id] = merged_adata
|
170
|
-
return {
|
201
|
+
return {
|
202
|
+
"status": "success",
|
203
|
+
"message": "Successfully merged all AnnData objects",
|
204
|
+
}
|
171
205
|
except ToolError as e:
|
172
206
|
raise ToolError(e)
|
173
207
|
except Exception as e:
|
174
|
-
if hasattr(e,
|
208
|
+
if hasattr(e, "__context__") and e.__context__:
|
175
209
|
raise ToolError(e.__context__)
|
176
210
|
else:
|
177
211
|
raise ToolError(e)
|
178
|
-
|
212
|
+
|
213
|
+
return Tool.from_function(_merge_adata, name="merge_adata", enabled=True)
|
179
214
|
|
180
215
|
def _tool_set_dpt_iroot(self):
|
181
|
-
def _set_dpt_iroot(
|
216
|
+
def _set_dpt_iroot(
|
217
|
+
request: DPTIROOTParam, adinfo: self.AdataInfo = self.AdataInfo()
|
218
|
+
):
|
182
219
|
"""Set the iroot cell"""
|
183
220
|
try:
|
184
221
|
result = forward_request("ul_set_dpt_iroot", request, adinfo)
|
185
222
|
if result is not None:
|
186
|
-
return result
|
223
|
+
return result
|
187
224
|
adata = get_ads().get_adata(adinfo=adinfo)
|
188
225
|
diffmap_key = request.diffmap_key
|
189
226
|
dimension = request.dimension
|
190
227
|
direction = request.direction
|
191
228
|
if diffmap_key not in adata.obsm:
|
192
|
-
raise ValueError(
|
229
|
+
raise ValueError(
|
230
|
+
f"Diffusion map key '{diffmap_key}' not found in adata.obsm"
|
231
|
+
)
|
193
232
|
if direction == "min":
|
194
233
|
adata.uns["iroot"] = adata.obsm[diffmap_key][:, dimension].argmin()
|
195
|
-
else:
|
234
|
+
else:
|
196
235
|
adata.uns["iroot"] = adata.obsm[diffmap_key][:, dimension].argmax()
|
197
|
-
|
198
|
-
func_kwargs = {
|
236
|
+
|
237
|
+
func_kwargs = {
|
238
|
+
"diffmap_key": diffmap_key,
|
239
|
+
"dimension": dimension,
|
240
|
+
"direction": direction,
|
241
|
+
}
|
199
242
|
add_op_log(adata, "set_dpt_iroot", func_kwargs, adinfo)
|
200
|
-
|
201
|
-
return {
|
243
|
+
|
244
|
+
return {
|
245
|
+
"status": "success",
|
246
|
+
"message": f"Successfully set root cell for DPT using {direction} of dimension {dimension}",
|
247
|
+
}
|
202
248
|
except ToolError as e:
|
203
249
|
raise ToolError(e)
|
204
250
|
except Exception as e:
|
205
|
-
if hasattr(e,
|
251
|
+
if hasattr(e, "__context__") and e.__context__:
|
206
252
|
raise ToolError(e.__context__)
|
207
253
|
else:
|
208
254
|
raise ToolError(e)
|
209
|
-
|
255
|
+
|
256
|
+
return Tool.from_function(_set_dpt_iroot, name="set_dpt_iroot", enabled=True)
|
210
257
|
|
211
258
|
def _tool_add_layer(self):
|
212
|
-
def _add_layer(
|
259
|
+
def _add_layer(
|
260
|
+
request: AddLayerParam, adinfo: self.AdataInfo = self.AdataInfo()
|
261
|
+
):
|
213
262
|
"""Add a layer to the AnnData object."""
|
214
263
|
try:
|
215
264
|
result = forward_request("ul_add_layer", request, adinfo)
|
216
265
|
if result is not None:
|
217
|
-
return result
|
266
|
+
return result
|
218
267
|
adata = get_ads().get_adata(adinfo=adinfo)
|
219
268
|
layer_name = request.layer_name
|
220
|
-
|
269
|
+
|
221
270
|
# Check if layer already exists
|
222
271
|
if layer_name in adata.layers:
|
223
|
-
raise ValueError(
|
272
|
+
raise ValueError(
|
273
|
+
f"Layer '{layer_name}' already exists in adata.layers"
|
274
|
+
)
|
224
275
|
# Add the data as a new layer
|
225
276
|
adata.layers[layer_name] = adata.X.copy()
|
226
277
|
|
227
278
|
func_kwargs = {"layer_name": layer_name}
|
228
279
|
add_op_log(adata, "add_layer", func_kwargs, adinfo)
|
229
|
-
|
280
|
+
|
230
281
|
return {
|
231
|
-
"status": "success",
|
232
|
-
"message": f"Successfully added layer '{layer_name}' to adata.layers"
|
282
|
+
"status": "success",
|
283
|
+
"message": f"Successfully added layer '{layer_name}' to adata.layers",
|
233
284
|
}
|
234
285
|
except ToolError as e:
|
235
286
|
raise ToolError(e)
|
236
287
|
except Exception as e:
|
237
|
-
if hasattr(e,
|
288
|
+
if hasattr(e, "__context__") and e.__context__:
|
238
289
|
raise ToolError(e.__context__)
|
239
290
|
else:
|
240
291
|
raise ToolError(e)
|
241
|
-
|
292
|
+
|
293
|
+
return Tool.from_function(_add_layer, name="add_layer", enabled=True)
|
242
294
|
|
243
295
|
def _tool_check_samples(self):
|
244
|
-
def _check_samples(request: None, adinfo: self.AdataInfo=self.AdataInfo()):
|
296
|
+
def _check_samples(request: None, adinfo: self.AdataInfo = self.AdataInfo()):
|
245
297
|
"""check the stored samples"""
|
246
298
|
try:
|
247
299
|
ads = get_ads()
|
248
|
-
return {
|
300
|
+
return {
|
301
|
+
"sampleid": [
|
302
|
+
list(ads.adata_dic[dk].keys()) for dk in ads.adata_dic.keys()
|
303
|
+
]
|
304
|
+
}
|
249
305
|
except ToolError as e:
|
250
306
|
raise ToolError(e)
|
251
307
|
except Exception as e:
|
252
|
-
if hasattr(e,
|
308
|
+
if hasattr(e, "__context__") and e.__context__:
|
253
309
|
raise ToolError(e.__context__)
|
254
310
|
else:
|
255
311
|
raise ToolError(e)
|
256
|
-
|
312
|
+
|
313
|
+
return Tool.from_function(_check_samples, name="check_samples", enabled=True)
|
scmcp_shared/util.py
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
import inspect
|
2
2
|
import os
|
3
|
-
from enum import Enum
|
4
3
|
from pathlib import Path
|
5
4
|
from fastmcp.server.dependencies import get_context
|
6
5
|
from fastmcp.exceptions import ToolError
|
7
6
|
import asyncio
|
8
7
|
import nest_asyncio
|
8
|
+
from pydantic import BaseModel
|
9
9
|
|
10
10
|
|
11
11
|
def get_env(key):
|
12
12
|
return os.environ.get(f"SCMCP_{key.upper()}")
|
13
13
|
|
14
|
-
|
14
|
+
|
15
15
|
def filter_args(request, func, **extra_kwargs):
|
16
16
|
kwargs = request.model_dump()
|
17
17
|
args = request.model_fields_set
|
@@ -25,7 +25,7 @@ def filter_args(request, func, **extra_kwargs):
|
|
25
25
|
def add_op_log(adata, func, kwargs, adinfo):
|
26
26
|
import hashlib
|
27
27
|
import json
|
28
|
-
|
28
|
+
|
29
29
|
if "operation" not in adata.uns:
|
30
30
|
adata.uns["operation"] = {}
|
31
31
|
adata.uns["operation"]["op"] = {}
|
@@ -41,26 +41,27 @@ def add_op_log(adata, func, kwargs, adinfo):
|
|
41
41
|
else:
|
42
42
|
func_name = str(func)
|
43
43
|
new_kwargs = {**adinfo.model_dump()}
|
44
|
-
for k,v in kwargs.items():
|
44
|
+
for k, v in kwargs.items():
|
45
45
|
if isinstance(v, tuple):
|
46
46
|
new_kwargs[k] = list(v)
|
47
47
|
else:
|
48
48
|
new_kwargs[k] = v
|
49
49
|
try:
|
50
50
|
kwargs_str = json.dumps(new_kwargs, sort_keys=True)
|
51
|
-
except:
|
52
|
-
|
51
|
+
except Exception as e:
|
52
|
+
print(e)
|
53
|
+
kwargs_str = f"{e}" + str(new_kwargs)
|
53
54
|
hash_input = f"{func_name}:{kwargs_str}"
|
54
55
|
hash_key = hashlib.md5(hash_input.encode()).hexdigest()
|
55
56
|
adata.uns["operation"]["op"][hash_key] = {func_name: new_kwargs}
|
56
57
|
adata.uns["operation"]["opid"] = list(adata.uns["operation"]["opid"])
|
57
58
|
adata.uns["operation"]["opid"].append(hash_key)
|
58
59
|
from .logging_config import setup_logger
|
60
|
+
|
59
61
|
logger = setup_logger(log_file=get_env("LOG_FILE"))
|
60
62
|
logger.info(f"{func}: {new_kwargs}")
|
61
63
|
|
62
64
|
|
63
|
-
|
64
65
|
def save_fig_path(axes, file):
|
65
66
|
from matplotlib.axes import Axes
|
66
67
|
|
@@ -76,12 +77,14 @@ def save_fig_path(axes, file):
|
|
76
77
|
ax.figure.savefig(file_path)
|
77
78
|
elif isinstance(axes, Axes):
|
78
79
|
axes.figure.savefig(file_path)
|
79
|
-
elif hasattr(axes,
|
80
|
+
elif hasattr(axes, "savefig"): # if Figure
|
80
81
|
axes.savefig(file_path)
|
81
|
-
elif hasattr(axes,
|
82
|
+
elif hasattr(axes, "save"): # for plotnine.ggplot.ggplot
|
82
83
|
axes.save(file_path)
|
83
84
|
else:
|
84
|
-
raise ValueError(
|
85
|
+
raise ValueError(
|
86
|
+
f"axes must be a Axes or plotnine object, but got {type(axes)}"
|
87
|
+
)
|
85
88
|
return file_path
|
86
89
|
except Exception as e:
|
87
90
|
raise e
|
@@ -102,7 +105,7 @@ def savefig(axes, func=None, **kwargs):
|
|
102
105
|
kwargs.pop("save", None)
|
103
106
|
kwargs.pop("show", None)
|
104
107
|
args = []
|
105
|
-
for k,v in kwargs.items():
|
108
|
+
for k, v in kwargs.items():
|
106
109
|
if isinstance(v, (tuple, list, set)):
|
107
110
|
v = v[:3] ## show first 3 elements
|
108
111
|
args.append(f"{k}-{'-'.join([str(i) for i in v])}")
|
@@ -119,7 +122,7 @@ def savefig(axes, func=None, **kwargs):
|
|
119
122
|
raise PermissionError("You don't have permission to save figure")
|
120
123
|
except Exception as e:
|
121
124
|
raise e
|
122
|
-
transport = get_env("TRANSPORT")
|
125
|
+
transport = get_env("TRANSPORT")
|
123
126
|
if transport == "stdio":
|
124
127
|
return fig_path
|
125
128
|
else:
|
@@ -129,36 +132,41 @@ def savefig(axes, func=None, **kwargs):
|
|
129
132
|
return fig_path
|
130
133
|
|
131
134
|
|
132
|
-
|
133
135
|
async def get_figure(request):
|
134
136
|
from starlette.responses import FileResponse, Response
|
135
137
|
|
136
138
|
figure_name = request.path_params["figure_name"]
|
137
139
|
figure_path = f"./figures/{figure_name}"
|
138
|
-
|
140
|
+
|
139
141
|
if not os.path.isfile(figure_path):
|
140
|
-
return Response(
|
141
|
-
|
142
|
+
return Response(
|
143
|
+
content={"error": "figure not found"}, media_type="application/json"
|
144
|
+
)
|
145
|
+
|
142
146
|
return FileResponse(figure_path)
|
143
147
|
|
144
148
|
|
145
149
|
def add_figure_route(server):
|
146
150
|
from starlette.routing import Route
|
147
|
-
|
151
|
+
|
152
|
+
server._additional_http_routes = [
|
153
|
+
Route("/figures/{figure_name}", endpoint=get_figure)
|
154
|
+
]
|
148
155
|
|
149
156
|
|
150
157
|
async def async_forward_request(func, request, adinfo, **kwargs):
|
151
158
|
from fastmcp import Client
|
159
|
+
|
152
160
|
forward_url = get_env("FORWARD")
|
153
161
|
request_kwargs = request.model_dump()
|
154
162
|
request_args = request.model_fields_set
|
155
163
|
func_kwargs = {
|
156
164
|
"request": {k: request_kwargs.get(k) for k in request_args},
|
157
|
-
"adinfo": adinfo.model_dump()
|
165
|
+
"adinfo": adinfo.model_dump(),
|
158
166
|
}
|
159
167
|
if not forward_url:
|
160
168
|
return None
|
161
|
-
|
169
|
+
|
162
170
|
client = Client(forward_url)
|
163
171
|
async with client:
|
164
172
|
tools = await client.list_tools()
|
@@ -169,13 +177,12 @@ async def async_forward_request(func, request, adinfo, **kwargs):
|
|
169
177
|
except ToolError as e:
|
170
178
|
raise ToolError(e)
|
171
179
|
except Exception as e:
|
172
|
-
if hasattr(e,
|
180
|
+
if hasattr(e, "__context__") and e.__context__:
|
173
181
|
raise Exception(f"{str(e.__context__)}")
|
174
182
|
else:
|
175
183
|
raise e
|
176
184
|
|
177
185
|
|
178
|
-
|
179
186
|
def forward_request(func, request, adinfo, **kwargs):
|
180
187
|
"""Synchronous wrapper for forward_request"""
|
181
188
|
try:
|
@@ -186,12 +193,13 @@ def forward_request(func, request, adinfo, **kwargs):
|
|
186
193
|
# If we're in a running event loop, use create_task
|
187
194
|
async def _run():
|
188
195
|
return await async_forward_request(func, request, adinfo, **kwargs)
|
196
|
+
|
189
197
|
return loop.run_until_complete(_run())
|
190
198
|
else:
|
191
199
|
# If no event loop is running, use asyncio.run()
|
192
200
|
return asyncio.run(async_forward_request(func, request, adinfo, **kwargs))
|
193
201
|
except Exception as e:
|
194
|
-
if hasattr(e,
|
202
|
+
if hasattr(e, "__context__") and e.__context__:
|
195
203
|
raise Exception(f"{str(e.__context__)}")
|
196
204
|
else:
|
197
205
|
raise e
|
@@ -203,7 +211,9 @@ def obsm2adata(adata, obsm_key):
|
|
203
211
|
if obsm_key not in adata.obsm_keys():
|
204
212
|
raise ValueError(f"key {obsm_key} not found in adata.obsm")
|
205
213
|
else:
|
206
|
-
return AnnData(
|
214
|
+
return AnnData(
|
215
|
+
adata.obsm[obsm_key], obs=adata.obs, obsm=adata.obsm, uns=adata.uns
|
216
|
+
)
|
207
217
|
|
208
218
|
|
209
219
|
def get_ads():
|
@@ -213,7 +223,11 @@ def get_ads():
|
|
213
223
|
|
214
224
|
|
215
225
|
def generate_msg(adinfo, adata, ads):
|
216
|
-
return {
|
226
|
+
return {
|
227
|
+
"sampleid": adinfo.sampleid or ads.active_id,
|
228
|
+
"adtype": adinfo.adtype,
|
229
|
+
"adata": adata,
|
230
|
+
}
|
217
231
|
|
218
232
|
|
219
233
|
def sc_like_plot(plot_func, adata, request, adinfo, **kwargs):
|
@@ -231,7 +245,9 @@ def sc_like_plot(plot_func, adata, request, adinfo, **kwargs):
|
|
231
245
|
def filter_tools(mcp, include_tools=None, exclude_tools=None):
|
232
246
|
import asyncio
|
233
247
|
import copy
|
248
|
+
|
234
249
|
mcp = copy.deepcopy(mcp)
|
250
|
+
|
235
251
|
async def _filter_tools(mcp, include_tools=None, exclude_tools=None):
|
236
252
|
tools = await mcp.get_tools()
|
237
253
|
for tool in tools:
|
@@ -240,43 +256,98 @@ def filter_tools(mcp, include_tools=None, exclude_tools=None):
|
|
240
256
|
if include_tools and tool not in include_tools:
|
241
257
|
mcp.remove_tool(tool)
|
242
258
|
return mcp
|
259
|
+
|
243
260
|
return asyncio.run(_filter_tools(mcp, include_tools, exclude_tools))
|
244
261
|
|
245
262
|
|
246
|
-
|
247
263
|
def set_env(log_file, forward, transport, host, port):
|
248
264
|
if log_file is not None:
|
249
|
-
os.environ[
|
265
|
+
os.environ["SCMCP_LOG_FILE"] = log_file
|
250
266
|
if forward is not None:
|
251
|
-
os.environ[
|
252
|
-
os.environ[
|
253
|
-
os.environ[
|
254
|
-
os.environ[
|
255
|
-
|
267
|
+
os.environ["SCMCP_FORWARD"] = forward
|
268
|
+
os.environ["SCMCP_TRANSPORT"] = transport
|
269
|
+
os.environ["SCMCP_HOST"] = host
|
270
|
+
os.environ["SCMCP_PORT"] = str(port)
|
256
271
|
|
257
272
|
|
258
273
|
def setup_mcp(mcp, sub_mcp_dic, modules=None):
|
259
274
|
import asyncio
|
275
|
+
|
260
276
|
if modules is None or modules == "all":
|
261
277
|
modules = sub_mcp_dic.keys()
|
262
278
|
for module in modules:
|
263
279
|
asyncio.run(mcp.import_server(module, sub_mcp_dic[module]))
|
264
280
|
return mcp
|
265
281
|
|
266
|
-
|
282
|
+
|
283
|
+
def _update_args(mcp, func, args_dic: dict):
|
267
284
|
for args, property_dic in args_dic.items():
|
268
285
|
for pk, v in property_dic.items():
|
269
|
-
mcp._tool_manager._tools[func].parameters["properties"][
|
270
|
-
|
286
|
+
mcp._tool_manager._tools[func].parameters["properties"][
|
287
|
+
"request"
|
288
|
+
].setdefault(pk, {})
|
289
|
+
mcp._tool_manager._tools[func].parameters["properties"]["request"][pk][
|
290
|
+
args
|
291
|
+
] = v
|
271
292
|
|
272
293
|
|
273
|
-
def update_mcp_args(mcp, tool_args
|
274
|
-
tools = mcp._tool_manager._tools.keys()
|
275
|
-
for tool in tool_args:
|
294
|
+
def update_mcp_args(mcp, tool_args: dict):
|
295
|
+
# tools = mcp._tool_manager._tools.keys()
|
296
|
+
for tool in tool_args:
|
276
297
|
_update_args(mcp, tool, tool_args[tool])
|
277
298
|
|
278
299
|
|
279
300
|
def check_adata(adata, adinfo, ads):
|
280
301
|
sampleid = adinfo.sampleid or ads.active_id
|
281
302
|
if sampleid != adata.uns["scmcp_sampleid"]:
|
282
|
-
raise ValueError(
|
303
|
+
raise ValueError(
|
304
|
+
f"sampleid mismatch: {sampleid} != {adata.uns['scmcp_sampleid']}"
|
305
|
+
)
|
306
|
+
|
307
|
+
|
308
|
+
def get_nbm():
|
309
|
+
ctx = get_context()
|
310
|
+
nbm = ctx.request_context.lifespan_context
|
311
|
+
return nbm
|
312
|
+
|
313
|
+
|
314
|
+
def parse_args(
|
315
|
+
kwargs: BaseModel | dict,
|
316
|
+
positional_args: list[str] | str | None = None,
|
317
|
+
func_args: list[str] | str | None = None,
|
318
|
+
) -> str:
|
319
|
+
if isinstance(kwargs, BaseModel):
|
320
|
+
kwargs = kwargs.model_dump()
|
321
|
+
elif isinstance(kwargs, dict):
|
322
|
+
kwargs = kwargs
|
323
|
+
else:
|
324
|
+
raise ValueError(f"Invalid type: {type(kwargs)}")
|
325
|
+
|
326
|
+
if func_args is not None:
|
327
|
+
if isinstance(func_args, str):
|
328
|
+
func_args = [func_args]
|
329
|
+
else:
|
330
|
+
func_args = []
|
331
|
+
kwargs_str_ls = []
|
332
|
+
if positional_args is not None:
|
333
|
+
if isinstance(positional_args, str):
|
334
|
+
kwargs_str_ls.append(kwargs.pop(positional_args))
|
335
|
+
elif isinstance(positional_args, list):
|
336
|
+
for arg in positional_args:
|
337
|
+
kwargs_str_ls.append(kwargs.pop(arg))
|
338
|
+
|
339
|
+
extra_kwargs = kwargs.pop("kwargs", {})
|
340
|
+
for k, v in kwargs.items():
|
341
|
+
if k in func_args:
|
342
|
+
kwargs_str_ls.append(f"{k}={v}")
|
343
|
+
continue
|
344
|
+
if isinstance(v, (list, tuple, dict, int, float, bool)):
|
345
|
+
kwargs_str_ls.append(f"{k}={v}")
|
346
|
+
elif isinstance(v, str):
|
347
|
+
kwargs_str_ls.append(f"{k}='{v}'")
|
348
|
+
|
349
|
+
if extra_kwargs:
|
350
|
+
kwargs_str_ls.append(f"**{extra_kwargs}")
|
351
|
+
|
352
|
+
kwargs_str = ", ".join(kwargs_str_ls)
|
353
|
+
return kwargs_str
|