Skip to content

Commit 5b1ce01

Browse files
fix(mcp): skip unbounded connector install in get_connector_info without Docker
Co-Authored-By: AJ Steers <aj@airbyte.io>
1 parent 6a1ce75 commit 5b1ce01

2 files changed

Lines changed: 61 additions & 6 deletions

File tree

airbyte/mcp/registry.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,12 @@ def get_connector_info(
148148
Field(description="The name of the connector to get information for."),
149149
],
150150
) -> ConnectorInfo | Literal["Connector not found."]:
151-
"""Get the documentation URL for a connector."""
151+
"""Get metadata, documentation URL, and config spec for a connector.
152+
153+
`config_spec_jsonschema` is populated only when Docker is available, since
154+
resolving it requires installing and running the connector. In a hosted,
155+
no-Docker runtime it is returned as `None` to avoid an unbounded install.
156+
"""
152157
if connector_name not in get_available_connectors():
153158
return "Connector not found."
154159

@@ -163,10 +168,14 @@ def get_connector_info(
163168
connector_metadata = get_connector_metadata(connector_name)
164169

165170
config_spec_jsonschema: dict[str, Any] | None = None
166-
with contextlib.suppress(Exception):
167-
# This requires running the connector. Install it if it isn't already installed.
168-
connector.install()
169-
config_spec_jsonschema = connector.config_spec
171+
if is_docker_installed():
172+
# Populating `config_spec` requires installing and running the connector.
173+
# Only attempt it when Docker is available (a fast image pull). In a hosted,
174+
# no-Docker runtime the fallback is a fresh pip/venv install on every call,
175+
# which is unbounded and has hung requests for minutes, so leave it as `None`.
176+
with contextlib.suppress(Exception):
177+
connector.install()
178+
config_spec_jsonschema = connector.config_spec
170179

171180
manifest_url = _DEFAULT_MANIFEST_URL.format(
172181
source_name=connector_name,

tests/unit_tests/test_mcp_connector_registry.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
_list_public_registry_connectors,
1818
)
1919
from airbyte.mcp.interactive._shared_models import ConnectorType, SupportLevel
20-
from airbyte.mcp.registry import get_api_docs_urls
20+
from airbyte.mcp.registry import get_api_docs_urls, get_connector_info
2121
from airbyte.registry import (
2222
ApiDocsUrl,
2323
ConnectorMetadata,
@@ -569,3 +569,49 @@ def test_prefab_generative_tools_include_airbyte_annotations() -> None:
569569
assert tool.annotations is not None
570570
assert getattr(tool.annotations, "mcp_module") == "interactive"
571571
assert getattr(tool.annotations, INTERACTIVE_UI_ANNOTATION) is True
572+
573+
574+
@pytest.mark.parametrize(
575+
("docker_installed", "expects_install"),
576+
[
577+
pytest.param(True, True, id="docker_installs_and_reads_config_spec"),
578+
pytest.param(False, False, id="no_docker_skips_unbounded_install"),
579+
],
580+
)
581+
def test_get_connector_info_only_installs_when_docker_available(
582+
docker_installed: bool,
583+
expects_install: bool,
584+
) -> None:
585+
"""`get_connector_info` must not run the unbounded install path without Docker.
586+
587+
In a hosted, no-Docker runtime `connector.install()` falls back to a fresh
588+
pip/venv install per call, which is unbounded and has hung requests. The tool
589+
should skip it and leave `config_spec_jsonschema` as `None` there, while still
590+
returning the fast registry metadata.
591+
"""
592+
connector = MagicMock()
593+
connector.name = "source-faker"
594+
connector.docs_url = "https://docs.airbyte.com/integrations/sources/faker"
595+
connector.config_spec = {"type": "object"}
596+
597+
with (
598+
patch(
599+
"airbyte.mcp.registry.get_available_connectors",
600+
return_value=["source-faker"],
601+
),
602+
patch("airbyte.mcp.registry.get_source", return_value=connector),
603+
patch("airbyte.mcp.registry.get_connector_metadata", return_value=None),
604+
patch(
605+
"airbyte.mcp.registry.is_docker_installed",
606+
return_value=docker_installed,
607+
),
608+
):
609+
result = get_connector_info("source-faker")
610+
611+
assert not isinstance(result, str)
612+
assert connector.install.called is expects_install
613+
if expects_install:
614+
assert result.config_spec_jsonschema == {"type": "object"}
615+
else:
616+
assert result.config_spec_jsonschema is None
617+
assert result.manifest_url is not None

0 commit comments

Comments
 (0)