152 lines
4.6 KiB
Python
152 lines
4.6 KiB
Python
"""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=<your-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 "<path-to-uvx>"
|
|
|
|
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))
|