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:
parent
8ab61eb1df
commit
355daaff01
@ -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"
|
||||
|
@ -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()
|
@ -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()
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user