From b96606b5d84168ff073aba5969664d63e9c0bbaf Mon Sep 17 00:00:00 2001 From: manish-wekan Date: Fri, 16 Oct 2026 12:05:02 +0530 Subject: [PATCH] validate: skip the vendored SDK `init` vendors the SDK into vendor/nitrostack. `validate` walked it as project code, and nitrostack/testing applies @mcp_app to a class defined inside a function, so every vendored project that imported nitrostack.testing failed with "uses @mcp_app but no decorated application class was found after import". Compose's typecheck runs `validate`, so a NitroStudio build could not get past it. Co-Authored-By: Claude Opus 5.5 --- nitrostack/cli/validators.py | 5 +++++ tests/test_cli.py | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) 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(