From 81ff649ab7f773a8878cfd36e4641a37844b6f58 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Mon, 13 Apr 2026 15:02:29 +0200 Subject: [PATCH] Fix stdio startup for Claude Desktop compatibility - Replace asyncio.run() with anyio.run() in serve command: FastMCP uses anyio.create_task_group() internally, and anyio.run() ensures the correct backend context. asyncio.run() can misbehave on Windows (ProactorEventLoop). - Add SERVER STARTING / MCP server ready messages to stderr for diagnostics. - Replace sys.exit(1) with early return + stderr write inside anyio context; sys.exit inside anyio.run() is less predictable than a clean return. - Eagerly import all modules at serve startup so ImportErrors surface on stderr immediately instead of silently killing the process. - Add __main__.py to allow python -m mcp_synology_container invocation. Co-Authored-By: Claude Sonnet 4.6 --- src/mcp_synology_container/__main__.py | 6 +++ src/mcp_synology_container/cli.py | 56 ++++++++++++++++++-------- 2 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 src/mcp_synology_container/__main__.py diff --git a/src/mcp_synology_container/__main__.py b/src/mcp_synology_container/__main__.py new file mode 100644 index 0000000..4fb694f --- /dev/null +++ b/src/mcp_synology_container/__main__.py @@ -0,0 +1,6 @@ +"""Allow running as: python -m mcp_synology_container.""" + +from mcp_synology_container.cli import main + +if __name__ == "__main__": + main() diff --git a/src/mcp_synology_container/cli.py b/src/mcp_synology_container/cli.py index 278c71c..3db6b33 100644 --- a/src/mcp_synology_container/cli.py +++ b/src/mcp_synology_container/cli.py @@ -284,34 +284,56 @@ async def _run_check(config_path: str | None) -> bool: @click.option("--config", "-c", "config_path", type=click.Path(), help="Config file path") def serve(config_path: str | None) -> None: """Start the MCP server in stdio mode.""" + sys.stderr.write("SERVER STARTING\n") + sys.stderr.flush() _configure_logging("warning") - asyncio.run(_run_serve(config_path)) + # Use anyio.run() — FastMCP uses anyio internally (anyio.create_task_group), + # and anyio.run() ensures the correct backend context is set up. + # asyncio.run() can cause issues on Windows (ProactorEventLoop + anyio). + import anyio + anyio.run(_run_serve, config_path) async def _run_serve(config_path: str | None) -> None: """Initialize and run the MCP server.""" + # Eagerly import all modules so any ImportError surfaces immediately on stderr + # instead of silently killing the process. from mcp_synology_container.auth import AuthManager from mcp_synology_container.config import load_config - from mcp_synology_container.dsm_client import DsmClient + from mcp_synology_container.dsm_client import DsmClient, SynologyError from mcp_synology_container.server import create_server + logger.debug("Loading config from: %s", config_path or "default path") try: config = load_config(config_path) except (FileNotFoundError, ValueError) as e: - click.echo(click.style(f"Config error: {e}", fg="red"), err=True) - sys.exit(1) + sys.stderr.write(f"Config error: {e}\n") + sys.stderr.flush() + return - async with DsmClient(config.base_url, config.connection.verify_ssl) as client: - await client.query_api_info() - auth = AuthManager(config) - client.set_auth_manager(auth) - # Login on startup - try: - client.sid = await auth.login(client) - except Exception as e: - logger.error("Initial login failed: %s", e) - sys.exit(1) + logger.debug("Connecting to %s", config.base_url) + try: + async with DsmClient(config.base_url, config.connection.verify_ssl) as client: + await client.query_api_info() + auth = AuthManager(config) + client.set_auth_manager(auth) - mcp_server = create_server(config, client) - logger.info("MCP server starting (stdio mode)") - await mcp_server.run_stdio_async() + try: + client.sid = await auth.login(client) + except Exception as e: + sys.stderr.write(f"Login failed: {e}\n") + sys.stderr.flush() + return + + logger.debug("Login OK, creating MCP server") + mcp_server = create_server(config, client) + sys.stderr.write("MCP server ready\n") + sys.stderr.flush() + await mcp_server.run_stdio_async() + except SynologyError as e: + sys.stderr.write(f"DSM error during startup: {e}\n") + sys.stderr.flush() + except Exception as e: + sys.stderr.write(f"Fatal error: {e}\n") + sys.stderr.flush() + raise