diff --git a/cloudsmith_cli/cli/commands/mcp.py b/cloudsmith_cli/cli/commands/mcp.py index eb00e451..7e2d0f04 100644 --- a/cloudsmith_cli/cli/commands/mcp.py +++ b/cloudsmith_cli/cli/commands/mcp.py @@ -1,17 +1,22 @@ """Main command/entrypoint.""" +from __future__ import annotations + import json import os import shutil import sys import tempfile from pathlib import Path +from typing import TYPE_CHECKING import click import json5 -from ...core.mcp import server -from ...core.mcp.data import OpenAPITool +if TYPE_CHECKING: + from ...core.mcp import server + from ...core.mcp.data import OpenAPITool + from .. import command, decorators, utils from .main import main diff --git a/cloudsmith_cli/cli/decorators.py b/cloudsmith_cli/cli/decorators.py index d4d9e60e..3eb1fe94 100644 --- a/cloudsmith_cli/cli/decorators.py +++ b/cloudsmith_cli/cli/decorators.py @@ -16,7 +16,6 @@ disabled_detectors_from_env, registered_detectors, ) -from ..core.mcp import server from ..core.rest import create_requests_session as _create_session from . import config, utils @@ -627,6 +626,8 @@ def initialise_mcp(f): @click.pass_context @functools.wraps(f) def wrapper(ctx, *args, **kwargs): + from ..core.mcp import server + opts = kwargs.get("opts") all_tools = kwargs.pop("all_tools") diff --git a/cloudsmith_cli/cli/tests/test_startup_imports.py b/cloudsmith_cli/cli/tests/test_startup_imports.py new file mode 100644 index 00000000..ea0553a1 --- /dev/null +++ b/cloudsmith_cli/cli/tests/test_startup_imports.py @@ -0,0 +1,37 @@ +"""Tests that a CLI import does not load heavy dependencies. + +The credential helpers run the CLI on every package-manager request, so +module-level imports are the dominant startup cost. These tests import the +CLI in a subprocess and inspect ``sys.modules``. +""" + +import json +import subprocess +import sys + +HEAVY_PREFIXES = ("mcp", "httpx") + + +def modules_loaded_by_cli_import(): + code = ( + "import json, sys\n" + "import cloudsmith_cli.cli.commands\n" + "print(json.dumps(sorted(sys.modules)))\n" + ) + result = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + return json.loads(result.stdout) + + +def test_cli_import_does_not_load_heavy_modules(): + modules = modules_loaded_by_cli_import() + heavy = [ + name + for name in modules + if any(name == p or name.startswith(p + ".") for p in HEAVY_PREFIXES) + ] + assert heavy == []