Source code for tooluniverse.smcp_server

#!/usr/bin/env python3
"""
SMCP Server Entry Point

This module provides the command-line entry point for running the SMCP (Scientific Model Context Protocol) server.
It creates a minimal SMCP server that exposes all ToolUniverse tools as MCP tools.
"""

import argparse
import sys
from .smcp import SMCP


[docs] def run_http_server(): """ Run SMCP server with streamable-http transport on localhost:8000 This function provides compatibility with the original MCP server's run_server function. """ parser = argparse.ArgumentParser( description="Start SMCP (Scientific Model Context Protocol) Server with HTTP transport", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Start server with all tools using HTTP transport tooluniverse-smcp-server # Start with specific categories tooluniverse-smcp-server --categories uniprot ChEMBL opentarget # Start with hooks enabled tooluniverse-smcp-server --hooks-enabled # Start with specific hook type tooluniverse-smcp-server --hook-type SummarizationHook # Start with custom hook configuration tooluniverse-smcp-server --hook-config-file /path/to/hook_config.json """, ) # Hook configuration options hook_group = parser.add_argument_group("Hook Configuration") hook_group.add_argument( "--hooks-enabled", action="store_true", help="Enable output processing hooks (default: False)", ) hook_group.add_argument( "--hook-type", choices=["SummarizationHook", "FileSaveHook"], help="Simple hook type selection (SummarizationHook or FileSaveHook)", ) hook_group.add_argument( "--hook-config-file", type=str, help="Path to custom hook configuration JSON file", ) # Server configuration options server_group = parser.add_argument_group("Server Configuration") server_group.add_argument( "--host", default="127.0.0.1", help="Server host address (default: 127.0.0.1)" ) server_group.add_argument( "--port", type=int, default=8000, help="Server port (default: 8000)" ) server_group.add_argument( "--name", default="ToolUniverse SMCP Server", help="Server name (default: ToolUniverse SMCP Server)", ) args = parser.parse_args() try: print("🚀 Starting ToolUniverse SMCP Server...") print("📡 Transport: streamable-http") print(f"🌐 Address: http://{args.host}:{args.port}") print(f"🏷️ Name: {args.name}") # Load hook configuration if specified hook_config = None if args.hook_config_file: import json with open(args.hook_config_file, "r") as f: hook_config = json.load(f) print(f"🔗 Hook config loaded from: {args.hook_config_file}") # Determine hook settings hooks_enabled = ( args.hooks_enabled or args.hook_type is not None or hook_config is not None ) if hooks_enabled: if args.hook_type: print(f"🔗 Hooks enabled: {args.hook_type}") elif hook_config: hook_count = len(hook_config.get("hooks", [])) print(f"🔗 Hooks enabled: {hook_count} custom hooks") else: print("🔗 Hooks enabled: default configuration") else: print("🔗 Hooks disabled") print() # Create SMCP server with hook support server = SMCP( name=args.name, auto_expose_tools=True, search_enabled=True, max_workers=5, stateless_http=True, # Enable stateless mode for MCPAutoLoaderTool compatibility hooks_enabled=hooks_enabled, hook_config=hook_config, hook_type=args.hook_type, ) # Run server with streamable-http transport server.run_simple(transport="http", host=args.host, port=args.port) except KeyboardInterrupt: print("\n🛑 Server stopped by user") sys.exit(0) except Exception as e: print(f"❌ Error starting server: {e}") sys.exit(1)
[docs] def run_stdio_server(): """ Run SMCP server with stdio transport for Claude Desktop integration This function provides compatibility with the original MCP server's run_claude_desktop function. It accepts the same arguments as run_smcp_server but forces transport='stdio'. """ parser = argparse.ArgumentParser( description="Start SMCP (Scientific Model Context Protocol) Server with stdio transport", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Start server with all tools using stdio transport (hooks enabled by default) tooluniverse-stdio # Start with specific categories tooluniverse-stdio --categories uniprot ChEMBL opentarget # Enable hooks tooluniverse-stdio --hooks # Use FileSaveHook instead of SummarizationHook tooluniverse-stdio --hook-type FileSaveHook # Use custom hook configuration tooluniverse-stdio --hook-config-file /path/to/hook_config.json # Start with categories but exclude specific tools tooluniverse-stdio --categories uniprot ChEMBL --exclude-tools "ChEMBL_get_molecule_by_chembl_id" # Start with all tools but exclude entire categories tooluniverse-stdio --exclude-categories mcp_auto_loader_boltz mcp_auto_loader_expert_feedback # Load only specific tools by name tooluniverse-stdio --include-tools "UniProt_get_entry_by_accession" "ChEMBL_get_molecule_by_chembl_id" # Load tools from a file tooluniverse-stdio --tools-file "/path/to/tool_names.txt" # Load additional config files tooluniverse-stdio --tool-config-files "custom:/path/to/custom_tools.json" # Include/exclude specific tool types tooluniverse-stdio --include-tool-types "OpenTarget" "ToolFinderEmbedding" tooluniverse-stdio --exclude-tool-types "ToolFinderLLM" "Unknown" # List available categories tooluniverse-stdio --list-categories # List all available tools tooluniverse-stdio --list-tools # Start minimal server with just search tools tooluniverse-stdio --categories special_tools tool_finder """, ) # Tool selection options tool_group = parser.add_mutually_exclusive_group() tool_group.add_argument( "--categories", nargs="*", metavar="CATEGORY", help="Specific tool categories to load (e.g., uniprot ChEMBL opentarget). Use --list-categories to see available options.", ) tool_group.add_argument( "--list-categories", action="store_true", help="List all available tool categories and exit", ) # Tool exclusion options parser.add_argument( "--exclude-tools", nargs="*", metavar="TOOL_NAME", help='Specific tool names to exclude from loading (e.g., "tool1" "tool2"). Can be used with --categories.', ) parser.add_argument( "--exclude-categories", nargs="*", metavar="CATEGORY", help="Tool categories to exclude from loading (e.g., mcp_auto_loader_boltz). Can be used with --categories.", ) # Tool inclusion options parser.add_argument( "--include-tools", nargs="*", metavar="TOOL_NAME", help="Specific tool names to include (only these tools will be loaded). Overrides category selection.", ) parser.add_argument( "--tools-file", metavar="FILE_PATH", help="Path to text file containing tool names to include (one per line). Overrides category selection.", ) parser.add_argument( "--tool-config-files", nargs="*", metavar="CATEGORY:PATH", help='Additional tool config files to load. Format: "category:/path/to/config.json"', ) # Tool type filtering options parser.add_argument( "--include-tool-types", nargs="*", metavar="TOOL_TYPE", help="Specific tool types to include (e.g., OpenTarget ToolFinderEmbedding). Only tools of these types will be loaded.", ) parser.add_argument( "--exclude-tool-types", nargs="*", metavar="TOOL_TYPE", help="Tool types to exclude from loading (e.g., ToolFinderLLM Unknown). Useful for excluding specific tool type classes.", ) # Listing options parser.add_argument( "--list-tools", action="store_true", help="List all available tools and exit" ) # Server configuration (stdio-specific) parser.add_argument( "--name", default="SMCP ToolUniverse Server", help="Server name (default: SMCP ToolUniverse Server)", ) parser.add_argument( "--no-search", action="store_true", help="Disable intelligent search functionality", ) parser.add_argument( "--max-workers", type=int, default=5, help="Maximum worker threads for concurrent execution (default: 5)", ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose logging" ) # Hook configuration options (default disabled for stdio) hook_group = parser.add_argument_group("Hook Configuration") hook_group.add_argument( "--hooks", action="store_true", help="Enable output processing hooks (default: disabled for stdio)", ) hook_group.add_argument( "--hook-type", choices=["SummarizationHook", "FileSaveHook"], help="Hook type to use (default: SummarizationHook when hooks are enabled)", ) hook_group.add_argument( "--hook-config-file", type=str, help="Path to custom hook configuration JSON file", ) args = parser.parse_args() # Handle --list-categories if args.list_categories: try: from .execute_function import ToolUniverse tu = ToolUniverse() tool_types = tu.get_tool_types() print("Available tool categories:") print("=" * 50) # Group categories for better readability scientific_db = [] literature = [] software = [] special = [] clinical = [] other = [] for category in sorted(tool_types): if category in [ "uniprot", "ChEMBL", "opentarget", "pubchem", "hpa", "rcsb_pdb", "reactome", "go", ]: scientific_db.append(category) elif category in [ "EuropePMC", "semantic_scholar", "pubtator", "OpenAlex", ]: literature.append(category) elif category.startswith("software_"): software.append(category) elif category in [ "special_tools", "tool_finder", "tool_composition", "agents", ]: special.append(category) elif category in [ "clinical_trials", "fda_drug_label", "fda_drug_adverse_event", "dailymed", "medlineplus", ]: clinical.append(category) else: other.append(category) if scientific_db: print("\n🔬 Scientific Databases:") for cat in scientific_db: print(f" {cat}") if literature: print("\n📚 Literature & Knowledge:") for cat in literature: print(f" {cat}") if clinical: print("\n🏥 Clinical & Drug Information:") for cat in clinical: print(f" {cat}") if software: print("\n💻 Software Tools:") for cat in software[:5]: # Show first 5 print(f" {cat}") if len(software) > 5: print(f" ... and {len(software) - 5} more software categories") if special: print("\n🛠 Special & Meta Tools:") for cat in special: print(f" {cat}") if other: print("\n📂 Other Categories:") for cat in other: print(f" {cat}") print(f"\nTotal: {len(tool_types)} categories available") print("\nCommon combinations:") print(" Scientific research: uniprot ChEMBL opentarget pubchem hpa") print(" Drug discovery: ChEMBL fda_drug_label clinical_trials pubchem") print(" Literature analysis: EuropePMC semantic_scholar pubtator") print(" Minimal setup: special_tools tool_finder") except Exception as e: print(f"❌ Error listing categories: {e}") sys.exit(1) return # Handle --list-tools if args.list_tools: try: from .execute_function import ToolUniverse tu = ToolUniverse() tu.load_tools() # Load all tools to list them print("Available tools:") print("=" * 50) # Group tools by category tools_by_category = {} for tool in tu.all_tools: tool_type = getattr(tool, "tool_type", "unknown") if tool_type not in tools_by_category: tools_by_category[tool_type] = [] tools_by_category[tool_type].append(tool.name) total_tools = 0 for category in sorted(tools_by_category.keys()): tools = sorted(tools_by_category[category]) print(f"\n📁 {category} ({len(tools)} tools):") for tool in tools[:10]: # Show first 10 tools per category print(f" {tool}") if len(tools) > 10: print(f" ... and {len(tools) - 10} more tools") total_tools += len(tools) print(f"\nTotal: {total_tools} tools available") print("\nNote: Use --exclude-tools to exclude specific tools by name") print(" Use --exclude-categories to exclude entire categories") except Exception as e: print(f"❌ Error listing tools: {e}") sys.exit(1) return try: print(f"🚀 Starting {args.name}...") print("📡 Transport: stdio (for Claude Desktop)") print(f"🔍 Search enabled: {not args.no_search}") if args.categories is not None: if len(args.categories) == 0: print("📂 No categories specified, loading all tools") tool_categories = None else: print(f"📂 Tool categories: {', '.join(args.categories)}") tool_categories = args.categories else: print("📂 Loading all tool categories") tool_categories = None # Handle exclusions and inclusions exclude_tools = args.exclude_tools or [] exclude_categories = args.exclude_categories or [] include_tools = args.include_tools or [] tools_file = args.tools_file include_tool_types = args.include_tool_types or [] exclude_tool_types = args.exclude_tool_types or [] # Parse tool config files tool_config_files = {} if args.tool_config_files: for config_spec in args.tool_config_files: if ":" in config_spec: category, path = config_spec.split(":", 1) tool_config_files[category] = path else: print(f"❌ Invalid tool config file format: {config_spec}") print(" Expected format: 'category:/path/to/config.json'") sys.exit(1) if exclude_tools: print(f"🚫 Excluding tools: {', '.join(exclude_tools)}") if exclude_categories: print(f"🚫 Excluding categories: {', '.join(exclude_categories)}") if include_tools: print(f"✅ Including only specific tools: {len(include_tools)} tools") if tools_file: print(f"📄 Loading tools from file: {tools_file}") if tool_config_files: print(f"📦 Additional config files: {', '.join(tool_config_files.keys())}") if include_tool_types: print(f"🎯 Including tool types: {', '.join(include_tool_types)}") if exclude_tool_types: print(f"🚫 Excluding tool types: {', '.join(exclude_tool_types)}") # Load hook configuration if specified hook_config = None if args.hook_config_file: import json with open(args.hook_config_file, "r") as f: hook_config = json.load(f) print(f"🔗 Hook config loaded from: {args.hook_config_file}") # Determine hook settings (default disabled for stdio) hooks_enabled = ( args.hooks or args.hook_type is not None or hook_config is not None ) # Set default hook type if hooks are enabled but no type specified hook_type = args.hook_type if hooks_enabled and hook_type is None: hook_type = "SummarizationHook" if hooks_enabled: if hook_type: print(f"🔗 Hooks enabled: {hook_type}") elif hook_config: hook_count = len(hook_config.get("hooks", [])) print(f"🔗 Hooks enabled: {hook_count} custom hooks") else: print("🔗 Hooks enabled: default configuration") else: print("🔗 Hooks disabled") print(f"⚡ Max workers: {args.max_workers}") print() # Create SMCP server with hook support server = SMCP( name=args.name, tool_categories=tool_categories, exclude_tools=exclude_tools, exclude_categories=exclude_categories, include_tools=include_tools, tools_file=tools_file, tool_config_files=tool_config_files, include_tool_types=include_tool_types, exclude_tool_types=exclude_tool_types, search_enabled=not args.no_search, max_workers=args.max_workers, stateless_http=True, # Enable stateless mode for MCPAutoLoaderTool compatibility hooks_enabled=hooks_enabled, hook_config=hook_config, hook_type=hook_type, ) # Run server with stdio transport (forced) server.run_simple(transport="stdio") except KeyboardInterrupt: print("\n🛑 Server stopped by user") sys.exit(0) except Exception as e: print(f"❌ Error starting server: {e}") if args.verbose: import traceback traceback.print_exc() sys.exit(1)
[docs] def run_smcp_server(): """ Main entry point for the SMCP server command. This function is called when running `tooluniverse-smcp` from the command line. """ parser = argparse.ArgumentParser( description="Start SMCP (Scientific Model Context Protocol) Server", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Start server with all tools on port 7890 tooluniverse-smcp --port 7890 # Start with specific categories tooluniverse-smcp --categories uniprot ChEMBL opentarget --port 8000 # Start with categories but exclude specific tools tooluniverse-smcp --categories uniprot ChEMBL --exclude-tools "ChEMBL_get_molecule_by_chembl_id" --port 8000 # Start with all tools but exclude entire categories tooluniverse-smcp --exclude-categories mcp_auto_loader_boltz mcp_auto_loader_expert_feedback --port 8000 # Load only specific tools by name tooluniverse-smcp --include-tools "UniProt_get_entry_by_accession" "ChEMBL_get_molecule_by_chembl_id" --port 8000 # Load tools from a file tooluniverse-smcp --tools-file "/path/to/tool_names.txt" --port 8000 # Load additional config files tooluniverse-smcp --tool-config-files "custom:/path/to/custom_tools.json" --port 8000 # Include/exclude specific tool types tooluniverse-smcp --include-tool-types "OpenTarget" "ToolFinderEmbedding" --port 8000 tooluniverse-smcp --exclude-tool-types "ToolFinderLLM" "Unknown" --port 8000 # List available categories tooluniverse-smcp --list-categories # List all available tools tooluniverse-smcp --list-tools # Start minimal server with just search tools tooluniverse-smcp --categories special_tools tool_finder --port 7000 # Start server for Claude Desktop (stdio transport) tooluniverse-smcp --transport stdio """, ) # Tool selection options tool_group = parser.add_mutually_exclusive_group() tool_group.add_argument( "--categories", nargs="*", metavar="CATEGORY", help="Specific tool categories to load (e.g., uniprot ChEMBL opentarget). Use --list-categories to see available options.", ) tool_group.add_argument( "--list-categories", action="store_true", help="List all available tool categories and exit", ) # Tool exclusion options parser.add_argument( "--exclude-tools", nargs="*", metavar="TOOL_NAME", help='Specific tool names to exclude from loading (e.g., "tool1" "tool2"). Can be used with --categories.', ) parser.add_argument( "--exclude-categories", nargs="*", metavar="CATEGORY", help="Tool categories to exclude from loading (e.g., mcp_auto_loader_boltz). Can be used with --categories.", ) # Tool inclusion options parser.add_argument( "--include-tools", nargs="*", metavar="TOOL_NAME", help="Specific tool names to include (only these tools will be loaded). Overrides category selection.", ) parser.add_argument( "--tools-file", metavar="FILE_PATH", help="Path to text file containing tool names to include (one per line). Overrides category selection.", ) parser.add_argument( "--tool-config-files", nargs="*", metavar="CATEGORY:PATH", help='Additional tool config files to load. Format: "category:/path/to/config.json"', ) # Tool type filtering options parser.add_argument( "--include-tool-types", nargs="*", metavar="TOOL_TYPE", help="Specific tool types to include (e.g., OpenTarget ToolFinderEmbedding). Only tools of these types will be loaded.", ) parser.add_argument( "--exclude-tool-types", nargs="*", metavar="TOOL_TYPE", help="Tool types to exclude from loading (e.g., ToolFinderLLM Unknown). Useful for excluding specific tool type classes.", ) # Listing options parser.add_argument( "--list-tools", action="store_true", help="List all available tools and exit" ) # Server configuration parser.add_argument( "--transport", choices=["stdio", "http", "sse"], default="http", help="Transport protocol to use (default: http)", ) parser.add_argument( "--host", default="0.0.0.0", help="Host to bind to for HTTP/SSE transport (default: 0.0.0.0)", ) parser.add_argument( "--port", type=int, default=7000, help="Port to bind to for HTTP/SSE transport (default: 7000)", ) parser.add_argument( "--name", default="SMCP ToolUniverse Server", help="Server name (default: SMCP ToolUniverse Server)", ) parser.add_argument( "--no-search", action="store_true", help="Disable intelligent search functionality", ) parser.add_argument( "--max-workers", type=int, default=5, help="Maximum worker threads for concurrent execution (default: 5)", ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose logging" ) # Hook configuration options hook_group = parser.add_argument_group("Hook Configuration") hook_group.add_argument( "--hooks-enabled", action="store_true", help="Enable output processing hooks (default: False)", ) hook_group.add_argument( "--hook-type", choices=["SummarizationHook", "FileSaveHook"], help="Simple hook type selection (SummarizationHook or FileSaveHook)", ) hook_group.add_argument( "--hook-config-file", type=str, help="Path to custom hook configuration JSON file", ) args = parser.parse_args() # Handle --list-categories if args.list_categories: try: from .execute_function import ToolUniverse tu = ToolUniverse() tool_types = tu.get_tool_types() print("Available tool categories:") print("=" * 50) # Group categories for better readability scientific_db = [] literature = [] software = [] special = [] clinical = [] other = [] for category in sorted(tool_types): if category in [ "uniprot", "ChEMBL", "opentarget", "pubchem", "hpa", "rcsb_pdb", "reactome", "go", ]: scientific_db.append(category) elif category in [ "EuropePMC", "semantic_scholar", "pubtator", "OpenAlex", ]: literature.append(category) elif category.startswith("software_"): software.append(category) elif category in [ "special_tools", "tool_finder", "tool_composition", "agents", ]: special.append(category) elif category in [ "clinical_trials", "fda_drug_label", "fda_drug_adverse_event", "dailymed", "medlineplus", ]: clinical.append(category) else: other.append(category) if scientific_db: print("\n🔬 Scientific Databases:") for cat in scientific_db: print(f" {cat}") if literature: print("\n📚 Literature & Knowledge:") for cat in literature: print(f" {cat}") if clinical: print("\n🏥 Clinical & Drug Information:") for cat in clinical: print(f" {cat}") if software: print("\n💻 Software Tools:") for cat in software[:5]: # Show first 5 print(f" {cat}") if len(software) > 5: print(f" ... and {len(software) - 5} more software categories") if special: print("\n🛠 Special & Meta Tools:") for cat in special: print(f" {cat}") if other: print("\n📂 Other Categories:") for cat in other: print(f" {cat}") print(f"\nTotal: {len(tool_types)} categories available") print("\nCommon combinations:") print(" Scientific research: uniprot ChEMBL opentarget pubchem hpa") print(" Drug discovery: ChEMBL fda_drug_label clinical_trials pubchem") print(" Literature analysis: EuropePMC semantic_scholar pubtator") print(" Minimal setup: special_tools tool_finder") except Exception as e: print(f"❌ Error listing categories: {e}") sys.exit(1) return # Handle --list-tools if args.list_tools: try: from .execute_function import ToolUniverse tu = ToolUniverse() tu.load_tools() # Load all tools to list them print("Available tools:") print("=" * 50) # Group tools by category tools_by_category = {} for tool in tu.all_tools: tool_type = getattr(tool, "tool_type", "unknown") if tool_type not in tools_by_category: tools_by_category[tool_type] = [] tools_by_category[tool_type].append(tool.name) total_tools = 0 for category in sorted(tools_by_category.keys()): tools = sorted(tools_by_category[category]) print(f"\n📁 {category} ({len(tools)} tools):") for tool in tools[:10]: # Show first 10 tools per category print(f" {tool}") if len(tools) > 10: print(f" ... and {len(tools) - 10} more tools") total_tools += len(tools) print(f"\nTotal: {total_tools} tools available") print("\nNote: Use --exclude-tools to exclude specific tools by name") print(" Use --exclude-categories to exclude entire categories") except Exception as e: print(f"❌ Error listing tools: {e}") sys.exit(1) return try: print(f"🚀 Starting {args.name}...") print(f"📡 Transport: {args.transport}") if args.transport in ["http", "sse"]: print(f"🌐 Address: http://{args.host}:{args.port}") print(f"🔍 Search enabled: {not args.no_search}") if args.categories is not None: if len(args.categories) == 0: print("📂 No categories specified, loading all tools") tool_categories = None else: print(f"📂 Tool categories: {', '.join(args.categories)}") tool_categories = args.categories else: print("📂 Loading all tool categories") tool_categories = None # Handle exclusions and inclusions exclude_tools = args.exclude_tools or [] exclude_categories = args.exclude_categories or [] include_tools = args.include_tools or [] tools_file = args.tools_file include_tool_types = args.include_tool_types or [] exclude_tool_types = args.exclude_tool_types or [] # Parse tool config files tool_config_files = {} if args.tool_config_files: for config_spec in args.tool_config_files: if ":" in config_spec: category, path = config_spec.split(":", 1) tool_config_files[category] = path else: print(f"❌ Invalid tool config file format: {config_spec}") print(" Expected format: 'category:/path/to/config.json'") sys.exit(1) if exclude_tools: print(f"🚫 Excluding tools: {', '.join(exclude_tools)}") if exclude_categories: print(f"🚫 Excluding categories: {', '.join(exclude_categories)}") if include_tools: print(f"✅ Including only specific tools: {len(include_tools)} tools") if tools_file: print(f"📄 Loading tools from file: {tools_file}") if tool_config_files: print(f"📦 Additional config files: {', '.join(tool_config_files.keys())}") if include_tool_types: print(f"🎯 Including tool types: {', '.join(include_tool_types)}") if exclude_tool_types: print(f"🚫 Excluding tool types: {', '.join(exclude_tool_types)}") # Load hook configuration if specified hook_config = None if args.hook_config_file: import json with open(args.hook_config_file, "r") as f: hook_config = json.load(f) print(f"🔗 Hook config loaded from: {args.hook_config_file}") # Determine hook settings hooks_enabled = ( args.hooks_enabled or args.hook_type is not None or hook_config is not None ) if hooks_enabled: if args.hook_type: print(f"🔗 Hooks enabled: {args.hook_type}") elif hook_config: hook_count = len(hook_config.get("hooks", [])) print(f"🔗 Hooks enabled: {hook_count} custom hooks") else: print("🔗 Hooks enabled: default configuration") else: print("🔗 Hooks disabled") print(f"⚡ Max workers: {args.max_workers}") print() # Create SMCP server with hook support server = SMCP( name=args.name, tool_categories=tool_categories, exclude_tools=exclude_tools, exclude_categories=exclude_categories, include_tools=include_tools, tools_file=tools_file, tool_config_files=tool_config_files, include_tool_types=include_tool_types, exclude_tool_types=exclude_tool_types, search_enabled=not args.no_search, max_workers=args.max_workers, stateless_http=True, # Enable stateless mode for MCPAutoLoaderTool compatibility hooks_enabled=hooks_enabled, hook_config=hook_config, hook_type=args.hook_type, ) # Run server server.run_simple(transport=args.transport, host=args.host, port=args.port) except KeyboardInterrupt: print("\n🛑 Server stopped by user") sys.exit(0) except Exception as e: print(f"❌ Error starting server: {e}") if args.verbose: import traceback traceback.print_exc() sys.exit(1)
if __name__ == "__main__": run_smcp_server()