tooluniverse 1.0.9.1__py3-none-any.whl → 1.0.11__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.
Potentially problematic release.
This version of tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +57 -1
- tooluniverse/admetai_tool.py +1 -1
- tooluniverse/agentic_tool.py +65 -17
- tooluniverse/base_tool.py +19 -8
- tooluniverse/blast_tool.py +132 -0
- tooluniverse/boltz_tool.py +3 -3
- tooluniverse/cache/result_cache_manager.py +167 -12
- tooluniverse/cbioportal_tool.py +42 -0
- tooluniverse/clinvar_tool.py +268 -74
- tooluniverse/compose_scripts/drug_safety_analyzer.py +1 -1
- tooluniverse/compose_scripts/multi_agent_literature_search.py +1 -1
- tooluniverse/compose_scripts/output_summarizer.py +4 -4
- tooluniverse/compose_scripts/tool_discover.py +1941 -443
- tooluniverse/compose_scripts/tool_graph_composer.py +1 -1
- tooluniverse/compose_scripts/tool_metadata_generator.py +1 -1
- tooluniverse/compose_tool.py +9 -9
- tooluniverse/core_tool.py +2 -2
- tooluniverse/ctg_tool.py +4 -4
- tooluniverse/custom_tool.py +1 -1
- tooluniverse/data/agentic_tools.json +0 -370
- tooluniverse/data/alphafold_tools.json +6 -6
- tooluniverse/data/blast_tools.json +112 -0
- tooluniverse/data/cbioportal_tools.json +87 -0
- tooluniverse/data/clinvar_tools.json +235 -0
- tooluniverse/data/compose_tools.json +0 -89
- tooluniverse/data/dbsnp_tools.json +275 -0
- tooluniverse/data/emdb_tools.json +61 -0
- tooluniverse/data/ensembl_tools.json +259 -0
- tooluniverse/data/file_download_tools.json +275 -0
- tooluniverse/data/geo_tools.json +200 -48
- tooluniverse/data/gnomad_tools.json +109 -0
- tooluniverse/data/gtopdb_tools.json +68 -0
- tooluniverse/data/gwas_tools.json +32 -0
- tooluniverse/data/interpro_tools.json +199 -0
- tooluniverse/data/jaspar_tools.json +70 -0
- tooluniverse/data/kegg_tools.json +356 -0
- tooluniverse/data/mpd_tools.json +87 -0
- tooluniverse/data/ols_tools.json +314 -0
- tooluniverse/data/package_discovery_tools.json +64 -0
- tooluniverse/data/packages/categorized_tools.txt +0 -1
- tooluniverse/data/packages/machine_learning_tools.json +0 -47
- tooluniverse/data/paleobiology_tools.json +91 -0
- tooluniverse/data/pride_tools.json +62 -0
- tooluniverse/data/pypi_package_inspector_tools.json +158 -0
- tooluniverse/data/python_executor_tools.json +341 -0
- tooluniverse/data/regulomedb_tools.json +50 -0
- tooluniverse/data/remap_tools.json +89 -0
- tooluniverse/data/screen_tools.json +89 -0
- tooluniverse/data/tool_discovery_agents.json +428 -0
- tooluniverse/data/tool_discovery_agents.json.backup +1343 -0
- tooluniverse/data/uniprot_tools.json +77 -0
- tooluniverse/data/web_search_tools.json +250 -0
- tooluniverse/data/worms_tools.json +55 -0
- tooluniverse/dataset_tool.py +2 -2
- tooluniverse/dbsnp_tool.py +196 -58
- tooluniverse/default_config.py +36 -3
- tooluniverse/emdb_tool.py +30 -0
- tooluniverse/enrichr_tool.py +14 -14
- tooluniverse/ensembl_tool.py +140 -47
- tooluniverse/execute_function.py +594 -29
- tooluniverse/extended_hooks.py +4 -4
- tooluniverse/file_download_tool.py +269 -0
- tooluniverse/gene_ontology_tool.py +1 -1
- tooluniverse/generate_tools.py +3 -3
- tooluniverse/geo_tool.py +81 -28
- tooluniverse/gnomad_tool.py +100 -52
- tooluniverse/gtopdb_tool.py +41 -0
- tooluniverse/humanbase_tool.py +10 -10
- tooluniverse/interpro_tool.py +72 -0
- tooluniverse/jaspar_tool.py +30 -0
- tooluniverse/kegg_tool.py +230 -0
- tooluniverse/logging_config.py +2 -2
- tooluniverse/mcp_client_tool.py +57 -129
- tooluniverse/mcp_integration.py +52 -49
- tooluniverse/mcp_tool_registry.py +147 -528
- tooluniverse/mpd_tool.py +42 -0
- tooluniverse/ncbi_eutils_tool.py +96 -0
- tooluniverse/ols_tool.py +435 -0
- tooluniverse/openalex_tool.py +8 -8
- tooluniverse/openfda_tool.py +2 -2
- tooluniverse/output_hook.py +15 -15
- tooluniverse/package_discovery_tool.py +217 -0
- tooluniverse/package_tool.py +1 -1
- tooluniverse/paleobiology_tool.py +30 -0
- tooluniverse/pmc_tool.py +2 -2
- tooluniverse/pride_tool.py +30 -0
- tooluniverse/pypi_package_inspector_tool.py +593 -0
- tooluniverse/python_executor_tool.py +711 -0
- tooluniverse/regulomedb_tool.py +30 -0
- tooluniverse/remap_tool.py +44 -0
- tooluniverse/remote/boltz/boltz_mcp_server.py +1 -1
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +3 -3
- tooluniverse/remote/immune_compass/compass_tool.py +3 -3
- tooluniverse/remote/pinnacle/pinnacle_tool.py +2 -2
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +3 -3
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +3 -3
- tooluniverse/remote_tool.py +4 -4
- tooluniverse/screen_tool.py +44 -0
- tooluniverse/scripts/filter_tool_files.py +2 -2
- tooluniverse/smcp.py +93 -12
- tooluniverse/smcp_server.py +100 -21
- tooluniverse/space/__init__.py +46 -0
- tooluniverse/space/loader.py +133 -0
- tooluniverse/space/validator.py +353 -0
- tooluniverse/tool_finder_embedding.py +5 -3
- tooluniverse/tool_finder_keyword.py +12 -10
- tooluniverse/tool_finder_llm.py +12 -8
- tooluniverse/tools/{UCSC_get_genes_by_region.py → BLAST_nucleotide_search.py} +22 -26
- tooluniverse/tools/BLAST_protein_search.py +63 -0
- tooluniverse/tools/ClinVar_search_variants.py +26 -15
- tooluniverse/tools/CodeQualityAnalyzer.py +3 -3
- tooluniverse/tools/EMDB_get_structure.py +46 -0
- tooluniverse/tools/GtoPdb_get_targets.py +52 -0
- tooluniverse/tools/InterPro_get_domain_details.py +46 -0
- tooluniverse/tools/InterPro_get_protein_domains.py +49 -0
- tooluniverse/tools/InterPro_search_domains.py +52 -0
- tooluniverse/tools/JASPAR_get_transcription_factors.py +52 -0
- tooluniverse/tools/MPD_get_phenotype_data.py +59 -0
- tooluniverse/tools/PRIDE_search_proteomics.py +52 -0
- tooluniverse/tools/PackageAnalyzer.py +55 -0
- tooluniverse/tools/Paleobiology_get_fossils.py +52 -0
- tooluniverse/tools/PyPIPackageInspector.py +59 -0
- tooluniverse/tools/ReMap_get_transcription_factor_binding.py +59 -0
- tooluniverse/tools/ReferenceInfoAnalyzer.py +55 -0
- tooluniverse/tools/RegulomeDB_query_variant.py +46 -0
- tooluniverse/tools/SCREEN_get_regulatory_elements.py +59 -0
- tooluniverse/tools/{ArgumentDescriptionOptimizer.py → TestResultsAnalyzer.py} +13 -13
- tooluniverse/tools/ToolDiscover.py +11 -11
- tooluniverse/tools/UniProt_id_mapping.py +63 -0
- tooluniverse/tools/UniProt_search.py +63 -0
- tooluniverse/tools/UnifiedToolGenerator.py +59 -0
- tooluniverse/tools/WoRMS_search_species.py +49 -0
- tooluniverse/tools/XMLToolOptimizer.py +55 -0
- tooluniverse/tools/__init__.py +119 -29
- tooluniverse/tools/_shared_client.py +3 -3
- tooluniverse/tools/alphafold_get_annotations.py +3 -3
- tooluniverse/tools/alphafold_get_prediction.py +3 -3
- tooluniverse/tools/alphafold_get_summary.py +3 -3
- tooluniverse/tools/cBioPortal_get_cancer_studies.py +46 -0
- tooluniverse/tools/cBioPortal_get_mutations.py +52 -0
- tooluniverse/tools/{gnomAD_query_variant.py → clinvar_get_clinical_significance.py} +8 -11
- tooluniverse/tools/clinvar_get_variant_details.py +49 -0
- tooluniverse/tools/dbSNP_get_variant_by_rsid.py +7 -7
- tooluniverse/tools/dbsnp_get_frequencies.py +46 -0
- tooluniverse/tools/dbsnp_search_by_gene.py +52 -0
- tooluniverse/tools/download_binary_file.py +66 -0
- tooluniverse/tools/download_file.py +71 -0
- tooluniverse/tools/download_text_content.py +55 -0
- tooluniverse/tools/dynamic_package_discovery.py +59 -0
- tooluniverse/tools/ensembl_get_sequence.py +52 -0
- tooluniverse/tools/{Ensembl_lookup_gene_by_symbol.py → ensembl_get_variants.py} +11 -11
- tooluniverse/tools/ensembl_lookup_gene.py +46 -0
- tooluniverse/tools/geo_get_dataset_info.py +46 -0
- tooluniverse/tools/geo_get_sample_info.py +46 -0
- tooluniverse/tools/geo_search_datasets.py +67 -0
- tooluniverse/tools/gnomad_get_gene_constraints.py +49 -0
- tooluniverse/tools/kegg_find_genes.py +52 -0
- tooluniverse/tools/kegg_get_gene_info.py +46 -0
- tooluniverse/tools/kegg_get_pathway_info.py +46 -0
- tooluniverse/tools/kegg_list_organisms.py +44 -0
- tooluniverse/tools/kegg_search_pathway.py +46 -0
- tooluniverse/tools/ols_find_similar_terms.py +63 -0
- tooluniverse/tools/{get_hyperopt_info.py → ols_get_ontology_info.py} +13 -10
- tooluniverse/tools/ols_get_term_ancestors.py +67 -0
- tooluniverse/tools/ols_get_term_children.py +67 -0
- tooluniverse/tools/{TestCaseGenerator.py → ols_get_term_info.py} +12 -9
- tooluniverse/tools/{CodeOptimizer.py → ols_search_ontologies.py} +22 -14
- tooluniverse/tools/ols_search_terms.py +71 -0
- tooluniverse/tools/python_code_executor.py +79 -0
- tooluniverse/tools/python_script_runner.py +79 -0
- tooluniverse/tools/web_api_documentation_search.py +63 -0
- tooluniverse/tools/web_search.py +71 -0
- tooluniverse/uniprot_tool.py +219 -16
- tooluniverse/url_tool.py +19 -1
- tooluniverse/uspto_tool.py +1 -1
- tooluniverse/utils.py +12 -12
- tooluniverse/web_search_tool.py +229 -0
- tooluniverse/worms_tool.py +64 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/METADATA +8 -3
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/RECORD +184 -92
- tooluniverse/data/genomics_tools.json +0 -174
- tooluniverse/tools/ToolDescriptionOptimizer.py +0 -67
- tooluniverse/tools/ToolImplementationGenerator.py +0 -67
- tooluniverse/tools/ToolOptimizer.py +0 -59
- tooluniverse/tools/ToolSpecificationGenerator.py +0 -67
- tooluniverse/tools/ToolSpecificationOptimizer.py +0 -63
- tooluniverse/ucsc_tool.py +0 -60
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/WHEEL +0 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/entry_points.txt +0 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/top_level.txt +0 -0
tooluniverse/humanbase_tool.py
CHANGED
|
@@ -35,19 +35,19 @@ class HumanBaseTool(BaseTool):
|
|
|
35
35
|
"""
|
|
36
36
|
Retrieve the official gene symbol (same as EnrichrTool method)
|
|
37
37
|
|
|
38
|
-
Parameters
|
|
38
|
+
Parameters
|
|
39
39
|
gene_name (str): The gene name or synonym to query.
|
|
40
40
|
|
|
41
|
-
Returns
|
|
41
|
+
Returns
|
|
42
42
|
str: The official gene symbol.
|
|
43
43
|
"""
|
|
44
44
|
"""
|
|
45
45
|
Retrieve the official gene symbol for a given gene name or synonym using the MyGene.info API.
|
|
46
46
|
|
|
47
|
-
Parameters
|
|
47
|
+
Parameters
|
|
48
48
|
gene_name (str): The gene name or synonym to query.
|
|
49
49
|
|
|
50
|
-
Returns
|
|
50
|
+
Returns
|
|
51
51
|
str: The official gene symbol if found; otherwise, raises an Exception.
|
|
52
52
|
"""
|
|
53
53
|
# URL-encode the gene_name to handle special characters
|
|
@@ -96,10 +96,10 @@ class HumanBaseTool(BaseTool):
|
|
|
96
96
|
"""
|
|
97
97
|
Convert gene names to Entrez IDs using NCBI Entrez API.
|
|
98
98
|
|
|
99
|
-
Parameters
|
|
99
|
+
Parameters
|
|
100
100
|
gene_names (list): List of gene names to convert.
|
|
101
101
|
|
|
102
|
-
Returns
|
|
102
|
+
Returns
|
|
103
103
|
list: List of Entrez IDs corresponding to the gene names.
|
|
104
104
|
"""
|
|
105
105
|
# Define the NCBI Entrez API URL for querying gene information
|
|
@@ -149,13 +149,13 @@ class HumanBaseTool(BaseTool):
|
|
|
149
149
|
"""
|
|
150
150
|
Retrieve protein-protein interactions and biological processes from HumanBase.
|
|
151
151
|
|
|
152
|
-
Parameters
|
|
152
|
+
Parameters
|
|
153
153
|
genes (list): List of gene names to analyze.
|
|
154
154
|
tissue (str): Tissue type for tissue-specific interactions.
|
|
155
155
|
max_node (int): Maximum number of nodes to retrieve.
|
|
156
156
|
interaction (str): Specific interaction type to filter by.
|
|
157
157
|
|
|
158
|
-
Returns
|
|
158
|
+
Returns
|
|
159
159
|
tuple: (NetworkX Graph of interactions, list of biological processes)
|
|
160
160
|
"""
|
|
161
161
|
genes = self.get_entrez_ids(genes)
|
|
@@ -243,13 +243,13 @@ class HumanBaseTool(BaseTool):
|
|
|
243
243
|
"""
|
|
244
244
|
Convert NetworkX graph and biological processes to string representation.
|
|
245
245
|
|
|
246
|
-
Parameters
|
|
246
|
+
Parameters
|
|
247
247
|
graph (networkx.Graph): The network graph.
|
|
248
248
|
bp_collection (list): List of biological processes.
|
|
249
249
|
original_genes (list): Original gene list provided by user.
|
|
250
250
|
tissue (str): Tissue type used for analysis.
|
|
251
251
|
|
|
252
|
-
Returns
|
|
252
|
+
Returns
|
|
253
253
|
str: Comprehensive string representation of the network data.
|
|
254
254
|
"""
|
|
255
255
|
output = []
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
from .base_tool import BaseTool
|
|
4
|
+
from .tool_registry import register_tool
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register_tool("InterProRESTTool")
|
|
8
|
+
class InterProRESTTool(BaseTool):
|
|
9
|
+
def __init__(self, tool_config: Dict):
|
|
10
|
+
super().__init__(tool_config)
|
|
11
|
+
self.base_url = "https://www.ebi.ac.uk/interpro/api"
|
|
12
|
+
self.session = requests.Session()
|
|
13
|
+
self.session.headers.update(
|
|
14
|
+
{"Accept": "application/json", "User-Agent": "ToolUniverse/1.0"}
|
|
15
|
+
)
|
|
16
|
+
self.timeout = 30
|
|
17
|
+
|
|
18
|
+
def _build_url(self, args: Dict[str, Any]) -> str:
|
|
19
|
+
"""Build URL from endpoint template and arguments"""
|
|
20
|
+
url = self.tool_config["fields"]["endpoint"]
|
|
21
|
+
for k, v in args.items():
|
|
22
|
+
url = url.replace(f"{{{k}}}", str(v))
|
|
23
|
+
return url
|
|
24
|
+
|
|
25
|
+
def _extract_data(self, data: Dict, extract_path: str = None) -> Any:
|
|
26
|
+
"""Extract specific data from API response"""
|
|
27
|
+
if not extract_path:
|
|
28
|
+
return data
|
|
29
|
+
|
|
30
|
+
# Handle specific InterPro extraction patterns
|
|
31
|
+
if extract_path == "results":
|
|
32
|
+
return data.get("results", [])
|
|
33
|
+
elif extract_path == "count":
|
|
34
|
+
return data.get("count", 0)
|
|
35
|
+
elif extract_path == "metadata":
|
|
36
|
+
return data.get("metadata", {})
|
|
37
|
+
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
41
|
+
"""Execute the InterPro API call"""
|
|
42
|
+
try:
|
|
43
|
+
# Build URL from endpoint template
|
|
44
|
+
url = self._build_url(arguments)
|
|
45
|
+
|
|
46
|
+
# Make API request
|
|
47
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
48
|
+
response.raise_for_status()
|
|
49
|
+
|
|
50
|
+
# Parse JSON response
|
|
51
|
+
data = response.json()
|
|
52
|
+
|
|
53
|
+
# Extract data if specified
|
|
54
|
+
extract_path = self.tool_config["fields"].get("extract_path")
|
|
55
|
+
if extract_path:
|
|
56
|
+
result = self._extract_data(data, extract_path)
|
|
57
|
+
else:
|
|
58
|
+
result = data
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"status": "success",
|
|
62
|
+
"data": result,
|
|
63
|
+
"url": url,
|
|
64
|
+
"count": len(result) if isinstance(result, list) else 1,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return {
|
|
69
|
+
"status": "error",
|
|
70
|
+
"error": f"InterPro API error: {str(e)}",
|
|
71
|
+
"url": url,
|
|
72
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
from .base_tool import BaseTool
|
|
4
|
+
from .tool_registry import register_tool
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register_tool("JASPARRESTTool")
|
|
8
|
+
class JASPARRESTTool(BaseTool):
|
|
9
|
+
def __init__(self, tool_config: Dict):
|
|
10
|
+
super().__init__(tool_config)
|
|
11
|
+
self.base_url = "https://jaspar.elixir.no/api/v1"
|
|
12
|
+
self.session = requests.Session()
|
|
13
|
+
self.session.headers.update({"Accept": "application/json"})
|
|
14
|
+
self.timeout = 30
|
|
15
|
+
|
|
16
|
+
def _build_url(self, args: Dict[str, Any]) -> str:
|
|
17
|
+
url = self.tool_config["fields"]["endpoint"]
|
|
18
|
+
for k, v in args.items():
|
|
19
|
+
url = url.replace(f"{{{k}}}", str(v))
|
|
20
|
+
return url
|
|
21
|
+
|
|
22
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
23
|
+
try:
|
|
24
|
+
url = self._build_url(arguments)
|
|
25
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
data = response.json()
|
|
28
|
+
return {"status": "success", "data": data, "url": url}
|
|
29
|
+
except Exception as e:
|
|
30
|
+
return {"status": "error", "error": f"JASPAR API error: {str(e)}"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KEGG Database REST API Tool
|
|
3
|
+
|
|
4
|
+
This tool provides access to the KEGG (Kyoto Encyclopedia of Genes and Genomes)
|
|
5
|
+
database for pathway analysis, gene information, and organism data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
from .base_tool import BaseTool
|
|
11
|
+
from .tool_registry import register_tool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KEGGRESTTool(BaseTool):
|
|
15
|
+
"""Base class for KEGG REST API tools."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, tool_config):
|
|
18
|
+
super().__init__(tool_config)
|
|
19
|
+
self.base_url = "https://rest.kegg.jp"
|
|
20
|
+
self.session = requests.Session()
|
|
21
|
+
self.session.headers.update(
|
|
22
|
+
{"Accept": "text/plain, application/json", "User-Agent": "ToolUniverse/1.0"}
|
|
23
|
+
)
|
|
24
|
+
self.timeout = 30
|
|
25
|
+
|
|
26
|
+
def _make_request(
|
|
27
|
+
self, endpoint: str, params: Optional[Dict] = None
|
|
28
|
+
) -> Dict[str, Any]:
|
|
29
|
+
"""Make a request to the KEGG API."""
|
|
30
|
+
url = f"{self.base_url}{endpoint}"
|
|
31
|
+
try:
|
|
32
|
+
response = self.session.get(url, params=params, timeout=self.timeout)
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
|
|
35
|
+
# KEGG API returns text/plain by default, parse as text
|
|
36
|
+
content = response.text.strip()
|
|
37
|
+
|
|
38
|
+
# Try to parse as structured data if possible
|
|
39
|
+
if content.startswith("{") or content.startswith("["):
|
|
40
|
+
try:
|
|
41
|
+
return {"status": "success", "data": response.json()}
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
# Return as text data
|
|
46
|
+
return {
|
|
47
|
+
"status": "success",
|
|
48
|
+
"data": content,
|
|
49
|
+
"url": url,
|
|
50
|
+
"content_type": response.headers.get("content-type", "text/plain"),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
except requests.exceptions.RequestException as e:
|
|
54
|
+
return {
|
|
55
|
+
"status": "error",
|
|
56
|
+
"error": f"KEGG API request failed: {str(e)}",
|
|
57
|
+
"url": url,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
61
|
+
"""Execute the tool with given arguments."""
|
|
62
|
+
return self._make_request(self.endpoint, arguments)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@register_tool("KEGGSearchPathway")
|
|
66
|
+
class KEGGSearchPathway(KEGGRESTTool):
|
|
67
|
+
"""Search KEGG pathways by keyword."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, tool_config):
|
|
70
|
+
super().__init__(tool_config)
|
|
71
|
+
self.endpoint = "/find/pathway"
|
|
72
|
+
|
|
73
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
74
|
+
"""Search pathways with keyword."""
|
|
75
|
+
keyword = arguments.get("keyword", "")
|
|
76
|
+
if not keyword:
|
|
77
|
+
return {"status": "error", "error": "keyword is required"}
|
|
78
|
+
|
|
79
|
+
# KEGG API requires the search term in the URL path
|
|
80
|
+
endpoint = f"{self.endpoint}/{keyword}"
|
|
81
|
+
result = self._make_request(endpoint)
|
|
82
|
+
|
|
83
|
+
# Parse pathway results
|
|
84
|
+
if result.get("status") == "success" and isinstance(result.get("data"), str):
|
|
85
|
+
lines = result["data"].split("\n")
|
|
86
|
+
pathways = []
|
|
87
|
+
for line in lines:
|
|
88
|
+
if "\t" in line:
|
|
89
|
+
parts = line.split("\t", 1)
|
|
90
|
+
if len(parts) == 2:
|
|
91
|
+
pathways.append(
|
|
92
|
+
{"pathway_id": parts[0], "description": parts[1]}
|
|
93
|
+
)
|
|
94
|
+
result["data"] = pathways
|
|
95
|
+
result["count"] = len(pathways)
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@register_tool("KEGGGetPathwayInfo")
|
|
101
|
+
class KEGGGetPathwayInfo(KEGGRESTTool):
|
|
102
|
+
"""Get detailed pathway information by pathway ID."""
|
|
103
|
+
|
|
104
|
+
def __init__(self, tool_config):
|
|
105
|
+
super().__init__(tool_config)
|
|
106
|
+
self.endpoint = "/get"
|
|
107
|
+
|
|
108
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
109
|
+
"""Get pathway information."""
|
|
110
|
+
pathway_id = arguments.get("pathway_id", "")
|
|
111
|
+
if not pathway_id:
|
|
112
|
+
return {"status": "error", "error": "pathway_id is required"}
|
|
113
|
+
|
|
114
|
+
# Add pathway prefix if not present
|
|
115
|
+
if not pathway_id.startswith("path:"):
|
|
116
|
+
pathway_id = f"path:{pathway_id}"
|
|
117
|
+
|
|
118
|
+
# KEGG API requires the ID in the URL path
|
|
119
|
+
endpoint = f"{self.endpoint}/{pathway_id}"
|
|
120
|
+
result = self._make_request(endpoint)
|
|
121
|
+
|
|
122
|
+
# Parse pathway data
|
|
123
|
+
if result.get("status") == "success" and isinstance(result.get("data"), str):
|
|
124
|
+
lines = result["data"].split("\n")
|
|
125
|
+
pathway_info = {
|
|
126
|
+
"pathway_id": pathway_id,
|
|
127
|
+
"raw_data": result["data"],
|
|
128
|
+
"lines": len(lines),
|
|
129
|
+
}
|
|
130
|
+
result["data"] = pathway_info
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@register_tool("KEGGFindGenes")
|
|
136
|
+
class KEGGFindGenes(KEGGRESTTool):
|
|
137
|
+
"""Find genes by keyword in KEGG database."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, tool_config):
|
|
140
|
+
super().__init__(tool_config)
|
|
141
|
+
self.endpoint = "/find/genes"
|
|
142
|
+
|
|
143
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
144
|
+
"""Find genes with keyword."""
|
|
145
|
+
keyword = arguments.get("keyword", "")
|
|
146
|
+
if not keyword:
|
|
147
|
+
return {"status": "error", "error": "keyword is required"}
|
|
148
|
+
|
|
149
|
+
# KEGG API requires the search term in the URL path
|
|
150
|
+
# For gene search, we don't need organism prefix in the URL
|
|
151
|
+
endpoint = f"{self.endpoint}/{keyword}"
|
|
152
|
+
result = self._make_request(endpoint)
|
|
153
|
+
|
|
154
|
+
# Parse gene results
|
|
155
|
+
if result.get("status") == "success" and isinstance(result.get("data"), str):
|
|
156
|
+
lines = result["data"].split("\n")
|
|
157
|
+
genes = []
|
|
158
|
+
for line in lines:
|
|
159
|
+
if "\t" in line:
|
|
160
|
+
parts = line.split("\t", 1)
|
|
161
|
+
if len(parts) == 2:
|
|
162
|
+
genes.append({"gene_id": parts[0], "description": parts[1]})
|
|
163
|
+
result["data"] = genes
|
|
164
|
+
result["count"] = len(genes)
|
|
165
|
+
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@register_tool("KEGGGetGeneInfo")
|
|
170
|
+
class KEGGGetGeneInfo(KEGGRESTTool):
|
|
171
|
+
"""Get detailed gene information by gene ID."""
|
|
172
|
+
|
|
173
|
+
def __init__(self, tool_config):
|
|
174
|
+
super().__init__(tool_config)
|
|
175
|
+
self.endpoint = "/get"
|
|
176
|
+
|
|
177
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
178
|
+
"""Get gene information."""
|
|
179
|
+
gene_id = arguments.get("gene_id", "")
|
|
180
|
+
if not gene_id:
|
|
181
|
+
return {"status": "error", "error": "gene_id is required"}
|
|
182
|
+
|
|
183
|
+
# KEGG API requires the ID in the URL path
|
|
184
|
+
endpoint = f"{self.endpoint}/{gene_id}"
|
|
185
|
+
result = self._make_request(endpoint)
|
|
186
|
+
|
|
187
|
+
# Parse gene data
|
|
188
|
+
if result.get("status") == "success" and isinstance(result.get("data"), str):
|
|
189
|
+
lines = result["data"].split("\n")
|
|
190
|
+
gene_info = {
|
|
191
|
+
"gene_id": gene_id,
|
|
192
|
+
"raw_data": result["data"],
|
|
193
|
+
"lines": len(lines),
|
|
194
|
+
}
|
|
195
|
+
result["data"] = gene_info
|
|
196
|
+
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@register_tool("KEGGListOrganisms")
|
|
201
|
+
class KEGGListOrganisms(KEGGRESTTool):
|
|
202
|
+
"""List available organisms in KEGG database."""
|
|
203
|
+
|
|
204
|
+
def __init__(self, tool_config):
|
|
205
|
+
super().__init__(tool_config)
|
|
206
|
+
self.endpoint = "/list/organism"
|
|
207
|
+
|
|
208
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
209
|
+
"""List organisms."""
|
|
210
|
+
result = self._make_request(self.endpoint)
|
|
211
|
+
|
|
212
|
+
# Parse organism list
|
|
213
|
+
if result.get("status") == "success" and isinstance(result.get("data"), str):
|
|
214
|
+
lines = result["data"].split("\n")
|
|
215
|
+
organisms = []
|
|
216
|
+
for line in lines:
|
|
217
|
+
if "\t" in line:
|
|
218
|
+
parts = line.split("\t")
|
|
219
|
+
if len(parts) >= 3:
|
|
220
|
+
organisms.append(
|
|
221
|
+
{
|
|
222
|
+
"organism_code": parts[0],
|
|
223
|
+
"organism_name": parts[1],
|
|
224
|
+
"description": parts[2] if len(parts) > 2 else "",
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
result["data"] = organisms
|
|
228
|
+
result["count"] = len(organisms)
|
|
229
|
+
|
|
230
|
+
return result
|
tooluniverse/logging_config.py
CHANGED
|
@@ -232,7 +232,7 @@ def get_logger(name: Optional[str] = None) -> logging.Logger:
|
|
|
232
232
|
Args:
|
|
233
233
|
name (str, optional): Logger name (usually __name__)
|
|
234
234
|
|
|
235
|
-
Returns
|
|
235
|
+
Returns
|
|
236
236
|
logging.Logger: Logger instance
|
|
237
237
|
"""
|
|
238
238
|
return _logger_manager.get_logger(name)
|
|
@@ -291,7 +291,7 @@ def get_hook_logger(name: str = "HookManager") -> logging.Logger:
|
|
|
291
291
|
Args:
|
|
292
292
|
name (str): Name of the logger. Defaults to 'HookManager'
|
|
293
293
|
|
|
294
|
-
Returns
|
|
294
|
+
Returns
|
|
295
295
|
logging.Logger: Configured logger for hook operations
|
|
296
296
|
"""
|
|
297
297
|
return get_logger(name)
|
tooluniverse/mcp_client_tool.py
CHANGED
|
@@ -8,11 +8,11 @@ supporting all MCP functionality including tools, resources, and prompts.
|
|
|
8
8
|
import json
|
|
9
9
|
import asyncio
|
|
10
10
|
import websockets
|
|
11
|
-
import aiohttp
|
|
12
|
-
import uuid
|
|
13
11
|
from typing import Dict, List, Any, Optional
|
|
14
12
|
from urllib.parse import urljoin
|
|
15
13
|
import warnings
|
|
14
|
+
from mcp.client.session import ClientSession
|
|
15
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
16
16
|
from .base_tool import BaseTool
|
|
17
17
|
from .tool_registry import register_tool
|
|
18
18
|
import os
|
|
@@ -35,8 +35,6 @@ class BaseMCPClient:
|
|
|
35
35
|
self.transport = normalized_transport
|
|
36
36
|
self.timeout = timeout
|
|
37
37
|
self.session = None
|
|
38
|
-
self.mcp_session_id = None
|
|
39
|
-
self._initialized = False
|
|
40
38
|
|
|
41
39
|
# Validate transport (accept 'stdio' via normalization above)
|
|
42
40
|
supported_transports = ["http", "websocket"]
|
|
@@ -44,24 +42,9 @@ class BaseMCPClient:
|
|
|
44
42
|
# Keep message concise to satisfy line length rules
|
|
45
43
|
raise ValueError("Invalid transport")
|
|
46
44
|
|
|
47
|
-
async def _ensure_session(self):
|
|
48
|
-
"""Ensure HTTP session is available for HTTP transport"""
|
|
49
|
-
if self.transport == "http" and self.session is None:
|
|
50
|
-
connector = aiohttp.TCPConnector()
|
|
51
|
-
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
52
|
-
self.session = aiohttp.ClientSession(connector=connector, timeout=timeout)
|
|
53
|
-
|
|
54
45
|
async def _close_session(self):
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
await self.session.close()
|
|
59
|
-
except Exception:
|
|
60
|
-
pass # Ignore errors during cleanup
|
|
61
|
-
finally:
|
|
62
|
-
self.session = None
|
|
63
|
-
self.mcp_session_id = None
|
|
64
|
-
self._initialized = False
|
|
46
|
+
"""Placeholder for compatibility; HTTP client calls are scoped per request."""
|
|
47
|
+
return
|
|
65
48
|
|
|
66
49
|
def _get_mcp_endpoint(self, path: str) -> str:
|
|
67
50
|
"""Get the full MCP endpoint URL"""
|
|
@@ -72,115 +55,55 @@ class BaseMCPClient:
|
|
|
72
55
|
return urljoin(base_url + "/", path)
|
|
73
56
|
return self.server_url
|
|
74
57
|
|
|
75
|
-
async def _initialize_mcp_session(self):
|
|
76
|
-
"""Initialize MCP session if needed (for compatibility with different MCP servers)"""
|
|
77
|
-
if self._initialized:
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
await self._ensure_session()
|
|
81
|
-
|
|
82
|
-
# Try to get session ID from server
|
|
83
|
-
try:
|
|
84
|
-
url = f"{self.server_url.rstrip('/')}/mcp"
|
|
85
|
-
test_payload = {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
|
|
86
|
-
|
|
87
|
-
headers = {
|
|
88
|
-
"Content-Type": "application/json",
|
|
89
|
-
"Accept": "application/json, text/event-stream",
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async with self.session.post(
|
|
93
|
-
url, json=test_payload, headers=headers
|
|
94
|
-
) as response:
|
|
95
|
-
session_id = response.headers.get("mcp-session-id")
|
|
96
|
-
if session_id:
|
|
97
|
-
self.mcp_session_id = session_id
|
|
98
|
-
|
|
99
|
-
if response.status in [200, 400, 406, 500]:
|
|
100
|
-
self._initialized = True
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
except Exception:
|
|
104
|
-
pass
|
|
105
|
-
|
|
106
|
-
# Fallback: generate session ID
|
|
107
|
-
if not self.mcp_session_id:
|
|
108
|
-
self.mcp_session_id = str(uuid.uuid4()).replace("-", "")
|
|
109
|
-
|
|
110
|
-
self._initialized = True
|
|
111
|
-
|
|
112
58
|
async def _make_mcp_request(
|
|
113
59
|
self, method: str, params: Optional[Dict] = None
|
|
114
60
|
) -> Dict[str, Any]:
|
|
115
61
|
"""Make an MCP JSON-RPC request"""
|
|
116
|
-
request_id = "1"
|
|
117
|
-
|
|
118
|
-
payload = {"jsonrpc": "2.0", "id": request_id, "method": method}
|
|
119
|
-
|
|
120
|
-
if params:
|
|
121
|
-
payload["params"] = params
|
|
122
|
-
|
|
123
62
|
if self.transport == "http":
|
|
124
|
-
await self._ensure_session()
|
|
125
|
-
await self._initialize_mcp_session() # Ensure session is initialized
|
|
126
|
-
|
|
127
|
-
headers = {
|
|
128
|
-
"Content-Type": "application/json",
|
|
129
|
-
"Accept": "application/json, text/event-stream",
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
# Add session ID if available
|
|
133
|
-
if self.mcp_session_id:
|
|
134
|
-
headers["mcp-session-id"] = self.mcp_session_id
|
|
135
|
-
|
|
136
63
|
endpoint = self._get_mcp_endpoint("")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
for line in response_text.split("\n"):
|
|
153
|
-
line = line.strip()
|
|
154
|
-
if line.startswith("data: "):
|
|
155
|
-
json_data = line[6:]
|
|
156
|
-
try:
|
|
157
|
-
result = json.loads(json_data)
|
|
158
|
-
break
|
|
159
|
-
except json.JSONDecodeError:
|
|
160
|
-
continue
|
|
161
|
-
else:
|
|
162
|
-
raise Exception(
|
|
163
|
-
f"Failed to parse SSE response: {response_text}"
|
|
64
|
+
async with streamablehttp_client(endpoint, timeout=self.timeout) as (
|
|
65
|
+
read_stream,
|
|
66
|
+
write_stream,
|
|
67
|
+
_,
|
|
68
|
+
):
|
|
69
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
70
|
+
await session.initialize()
|
|
71
|
+
|
|
72
|
+
if method == "tools/list":
|
|
73
|
+
result = await session.list_tools()
|
|
74
|
+
elif method == "tools/call":
|
|
75
|
+
if not params or "name" not in params:
|
|
76
|
+
raise ValueError("Missing tool name for tools/call")
|
|
77
|
+
result = await session.call_tool(
|
|
78
|
+
params["name"], params.get("arguments") or {}
|
|
164
79
|
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
result = await
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
80
|
+
elif method == "resources/list":
|
|
81
|
+
result = await session.list_resources()
|
|
82
|
+
elif method == "resources/read":
|
|
83
|
+
if not params or "uri" not in params:
|
|
84
|
+
raise ValueError("Missing uri for resources/read")
|
|
85
|
+
result = await session.read_resource(params["uri"])
|
|
86
|
+
elif method == "prompts/list":
|
|
87
|
+
result = await session.list_prompts()
|
|
88
|
+
elif method == "prompts/get":
|
|
89
|
+
if not params or "name" not in params:
|
|
90
|
+
raise ValueError("Missing prompt name for prompts/get")
|
|
91
|
+
result = await session.get_prompt(
|
|
92
|
+
params["name"], params.get("arguments")
|
|
175
93
|
)
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError(f"Unsupported MCP method: {method}")
|
|
176
96
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return result.get("result", {})
|
|
97
|
+
if hasattr(result, "model_dump"):
|
|
98
|
+
return result.model_dump(mode="json")
|
|
99
|
+
return result
|
|
181
100
|
|
|
182
101
|
elif self.transport == "websocket":
|
|
183
102
|
async with websockets.connect(self.server_url) as websocket:
|
|
103
|
+
request_id = "1"
|
|
104
|
+
payload = {"jsonrpc": "2.0", "id": request_id, "method": method}
|
|
105
|
+
if params:
|
|
106
|
+
payload["params"] = params
|
|
184
107
|
await websocket.send(json.dumps(payload))
|
|
185
108
|
response = await websocket.recv()
|
|
186
109
|
result = json.loads(response)
|
|
@@ -575,7 +498,6 @@ class MCPAutoLoaderTool(BaseTool, BaseMCPClient):
|
|
|
575
498
|
async def discover_tools(self) -> Dict[str, Any]:
|
|
576
499
|
"""Discover all available tools from the MCP server"""
|
|
577
500
|
try:
|
|
578
|
-
await self._initialize_mcp_session()
|
|
579
501
|
tools_response = await self._make_mcp_request("tools/list")
|
|
580
502
|
tools = tools_response.get("tools", [])
|
|
581
503
|
|
|
@@ -631,22 +553,28 @@ class MCPAutoLoaderTool(BaseTool, BaseMCPClient):
|
|
|
631
553
|
return configs
|
|
632
554
|
|
|
633
555
|
def register_tools_in_engine(self, engine):
|
|
634
|
-
"""Register discovered tools
|
|
556
|
+
"""Register discovered tools using ToolUniverse public API"""
|
|
635
557
|
try:
|
|
636
558
|
configs = self.generate_proxy_tool_configs()
|
|
637
559
|
|
|
638
560
|
for config in configs:
|
|
639
|
-
|
|
640
|
-
|
|
561
|
+
config["type"]
|
|
562
|
+
tool_name = config["name"]
|
|
641
563
|
|
|
642
|
-
#
|
|
643
|
-
|
|
564
|
+
# Use public API to register tool
|
|
565
|
+
engine.register_custom_tool(
|
|
566
|
+
tool_class=MCPProxyTool,
|
|
567
|
+
tool_name=tool_name,
|
|
568
|
+
tool_config=config,
|
|
569
|
+
instantiate=True, # Immediately instantiate and cache
|
|
570
|
+
)
|
|
644
571
|
|
|
645
|
-
#
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
572
|
+
# Keep reference for tracking
|
|
573
|
+
# Use the actual key that register_custom_tool uses
|
|
574
|
+
actual_key = config.get("name", tool_name)
|
|
575
|
+
self._registered_tools[tool_name] = engine.callable_functions[
|
|
576
|
+
actual_key
|
|
577
|
+
]
|
|
650
578
|
|
|
651
579
|
return len(configs)
|
|
652
580
|
except Exception as e:
|