tooluniverse 1.0.8__py3-none-any.whl → 1.0.9.1__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 (33) hide show
  1. tooluniverse/__init__.py +8 -0
  2. tooluniverse/admetai_tool.py +8 -1
  3. tooluniverse/compose_scripts/output_summarizer.py +87 -33
  4. tooluniverse/compose_tool.py +2 -2
  5. tooluniverse/data/adverse_event_tools.json +97 -98
  6. tooluniverse/data/agentic_tools.json +81 -162
  7. tooluniverse/data/compose_tools.json +0 -54
  8. tooluniverse/data/drug_discovery_agents.json +10 -20
  9. tooluniverse/data/literature_search_tools.json +15 -35
  10. tooluniverse/data/monarch_tools.json +1 -2
  11. tooluniverse/data/opentarget_tools.json +8 -16
  12. tooluniverse/data/output_summarization_tools.json +23 -20
  13. tooluniverse/data/packages/bioinformatics_core_tools.json +2 -2
  14. tooluniverse/data/packages/cheminformatics_tools.json +1 -1
  15. tooluniverse/data/packages/genomics_tools.json +1 -1
  16. tooluniverse/data/packages/single_cell_tools.json +1 -1
  17. tooluniverse/data/packages/structural_biology_tools.json +1 -1
  18. tooluniverse/data/tool_composition_tools.json +2 -4
  19. tooluniverse/execute_function.py +39 -1
  20. tooluniverse/logging_config.py +64 -2
  21. tooluniverse/molecule_2d_tool.py +9 -3
  22. tooluniverse/molecule_3d_tool.py +9 -3
  23. tooluniverse/output_hook.py +217 -150
  24. tooluniverse/smcp.py +8 -1
  25. tooluniverse/smcp_server.py +92 -201
  26. tooluniverse/tools/__init__.py +1 -3
  27. {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.1.dist-info}/METADATA +3 -2
  28. {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.1.dist-info}/RECORD +32 -33
  29. {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.1.dist-info}/entry_points.txt +0 -3
  30. tooluniverse/tools/MultiAgentLiteratureSearch.py +0 -59
  31. {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.1.dist-info}/WHEEL +0 -0
  32. {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.1.dist-info}/licenses/LICENSE +0 -0
  33. {tooluniverse-1.0.8.dist-info → tooluniverse-1.0.9.1.dist-info}/top_level.txt +0 -0
@@ -16,8 +16,12 @@ leveraging AgenticTool and ComposeTool for intelligent output processing.
16
16
  """
17
17
 
18
18
  import json
19
+ from dataclasses import dataclass
19
20
  from typing import Dict, Any, List, Optional
20
21
  from pathlib import Path
22
+ from tooluniverse.logging_config import get_logger
23
+
24
+ _logger = get_logger(__name__)
21
25
 
22
26
 
23
27
  class HookRule:
@@ -164,9 +168,9 @@ class OutputHook:
164
168
  def process(
165
169
  self,
166
170
  result: Any,
167
- tool_name: str,
168
- arguments: Dict[str, Any],
169
- context: Dict[str, Any],
171
+ tool_name: str | None = None,
172
+ arguments: Dict[str, Any] | None = None,
173
+ context: Dict[str, Any] | None = None,
170
174
  ) -> Any:
171
175
  """
172
176
  Process the tool output.
@@ -189,6 +193,32 @@ class OutputHook:
189
193
  raise NotImplementedError("Subclasses must implement process method")
190
194
 
191
195
 
196
+ @dataclass
197
+ class SummarizationHookConfig:
198
+ composer_tool: str = "OutputSummarizationComposer"
199
+ chunk_size: int = (
200
+ 30000 # Increased to 30000 to minimize chunk count and improve success rate
201
+ )
202
+ focus_areas: str = "key_findings_and_results"
203
+ max_summary_length: int = 3000
204
+ composer_timeout_sec: int = 60
205
+
206
+ def validate(self) -> "SummarizationHookConfig":
207
+ # Validate numeric fields; clamp to sensible defaults if invalid
208
+ if not isinstance(self.chunk_size, int) or self.chunk_size <= 0:
209
+ self.chunk_size = 30000
210
+ if not isinstance(self.max_summary_length, int) or self.max_summary_length <= 0:
211
+ self.max_summary_length = 3000
212
+ if (
213
+ not isinstance(self.composer_timeout_sec, int)
214
+ or self.composer_timeout_sec <= 0
215
+ ):
216
+ self.composer_timeout_sec = 60
217
+ if not isinstance(self.composer_tool, str) or not self.composer_tool:
218
+ self.composer_tool = "OutputSummarizationComposer"
219
+ return self
220
+
221
+
192
222
  class SummarizationHook(OutputHook):
