Fix package entry point and remove legacy CLI files

- Update pyproject.toml to use new Typer-based CLI
- Remove old mcmqtt.py and mcmqtt_old.py legacy files
- Package now correctly loads modern CLI with proper commands
- Version command works: mcmqtt version -> 2025.9.17
- Ready for PyPI publication
This commit is contained in:
Ryan Malloy 2025-09-17 05:53:00 -06:00
parent 8ab61eb1df
commit 355daaff01
4 changed files with 6 additions and 423 deletions

View File

@ -34,8 +34,7 @@ dev = [
]
[project.scripts]
mcmqtt = "mcmqtt.mcmqtt:main"
mcmqtt-server = "mcmqtt.main:main"
mcmqtt = "mcmqtt.main:main"
[project.urls]
Homepage = "https://git.supported.systems/MCP/mcmqtt"

View File

@ -1,86 +0,0 @@
#!/usr/bin/env python3
"""
mcmqtt - FastMCP MQTT Server launcher script.
Standard MCP server entry point that defaults to STDIO transport
for seamless integration with MCP clients like Claude Desktop.
This module has been refactored for better testability and maintainability.
"""
import asyncio
import sys
import structlog
from .cli import parse_arguments, get_version
from .config import create_mqtt_config_from_env, create_mqtt_config_from_args
from .logging import setup_logging
from .server import run_stdio_server, run_http_server
from .mcp.server import MCMQTTServer
def main():
"""Main entry point for mcmqtt MCP server."""
args = parse_arguments()
if args.version:
print(f"mcmqtt version {get_version()}")
sys.exit(0)
# Setup logging
setup_logging(args.log_level, args.log_file)
logger = structlog.get_logger()
# Create MQTT configuration
mqtt_config = None
if args.mqtt_host:
# Use command line arguments
mqtt_config = create_mqtt_config_from_args(args)
if mqtt_config:
logger.info("MQTT configuration from command line",
broker=f"{args.mqtt_host}:{args.mqtt_port}")
else:
# Try environment variables
mqtt_config = create_mqtt_config_from_env()
if mqtt_config:
logger.info("MQTT configuration from environment",
broker=f"{mqtt_config.broker_host}:{mqtt_config.broker_port}")
else:
logger.info("No MQTT configuration provided - use tools to configure at runtime")
# Create server instance
server = MCMQTTServer(mqtt_config)
# Log startup info
logger.info("Starting mcmqtt FastMCP server",
version=get_version(),
transport=args.transport,
auto_connect=args.auto_connect)
# Run server based on transport
try:
if args.transport == "stdio":
asyncio.run(run_stdio_server(
server,
auto_connect=args.auto_connect,
log_file=args.log_file
))
elif args.transport == "http":
asyncio.run(run_http_server(
server,
host=args.host,
port=args.port,
auto_connect=args.auto_connect
))
except KeyboardInterrupt:
logger.info("Server stopped by user")
sys.exit(0)
except Exception as e:
logger.error("Failed to start server", error=str(e))
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -1,330 +0,0 @@
#!/usr/bin/env python3
"""
mcmqtt - FastMCP MQTT Server launcher script.
Standard MCP server entry point that defaults to STDIO transport
for seamless integration with MCP clients like Claude Desktop.
"""
import asyncio
import logging
import os
import sys
from typing import Optional
import argparse
import structlog
from fastmcp import FastMCP
from .mcp.server import MCMQTTServer
from .mqtt.types import MQTTConfig
def setup_logging(log_level: str = "WARNING", log_file: Optional[str] = None):
"""Set up logging for MCP server."""
# For STDIO transport, we need to be careful about logging to avoid interfering
# with MCP protocol communication over stdout/stdin
handlers = []
if log_file:
# Log to file when specified
handlers.append(logging.FileHandler(log_file))
else:
# For STDIO mode, log to stderr to avoid protocol interference
handlers.append(logging.StreamHandler(sys.stderr))
logging.basicConfig(
level=getattr(logging, log_level.upper()),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=handlers
)
# Configure structlog for clean logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer()
],
wrapper_class=structlog.stdlib.BoundLogger,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
def get_version() -> str:
"""Get package version."""
try:
from importlib.metadata import version
return version("mcmqtt")
except Exception:
return "0.1.0"
def create_mqtt_config_from_env() -> Optional[MQTTConfig]:
"""Create MQTT configuration from environment variables."""
try:
broker_host = os.getenv("MQTT_BROKER_HOST")
if not broker_host:
return None
from .mqtt.types import MQTTQoS
return MQTTConfig(
broker_host=broker_host,
broker_port=int(os.getenv("MQTT_BROKER_PORT", "1883")),
client_id=os.getenv("MQTT_CLIENT_ID", f"mcmqtt-{os.getpid()}"),
username=os.getenv("MQTT_USERNAME"),
password=os.getenv("MQTT_PASSWORD"),
keepalive=int(os.getenv("MQTT_KEEPALIVE", "60")),
qos=MQTTQoS(int(os.getenv("MQTT_QOS", "1"))),
use_tls=os.getenv("MQTT_USE_TLS", "false").lower() == "true",
clean_session=os.getenv("MQTT_CLEAN_SESSION", "true").lower() == "true",
reconnect_interval=int(os.getenv("MQTT_RECONNECT_INTERVAL", "5")),
max_reconnect_attempts=int(os.getenv("MQTT_MAX_RECONNECT_ATTEMPTS", "10"))
)
except Exception as e:
logging.error(f"Error creating MQTT config from environment: {e}")
return None
async def run_stdio_server(
server: MCMQTTServer,
auto_connect: bool = False,
log_file: Optional[str] = None
):
"""Run FastMCP server with STDIO transport."""
logger = structlog.get_logger()
try:
# Auto-connect to MQTT if configured and requested
if auto_connect and server.mqtt_config:
logger.info("Auto-connecting to MQTT broker",
broker=f"{server.mqtt_config.broker_host}:{server.mqtt_config.broker_port}")
success = await server.initialize_mqtt_client(server.mqtt_config)
if success:
await server.connect_mqtt()
logger.info("Connected to MQTT broker")
else:
logger.warning("Failed to connect to MQTT broker", error=server._last_error)
# Get FastMCP instance and run with STDIO transport
mcp = server.get_mcp_server()
# Run server with STDIO transport (default for MCP)
await mcp.run_stdio_async()
except KeyboardInterrupt:
logger.info("Server shutting down...")
await server.disconnect_mqtt()
except Exception as e:
logger.error("Server error", error=str(e))
await server.disconnect_mqtt()
sys.exit(1)
async def run_http_server(
server: MCMQTTServer,
host: str = "0.0.0.0",
port: int = 3000,
auto_connect: bool = False
):
"""Run FastMCP server with HTTP transport."""
logger = structlog.get_logger()
try:
# Auto-connect to MQTT if configured and requested
if auto_connect and server.mqtt_config:
logger.info("Auto-connecting to MQTT broker",
broker=f"{server.mqtt_config.broker_host}:{server.mqtt_config.broker_port}")
success = await server.initialize_mqtt_client(server.mqtt_config)
if success:
await server.connect_mqtt()
logger.info("Connected to MQTT broker")
else:
logger.warning("Failed to connect to MQTT broker", error=server._last_error)
# Get FastMCP instance and run with HTTP transport
mcp = server.get_mcp_server()
# Run server with HTTP transport
await mcp.run_http_async(host=host, port=port)
except KeyboardInterrupt:
logger.info("Server shutting down...")
await server.disconnect_mqtt()
except Exception as e:
logger.error("Server error", error=str(e))
await server.disconnect_mqtt()
sys.exit(1)
def main():
"""Main entry point for mcmqtt MCP server."""
parser = argparse.ArgumentParser(
description="mcmqtt - FastMCP MQTT Server",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
mcmqtt # Run with STDIO transport (default)
mcmqtt --transport http --port 3000 # Run with HTTP transport
mcmqtt --auto-connect # Auto-connect to MQTT broker
mcmqtt --log-level INFO --log-file mcp.log # Enable logging to file
Environment Variables:
MQTT_BROKER_HOST MQTT broker hostname
MQTT_BROKER_PORT MQTT broker port (default: 1883)
MQTT_CLIENT_ID MQTT client ID
MQTT_USERNAME MQTT username
MQTT_PASSWORD MQTT password
MQTT_USE_TLS Enable TLS (true/false)
MQTT_QOS QoS level (0, 1, 2)
"""
)
# Transport options
parser.add_argument(
"--transport", "-t",
choices=["stdio", "http"],
default="stdio",
help="Transport protocol (default: stdio)"
)
# HTTP transport options
parser.add_argument(
"--host",
default="0.0.0.0",
help="Host for HTTP transport (default: 0.0.0.0)"
)
parser.add_argument(
"--port", "-p",
type=int,
default=3000,
help="Port for HTTP transport (default: 3000)"
)
# MQTT configuration
parser.add_argument(
"--mqtt-host",
help="MQTT broker hostname (overrides MQTT_BROKER_HOST)"
)
parser.add_argument(
"--mqtt-port",
type=int,
default=1883,
help="MQTT broker port (default: 1883)"
)
parser.add_argument(
"--mqtt-client-id",
help="MQTT client ID"
)
parser.add_argument(
"--mqtt-username",
help="MQTT username"
)
parser.add_argument(
"--mqtt-password",
help="MQTT password"
)
parser.add_argument(
"--auto-connect",
action="store_true",
help="Automatically connect to MQTT broker on startup"
)
# Logging options
parser.add_argument(
"--log-level",
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
default="WARNING",
help="Log level (default: WARNING)"
)
parser.add_argument(
"--log-file",
help="Log file path (logs to stderr if not specified)"
)
# Version
parser.add_argument(
"--version",
action="store_true",
help="Show version and exit"
)
args = parser.parse_args()
if args.version:
print(f"mcmqtt version {get_version()}")
sys.exit(0)
# Setup logging
setup_logging(args.log_level, args.log_file)
logger = structlog.get_logger()
# Create MQTT configuration
mqtt_config = None
if args.mqtt_host:
# Use command line arguments
mqtt_config = MQTTConfig(
broker_host=args.mqtt_host,
broker_port=args.mqtt_port,
client_id=args.mqtt_client_id or f"mcmqtt-{os.getpid()}",
username=args.mqtt_username,
password=args.mqtt_password
)
logger.info("MQTT configuration from command line",
broker=f"{args.mqtt_host}:{args.mqtt_port}")
else:
# Try environment variables
mqtt_config = create_mqtt_config_from_env()
if mqtt_config:
logger.info("MQTT configuration from environment",
broker=f"{mqtt_config.broker_host}:{mqtt_config.broker_port}")
else:
logger.info("No MQTT configuration provided - use tools to configure at runtime")
# Create server instance
server = MCMQTTServer(mqtt_config)
# Log startup info
logger.info("Starting mcmqtt FastMCP server",
version=get_version(),
transport=args.transport,
auto_connect=args.auto_connect)
# Run server based on transport
try:
if args.transport == "stdio":
asyncio.run(run_stdio_server(
server,
auto_connect=args.auto_connect,
log_file=args.log_file
))
elif args.transport == "http":
asyncio.run(run_http_server(
server,
host=args.host,
port=args.port,
auto_connect=args.auto_connect
))
except KeyboardInterrupt:
logger.info("Server stopped by user")
sys.exit(0)
except Exception as e:
logger.error("Failed to start server", error=str(e))
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -52,16 +52,16 @@ class TestCLI:
assert result.exit_code == 1
assert "Cannot connect to server" in result.stdout
@patch('mcmqtt.main.uvicorn.Server')
@patch('mcmqtt.main.MCMQTTServer')
def test_serve_command_basic(self, mock_server_class, mock_uvicorn_server):
def test_serve_command_basic(self, mock_server_class):
"""Test basic serve command."""
mock_server = MagicMock()
mock_server.run_server = MagicMock()
mock_server.initialize_mqtt_client = MagicMock()
mock_server.connect_mqtt = MagicMock()
mock_server.disconnect_mqtt = MagicMock()
mock_server_class.return_value = mock_server
mock_uvicorn_instance = MagicMock()
mock_uvicorn_server.return_value = mock_uvicorn_instance
runner = CliRunner()
# Mock asyncio.run to avoid actually starting server