Skip to main content
Glama
ingeno
by ingeno
cli.py8.66 kB
from __future__ import annotations import json import os from importlib.metadata import version from pathlib import Path from typing import TYPE_CHECKING, Any, Literal from pydantic import ValidationError from rich.align import Align from rich.console import Console, Group from rich.panel import Panel from rich.table import Table from rich.text import Text import fastmcp from fastmcp.utilities.logging import get_logger from fastmcp.utilities.mcp_server_config import MCPServerConfig from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource from fastmcp.utilities.types import get_cached_typeadapter if TYPE_CHECKING: from fastmcp import FastMCP logger = get_logger("cli.config") def is_already_in_uv_subprocess() -> bool: """Check if we're already running in a FastMCP uv subprocess.""" return bool(os.environ.get("FASTMCP_UV_SPAWNED")) def load_and_merge_config( server_spec: str | None, **cli_overrides, ) -> tuple[MCPServerConfig, str]: """Load config from server_spec and apply CLI overrides. This consolidates the config parsing logic that was duplicated across run, inspect, and dev commands. Args: server_spec: Python file, config file, URL, or None to auto-detect cli_overrides: CLI arguments that override config values Returns: Tuple of (MCPServerConfig, resolved_server_spec) """ config = None config_path = None # Auto-detect fastmcp.json if no server_spec provided if server_spec is None: config_path = Path("fastmcp.json") if not config_path.exists(): found_config = MCPServerConfig.find_config() if found_config: config_path = found_config else: logger.error( "No server specification provided and no fastmcp.json found in current directory.\n" "Please specify a server file or create a fastmcp.json configuration." ) raise FileNotFoundError("No server specification or fastmcp.json found") resolved_spec = str(config_path) logger.info(f"Using configuration from {config_path}") else: resolved_spec = server_spec # Load config if server_spec is a .json file if resolved_spec.endswith(".json"): config_path = Path(resolved_spec) if config_path.exists(): try: with open(config_path) as f: data = json.load(f) # Check if it's an MCPConfig first (has canonical mcpServers key) if "mcpServers" in data: # MCPConfig - we don't process these here, just pass through pass else: # Try to parse as MCPServerConfig try: adapter = get_cached_typeadapter(MCPServerConfig) config = adapter.validate_python(data) # Apply deployment settings if config.deployment: config.deployment.apply_runtime_settings(config_path) except ValidationError: # Not a valid MCPServerConfig, just pass through pass except (json.JSONDecodeError, FileNotFoundError): # Not a valid JSON file, just pass through pass # If we don't have a config object yet, create one from filesystem source if config is None: source = FileSystemSource(path=resolved_spec) config = MCPServerConfig(source=source) # Convert to dict for immutable transformation config_dict = config.model_dump() # Apply CLI overrides to config's environment (always exists due to default_factory) if python_override := cli_overrides.get("python"): config_dict["environment"]["python"] = python_override if packages_override := cli_overrides.get("with_packages"): # Merge packages - CLI packages are added to config packages existing = config_dict["environment"].get("dependencies") or [] config_dict["environment"]["dependencies"] = packages_override + existing if requirements_override := cli_overrides.get("with_requirements"): config_dict["environment"]["requirements"] = str(requirements_override) if project_override := cli_overrides.get("project"): config_dict["environment"]["project"] = str(project_override) if editable_override := cli_overrides.get("editable"): config_dict["environment"]["editable"] = editable_override # Apply deployment CLI overrides (always exists due to default_factory) if transport_override := cli_overrides.get("transport"): config_dict["deployment"]["transport"] = transport_override if host_override := cli_overrides.get("host"): config_dict["deployment"]["host"] = host_override if port_override := cli_overrides.get("port"): config_dict["deployment"]["port"] = port_override if path_override := cli_overrides.get("path"): config_dict["deployment"]["path"] = path_override if log_level_override := cli_overrides.get("log_level"): config_dict["deployment"]["log_level"] = log_level_override if server_args_override := cli_overrides.get("server_args"): config_dict["deployment"]["args"] = server_args_override # Create new config from modified dict new_config = MCPServerConfig(**config_dict) return new_config, resolved_spec LOGO_ASCII = r""" _ __ ___ _____ __ __ _____________ ____ ____ _ __ ___ .'____/___ ______/ /_/ |/ / ____/ __ \ |___ \ / __ \ _ __ ___ / /_ / __ `/ ___/ __/ /|_/ / / / /_/ / ___/ / / / / / _ __ ___ / __/ / /_/ (__ ) /_/ / / / /___/ ____/ / __/_/ /_/ / _ __ ___ /_/ \____/____/\__/_/ /_/\____/_/ /_____(*)____/ """.lstrip("\n") def log_server_banner( server: FastMCP[Any], transport: Literal["stdio", "http", "sse", "streamable-http"], *, host: str | None = None, port: int | None = None, path: str | None = None, ) -> None: """Creates and logs a formatted banner with server information and logo. Args: transport: The transport protocol being used server_name: Optional server name to display host: Host address (for HTTP transports) port: Port number (for HTTP transports) path: Server path (for HTTP transports) """ # Create the logo text logo_text = Text(LOGO_ASCII, style="bold green") # Create the main title title_text = Text("FastMCP 2.0", style="bold blue") # Create the information table info_table = Table.grid(padding=(0, 1)) info_table.add_column(style="bold", justify="center") # Emoji column info_table.add_column(style="cyan", justify="left") # Label column info_table.add_column(style="dim", justify="left") # Value column match transport: case "http" | "streamable-http": display_transport = "Streamable-HTTP" case "sse": display_transport = "SSE" case "stdio": display_transport = "STDIO" info_table.add_row("🖥️", "Server name:", server.name) info_table.add_row("📦", "Transport:", display_transport) # Show connection info based on transport if transport in ("http", "streamable-http", "sse"): if host and port: server_url = f"http://{host}:{port}" if path: server_url += f"/{path.lstrip('/')}" info_table.add_row("🔗", "Server URL:", server_url) # Add version information with explicit style overrides info_table.add_row("", "", "") info_table.add_row( "🏎️", "FastMCP version:", Text(fastmcp.__version__, style="dim white", no_wrap=True), ) info_table.add_row( "🤝", "MCP SDK version:", Text(version("mcp"), style="dim white", no_wrap=True), ) # Add documentation link info_table.add_row("", "", "") info_table.add_row("📚", "Docs:", "https://gofastmcp.com") info_table.add_row("🚀", "Deploy:", "https://fastmcp.cloud") # Create panel with logo, title, and information using Group panel_content = Group( Align.center(logo_text), Align.center(title_text), "", "", Align.center(info_table), ) panel = Panel( panel_content, border_style="dim", padding=(1, 4), expand=False, ) console = Console(stderr=True) console.print(Group("\n", panel, "\n"))

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/ingeno/mcp-openapi-lambda'

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