"""CLI entry point for mcp-familywall. Commands: - setup : Interactive credential setup; stores email+password in OS keyring. - check : Validate stored credentials against the Family Wall API. - serve : Start the MCP server (launched by Claude Desktop). """ from __future__ import annotations import json import shutil import sys import click from mcp_familywall import __version__ @click.group(context_settings={"help_option_names": ["-h", "--help"]}, invoke_without_command=True) @click.version_option(__version__, "-v", "--version", prog_name="mcp-familywall") @click.pass_context def app(ctx: click.Context) -> None: """mcp-familywall — MCP server for Family Wall (read-only).""" if ctx.invoked_subcommand is None: click.echo(ctx.get_help()) # --------------------------------------------------------------------------- # setup # --------------------------------------------------------------------------- @app.command() def setup() -> None: """Interactive credential setup. Prompts for email and password, verifies them against the Family Wall API, stores them in the OS keyring, and prints a Claude Desktop config snippet. """ from mcp_familywall.auth import store_credentials from mcp_familywall.config import save_config from mcp_familywall.fw_client import FamilyWallClient, FamilyWallError click.echo("mcp-familywall setup\n") email = click.prompt("Family Wall E-Mail") password = click.prompt("Family Wall password", hide_input=True) # Verify credentials click.echo("\nVerifying credentials...") try: with FamilyWallClient() as client: client.login(email, password) client.logout() except FamilyWallError as exc: click.echo(click.style(f"Login failed: {exc}", fg="red"), err=True) sys.exit(1) except Exception as exc: click.echo(click.style(f"Connection error: {exc}", fg="red"), err=True) sys.exit(1) click.echo(click.style("Login successful!", fg="green")) # Store credentials ok = store_credentials(email, password) if ok: click.echo("Credentials stored in OS keyring.") else: click.echo( click.style("Keyring not available.", fg="yellow") + " Set environment variables instead:\n" f" FW_EMAIL={email}\n" " FW_PASSWORD=" ) # Ensure config file exists save_config() # Print Claude Desktop snippet _emit_claude_desktop_snippet() # --------------------------------------------------------------------------- # check # --------------------------------------------------------------------------- @app.command() def check() -> None: """Validate stored credentials against the Family Wall API.""" from mcp_familywall.auth import get_credentials from mcp_familywall.fw_client import FamilyWallClient, FamilyWallError click.echo("Checking Family Wall credentials...") try: email, password = get_credentials() except RuntimeError as exc: click.echo(click.style(f"Error: {exc}", fg="red"), err=True) sys.exit(1) click.echo(f"Email: {email}") try: with FamilyWallClient() as client: client.login(email, password) client.logout() except FamilyWallError as exc: click.echo(click.style(f"Authentication failed: {exc}", fg="red"), err=True) sys.exit(1) except Exception as exc: click.echo(click.style(f"Connection error: {exc}", fg="red"), err=True) sys.exit(1) click.echo(click.style("Authentication successful!", fg="green")) # --------------------------------------------------------------------------- # serve # --------------------------------------------------------------------------- @app.command() def serve() -> None: """Start the MCP server (launched by Claude Desktop).""" from mcp_familywall.server import create_server server = create_server() server.run(transport="stdio") # --------------------------------------------------------------------------- # helpers # --------------------------------------------------------------------------- def _emit_claude_desktop_snippet() -> None: """Print a Claude Desktop JSON config snippet.""" uvx_path = shutil.which("uvx") or "" snippet = { "mcpServers": { "familywall": { "command": uvx_path, "args": ["mcp-familywall", "serve"], } } } click.echo("\nAdd this to your Claude Desktop config (claude_desktop_config.json):\n") click.echo(json.dumps(snippet, indent=2))