tooluniverse 1.0.9__py3-none-any.whl → 1.0.10__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.

Files changed (57) hide show
  1. tooluniverse/admetai_tool.py +1 -1
  2. tooluniverse/agentic_tool.py +65 -17
  3. tooluniverse/base_tool.py +19 -8
  4. tooluniverse/boltz_tool.py +1 -1
  5. tooluniverse/cache/result_cache_manager.py +167 -12
  6. tooluniverse/compose_scripts/drug_safety_analyzer.py +1 -1
  7. tooluniverse/compose_scripts/multi_agent_literature_search.py +1 -1
  8. tooluniverse/compose_scripts/output_summarizer.py +4 -4
  9. tooluniverse/compose_scripts/tool_graph_composer.py +1 -1
  10. tooluniverse/compose_scripts/tool_metadata_generator.py +1 -1
  11. tooluniverse/compose_tool.py +9 -9
  12. tooluniverse/core_tool.py +2 -2
  13. tooluniverse/ctg_tool.py +4 -4
  14. tooluniverse/custom_tool.py +1 -1
  15. tooluniverse/dataset_tool.py +2 -2
  16. tooluniverse/default_config.py +1 -1
  17. tooluniverse/enrichr_tool.py +14 -14
  18. tooluniverse/execute_function.py +520 -15
  19. tooluniverse/extended_hooks.py +4 -4
  20. tooluniverse/gene_ontology_tool.py +1 -1
  21. tooluniverse/generate_tools.py +3 -3
  22. tooluniverse/humanbase_tool.py +10 -10
  23. tooluniverse/logging_config.py +2 -2
  24. tooluniverse/mcp_client_tool.py +57 -129
  25. tooluniverse/mcp_integration.py +52 -49
  26. tooluniverse/mcp_tool_registry.py +147 -528
  27. tooluniverse/openalex_tool.py +8 -8
  28. tooluniverse/openfda_tool.py +2 -2
  29. tooluniverse/output_hook.py +15 -15
  30. tooluniverse/package_tool.py +1 -1
  31. tooluniverse/pmc_tool.py +2 -2
  32. tooluniverse/remote/boltz/boltz_mcp_server.py +1 -1
  33. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +2 -2
  34. tooluniverse/remote/immune_compass/compass_tool.py +3 -3
  35. tooluniverse/remote/pinnacle/pinnacle_tool.py +2 -2
  36. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +3 -3
  37. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +3 -3
  38. tooluniverse/remote_tool.py +4 -4
  39. tooluniverse/scripts/filter_tool_files.py +2 -2
  40. tooluniverse/smcp.py +93 -12
  41. tooluniverse/smcp_server.py +100 -20
  42. tooluniverse/space/__init__.py +46 -0
  43. tooluniverse/space/loader.py +133 -0
  44. tooluniverse/space/validator.py +353 -0
  45. tooluniverse/tool_finder_embedding.py +2 -2
  46. tooluniverse/tool_finder_keyword.py +9 -9
  47. tooluniverse/tool_finder_llm.py +6 -6
  48. tooluniverse/tools/_shared_client.py +3 -3
  49. tooluniverse/url_tool.py +1 -1
  50. tooluniverse/uspto_tool.py +1 -1
  51. tooluniverse/utils.py +10 -10
  52. {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/METADATA +7 -3
  53. {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/RECORD +57 -54
  54. {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/WHEEL +0 -0
  55. {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/entry_points.txt +0 -0
  56. {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/licenses/LICENSE +0 -0
  57. {tooluniverse-1.0.9.dist-info → tooluniverse-1.0.10.dist-info}/top_level.txt +0 -0
@@ -37,14 +37,14 @@ class OpenAlexTool(BaseTool):
37
37
  """
38
38
  Search for literature using OpenAlex API.
39
39
 
40
- Parameters:
40
+ Parameters
41
41
  search_keywords (str): Keywords to search for in title, abstract, and content.
42
42
  max_results (int): Maximum number of results to return (default: 10).
43
43
  year_from (int): Start year for publication date filter (optional).
44
44
  year_to (int): End year for publication date filter (optional).
45
45
  open_access (bool): Filter for open access papers only (optional).
46
46
 
47
- Returns:
47
+ Returns
48
48
  list: List of dictionaries containing paper information.
49
49
  """
50
50
  # Encode search keywords for URL
@@ -98,10 +98,10 @@ class OpenAlexTool(BaseTool):
98
98
  """
99
99
  Extract relevant information from a work object returned by OpenAlex API.
100
100
 
101
- Parameters:
101
+ Parameters
102
102
  work (dict): Work object from OpenAlex API response.
103
103
 
104
- Returns:
104
+ Returns
105
105
  dict: Formatted paper information.
106
106
  """
107
107
  # Extract title
@@ -204,10 +204,10 @@ class OpenAlexTool(BaseTool):
204
204
  """
205
205
  Retrieve a specific paper by its DOI.
206
206
 
207
- Parameters:
207
+ Parameters
208
208
  doi (str): DOI of the paper to retrieve.
209
209
 
210
- Returns:
210
+ Returns
211
211
  dict: Paper information or None if not found.
212
212
  """
213
213
  try:
@@ -229,11 +229,11 @@ class OpenAlexTool(BaseTool):
229
229
  """
230
230
  Retrieve papers by a specific author.
231
231
 
232
- Parameters:
232
+ Parameters
233
233
  author_name (str): Name of the author to search for.
234
234
  max_results (int): Maximum number of results to return.
235
235
 
236
- Returns:
236
+ Returns
237
237
  list: List of papers by the author.
238
238
  """
239
239
  try:
@@ -85,11 +85,11 @@ def extract_sentences_with_keywords(text_list, keywords):
85
85
  """
86
86
  Extracts sentences containing any of the specified keywords from the text.
87
87
 
88
- Parameters:
88
+ Parameters
89
89
  - text (str): The input text from which to extract sentences.
90
90
  - keywords (list): A list of keywords to search for in the text.
91
91
 
92
- Returns:
92
+ Returns
93
93
  - list: A list of sentences containing any of the keywords.
94
94
  """
95
95
  sentences_with_keywords = []
@@ -65,7 +65,7 @@ class HookRule:
65
65
  arguments (Dict[str, Any]): Arguments passed to the tool
66
66
  context (Dict[str, Any]): Additional context information
67
67
 
68
- Returns:
68
+ Returns
69
69
  bool: True if conditions are met, False otherwise
70
70
  """
71
71
  # Evaluate output length conditions
@@ -158,7 +158,7 @@ class OutputHook:
158
158
  arguments (Dict[str, Any]): Arguments passed to the tool
159
159
  context (Dict[str, Any]): Additional context information
160
160
 
161
- Returns:
161
+ Returns
162
162
  bool: True if hook should trigger, False otherwise
163
163
  """
164
164
  if not self.enabled:
@@ -184,7 +184,7 @@ class OutputHook:
184
184
  arguments (Dict[str, Any]): Arguments passed to the tool
185
185
  context (Dict[str, Any]): Additional context information
186
186
 
187
- Returns:
187
+ Returns
188
188
  Any: The processed output
189
189
 
190
190
  Raises:
@@ -290,7 +290,7 @@ class SummarizationHook(OutputHook):
290
290
  arguments (Dict[str, Any]): Arguments passed to the tool
291
291
  context (Dict[str, Any]): Additional context information
292
292
 
293
- Returns:
293
+ Returns
294
294
  Any: The summarized output, or original output if summarization fails
295
295
  """
296
296
  try:
@@ -408,7 +408,7 @@ class SummarizationHook(OutputHook):
408
408
  Args:
409
409
  context (Dict[str, Any]): Execution context containing arguments and metadata
410
410
 
411
- Returns:
411
+ Returns
412
412
  str: Extracted query context or fallback description
413
413
  """
414
414
  arguments = context.get("arguments", {})
@@ -519,7 +519,7 @@ class HookManager:
519
519
  arguments (Dict[str, Any]): Arguments passed to the tool
520
520
  context (Dict[str, Any]): Additional context information
521
521
 
522
- Returns:
522
+ Returns
523
523
  Any: The processed output after applying all applicable hooks
524
524
  """
525
525
  if not self.enabled:
@@ -553,7 +553,7 @@ class HookManager:
553
553
  """
554
554
  Validate that LLM API keys are available for hook tools.
555
555
 
556
- Returns:
556
+ Returns
557
557
  bool: True if API keys are available, False otherwise
558
558
  """
559
559
  from .agentic_tool import AgenticTool
@@ -641,7 +641,7 @@ class HookManager:
641
641
  Args:
642
642
  hook_name (str): Name of the hook to retrieve
643
643
 
644
- Returns:
644
+ Returns
645
645
  Optional[OutputHook]: Hook instance if found, None otherwise
646
646
  """
647
647
  for hook in self.hooks:
@@ -684,7 +684,7 @@ class HookManager:
684
684
  """
685
685
  Get the path to the hook configuration file.
686
686
 
687
- Returns:
687
+ Returns
688
688
  Path: Path to the configuration file
689
689
  """
690
690
  try:
@@ -934,7 +934,7 @@ class HookManager:
934
934
  Args:
935
935
  tool_name (str): Name of the tool to check
936
936
 
937
- Returns:
937
+ Returns
938
938
  bool: True if the tool is a hook tool and should be excluded from hook processing
939
939
  """
940
940
  hook_tool_names = [
@@ -956,7 +956,7 @@ class HookManager:
956
956
  Args:
957
957
  hook_config (Dict[str, Any]): Hook configuration
958
958
 
959
- Returns:
959
+ Returns
960
960
  Optional[OutputHook]: Created hook instance or None if type not supported
961
961
  """
962
962
  hook_type = hook_config.get("type", "SummarizationHook")
@@ -985,7 +985,7 @@ class HookManager:
985
985
  Args:
986
986
  hook_config (Dict[str, Any]): Original hook configuration
987
987
 
988
- Returns:
988
+ Returns
989
989
  Dict[str, Any]: Enhanced configuration with defaults applied
990
990
  """
991
991
  hook_type = hook_config.get("type", "SummarizationHook")
@@ -1052,7 +1052,7 @@ class HookManager:
1052
1052
  tool_name (str): Name of the current tool
1053
1053
  context (Dict[str, Any]): Execution context
1054
1054
 
1055
- Returns:
1055
+ Returns
1056
1056
  bool: True if hook is applicable, False otherwise
1057
1057
  """
1058
1058
  # Check tool-specific hooks
@@ -1138,7 +1138,7 @@ class FileSaveHook(OutputHook):
1138
1138
  arguments (Dict[str, Any]): Arguments passed to the tool
1139
1139
  context (Dict[str, Any]): Execution context
1140
1140
 
1141
- Returns:
1141
+ Returns
1142
1142
  Dict[str, Any]: Dictionary containing file information:
1143
1143
  - file_path: Path to the saved file
1144
1144
  - data_format: Format of the data (json, text, binary, etc.)
@@ -1213,7 +1213,7 @@ class FileSaveHook(OutputHook):
1213
1213
  Args:
1214
1214
  data (Any): The data to analyze
1215
1215
 
1216
- Returns:
1216
+ Returns
1217
1217
  tuple[str, str]: (data_format, data_structure)
1218
1218
  """
1219
1219
  if isinstance(data, dict):
@@ -27,7 +27,7 @@ class PackageTool(BaseTool):
27
27
  Args:
28
28
  arguments (dict): Optional parameters for customization
29
29
 
30
- Returns:
30
+ Returns
31
31
  dict: Package information including name, description, installation, docs, usage
32
32
  """
33
33
  include_examples = arguments.get("include_examples", True)
tooluniverse/pmc_tool.py CHANGED
@@ -43,7 +43,7 @@ class PMCTool(BaseTool):
43
43
  date_to: End date filter (YYYY/MM/DD)
44
44
  article_type: Article type filter (e.g., 'research-article', 'review')
45
45
 
46
- Returns:
46
+ Returns
47
47
  List of paper dictionaries
48
48
  """
49
49
  try:
@@ -158,7 +158,7 @@ class PMCTool(BaseTool):
158
158
  Args:
159
159
  tool_arguments: Dictionary containing search parameters
160
160
 
161
- Returns:
161
+ Returns
162
162
  List of paper dictionaries
163
163
  """
164
164
  query = tool_arguments.get("query", "")
@@ -35,7 +35,7 @@ def run_boltz2(query: dict):
35
35
  - without_potentials (bool): Whether to run without potentials (default: False)
36
36
  - diffusion_samples (int): Number of diffusion samples to generate (default: 1)
37
37
  - Additional constraint keys may be included as needed
38
- Returns:
38
+ Returns
39
39
  dict: A dictionary containing the docking results with the following structure:
40
40
  - predicted_structure (str): The predicted protein-ligand complex structure in CIF format
41
41
  - structure_format (str): Format of the structure file (typically 'cif')
@@ -163,7 +163,7 @@ class DepmapCorrelationTool:
163
163
  gene_a (str): First gene symbol (e.g., 'BRAF', 'TP53'). Must be present in dataset.
164
164
  gene_b (str): Second gene symbol (e.g., 'MAPK1', 'MDM2'). Must be present in dataset.
165
165
 
166
- Returns:
166
+ Returns
167
167
  Dict[str, float]: Dictionary containing correlation analysis results:
168
168
  - 'correlation': Pearson correlation coefficient (-1.0 to 1.0)
169
169
  - 'p_value': Statistical significance of correlation
@@ -272,7 +272,7 @@ async def compute_depmap24q2_gene_correlations(
272
272
  gene_b (str): Second gene symbol for correlation analysis (e.g., 'MAPK1', 'MDM2').
273
273
  Must use standard HUGO gene nomenclature.
274
274
 
275
- Returns:
275
+ Returns
276
276
  dict: Comprehensive correlation analysis results containing:
277
277
  - 'correlation_data' (dict): Statistical measures including:
278
278
  * 'correlation': Pearson correlation coefficient (-1.0 to 1.0)
@@ -118,7 +118,7 @@ class CompassTool:
118
118
  exclude (List[str]): List of column names to exclude from results.
119
119
  Defaults to ['CANCER', 'Reference'].
120
120
 
121
- Returns:
121
+ Returns
122
122
  List[List[Tuple[str, float]]]: For each sample, a list of tuples containing
123
123
  (concept_name, concept_score) sorted by score descending.
124
124
  """
@@ -161,7 +161,7 @@ class CompassTool:
161
161
  batch_size (int): Batch size for model inference. Larger values may improve speed
162
162
  but require more memory. Defaults to 128.
163
163
 
164
- Returns:
164
+ Returns
165
165
  Tuple[bool, List[Tuple[str, float]]]: A tuple containing:
166
166
  - bool: True if predicted as responder (probability ≥ threshold), False otherwise
167
167
  - List[Tuple[str, float]]: Top immune cell concepts ranked by importance,
@@ -222,7 +222,7 @@ async def run_compass_prediction(
222
222
  Default 0.5 provides balanced sensitivity/specificity.
223
223
  Consider lower thresholds (~0.3) for higher sensitivity.
224
224
 
225
- Returns:
225
+ Returns
226
226
  dict: Structured prediction results containing:
227
227
  - 'prediction' (dict): Core prediction results with:
228
228
  * 'is_responder' (bool): True if predicted responder (probability ≥ threshold)
@@ -101,7 +101,7 @@ class PinnaclePPITool:
101
101
  The method handles various naming conventions including spaces,
102
102
  hyphens, underscores, and capitalization differences.
103
103
 
104
- Returns:
104
+ Returns
105
105
  Tuple[Dict[str, torch.Tensor], str]: A tuple containing:
106
106
  - Dict mapping protein/gene names to their embedding tensors (empty if no match)
107
107
  - Status message indicating match quality and selected cell type
@@ -198,7 +198,7 @@ async def run_pinnacle_ppi_retrieval(cell_type: str, embed_path: Optional[str] =
198
198
  - Tissue types: 'liver', 'heart', 'brain', 'immune'
199
199
  The tool performs intelligent matching to find the best available match.
200
200
 
201
- Returns:
201
+ Returns
202
202
  dict: Comprehensive embedding retrieval results containing:
203
203
  - 'embeddings' (dict, optional): Protein-to-embedding mapping where:
204
204
  * Keys: Gene/protein symbols (e.g., 'TP53', 'EGFR', 'BRCA1')
@@ -105,7 +105,7 @@ class TranscriptformerEmbeddingTool:
105
105
  Args:
106
106
  disease (str): Disease identifier (normalized to lowercase with underscores).
107
107
 
108
- Returns:
108
+ Returns
109
109
  Dict: Cached metadata dictionary containing:
110
110
  - store_path: Path to disease-specific embedding store
111
111
  - ensembl_ids_ordered: Ordered list of Ensembl gene IDs
@@ -213,7 +213,7 @@ class TranscriptformerEmbeddingTool:
213
213
  disease (str): Disease context identifier (e.g., 'breast_cancer', 'diabetes').
214
214
  Must match available disease stores.
215
215
 
216
- Returns:
216
+ Returns
217
217
  Tuple[Optional[Dict[str, np.ndarray]], List[str]]: A tuple containing:
218
218
  - Dictionary mapping gene names to embedding vectors (None if failed)
219
219
  - List of context information and error messages
@@ -416,7 +416,7 @@ async def run_transcriptformer_embedding_retrieval(
416
416
  - 'alzheimer': Alzheimer's disease contexts
417
417
  Must match available disease stores.
418
418
 
419
- Returns:
419
+ Returns
420
420
  dict: Comprehensive embedding retrieval results containing:
421
421
  - 'embeddings' (dict, optional): Gene-to-embedding mapping where:
422
422
  * Keys: Gene identifiers (symbols or Ensembl IDs as provided)
@@ -29,7 +29,7 @@ def download_abst(query: dict):
29
29
  """Retrieve the abstract of a patent application by its application number.
30
30
  Args:
31
31
  "query" dict: A dictionary containing the application number under the key "applicationNumberText".
32
- Returns:
32
+ Returns
33
33
  dict: A dictionary containing the abstract text under the 'result' key or an error message under the 'error' key if the document could not be retrieved.
34
34
  """
35
35
  return agents["get_abstract_from_patent_app_number"].run(query)
@@ -40,7 +40,7 @@ def download_claims(query: dict):
40
40
  """Retrieve the claims of a patent application by its application number.
41
41
  Args:
42
42
  "query" dict: A dictionary containing the application number under the key "applicationNumberText".
43
- Returns:
43
+ Returns
44
44
  dict: A dictionary containing the claims text under the 'result' key or an error message under the 'error' key if the document could not be retrieved.
45
45
  """
46
46
  return agents["get_claims_from_patent_app_number"].run(query)
@@ -51,7 +51,7 @@ def download_full_text(query: dict):
51
51
  """Retrieve the full text of a patent application by its application number.
52
52
  Args:
53
53
  "query" dict: A dictionary containing the application number under the key "applicationNumberText".
54
- Returns:
54
+ Returns
55
55
  dict: A dictionary containing the full text under the 'result' key or an error message under the 'error' key if the document could not be retrieved.
56
56
  """
57
57
  return agents["get_full_text_from_patent_app_number"].run(query)
@@ -45,7 +45,7 @@ class RemoteTool(BaseTool):
45
45
  Args:
46
46
  arguments (dict, optional): Tool arguments (ignored)
47
47
 
48
- Returns:
48
+ Returns
49
49
  dict: Error message indicating the tool is not available locally
50
50
  """
51
51
  server_type = self.remote_info.get("server_type", "Unknown")
@@ -69,7 +69,7 @@ class RemoteTool(BaseTool):
69
69
  """
70
70
  Get information about the remote server hosting this tool.
71
71
 
72
- Returns:
72
+ Returns
73
73
  dict: Remote server information including server type, URL, and original tool type
74
74
  """
75
75
  return self.remote_info.copy()
@@ -78,7 +78,7 @@ class RemoteTool(BaseTool):
78
78
  """
79
79
  Check if this tool is available for local execution.
80
80
 
81
- Returns:
81
+ Returns
82
82
  bool: Always False for RemoteTool instances
83
83
  """
84
84
  return False
@@ -87,7 +87,7 @@ class RemoteTool(BaseTool):
87
87
  """
88
88
  Get server connection information for this remote tool.
89
89
 
90
- Returns:
90
+ Returns
91
91
  dict: Server connection details
92
92
  """
93
93
  return {
@@ -46,7 +46,7 @@ def filter_tool_relationship_graph(data, valid_tool_names):
46
46
  data: The loaded JSON data
47
47
  valid_tool_names: Set of valid tool names
48
48
 
49
- Returns:
49
+ Returns
50
50
  Filtered data
51
51
  """
52
52
  if not isinstance(data, dict):
@@ -111,7 +111,7 @@ def filter_v4_all_tools(data, valid_tool_names):
111
111
  data: The loaded JSON data
112
112
  valid_tool_names: Set of valid tool names
113
113
 
114
- Returns:
114
+ Returns
115
115
  Filtered data
116
116
  """
117
117
  if not isinstance(data, list):
tooluniverse/smcp.py CHANGED
@@ -10,8 +10,8 @@ The SMCP module provides a complete solution for exposing scientific computation
10
10
  resources through the standardized MCP protocol, making it easy for AI agents to
11
11
  discover, understand, and execute scientific tools in a unified manner.
12
12
 
13
- Usage Patterns:
14
- ===============
13
+ Usage Patterns
14
+ --------------
15
15
 
16
16
  Quick Start:
17
17
 
@@ -47,8 +47,8 @@ result = await client.call_tool("UniProt_get_entry_by_accession", {
47
47
  })
48
48
  ```
49
49
 
50
- Architecture:
51
- =============
50
+ Architecture
51
+ ------------
52
52
 
53
53
  ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
54
54
  │ MCP Client │◄──►│ SMCP │◄──►│ ToolUniverse │
@@ -69,8 +69,8 @@ The SMCP server acts as an intelligent middleware layer that:
69
69
  4. Returns formatted results via MCP protocol
70
70
  5. Provides intelligent tool discovery and recommendation
71
71
 
72
- Integration Points:
73
- ==================
72
+ Integration Points
73
+ ------------------
74
74
 
75
75
  MCP Protocol Layer:
76
76
  - Standard MCP methods (tools/list, tools/call, etc.)
@@ -219,6 +219,21 @@ class SMCP(FastMCP):
219
219
  during tool loading. Useful for excluding entire categories of tools
220
220
  (e.g., all ToolFinder types or all OpenTarget tools).
221
221
 
222
+ space : str or list of str, optional
223
+ Space configuration URI(s) to load. Can be a single URI string or a list
224
+ of URIs for loading multiple Space configurations. Supported formats:
225
+ - Local file: "./config.yaml" or "/path/to/config.yaml"
226
+ - HuggingFace: "hf:username/repo" or "hf:username/repo/file.yaml"
227
+ - HTTP URL: "https://example.com/config.yaml"
228
+
229
+ When provided, Space configurations are loaded after tool initialization,
230
+ applying LLM settings, hooks, and tool selections from the configuration files.
231
+ Multiple spaces can be loaded sequentially, with later configurations
232
+ potentially overriding earlier ones.
233
+
234
+ Example: space="./my-workspace.yaml"
235
+ Example: space=["hf:community/bio-tools", "./custom-tools.yaml"]
236
+
222
237
  auto_expose_tools : bool, default True
223
238
  Whether to automatically expose ToolUniverse tools as MCP tools.
224
239
  When True, all loaded tools become available via the MCP interface
@@ -281,6 +296,7 @@ class SMCP(FastMCP):
281
296
  tool_config_files: Optional[Dict[str, str]] = None,
282
297
  include_tool_types: Optional[List[str]] = None,
283
298
  exclude_tool_types: Optional[List[str]] = None,
299
+ space: Optional[Union[str, List[str]]] = None,
284
300
  auto_expose_tools: bool = True,
285
301
  search_enabled: bool = True,
286
302
  max_workers: int = 5,
@@ -325,6 +341,7 @@ class SMCP(FastMCP):
325
341
  self.tool_config_files = tool_config_files or {}
326
342
  self.include_tool_types = include_tool_types or []
327
343
  self.exclude_tool_types = exclude_tool_types or []
344
+ self.space = space
328
345
  self.auto_expose_tools = auto_expose_tools
329
346
  self.search_enabled = search_enabled
330
347
  self.max_workers = max_workers
@@ -332,18 +349,61 @@ class SMCP(FastMCP):
332
349
  self.hook_config = hook_config
333
350
  self.hook_type = hook_type
334
351
 
352
+ # Space configuration storage
353
+ self.space_llm_config = None
354
+ self.space_metadata = None
355
+
335
356
  # Thread pool for concurrent tool execution
336
357
  self.executor = ThreadPoolExecutor(max_workers=max_workers)
337
358
 
338
359
  # Track exposed tools to avoid duplicates
339
360
  self._exposed_tools = set()
340
361
 
341
- # Initialize SMCP-specific features
362
+ # Load Space configurations first if provided
363
+ if space:
364
+ self._load_space_configs(space)
365
+
366
+ # Initialize SMCP-specific features (after Space is loaded)
342
367
  self._setup_smcp_tools()
343
368
 
344
369
  # Register custom MCP methods
345
370
  self._register_custom_mcp_methods()
346
371
 
372
+ def _load_space_configs(self, space: Union[str, List[str]]):
373
+ """
374
+ Load Space configurations.
375
+
376
+ This method loads Space configuration(s) and retrieves the LLM config
377
+ and metadata from ToolUniverse. It completely reuses ToolUniverse's
378
+ load_space functionality without reimplementing any logic.
379
+
380
+ Args:
381
+ space: Space URI or list of URIs (e.g., "./config.yaml",
382
+ "hf:user/repo", or ["config1.yaml", "config2.yaml"])
383
+ """
384
+ space_list = [space] if isinstance(space, str) else space
385
+
386
+ for uri in space_list:
387
+ print(f"📦 Loading Space: {uri}")
388
+
389
+ # Directly call ToolUniverse's method (complete reuse)
390
+ config = self.tooluniverse.load_space(uri)
391
+
392
+ # Get configurations from ToolUniverse (complete reuse)
393
+ self.space_metadata = self.tooluniverse.get_space_metadata()
394
+ self.space_llm_config = self.tooluniverse.get_space_llm_config()
395
+
396
+ print(f"✅ Space loaded: {config.get('name', 'Unknown')}")
397
+
398
+ def get_llm_config(self) -> Optional[Dict[str, Any]]:
399
+ """
400
+ Get the current Space LLM configuration.
401
+
402
+ Returns:
403
+ LLM configuration dictionary or None if not set
404
+ """
405
+ return self.space_llm_config
406
+
347
407
  def _register_custom_mcp_methods(self):
348
408
  """
349
409
  Register custom MCP protocol methods for enhanced functionality.
@@ -857,9 +917,28 @@ class SMCP(FastMCP):
857
917
  - All setup phases include comprehensive error handling
858
918
  - Performance scales with the number of tools being loaded and exposed
859
919
  """
860
- # Always ensure full tool set is loaded (hooks may have preloaded a minimal set)
861
- # Deduplication in ToolUniverse.load_tools prevents duplicates, so reloading is safe
862
- if self.tool_categories:
920
+ # Determine if ToolUniverse already has tools loaded (e.g., provided pre-configured instance)
921
+ preloaded_tools = getattr(self.tooluniverse, "all_tools", [])
922
+ preloaded_count = (
923
+ len(preloaded_tools) if isinstance(preloaded_tools, list) else 0
924
+ )
925
+
926
+ if preloaded_count > 0:
927
+ self.logger.info(
928
+ f"ToolUniverse already pre-configured with {preloaded_count} tool(s); skipping automatic loading."
929
+ )
930
+
931
+ # Check if Space has already loaded specific tools
932
+ if (
933
+ self.space
934
+ and hasattr(self.tooluniverse, "_current_space_config")
935
+ and preloaded_count > 0
936
+ ):
937
+ # Space has already loaded specific tools, don't reload all tools
938
+ self.logger.info(
939
+ f"Space configuration loaded {preloaded_count} tool(s), skipping additional loading"
940
+ )
941
+ elif preloaded_count == 0 and self.tool_categories:
863
942
  try:
864
943
  # Validate categories first
865
944
  valid_categories = self._get_valid_categories()
@@ -924,8 +1003,10 @@ class SMCP(FastMCP):
924
1003
  include_tool_types=self.include_tool_types,
925
1004
  exclude_tool_types=self.exclude_tool_types,
926
1005
  )
927
- elif self.auto_expose_tools:
928
- # Load all tools by default
1006
+ elif self.auto_expose_tools and not (
1007
+ self.space and hasattr(self.tooluniverse, "_current_space_config")
1008
+ ):
1009
+ # Load all tools by default (unless Space already handled tool loading)
929
1010
  self.tooluniverse.load_tools(
930
1011
  exclude_tools=self.exclude_tools,
931
1012
  exclude_categories=self.exclude_categories,