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]
|
[project.scripts]
|
||||||
mcmqtt = "mcmqtt.mcmqtt:main"
|
mcmqtt = "mcmqtt.main:main"
|
||||||
mcmqtt-server = "mcmqtt.main:main"
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://git.supported.systems/MCP/mcmqtt"
|
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 result.exit_code == 1
|
||||||
assert "Cannot connect to server" in result.stdout
|
assert "Cannot connect to server" in result.stdout
|
||||||
|
|
||||||
@patch('mcmqtt.main.uvicorn.Server')
|
|
||||||
@patch('mcmqtt.main.MCMQTTServer')
|
@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."""
|
"""Test basic serve command."""
|
||||||
mock_server = MagicMock()
|
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_server_class.return_value = mock_server
|
||||||
|
|
||||||
mock_uvicorn_instance = MagicMock()
|
|
||||||
mock_uvicorn_server.return_value = mock_uvicorn_instance
|
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
# Mock asyncio.run to avoid actually starting server
|
# Mock asyncio.run to avoid actually starting server
|
||||||
|
Loading…
x
Reference in New Issue
Block a user