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 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
"""Allow running as: python -m mcp_synology_container."""
|
||||||
|
|
||||||
|
from mcp_synology_container.cli import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -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")
|
@click.option("--config", "-c", "config_path", type=click.Path(), help="Config file path")
|
||||||
def serve(config_path: str | None) -> None:
|
def serve(config_path: str | None) -> None:
|
||||||
"""Start the MCP server in stdio mode."""
|
"""Start the MCP server in stdio mode."""
|
||||||
|
sys.stderr.write("SERVER STARTING\n")
|
||||||
|
sys.stderr.flush()
|
||||||
_configure_logging("warning")
|
_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:
|
async def _run_serve(config_path: str | None) -> None:
|
||||||
"""Initialize and run the MCP server."""
|
"""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.auth import AuthManager
|
||||||
from mcp_synology_container.config import load_config
|
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
|
from mcp_synology_container.server import create_server
|
||||||
|
|
||||||
|
logger.debug("Loading config from: %s", config_path or "default path")
|
||||||
try:
|
try:
|
||||||
config = load_config(config_path)
|
config = load_config(config_path)
|
||||||
except (FileNotFoundError, ValueError) as e:
|
except (FileNotFoundError, ValueError) as e:
|
||||||
click.echo(click.style(f"Config error: {e}", fg="red"), err=True)
|
sys.stderr.write(f"Config error: {e}\n")
|
||||||
sys.exit(1)
|
sys.stderr.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug("Connecting to %s", config.base_url)
|
||||||
|
try:
|
||||||
async with DsmClient(config.base_url, config.connection.verify_ssl) as client:
|
async with DsmClient(config.base_url, config.connection.verify_ssl) as client:
|
||||||
await client.query_api_info()
|
await client.query_api_info()
|
||||||
auth = AuthManager(config)
|
auth = AuthManager(config)
|
||||||
client.set_auth_manager(auth)
|
client.set_auth_manager(auth)
|
||||||
# Login on startup
|
|
||||||
try:
|
try:
|
||||||
client.sid = await auth.login(client)
|
client.sid = await auth.login(client)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Initial login failed: %s", e)
|
sys.stderr.write(f"Login failed: {e}\n")
|
||||||
sys.exit(1)
|
sys.stderr.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug("Login OK, creating MCP server")
|
||||||
mcp_server = create_server(config, client)
|
mcp_server = create_server(config, client)
|
||||||
logger.info("MCP server starting (stdio mode)")
|
sys.stderr.write("MCP server ready\n")
|
||||||
|
sys.stderr.flush()
|
||||||
await mcp_server.run_stdio_async()
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user