193
223
  """
194
224
  Hook for intelligent output summarization using AI.
@@ -203,13 +233,13 @@ class SummarizationHook(OutputHook):
203
233
 
204
234
  Attributes:
205
235
  tooluniverse: ToolUniverse instance for tool execution
206
- composer_tool_name (str): Name of the ComposeTool for summarization
236
+ composer_tool (str): Name of the ComposeTool for summarization
207
237
  chunk_size (int): Size of chunks for processing large outputs
208
238
  focus_areas (str): Areas to focus on during summarization
209
239
  max_summary_length (int): Maximum length of final summary
210
240
  """
211
241
 
212
- def __init__(self, config: Dict[str, Any], tooluniverse):
242
+ def __init__(self, config: Dict[str, Any] | SummarizationHookConfig, tooluniverse):
213
243
  """
214
244
  Initialize the summarization hook.
215
245
 
@@ -217,25 +247,34 @@ class SummarizationHook(OutputHook):
217
247
  config (Dict[str, Any]): Hook configuration
218
248
  tooluniverse: ToolUniverse instance for executing summarization tools
219
249
  """
220
- super().__init__(config)
250
+ super().__init__(config if isinstance(config, dict) else {"hook_config": {}})
221
251
  self.tooluniverse = tooluniverse
222
- hook_config = config.get("hook_config", {})
223
- self.composer_tool_name = hook_config.get(
224
- "composer_tool", "OutputSummarizationComposer"
225
- )
226
- self.chunk_size = hook_config.get("chunk_size", 2000)
227
- self.focus_areas = hook_config.get("focus_areas", "key_findings_and_results")
228
- self.max_summary_length = hook_config.get("max_summary_length", 3000)
229
- # Optional timeout to prevent hangs in composer / LLM calls
230
- # If the composer does not return within this window, we gracefully fall back
231
- self.composer_timeout_sec = hook_config.get("composer_timeout_sec", 20)
252
+ # Normalize input to config dataclass
253
+ if isinstance(config, SummarizationHookConfig):
254
+ cfg = config
255
+ else:
256
+ raw = config.get("hook_config", {}) if isinstance(config, dict) else {}
257
+ # Breaking change: only support composer_tool going forward
258
+ cfg = SummarizationHookConfig(
259
+ composer_tool=raw.get("composer_tool", "OutputSummarizationComposer"),
260
+ chunk_size=raw.get("chunk_size", 2000),
261
+ focus_areas=raw.get("focus_areas", "key_findings_and_results"),
262
+ max_summary_length=raw.get("max_summary_length", 3000),
263
+ composer_timeout_sec=raw.get("composer_timeout_sec", 60),
264
+ )
265
+ self.config_obj = cfg.validate()
266
+ self.composer_tool = self.config_obj.composer_tool
267
+ self.chunk_size = self.config_obj.chunk_size
268
+ self.focus_areas = self.config_obj.focus_areas
269
+ self.max_summary_length = self.config_obj.max_summary_length
270
+ self.composer_timeout_sec = self.config_obj.composer_timeout_sec
232
271
 
233
272
  def process(
234
273
  self,
235
274
  result: Any,
236
- tool_name: str,
237
- arguments: Dict[str, Any],
238
- context: Dict[str, Any],
275
+ tool_name: Optional[str] = None,
276
+ arguments: Optional[Dict[str, Any]] = None,
277
+ context: Optional[Dict[str, Any]] = None,
239
278
  ) -> Any:
240
279
  """
241
280
  Execute summarization processing using Compose Summarizer Tool.
@@ -255,31 +294,39 @@ class SummarizationHook(OutputHook):
255
294
  Any: The summarized output, or original output if summarization fails
256
295
  """
257
296
  try:
