From 06d5823564523feb7addf5ffe2e470c9bfa87cce Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 20:43:48 +0000 Subject: [PATCH 1/2] fix: Add validation for manifest streams structure to prevent AttributeError - Add defensive validation in run_connector_readiness_test_report to check that all streams in the manifest are properly formatted dicts - Raise clear ValueError with actionable message when streams field contains invalid entries (e.g., strings instead of stream definition objects) - Add test case to verify the validation provides clear error messages - Fixes issue where AI-generated manifests with malformed streams field caused AttributeError: 'str' object has no attribute 'get' This distinguishes between: - Input arg type coercion (streams param as string) - continues to work as before - Manifest structure validation - now validates and errors with clear message Related CI run: https://github.com/airbytehq/connector-builder-mcp/actions/runs/18416705139/job/52481958952?pr=129 Co-Authored-By: AJ Steers --- connector_builder_mcp/validation_testing.py | 10 +++++++ .../test_validation_and_testing.py | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/connector_builder_mcp/validation_testing.py b/connector_builder_mcp/validation_testing.py index 52ed3dd..b9d320a 100644 --- a/connector_builder_mcp/validation_testing.py +++ b/connector_builder_mcp/validation_testing.py @@ -566,6 +566,16 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too if isinstance(streams, str): stream_names = [s.strip() for s in streams.split(",") if s.strip()] else: + if available_streams: + invalid_streams = [s for s in available_streams if not isinstance(s, dict)] + if invalid_streams: + raise ValueError( + f"Invalid manifest structure: 'streams' must be a list of stream definition objects (dicts), " + f"but found {len(invalid_streams)} invalid entry(ies). " + f"Each stream should be a dict with at least a 'name' field and stream configuration. " + f"Invalid entries: {invalid_streams[:3]}" + ) + stream_names = [ stream.get("name", f"stream_{i}") for i, stream in enumerate(available_streams) ] diff --git a/tests/integration/test_validation_and_testing.py b/tests/integration/test_validation_and_testing.py index f043d66..df333ac 100644 --- a/tests/integration/test_validation_and_testing.py +++ b/tests/integration/test_validation_and_testing.py @@ -174,6 +174,32 @@ def test_simple_api_manifest_workflow(simple_api_manifest_yaml) -> None: assert "streams" in resolved_manifest +def test_malformed_manifest_streams_validation() -> None: + """Test that malformed manifest with streams as list of strings raises clear error.""" + malformed_manifest = """ +version: 4.6.2 +type: DeclarativeSource +check: + type: CheckStream + stream_names: + - test_stream +streams: + - test_stream + - another_stream +spec: + type: Spec + connection_specification: + type: object + properties: {} +""" + + with pytest.raises( + ValueError, + match=r"Invalid manifest structure.*streams.*must be a list of stream definition objects", + ): + run_connector_readiness_test_report(manifest=malformed_manifest, config={}, max_records=5) + + @pytest.mark.parametrize( "manifest_fixture,stream_name", [ From 34e5ddb38d9ecf194a48e5c79f01365e8b86932b Mon Sep 17 00:00:00 2001 From: "Aaron (\"AJ\") Steers" Date: Fri, 10 Oct 2025 14:02:30 -0700 Subject: [PATCH 2/2] Apply suggestion from @aaronsteers --- connector_builder_mcp/validation_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_builder_mcp/validation_testing.py b/connector_builder_mcp/validation_testing.py index b9d320a..97bc80a 100644 --- a/connector_builder_mcp/validation_testing.py +++ b/connector_builder_mcp/validation_testing.py @@ -572,7 +572,7 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too raise ValueError( f"Invalid manifest structure: 'streams' must be a list of stream definition objects (dicts), " f"but found {len(invalid_streams)} invalid entry(ies). " - f"Each stream should be a dict with at least a 'name' field and stream configuration. " + f"Each stream should be an object with at least a 'name' field and stream configuration. " f"Invalid entries: {invalid_streams[:3]}" )