Skip to main content
Glama
main.py30.9 kB
"""Unified launcher for Ludus MCP client with multiple UI modes.""" import asyncio import os import subprocess import sys from pathlib import Path from typing import Optional try: import click except ImportError: print("ERROR: click library not installed. Install with: pip install click") sys.exit(1) @click.group(invoke_without_command=True, context_settings={"max_content_width": 120}) @click.option('--version', is_flag=True, help='Show version information') @click.pass_context def cli(ctx, version): """Ludus FastMCP Client - AI-Powered Cyber Range Management \b Features (v1.0): - 157 FastMCP tools across 15 modules - Skeleton templates for VMs and ranges - Ansible Galaxy & custom role management - Enhanced AI-assisted range building \b Setup: ludus-ai setup-llm # Setup local LLM with Ollama (interactive) \b Chat Interfaces: ludus-ai install anythingllm # Install AnythingLLM (Recommended) ludus-ai install openwebui # Install Open WebUI ludus-ai install opencode # Install OpenCode AI \b Maintenance: ludus-ai clear-cache --opencode # Clear OpenCode cache ludus-ai clear-cache --all # Clear all caches ludus-ai uninstall opencode # Uninstall OpenCode AI ludus-ai uninstall anythingllm # Uninstall AnythingLLM ludus-ai uninstall openwebui # Uninstall Open WebUI \b Direct Tools: ludus-ai tool list-tools # List all 157 FastMCP tools ludus-ai tool list-ranges # List all ranges with details ludus-ai tool get-inventory # Get range inventory ludus-ai tool call-tool <name> # Call any FastMCP tool directly """ if version: click.echo("Ludus FastMCP Client v1.0") click.echo("FastMCP Server: 157 tools") ctx.exit(0) # Store context for subcommands ctx.ensure_object(dict) # If no subcommand is provided, show help if ctx.invoked_subcommand is None: click.echo(ctx.get_help()) ctx.exit(0) # Web, GUI, and Chat commands removed - use AnythingLLM or Open WebUI instead @cli.group(name='install') def install_group(): """ Install professional chat interfaces for Ludus MCP. Choose between three community-maintained chat interfaces with beautiful dark themes and modern UIs. Examples: ludus-ai install anythingllm # Install AnythingLLM (Recommended) ludus-ai install openwebui # Install Open WebUI ludus-ai install opencode # Install OpenCode AI """ pass @cli.group(name='uninstall') def uninstall_group(): """ Uninstall chat interfaces and clean up configurations. Examples: ludus-ai uninstall opencode # Uninstall OpenCode AI ludus-ai uninstall anythingllm # Uninstall AnythingLLM ludus-ai uninstall openwebui # Uninstall Open WebUI """ pass @cli.command('clear-cache') @click.option('--all', 'clear_all', is_flag=True, help='Clear all caches (OpenCode, pipx, Python)') @click.option('--opencode', is_flag=True, help='Clear OpenCode cache only') @click.option('--pipx', is_flag=True, help='Clear pipx cache only') def clear_cache(clear_all, opencode, pipx): """ Clear caches for installed components. This clears cached data that might cause issues with updated versions. Examples: ludus-ai clear-cache --all # Clear all caches ludus-ai clear-cache --opencode # Clear OpenCode cache ludus-ai clear-cache --pipx # Clear pipx cache """ import shutil if not any([clear_all, opencode, pipx]): click.echo("[ERROR] Please specify which cache to clear", err=True) click.echo(" Use --all, --opencode, or --pipx", err=True) click.echo(" Run: ludus-ai clear-cache --help", err=True) sys.exit(1) cleared = [] errors = [] # Clear OpenCode cache if clear_all or opencode: opencode_cache = Path.home() / ".cache" / "opencode" if opencode_cache.exists(): try: shutil.rmtree(opencode_cache) cleared.append(f"[OK] OpenCode cache ({opencode_cache})") except Exception as e: errors.append(f"[ERROR] OpenCode cache: {e}") else: cleared.append("[OK] OpenCode cache (already clean)") # Clear pipx cache if clear_all or pipx: pipx_cache = Path.home() / ".local" / "pipx" / ".cache" if pipx_cache.exists(): try: shutil.rmtree(pipx_cache) cleared.append(f"[OK] Pipx cache ({pipx_cache})") except Exception as e: errors.append(f"[ERROR] Pipx cache: {e}") else: cleared.append("[OK] Pipx cache (already clean)") # Clear Python __pycache__ if clear_all: project_root = Path.cwd() pycache_count = 0 for pycache_dir in project_root.rglob("__pycache__"): try: shutil.rmtree(pycache_dir) pycache_count += 1 except Exception as e: errors.append(f"[ERROR] {pycache_dir}: {e}") if pycache_count > 0: cleared.append(f"[OK] Removed {pycache_count} __pycache__ directories") # Print results click.echo("\n🧹 Cache Cleanup Results:\n") for msg in cleared: click.echo(f" {msg}") if errors: click.echo("\n[WARNING] Errors:\n") for msg in errors: click.echo(f" {msg}", err=True) click.echo("\n[OK] Cache cleanup complete!") if opencode or clear_all: click.echo("\n[TIP] Restart OpenCode to use the updated MCP server:") click.echo(" opencode") @install_group.command('anythingllm') def install_anythingllm(): """ Install AnythingLLM chat interface (Recommended). AnythingLLM provides a beautiful dark theme, professional UI, and native MCP support. Best for most users. Features: - Beautiful dark theme UI - Desktop app or Docker - Native MCP support - Built-in RAG capabilities - ChatGPT-like experience After installation, access at: http://localhost:3001 """ # Find the script relative to the package installation script_path = Path(__file__).parent.parent / "scripts" / "install-anythingllm.sh" # If not found, try relative to current working directory if not script_path.exists(): script_path = Path.cwd() / "scripts" / "install-anythingllm.sh" if not script_path.exists(): click.echo("[ERROR] Installation script not found.", err=True) click.echo(f" Expected location: {script_path}", err=True) click.echo("\nPlease ensure you're running this from the ludus-fastmcp directory,", err=True) click.echo("or run the script manually:", err=True) click.echo(" ./scripts/install-anythingllm.sh", err=True) sys.exit(1) click.echo("[INFO] Installing AnythingLLM...") click.echo(f"[INFO] Script: {script_path}\n") try: result = subprocess.run( ["bash", str(script_path)], check=False, env=os.environ.copy() ) if result.returncode == 0: click.echo("\n[OK] AnythingLLM installation complete!") click.echo(" Access at: http://localhost:3001") # Configure MCP server click.echo("\n[INFO] Configuring Ludus FastMCP server...") try: from ludus_mcp.utils.mcp_config import configure_anythingllm success, message = configure_anythingllm(overwrite=False) if success: click.echo(f"[OK] {message}") else: click.echo(f"[WARNING] {message}", err=True) click.echo(" You can configure it manually later.", err=True) except Exception as e: click.echo(f"[WARNING] Failed to auto-configure MCP: {e}", err=True) click.echo(" You can configure it manually later.", err=True) else: click.echo(f"\n[ERROR] Installation failed with exit code {result.returncode}", err=True) sys.exit(result.returncode) except Exception as e: click.echo(f"\n[ERROR] Error during installation: {e}", err=True) sys.exit(1) @install_group.command('openwebui') def install_openwebui(): """ Install Open WebUI chat interface. Open WebUI provides a gorgeous modern UI with extensive customization options. Best for teams and production environments. Features: - Gorgeous modern dark theme - Multi-user support - Extensive customization - Production-ready - Web-only interface After installation, access at: http://localhost:3000 """ # Find the script relative to the package installation script_path = Path(__file__).parent.parent / "scripts" / "install-openwebui.sh" # If not found, try relative to current working directory if not script_path.exists(): script_path = Path.cwd() / "scripts" / "install-openwebui.sh" if not script_path.exists(): click.echo("[ERROR] Installation script not found.", err=True) click.echo(f" Expected location: {script_path}", err=True) click.echo("\nPlease ensure you're running this from the ludus-fastmcp directory,", err=True) click.echo("or run the script manually:", err=True) click.echo(" ./scripts/install-openwebui.sh", err=True) sys.exit(1) click.echo("[INFO] Installing Open WebUI...") click.echo(f"[INFO] Script: {script_path}\n") try: result = subprocess.run( ["bash", str(script_path)], check=False, env=os.environ.copy() ) if result.returncode == 0: click.echo("\n[OK] Open WebUI installation complete!") click.echo(" Access at: http://localhost:3000") # Configure MCP server click.echo("\n[INFO] Configuring Ludus FastMCP server...") try: from ludus_mcp.utils.mcp_config import configure_openwebui success, message = configure_openwebui(overwrite=False) if success: click.echo(f"[OK] {message}") else: click.echo(f"[WARNING] {message}", err=True) click.echo(" You can configure it manually later.", err=True) except Exception as e: click.echo(f"[WARNING] Failed to auto-configure MCP: {e}", err=True) click.echo(" You can configure it manually later.", err=True) else: click.echo(f"\n[ERROR] Installation failed with exit code {result.returncode}", err=True) sys.exit(result.returncode) except Exception as e: click.echo(f"\n[ERROR] Error during installation: {e}", err=True) sys.exit(1) @install_group.command('opencode') def install_opencode(): """ Install OpenCode AI terminal agent. OpenCode AI is a powerful terminal-native AI coding agent with native MCP support. Best for developers who prefer terminal-based workflows. Features: - Terminal-native AI agent - Native MCP support - LSP integration - Multi-session support - 75+ LLM providers - Privacy-first, local execution After installation, run: opencode """ # Find the script relative to the package installation script_path = Path(__file__).parent.parent / "scripts" / "install-opencode.sh" # If not found, try relative to current working directory if not script_path.exists(): script_path = Path.cwd() / "scripts" / "install-opencode.sh" if not script_path.exists(): click.echo("[ERROR] Installation script not found.", err=True) click.echo(f" Expected location: {script_path}", err=True) click.echo("\nPlease ensure you're running this from the ludus-fastmcp directory,", err=True) click.echo("or run the script manually:", err=True) click.echo(" ./scripts/install-opencode.sh", err=True) sys.exit(1) click.echo("[INFO] Installing OpenCode AI...") click.echo(f"[INFO] Script: {script_path}\n") try: result = subprocess.run( ["bash", str(script_path)], check=False, env=os.environ.copy() ) if result.returncode == 0: click.echo("\n[OK] OpenCode AI installation complete!") click.echo(" Run: opencode") # Configure MCP server click.echo("\n[INFO] Configuring Ludus FastMCP server...") try: from ludus_mcp.utils.mcp_config import configure_opencode success, message = configure_opencode(overwrite=False) if success: click.echo(f"[OK] {message}") else: click.echo(f"[WARNING] {message}", err=True) click.echo(" You can configure it manually later.", err=True) except Exception as e: click.echo(f"[WARNING] Failed to auto-configure MCP: {e}", err=True) click.echo(" You can configure it manually later.", err=True) else: click.echo(f"\n[ERROR] Installation failed with exit code {result.returncode}", err=True) sys.exit(result.returncode) except Exception as e: click.echo(f"\n[ERROR] Error during installation: {e}", err=True) sys.exit(1) @uninstall_group.command('opencode') @click.option('--keep-config', is_flag=True, help='Keep OpenCode configuration files') def uninstall_opencode(keep_config): """ Uninstall OpenCode AI and optionally remove configuration. This will: - Uninstall OpenCode AI binary - Optionally remove configuration files (unless --keep-config is used) - Clear OpenCode cache Examples: ludus-ai uninstall opencode # Full uninstall ludus-ai uninstall opencode --keep-config # Keep configs """ import shutil click.echo("🗑️ Uninstalling OpenCode AI...\n") removed = [] errors = [] # Uninstall OpenCode binary opencode_bin = Path.home() / ".opencode" if opencode_bin.exists(): try: shutil.rmtree(opencode_bin) removed.append(f"[OK] OpenCode binary ({opencode_bin})") except Exception as e: errors.append(f"[ERROR] OpenCode binary: {e}") else: removed.append("[OK] OpenCode binary (not found)") # Remove configuration (unless --keep-config) if not keep_config: opencode_config = Path.home() / ".config" / "opencode" if opencode_config.exists(): try: shutil.rmtree(opencode_config) removed.append(f"[OK] OpenCode config ({opencode_config})") except Exception as e: errors.append(f"[ERROR] OpenCode config: {e}") else: removed.append("[OK] OpenCode config (not found)") else: click.echo("ℹ️ Keeping OpenCode configuration files") # Clear cache opencode_cache = Path.home() / ".cache" / "opencode" if opencode_cache.exists(): try: shutil.rmtree(opencode_cache) removed.append(f"[OK] OpenCode cache ({opencode_cache})") except Exception as e: errors.append(f"[ERROR] OpenCode cache: {e}") # Print results click.echo("\n[INFO] Uninstall Results:\n") for msg in removed: click.echo(f" {msg}") if errors: click.echo("\n[WARNING] Errors:\n") for msg in errors: click.echo(f" {msg}", err=True) if errors: click.echo("\n[WARNING] Uninstall completed with errors") sys.exit(1) else: click.echo("\n[OK] OpenCode AI uninstalled successfully!") @uninstall_group.command('anythingllm') @click.option('--keep-data', is_flag=True, help='Keep AnythingLLM data and configurations') def uninstall_anythingllm(keep_data): """ Uninstall AnythingLLM and optionally remove data. This will: - Stop and remove AnythingLLM Docker container - Optionally remove data directory (unless --keep-data is used) Examples: ludus-ai uninstall anythingllm # Full uninstall ludus-ai uninstall anythingllm --keep-data # Keep data """ click.echo("🗑️ Uninstalling AnythingLLM...\n") removed = [] errors = [] # Stop and remove Docker container try: result = subprocess.run( ["docker", "stop", "anythingllm"], capture_output=True, text=True ) if result.returncode == 0: removed.append("[OK] Stopped AnythingLLM container") except Exception as e: errors.append(f"[ERROR] Stop container: {e}") try: result = subprocess.run( ["docker", "rm", "anythingllm"], capture_output=True, text=True ) if result.returncode == 0: removed.append("[OK] Removed AnythingLLM container") except Exception as e: errors.append(f"[ERROR] Remove container: {e}") # Remove data (unless --keep-data) if not keep_data: import shutil anythingllm_data = Path.home() / ".anythingllm" if anythingllm_data.exists(): try: shutil.rmtree(anythingllm_data) removed.append(f"[OK] AnythingLLM data ({anythingllm_data})") except Exception as e: errors.append(f"[ERROR] AnythingLLM data: {e}") else: click.echo("ℹ️ Keeping AnythingLLM data directory") # Print results click.echo("\n[INFO] Uninstall Results:\n") for msg in removed: click.echo(f" {msg}") if errors: click.echo("\n[WARNING] Errors:\n") for msg in errors: click.echo(f" {msg}", err=True) if errors: click.echo("\n[WARNING] Uninstall completed with errors") sys.exit(1) else: click.echo("\n[OK] AnythingLLM uninstalled successfully!") @uninstall_group.command('openwebui') @click.option('--keep-data', is_flag=True, help='Keep Open WebUI data and configurations') def uninstall_openwebui(keep_data): """ Uninstall Open WebUI and optionally remove data. This will: - Stop and remove Open WebUI Docker container - Optionally remove data directory (unless --keep-data is used) Examples: ludus-ai uninstall openwebui # Full uninstall ludus-ai uninstall openwebui --keep-data # Keep data """ click.echo("🗑️ Uninstalling Open WebUI...\n") removed = [] errors = [] # Stop and remove Docker container try: result = subprocess.run( ["docker", "stop", "open-webui"], capture_output=True, text=True ) if result.returncode == 0: removed.append("[OK] Stopped Open WebUI container") except Exception as e: errors.append(f"[ERROR] Stop container: {e}") try: result = subprocess.run( ["docker", "rm", "open-webui"], capture_output=True, text=True ) if result.returncode == 0: removed.append("[OK] Removed Open WebUI container") except Exception as e: errors.append(f"[ERROR] Remove container: {e}") # Remove data (unless --keep-data) if not keep_data: import shutil openwebui_data = Path.home() / ".open-webui" if openwebui_data.exists(): try: shutil.rmtree(openwebui_data) removed.append(f"[OK] Open WebUI data ({openwebui_data})") except Exception as e: errors.append(f"[ERROR] Open WebUI data: {e}") else: click.echo("ℹ️ Keeping Open WebUI data directory") # Print results click.echo("\n[INFO] Uninstall Results:\n") for msg in removed: click.echo(f" {msg}") if errors: click.echo("\n[WARNING] Errors:\n") for msg in errors: click.echo(f" {msg}", err=True) if errors: click.echo("\n[WARNING] Uninstall completed with errors") sys.exit(1) else: click.echo("\n[OK] Open WebUI uninstalled successfully!") @cli.command('setup-llm') def setup_llm(): """ Setup local LLM with Ollama (interactive). This interactive script will: - Check if Ollama is installed and running - Show available models - Help you download a recommended model - Verify your configuration - Provide next steps for each chat interface Recommended models: - qwen2.5-coder:7b (Best for coding/Ludus tasks) - llama3.2:3b (Fast, low RAM) - deepseek-coder-v2:16b (Most capable, high RAM) After setup, all three interfaces (OpenCode, AnythingLLM, Open WebUI) can use your local LLM via Ollama. """ # Find the script relative to the package installation script_path = Path(__file__).parent.parent / "scripts" / "setup-local-llm.sh" # If not found, try relative to current working directory if not script_path.exists(): script_path = Path.cwd() / "scripts" / "setup-local-llm.sh" if not script_path.exists(): click.echo("[ERROR] setup-local-llm.sh script not found", err=True) click.echo(" Expected locations:", err=True) click.echo(f" - {Path(__file__).parent.parent / 'scripts' / 'setup-local-llm.sh'}", err=True) click.echo(" - ./scripts/setup-local-llm.sh", err=True) sys.exit(1) click.echo("[INFO] Setting up local LLM with Ollama...") click.echo(f"[INFO] Script: {script_path}\n") try: result = subprocess.run( ["bash", str(script_path)], check=False, env=os.environ.copy() ) if result.returncode != 0: click.echo(f"\n[ERROR] Setup failed with exit code {result.returncode}", err=True) sys.exit(result.returncode) except Exception as e: click.echo(f"\n[ERROR] Error during setup: {e}", err=True) sys.exit(1) @cli.group(name='tool') def tool_group(): """ Execute Ludus MCP tools directly (for scripting). This provides direct tool execution without interactive chat. Perfect for scripts and automation. Examples: ludus-ai tool list-tools ludus-ai tool list-scenarios ludus-ai tool list-ranges """ pass @tool_group.command('list-tools') @click.option('--command', default='ludus-fastmcp', help='FastMCP server command (default: ludus-fastmcp)') @click.option('--env', multiple=True, help='Environment variables (KEY=VALUE)') def list_tools(command, env): """List all available FastMCP tools (125 total).""" async def _run(): from ludus_mcp.mcp_client.unified_client import UnifiedMCPClient env_dict = {} for e in env: if '=' in e: key, value = e.split('=', 1) env_dict[key] = value async with UnifiedMCPClient(command=command, env=env_dict) as client: tools = await client.list_tools() click.echo("\nAvailable Ludus MCP Tools:\n") for tool in tools: click.echo(f" - {tool['name']}") if 'description' in tool: click.echo(f" {tool['description']}") click.echo() try: asyncio.run(_run()) except Exception as e: click.echo(f"[ERROR] Error: {e}", err=True) sys.exit(1) @tool_group.command('list-scenarios') @click.option('--command', default='ludus-fastmcp', help='FastMCP server command (default: ludus-fastmcp)') @click.option('--env', multiple=True, help='Environment variables (KEY=VALUE)') def list_scenarios(command, env): """List all available scenarios.""" async def _run(): from ludus_mcp.mcp_client.unified_client import UnifiedMCPClient import json env_dict = {} for e in env: if '=' in e: key, value = e.split('=', 1) env_dict[key] = value async with UnifiedMCPClient(command=command, env=env_dict) as client: result = await client.call_tool("ludus.list_scenarios", {}) click.echo("\nAvailable Scenarios:\n") # Parse the result dict from UnifiedMCPClient if isinstance(result, dict) and 'result' in result: # Parse the JSON string result try: result_data = json.loads(result['result']) if 'error' in result_data: click.echo(f"[ERROR] Error: {result_data['error']}", err=True) else: click.echo(json.dumps(result_data, indent=2)) except json.JSONDecodeError: click.echo(result['result']) else: click.echo(json.dumps(result, indent=2)) try: asyncio.run(_run()) except Exception as e: click.echo(f"[ERROR] Error: {e}", err=True) sys.exit(1) @tool_group.command('get-inventory') @click.option('--command', default='ludus-fastmcp', help='FastMCP server command (default: ludus-fastmcp)') @click.option('--env', multiple=True, help='Environment variables (KEY=VALUE)') def get_inventory(command, env): """Get Ansible inventory for your range.""" async def _run(): from ludus_mcp.mcp_client.unified_client import UnifiedMCPClient import json env_dict = {} for e in env: if '=' in e: key, value = e.split('=', 1) env_dict[key] = value async with UnifiedMCPClient(command=command, env=env_dict) as client: result = await client.call_tool("ludus.get_range_ansible_inventory", {}) click.echo("\nAnsible Inventory:\n") # Parse the result dict from UnifiedMCPClient if isinstance(result, dict) and 'result' in result: # Parse the JSON string result try: result_data = json.loads(result['result']) if 'error' in result_data: click.echo(f"[ERROR] Error: {result_data['error']}", err=True) else: click.echo(json.dumps(result_data, indent=2)) except json.JSONDecodeError: click.echo(result['result']) else: click.echo(json.dumps(result, indent=2)) try: asyncio.run(_run()) except Exception as e: click.echo(f"[ERROR] Error: {e}", err=True) sys.exit(1) @tool_group.command('list-ranges') @click.option('--command', default='ludus-fastmcp', help='FastMCP server command (default: ludus-fastmcp)') @click.option('--env', multiple=True, help='Environment variables (KEY=VALUE)') def list_ranges(command, env): """List all ranges (uses new range management tools).""" async def _run(): from ludus_mcp.mcp_client.unified_client import UnifiedMCPClient import json env_dict = {} for e in env: if '=' in e: key, value = e.split('=', 1) env_dict[key] = value async with UnifiedMCPClient(command=command, env=env_dict) as client: result = await client.call_tool("ludus.list_ranges", {}) click.echo("\nAvailable Ranges:\n") # Parse the result dict from UnifiedMCPClient if isinstance(result, dict) and 'result' in result: # Parse the JSON string result try: result_data = json.loads(result['result']) if 'error' in result_data: click.echo(f"[ERROR] Error: {result_data['error']}", err=True) else: click.echo(json.dumps(result_data, indent=2)) except json.JSONDecodeError: click.echo(result['result']) else: click.echo(json.dumps(result, indent=2)) try: asyncio.run(_run()) except Exception as e: click.echo(f"[ERROR] Error: {e}", err=True) sys.exit(1) @tool_group.command('call-tool') @click.argument('tool_name') @click.option('--args', default='{}', help='Tool arguments as JSON') @click.option('--command', default='ludus-fastmcp', help='FastMCP server command (default: ludus-fastmcp)') @click.option('--env', multiple=True, help='Environment variables (KEY=VALUE)') def call_tool(tool_name, args, command, env): """Call a specific FastMCP tool with arguments (125 tools available). Example: ludus-ai tool call-tool list_all_ranges_detailed ludus-ai tool call-tool create_custom_os_template --args '{"name": "kali-custom", "os_type": "debian"}' """ async def _run(): from ludus_mcp.mcp_client.unified_client import UnifiedMCPClient import json env_dict = {} for e in env: if '=' in e: key, value = e.split('=', 1) env_dict[key] = value # Parse the JSON arguments try: tool_args = json.loads(args) except json.JSONDecodeError as e: click.echo(f"[ERROR] Invalid JSON arguments: {e}", err=True) sys.exit(1) async with UnifiedMCPClient(command=command, env=env_dict) as client: click.echo(f"\nCalling tool: {tool_name}") if tool_args: click.echo(f"Arguments: {json.dumps(tool_args, indent=2)}\n") result = await client.call_tool(tool_name, tool_args) # Parse the result dict from UnifiedMCPClient if isinstance(result, dict) and 'result' in result: # Parse the JSON string result try: result_data = json.loads(result['result']) if 'error' in result_data: click.echo(f"\n[ERROR] Error: {result_data['error']}", err=True) else: click.echo(f"\nResult:\n{json.dumps(result_data, indent=2)}") except json.JSONDecodeError: click.echo(f"\nResult:\n{result['result']}") else: click.echo(f"\nResult:\n{json.dumps(result, indent=2)}") try: asyncio.run(_run()) except Exception as e: click.echo(f"[ERROR] Error: {e}", err=True) sys.exit(1) # Sessions command removed - session management is handled by AnythingLLM or Open WebUI def main(): """Main entry point.""" cli() if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tjnull/Ludus-FastMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server