297
+ # Backward-compat: allow calling process(result) only
298
+ if tool_name is None:
299
+ tool_name = "unknown_tool"
300
+ if arguments is None:
301
+ arguments = {}
302
+ if context is None:
303
+ context = {}
304
+ # Explicitly preserve None and empty string semantics
305
+ if result is None:
306
+ return None
307
+ if isinstance(result, str) and result == "":
308
+ return ""
258
309
  # Debug: basic context
259
310
  try:
260
311
  _len = len(str(result))
261
312
  except Exception:
262
313
  _len = -1
263
- import sys as _sys
264
-
265
- print(
266
- f"[SummarizationHook] process: tool={tool_name}, result_len={_len}, "
267
- f"chunk_size={self.chunk_size}, max_summary_length={self.max_summary_length}",
268
- file=_sys.stderr,
269
- flush=True,
314
+ _logger.debug(
315
+ "SummarizationHook process: tool=%s, result_len=%s, chunk_size=%s, max_summary_length=%s",
316
+ tool_name,
317
+ _len,
318
+ self.chunk_size,
319
+ self.max_summary_length,
270
320
  )
271
321
  # Check if the required tools are available
272
322
  if (
273
- self.composer_tool_name not in self.tooluniverse.callable_functions
274
- and self.composer_tool_name not in self.tooluniverse.all_tool_dict
323
+ self.composer_tool not in self.tooluniverse.callable_functions
324
+ and self.composer_tool not in self.tooluniverse.all_tool_dict
275
325
  ):
276
- print(
277
- f" SummarizationHook: {self.composer_tool_name} tool is not available."
326
+ _logger.warning(
327
+ "Summarization tool '%s' not available; returning original output",
328
+ self.composer_tool,
278
329
  )
279
- print(
280
- " This usually means the output_summarization tools are not loaded."
281
- )
282
- print(" Returning original output without summarization.")
283
330
  return result
284
331
 
285
332
  # Prepare parameters for Compose Summarizer Tool
@@ -293,10 +340,10 @@ class SummarizationHook(OutputHook):
293
340
  }
294
341
 
295
342
  # Call Compose Summarizer Tool through ToolUniverse
296
- print(
297
- f"[SummarizationHook] calling composer tool: {self.composer_tool_name} (timeout={self.composer_timeout_sec}s)",
298
- file=_sys.stderr,
299
- flush=True,
343
+ _logger.debug(
344
+ "Calling composer tool '%s' (timeout=%ss)",
345
+ self.composer_tool,
346
+ self.composer_timeout_sec,
300
347
  )
301
348
  # Run composer with timeout to avoid hangs
302
349
  try:
@@ -306,7 +353,7 @@ class SummarizationHook(OutputHook):
306
353
 
307
354
  def _call_composer():
308
355
  return self.tooluniverse.run_one_function(
309
- {"name": self.composer_tool_name, "arguments": composer_args}
356
+ {"name": self.composer_tool, "arguments": composer_args}
310
357
  )
311
358
 
312
359
  with ThreadPoolExecutor(max_workers=1) as _pool:
@@ -314,28 +361,20 @@ class SummarizationHook(OutputHook):
314
361
  composer_result = _future.result(timeout=self.composer_timeout_sec)
315
362
  except Exception as _e_timeout:
316
363
  # Timeout or execution error; log and fall back to original output
317
- print(
318
- f"[SummarizationHook] composer execution failed/timeout: {_e_timeout}",
319
- file=_sys.stderr,
320
- flush=True,
321
- )
364
+ _logger.warning("Composer execution failed/timeout: %s", _e_timeout)
322
365
  return result
323
366
  # Debug: show composer result meta
324
367
  try:
325
368
  if isinstance(composer_result, dict):
326
369
  success = composer_result.get("success", False)
327
370
  summary_len = len(composer_result.get("summary", ""))
328
- print(
329
- f"[SummarizationHook] composer_result: success={success} summary_len={summary_len}",
330
- file=_sys.stderr,
331
- flush=True,
371
+ _logger.debug(
372
+ "Composer result: success=%s summary_len=%s",
373
+ success,
374
+ summary_len,
332
375
  )
333
376
  except Exception as _e_dbg:
334
- print(
335
- f"[SummarizationHook] debug error inspecting composer_result: {_e_dbg}",
336
- file=_sys.stderr,
337
- flush=True,
338
- )
377
+ _logger.debug("Debug error inspecting composer_result: %s", _e_dbg)
339
378
 
340
379
  # Process Compose Tool result
341
380
  if isinstance(composer_result, dict) and composer_result.get("success"):
@@ -343,28 +382,20 @@ class SummarizationHook(OutputHook):
343
382
  elif isinstance(composer_result, str):
344
383
  return composer_result
345
384
  else:
346
- print(
347
- f"Warning: Compose Summarizer Tool returned unexpected result: {composer_result}"
385
+ _logger.warning(
386
+ "Compose Summarizer Tool returned unexpected result: %s",
387
+ composer_result,
348
388
  )
349
389
  return result
350
390
 
351
391
  except Exception as e:
352
392
  error_msg = str(e)
353
- import sys as _sys
354
-
355
- print(
356
- f"Error in summarization hook: {error_msg}",
357
- file=_sys.stderr,
358
- flush=True,
359
- )
360
-
393
+ _logger.error("Error in summarization hook: %s", error_msg)
361
394
  # Check if the error is due to missing tools
362
395
  if "not found" in error_msg.lower() or "ToolOutputSummarizer" in error_msg:
363
- print(
364
- "❌ SummarizationHook: Required summarization tools are not available."
396
+ _logger.error(
397
+ "Required summarization tools are not available. Please ensure the SMCP server is started with hooks enabled."
365
398
  )
366
- print(" Please ensure the SMCP server is started with hooks enabled.")
367
-
368
399
  return result
369
400
 
370
401
  def _extract_query_context(self, context: Dict[str, Any]) -> str:
@@ -424,20 +455,49 @@ class HookManager:
424
455
  self.tooluniverse = tooluniverse
425
456
  self.hooks: List[OutputHook] = []
426
457
  self.enabled = True
458
+ # Alias for tests that expect hooks_enabled flag
459
+ self.hooks_enabled = self.enabled
427
460
  self.config_path = config.get("config_path", "template/hook_config.json")
428
461
  self._pending_tools_to_load: List[str] = []
429
462
  self._load_hook_config()
430
463
 
431
464
  # Validate LLM API keys before loading hooks
432
465
  if not self._validate_llm_api_keys():
433
- print("⚠️ Warning: LLM API keys not available. Hooks will be disabled.")
434
- print(
435
- " To enable hooks, please set AZURE_OPENAI_API_KEY environment variable."
466
+ _logger.warning("LLM API keys not available. Hooks will be disabled.")
467
+ _logger.info(
468
+ "To enable hooks, please set LLM API keys environment variable."
436
469
  )
470
+ # Disable hook processing but still ensure required tools are available
471
+ # Tests expect hook-related tools (e.g., ToolOutputSummarizer) to be registered
472
+ # in ToolUniverse.callable_functions even when hooks are disabled.
437
473
  self.enabled = False
474
+
475
+ try:
476
+ # Proactively load tools required by summarization hooks so they are discoverable
477
+ all_hook_configs = []
478
+ global_hooks = (
479
+ self.config.get("hooks", [])
480
+ if isinstance(self.config, dict)
481
+ else []
482
+ )
483
+ for hook_cfg in global_hooks:
484
+ all_hook_configs.append(hook_cfg)
485
+
486
+ # Attempt auto-load based on config; if config is empty, fall back to ensuring summarization tools
487
+ self._auto_load_hook_tools(all_hook_configs)
488
+ # Ensure tools are pre-instantiated into callable_functions if possible
489
+ self._ensure_hook_tools_loaded()
490
+ except Exception as _e:
491
+ # Non-fatal: we still proceed with disabled hooks
492
+ _logger.warning("Failed to preload hook tools without API keys: %s", _e)
493
+
494
+ # Do not proceed to create hook instances when disabled
495
+ # Keep hooks list empty and reflect flags
496
+ self.hooks_enabled = self.enabled
438
497
  return
439
498
 
440
499
  self._load_hooks()
500
+ self.hooks_enabled = self.enabled
441
501
 
442
502
  def apply_hooks(
443
503
  self,
@@ -482,7 +542,9 @@ class HookManager:
482
542
  # Check if hook is applicable to current tool
483
543
  if self._is_hook_applicable(hook, tool_name, context):
484
544
  if hook.should_trigger(result, tool_name, arguments, context):
485
- print(f"🔧 Applying hook: {hook.name} for tool: {tool_name}")
545
+ _logger.debug(
546
+ "Applying hook: %s for tool: %s", hook.name, tool_name
547
+ )
486
548
  result = hook.process(result, tool_name, arguments, context)
487
549
 
488
550
  return result
@@ -497,11 +559,11 @@ class HookManager:
497
559
  from .agentic_tool import AgenticTool
498
560
 
499
561
  if AgenticTool.has_any_api_keys():
500
- print("LLM API keys validated successfully")
562
+ _logger.debug("LLM API keys validated successfully")
501
563
  return True
502
564
  else:
503
- print("LLM API key validation failed: No API keys available")
504
- print(" To enable hooks, please set API key environment variables.")
565
+ _logger.error("LLM API key validation failed: No API keys available")
566
+ _logger.info("To enable hooks, please set API key environment variables.")
505
567
  return False
506
568
 
507
569
  def enable_hook(self, hook_name: str):
@@ -514,9 +576,9 @@ class HookManager:
514
576
  hook = self.get_hook(hook_name)
515
577
  if hook:
516
578
  hook.enabled = True
517
- print(f"Enabled hook: {hook_name}")
579
+ _logger.info("Enabled hook: %s", hook_name)
518
580
  else:
519
- print(f"Hook not found: {hook_name}")
581
+ _logger.error("Hook not found: %s", hook_name)
520
582
 
521
583
  def disable_hook(self, hook_name: str):
522
584
  """
@@ -528,9 +590,9 @@ class HookManager:
528
590
  hook = self.get_hook(hook_name)
529
591
  if hook:
530
592
  hook.enabled = False
531
- print(f"Disabled hook: {hook_name}")
593
+ _logger.info("Disabled hook: %s", hook_name)
532
594
  else:
533
- print(f"Hook not found: {hook_name}")
595
+ _logger.error("Hook not found: %s", hook_name)
534
596
 
535
597
  def toggle_hooks(self, enabled: bool):
536
598
  """
@@ -540,8 +602,23 @@ class HookManager:
540
602
  enabled (bool): True to enable all hooks, False to disable
541
603
  """
542
604
  self.enabled = enabled
605
+ self.hooks_enabled = enabled
543
606
  status = "enabled" if enabled else "disabled"
544
- print(f"🔧 Hooks {status}")
607
+ _logger.info("Hooks %s", status)
608
+
609
+ # Backward-compat: tests reference enable_hooks/disable_hooks APIs
610
+ def enable_hooks(self):
611
+ """Enable hooks and (re)load configurations and required tools."""
612
+ self.toggle_hooks(True)
613
+ # Ensure tools and hooks are ready
614
+ self._ensure_hook_tools_loaded()
615
+ self._load_hooks()
616
+
617
+ def disable_hooks(self):
618
+ """Disable hooks and clear in-memory hook instances."""
619
+ self.toggle_hooks(False)
620
+ # Do not destroy config; just clear active hooks
621
+ self.hooks = []
545
622
 
546
623
  def reload_config(self, config_path: Optional[str] = None):
547
624
  """
@@ -555,7 +632,7 @@ class HookManager:
555
632
  self.config_path = config_path
556
633
  self._load_hook_config()
557
634
  self._load_hooks()
558
- print("🔄 Reloaded hook configuration")
635
+ _logger.info("Reloaded hook configuration")
559
636
 
560
637
  def get_hook(self, hook_name: str) -> Optional[OutputHook]:
561
638
  """
@@ -664,8 +741,8 @@ class HookManager:
664
741
  # Auto-load required tools for hooks
665
742
  self._auto_load_hook_tools(all_hook_configs)
666
743
 
667
- # Ensure hook tools are loaded
668
- self._ensure_hook_tools_loaded()
744
+ # Note: Hook tools will be pre-loaded when ToolUniverse.load_tools() is called
745
+ # This is handled in the _load_pending_tools method
669
746
 
670
747
  # Create hook instances
671
748
  for hook_config in all_hook_configs:
@@ -742,23 +819,25 @@ class HookManager:
742
819
  missing_tools.append(tool)
743
820
 
744
821
  if missing_tools:
745
- print(
746
- f"⚠️ Warning: Some hook tools could not be loaded: {missing_tools}"
822
+ _logger.warning(
823
+ "Some hook tools could not be loaded: %s", missing_tools
747
824
  )
748
- print(" This may cause summarization hooks to fail.")
825
+ _logger.info("This may cause summarization hooks to fail.")
749
826
  else:
750
- print(
751
- f"🔧 Auto-loaded hook tools: {', '.join(tools_to_load)}"
827
+ _logger.info(
828
+ "Auto-loaded hook tools: %s",
829
+ ", ".join(tools_to_load),
752
830
  )
753
831
  else:
754
832
  # Store tools to load later when ToolUniverse is ready
755
833
  self._pending_tools_to_load = tools_to_load
756
- print(
757
- f"🔧 Hook tools queued for loading: {', '.join(tools_to_load)}"
834
+ _logger.info(
835
+ "Hook tools queued for loading: %s",
836
+ ", ".join(tools_to_load),
758
837
  )
759
838
  except Exception as e:
760
- print(f"⚠️ Warning: Could not auto-load hook tools: {e}")
761
- print(" This will cause summarization hooks to fail.")
839
+ _logger.warning("Could not auto-load hook tools: %s", e)
840
+ _logger.info("This will cause summarization hooks to fail.")
762
841
 
763
842
  def _ensure_hook_tools_loaded(self):
764
843
  """
@@ -779,34 +858,49 @@ class HookManager:
779
858
  not hasattr(self.tooluniverse, "tool_category_dicts")
780
859
  or "output_summarization" not in self.tooluniverse.tool_category_dicts
781
860
  ):
782
- print("🔧 Loading output_summarization tools for hooks")
861
+ _logger.info("Loading output_summarization tools for hooks")
783
862
  self.tooluniverse.load_tools(["output_summarization"])
784
863
 
785
- # Verify the tools were loaded
786
- missing_tools = []
787
- required_tools = ["ToolOutputSummarizer", "OutputSummarizationComposer"]
788
- for tool in required_tools:
789
- if (
790
- hasattr(self.tooluniverse, "callable_functions")
791
- and tool not in self.tooluniverse.callable_functions
792
- and hasattr(self.tooluniverse, "all_tool_dict")
793
- and tool not in self.tooluniverse.all_tool_dict
794
- ):
795
- missing_tools.append(tool)
796
-
797
- if missing_tools:
798
- print(
799
- f"⚠️ Warning: Some hook tools could not be loaded: {missing_tools}"
800
- )
801
- print(" This may cause summarization hooks to fail")
802
- else:
803
- print(f"✅ Hook tools loaded successfully: {required_tools}")
864
+ # Pre-instantiate hook tools to ensure they're available in callable_functions
865
+ required_tools = ["ToolOutputSummarizer", "OutputSummarizationComposer"]
866
+ for tool_name in required_tools:
867
+ if (
868
+ tool_name in self.tooluniverse.all_tool_dict
869
+ and tool_name not in self.tooluniverse.callable_functions
870
+ ):
871
+ try:
872
+ self.tooluniverse.init_tool(
873
+ self.tooluniverse.all_tool_dict[tool_name],
874
+ add_to_cache=True,
875
+ )
876
+ _logger.debug("Pre-loaded hook tool: %s", tool_name)
877
+ except Exception as e:
878
+ _logger.warning(
879
+ "Failed to pre-load hook tool %s: %s", tool_name, e
880
+ )
881
+
882
+ # Verify the tools were loaded
883
+ missing_tools = []
884
+ for tool in required_tools:
885
+ if (
886
+ hasattr(self.tooluniverse, "callable_functions")
887
+ and tool not in self.tooluniverse.callable_functions
888
+ and hasattr(self.tooluniverse, "all_tool_dict")
889
+ and tool not in self.tooluniverse.all_tool_dict
890
+ ):
891
+ missing_tools.append(tool)
892
+
893
+ if missing_tools:
894
+ _logger.warning(
895
+ "Some hook tools could not be loaded: %s", missing_tools
896
+ )
897
+ _logger.info("This may cause summarization hooks to fail")
804
898
  else:
805
- print("🔧 Output_summarization tools already loaded")
899
+ _logger.info("Hook tools loaded successfully: %s", required_tools)
806
900
 
807
901
  except Exception as e:
808
- print(f"Error loading hook tools: {e}")
809
- print(" This will cause summarization hooks to fail")
902
+ _logger.error("Error loading hook tools: %s", e)
903
+ _logger.info("This will cause summarization hooks to fail")
810
904
 
811
905
  def _load_pending_tools(self):
812
906
  """
@@ -819,12 +913,16 @@ class HookManager:
819
913
  if self._pending_tools_to_load and hasattr(self.tooluniverse, "all_tools"):
820
914
  try:
821
915
  self.tooluniverse.load_tools(self._pending_tools_to_load)
822
- print(
823
- f"🔧 Loaded pending hook tools: {', '.join(self._pending_tools_to_load)}"
916
+ _logger.info(
917
+ "Loaded pending hook tools: %s",
918
+ ", ".join(self._pending_tools_to_load),
824
919
  )
825
920
  self._pending_tools_to_load = [] # Clear the pending list
826
921
  except Exception as e:
827
- print(f"⚠️ Warning: Could not load pending hook tools: {e}")
922
+ _logger.warning("Could not load pending hook tools: %s", e)
923
+
924
+ # Pre-load hook tools if they're available but not instantiated
925
+ self._ensure_hook_tools_loaded()
828
926
 
829
927
  def _is_hook_tool(self, tool_name: str) -> bool:
830
928
  """
@@ -874,7 +972,7 @@ class HookManager:
874
972
  file_save_config.update(enhanced_config.get("hook_config", {}))
875
973
  return FileSaveHook(file_save_config)
876
974
  else:
877
- print(f"Unknown hook type: {hook_type}")
975
+ _logger.error("Unknown hook type: %s", hook_type)
878
976
  return None
879
977
 
880
978
  def _apply_hook_type_defaults(self, hook_config: Dict[str, Any]) -> Dict[str, Any]:
@@ -918,38 +1016,7 @@ class HookManager:
918
1016
  "default_max_summary_length", 3000
919
1017
  ),
920
1018
  }
921
- elif hook_type == "FilteringHook":
922
- defaults = {
923
- "replacement_text": hook_type_defaults.get(
924
- "default_replacement_text", "[REDACTED]"
925
- ),
926
- "preserve_structure": hook_type_defaults.get(
927
- "default_preserve_structure", True
928
- ),
929
- "log_filtered_items": hook_type_defaults.get(
930
- "default_log_filtered_items", False
931
- ),
932
- }
933
- elif hook_type == "FormattingHook":
934
- defaults = {
935
- "indent_size": hook_type_defaults.get("default_indent_size", 2),
936
- "sort_keys": hook_type_defaults.get("default_sort_keys", True),
937
- "pretty_print": hook_type_defaults.get("default_pretty_print", True),
938
- "max_line_length": hook_type_defaults.get(
939
- "default_max_line_length", 100
940
- ),
941
- }
942
- elif hook_type == "ValidationHook":
943
- defaults = {
944
- "strict_mode": hook_type_defaults.get("default_strict_mode", False),
945
- "error_action": hook_type_defaults.get("default_error_action", "warn"),
946
- }
947
- elif hook_type == "LoggingHook":
948
- defaults = {
949
- "log_level": hook_type_defaults.get("default_log_level", "INFO"),
950
- "log_format": hook_type_defaults.get("default_log_format", "simple"),
951
- "max_log_size": hook_type_defaults.get("default_max_log_size", 1000),
952
- }
1019
+ # Removed unsupported hook types to avoid confusion
953
1020
  elif hook_type == "FileSaveHook":
954
1021
  defaults = {
955
1022
  "temp_dir": hook_type_defaults.get("default_temp_dir", None),
tooluniverse/smcp.py CHANGED
@@ -1842,7 +1842,14 @@ class SMCP(FastMCP):
1842
1842
  │ │
1843
1843
  ╰────────────────────────────────────────────────────────────────────────────╯
1844
1844
  """
1845
- print(banner)
1845
+ # In stdio mode, ensure the banner goes to stderr to avoid polluting stdout
1846
+ # which must exclusively carry JSON-RPC messages.
1847
+ import sys as _sys
1848
+
1849
+ if getattr(self, "_transport_type", None) == "stdio":
1850
+ print(banner, file=_sys.stderr)
1851
+ else:
1852
+ print(banner)
1846
1853
 
1847
1854
  def run(self, *args, **kwargs):
1848
1855
  """