cli.py•6.59 kB
"""
Command-line interface for the MCP server.
"""
import asyncio
import sys
from pathlib import Path
from typing import Optional
import click
from .app import create_app
from .core.config_loader import ConfigLoader, Settings, load_config
from .core.logger import configure_logging, get_logger
@click.group()
@click.version_option(version="0.1.0", prog_name="mcp-template")
def cli() -> None:
"""MCP Template - Production-ready MCP server template."""
pass
@cli.command()
@click.option(
"--config",
"-c",
type=click.Path(exists=True, path_type=Path),
help="Path to configuration file (default: config/config.yaml)",
)
@click.option(
"--env-file",
"-e",
type=click.Path(exists=True, path_type=Path),
help="Path to .env file (default: .env)",
)
@click.option(
"--debug",
is_flag=True,
help="Enable debug mode",
)
def run(
config: Optional[Path],
env_file: Optional[Path],
debug: bool,
) -> None:
"""Run the MCP server."""
try:
# Load configuration
settings = load_config(config_path=config, env_file=env_file)
# Override debug setting if flag is set
if debug:
settings.server.debug = True
settings.logging.level = "DEBUG"
# Create and run the application
app = create_app(settings)
asyncio.run(app.run())
except FileNotFoundError as e:
click.echo(f"Error: {e}", err=True)
click.echo("\nMake sure config/config.yaml exists or specify --config", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"Error: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option(
"--config",
"-c",
type=click.Path(exists=True, path_type=Path),
help="Path to configuration file",
)
@click.option(
"--env-file",
"-e",
type=click.Path(exists=True, path_type=Path),
help="Path to .env file",
)
def validate(config: Optional[Path], env_file: Optional[Path]) -> None:
"""Validate the configuration file."""
try:
settings = load_config(config_path=config, env_file=env_file)
click.echo("Configuration is valid!")
click.echo(f"\nServer: {settings.server.name} v{settings.server.version}")
click.echo(f"Registry enabled: {settings.registry.enabled}")
if settings.registry.enabled:
click.echo(f"Registry URL: {settings.registry.url}")
click.echo(f"\nEnabled tools: {', '.join(settings.tools.enabled)}")
click.echo(f"Enabled resources: {', '.join(settings.resources.enabled)}")
click.echo(f"Enabled prompts: {', '.join(settings.prompts.enabled)}")
except FileNotFoundError as e:
click.echo(f"Error: {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"Validation failed: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option(
"--config",
"-c",
type=click.Path(exists=True, path_type=Path),
help="Path to configuration file",
)
@click.option(
"--env-file",
"-e",
type=click.Path(exists=True, path_type=Path),
help="Path to .env file",
)
def health(config: Optional[Path], env_file: Optional[Path]) -> None:
"""Check server health and configuration."""
async def run_health_check() -> None:
try:
settings = load_config(config_path=config, env_file=env_file)
# Configure logging for health check
configure_logging(level=settings.logging.level, format=settings.logging.format)
click.echo("Running health checks...\n")
# Check configuration
click.echo("Configuration: OK")
# Check registry if enabled
if settings.registry.enabled:
from .registry.http_client import HTTPRegistryClient
async with HTTPRegistryClient(settings.registry) as client:
registry_healthy = await client.health_check()
status = "OK" if registry_healthy else "FAILED"
click.echo(f"Registry ({settings.registry.url}): {status}")
else:
click.echo("Registry: DISABLED")
click.echo("\nAll checks passed!")
except Exception as e:
click.echo(f"Health check failed: {e}", err=True)
sys.exit(1)
asyncio.run(run_health_check())
@cli.command()
def init() -> None:
"""Initialize a new MCP server configuration."""
config_dir = Path("config")
config_file = config_dir / "config.yaml"
env_file = Path(".env")
# Check if config already exists
if config_file.exists():
if not click.confirm(
f"{config_file} already exists. Overwrite?", default=False
):
click.echo("Initialization cancelled.")
return
# Create config directory
config_dir.mkdir(exist_ok=True)
# Copy default config
from importlib.resources import files
import shutil
click.echo(f"Creating {config_file}...")
# Since we can't use package resources easily, create a basic config
default_config = """# MCP Server Configuration
server:
name: "my-mcp-server"
version: "0.1.0"
description: "My MCP server"
debug: false
logging:
level: "INFO"
format: "json"
registry:
enabled: false
url: "https://registry.example.com/api/v1"
auth:
type: "api_key"
api_key: "${REGISTRY_API_KEY}"
tools:
enabled:
- "example_calculator"
- "example_search"
resources:
enabled:
- "example_config"
- "example_status"
prompts:
enabled:
- "example_prompt"
"""
with open(config_file, "w") as f:
f.write(default_config)
# Create .env.template
if not env_file.exists():
click.echo(f"Creating {env_file}...")
env_template = """# MCP Server Environment Variables
# Registry API Key (if using registry)
# REGISTRY_API_KEY=your-api-key-here
# Server configuration overrides
# SERVER__NAME=my-server
# SERVER__DEBUG=false
# Logging configuration
# LOGGING__LEVEL=INFO
"""
with open(env_file, "w") as f:
f.write(env_template)
click.echo("\nInitialization complete!")
click.echo(f"Configuration file created at: {config_file}")
click.echo(f"Environment file created at: {env_file}")
click.echo("\nNext steps:")
click.echo("1. Edit config/config.yaml to customize your server")
click.echo("2. Set environment variables in .env")
click.echo("3. Run: mcp-template run")
def main() -> None:
"""Main entry point."""
cli()
if __name__ == "__main__":
main()