diff --git a/nitrostack/cli/validators.py b/nitrostack/cli/validators.py index 990b154..3015aa4 100644 --- a/nitrostack/cli/validators.py +++ b/nitrostack/cli/validators.py @@ -21,6 +21,7 @@ read_text, split_requirement, ) +from nitrostack.cli.vendor import VENDOR_REL @dataclass @@ -39,10 +40,14 @@ def format(self) -> str: def _iter_python_files(root: str) -> Iterable[str]: + # The vendored SDK is not project code; its testing helpers apply @mcp_app + # inside functions, which reads as an app with no decorated class. + vendored_sdk = os.path.join(os.path.abspath(root), VENDOR_REL) for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [ d for d in dirnames if d not in EXCLUDE_DIR_NAMES and not d.endswith(".egg-info") + and os.path.join(os.path.abspath(dirpath), d) != vendored_sdk ] for filename in filenames: if filename.endswith(".py"): diff --git a/tests/test_cli.py b/tests/test_cli.py index 2aaf2ed..95046d9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1154,6 +1154,33 @@ def test_validate_catches_broken_imports_and_module_refs(tmp_path: Path): assert "→" in messages or "Fix the import" in messages +def test_validate_skips_the_vendored_sdk(tmp_path: Path): + """`init` vendors the SDK; its testing helpers apply @mcp_app inside functions.""" + (tmp_path / "pyproject.toml").write_text( + "[project]\n" + 'name = "vendored"\n' + 'version = "0.1.0"\n' + "dependencies = [\n" + ' "nitrostack",\n' + "]\n", + encoding="utf-8", + ) + sdk = tmp_path / "vendor" / "nitrostack" / "nitrostack" / "testing" + sdk.mkdir(parents=True) + (sdk / "__init__.py").write_text( + "from nitrostack import mcp_app, ServerConfig\n\n" + "def create(module):\n" + " @mcp_app(module=module, server=ServerConfig(name='test'))\n" + " class TestApp:\n" + " pass\n" + " return TestApp\n", + encoding="utf-8", + ) + + issues = validate_project(str(tmp_path)) + assert not [issue for issue in issues if "vendor" in issue.path], [i.format() for i in issues] + + def test_validate_accepts_module_only_bootstrap(tmp_path: Path): """Init templates use @module + McpApplicationFactory.create — no @mcp_app.""" (tmp_path / "pyproject.toml").write_text(