"""Hex MCP - Command-line interface for Hex MCP Server.
This module provides the CLI commands for configuring, installing, and running
the Hex MCP server. The CLI is built with Typer and supports multiple commands:
Commands:
run: Start the MCP server for client connections
config: Configure API credentials
show-config: Display current configuration
install: Generate Cursor IDE integration configuration
The server integrates with Hex API to provide project management, cell operations,
and permission control through the Model Context Protocol.
"""
from . import server
import importlib.metadata
import typer
from typing import Optional
import os
import yaml
import json
from pathlib import Path
from . import config as config_module
app = typer.Typer(help="Hex MCP Server - Model Context Protocol server for Hex")
def version_callback(value: bool):
"""Print version and exit"""
if value:
version = importlib.metadata.version("hex-mcp")
typer.echo(f"hex-mcp version {version}")
raise typer.Exit()
@app.callback()
def main(
version: Optional[bool] = typer.Option(
None,
"--version",
callback=version_callback,
is_eager=True,
help="Show the version of hex-mcp",
),
):
"""Main entry point for the Hex MCP Server.
Provides a CLI to run the Hex MCP server.
"""
@app.command()
def run():
"""Run the MCP server normally."""
server.mcp.run()
@app.command()
def config(
api_key: str = typer.Option(..., help="API key for Hex authentication"),
api_url: str = typer.Option("https://app.hex.tech/api/v1", help="Hex API URL"),
):
"""Configure Hex MCP server with API credentials.
Saves the configuration to a YAML file in the user's home directory.
"""
# Create or update config file
config_data = {
"api_key": api_key,
"api_url": api_url,
}
# Save config using the config module
config_module.save_config(config_data)
config_file = config_module.get_config_file_path()
typer.echo(f"Configuration saved to {config_file}")
typer.echo("API Key: " + api_key[:4] + "..." + api_key[-4:] if len(api_key) > 8 else "API Key: [Hidden]")
typer.echo(f"API URL: {api_url}")
@app.command()
def show_config():
"""Show the current configuration."""
api_key = config_module.get_api_key()
api_url = config_module.get_api_url()
config_file = config_module.get_config_file_path()
typer.echo(f"Configuration file: {config_file}")
if api_key:
# Show only first and last 4 characters of the API key
masked_key = api_key[:4] + "..." + api_key[-4:] if len(api_key) > 8 else "[Hidden]"
typer.echo(f"API Key: {masked_key}")
else:
typer.echo("API Key: Not configured")
typer.echo(f"API URL: {api_url}")
@app.command()
def install(
uv: bool = typer.Option(
False,
"--uv",
help="Use 'uv run' command structure instead of direct command",
),
server_name: str = typer.Option(
"Hex Projects",
"--name",
help="Name for the MCP server in Cursor",
),
preview: bool = typer.Option(
False,
"--preview",
help="Preview the configuration without writing it to disk",
),
force: bool = typer.Option(
False,
"--force",
"-f",
help="Force overwrite of existing configuration without prompting",
),
skip_config_check: bool = typer.Option(
False,
"--skip-config-check",
help="Skip checking for Hex API configuration",
),
):
"""Install the Hex MCP server configuration for Cursor.
Creates a .cursor/mcp.json file in the current directory that Cursor can use to
integrate with the Hex MCP server.
"""
# Check if Hex API key is configured
if not skip_config_check:
api_key = config_module.get_api_key()
if api_key is None:
typer.echo("Warning: No Hex API key configured. The MCP server won't be functional.")
typer.echo("Run 'hex-mcp config' to set up your Hex API credentials.")
if not typer.confirm("Continue with installation anyway?", default=False):
typer.echo("Installation cancelled.")
return
# Define the MCP config based on the --uv flag
if uv:
mcp_config = {"mcpServers": {server_name: {"command": "uv", "args": ["run", "hex-mcp", "run"]}}}
else:
mcp_config = {"mcpServers": {server_name: {"command": "hex-mcp", "args": ["run"]}}}
# Preview mode - just show the configuration
if preview:
typer.echo("Preview of MCP configuration:")
typer.echo(json.dumps(mcp_config, indent=2))
typer.echo(f"\nServer name: {server_name}")
typer.echo(f"Command: {'uv run hex-mcp run' if uv else 'hex-mcp run'}")
return
# Create .cursor directory if it doesn't exist
cursor_dir = Path(".cursor")
os.makedirs(cursor_dir, exist_ok=True)
# Check if file already exists
mcp_config_path = cursor_dir / "mcp.json"
if mcp_config_path.exists() and not force:
overwrite = typer.confirm(f"Configuration file {mcp_config_path} already exists. Overwrite?", default=False)
if not overwrite:
typer.echo("Installation cancelled.")
return
# Write the configuration file
with open(mcp_config_path, "w") as f:
json.dump(mcp_config, f, indent=2)
typer.echo(f"MCP configuration installed to {mcp_config_path}")
typer.echo(f"Server name: {server_name}")
typer.echo(f"Command: {'uv run hex-mcp run' if uv else 'hex-mcp run'}")
# Keep this to maintain backward compatibility with the entry point
def main_entry():
"""Entry point for the package."""
app()
# Optionally expose other important items at package level
__all__ = ["main_entry", "server"]