Repository navigation
feat(mcp): add batch stream config overrides and update_cloud_connection_unsafe - #1397
Aaron ("AJ") Steers (aaronsteers) wants to merge 17 commits into
Conversation
…loud connections Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
…_catalog_refresh Co-Authored-By: AJ Steers <[email protected]>
…nto devin/1792179543-rc-stream-sync-mode Co-Authored-By: AJ Steers <[email protected]> # Conflicts: # airbyte/mcp/cloud.py
…/1792117913-rc-stream-sync-mode-refresh-catalog
…nto devin/1792179543-rc-stream-sync-mode
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This PyAirbyte VersionYou can test this version of PyAirbyte using the following: # Run PyAirbyte CLI from this branch:
uvx --from 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1792179543-rc-stream-sync-mode' pyairbyte --help
# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1792179543-rc-stream-sync-mode'PR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
📚 Show Repo GuidanceHelpful ResourcesCommunity SupportQuestions? Join the #pyairbyte channel in our Slack workspace. |
Code coverage overviewLanguages: Python Python / code-coverage/pytest-no-creds
View coverage of the 10 most impacted files.
Python / code-coverage/pytest
View coverage of the 10 most impacted files.
Python / code-coverage/pytest-unit
Line coverage data for the release-candidate/v1 branch is not yet available. View coverage of the 10 most covered files.
ℹ️ Calculations represent line coverage. Results may change as calculations are updated. Updated |
Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
update_cloud_connection_unsafe
There was a problem hiding this comment.
Devin Review found 4 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
There was a problem hiding this comment.
🟡 Changes recommended
Safety decisions can become stale before catalog application, allowing unsafe writes through the safe tool.
3 open issues
What changed in this PR
Adds batch stream configuration updates to Cloud connections, with separate safe and unsafe MCP tools.
Changes:
- Adds override models, batch validation, and saved-state safety checks.
- Introduces unsafe updates with reset controls and reporting.
- Adds tests, tracing metadata, and catalog-cache invalidation.
| File | Description |
|---|---|
| tests/unit_tests/test_mcp_tool_errors.py | Tests unsafe-change error handling. |
| tests/unit_tests/test_mcp_connection_schedules.py | Tests tool annotations, resets, and guidance. |
| tests/unit_tests/test_cloud_connection_catalog_edits.py | Tests batch validation and catalog updates. |
| tests/unit_tests/fixtures/arg_trace_plan.json | Adds override and unsafe-tool tracing expectations. |
| airbyte/mcp/cloud.py | Exposes safe and unsafe batch updates. |
| airbyte/mcp/_error_handling.py | Registers unsafe-change errors. |
| airbyte/exceptions.py | Defines the unsafe-change exception. |
| airbyte/cloud/models.py | Defines override and result models. |
| airbyte/cloud/connections.py | Implements planning, safety checks, and application. |
| airbyte/cloud/__init__.py | Exports stream configuration models. |
| airbyte/_util/api_util.py | Adds catalog reset control. |
Review effort: Balanced
…ection update tool Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
…/1792179543-rc-stream-sync-mode Co-Authored-By: AJ Steers <[email protected]> # Conflicts: # airbyte/cloud/connections.py
Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]>
Co-Authored-By: AJ Steers <[email protected]> # Conflicts: # airbyte/mcp/cloud.py
| @mcp_tool( | ||
| destructive=True, | ||
| ) | ||
| def update_cloud_connection_unsafe( |
There was a problem hiding this comment.
Let's also hide this "unsafe" version of the tool behind the insiders flag for now. And can you confirm our prompts won't direct callers to the unsafe version if it isn't registered based on specific settings?
There was a problem hiding this comment.
Agreed on hiding it: update_cloud_connection_unsafe will get insiders_only=True whichever option we pick below.
To confirm your question: today, no. Two places point callers at the unsafe tool without checking whether it's registered:
- The
override_stream_configsfield description onupdate_cloud_connectionsays unsafe changes "must be run withupdate_cloud_connection_unsafe". - The unsafe-change error guidance in
_apply_stream_config_overridessays "Retry withupdate_cloud_connection_unsafe" wheneverdestructive_tools_allowed(ctx)isn'tFalse. It doesn't check insiders mode, read-only mode, pipeline-change mode, or module/tool exclusions.
Order of operations: the field description is part of the tool schema. It's built once at registration, so it can't vary per request. The error guidance is built at call time, inside the request, so it can check the caller's settings.
Options:
- Shared enabledness helper.
unsafe_update_tool_enabled(ctx)combines the checks that hide the tool:_insiders_mode,destructive_tools_allowed,pipeline_changes_allowed, and read-only mode. The guidance uses it. Downside: it duplicates the filter chain, so it misses module include/exclude and the standard tool-exclusion list, and it can drift when filters change. - Ask the server at error time.
await ctx.fastmcp.list_tools()runs every tool-filter middleware for the current request, so it returns exactly what this caller can see, with nothing duplicated. FastMCP 4.0.9 runs our sync tools in anyio's threadpool, so the sync tool can call it withanyio.from_thread.run. On any failure, the guidance falls back to "use the Airbyte UI". I've confirmed those two FastMCP facts in the installed source, but I haven't proven yet that it works inside a real tool call. AClienttest with insiders on and off would prove it. - Tool-neutral description. The description drops the tool name and says something like "Changes that would invalidate a stream's saved state are rejected; the error says how to proceed." The schema then never mentions a tool that may not exist, and the guidance can be exact.
- Conditional phrasing everywhere, for example "If
update_cloud_connection_unsafeis available, retry with it; otherwise ask the user to make the change in the Airbyte UI." Simplest, but agents may still try the hidden tool.
My pick is 2 + 3, or 1 + 3 if you'd rather not call list_tools from inside a tool. I won't change any code until you pick.
There was a problem hiding this comment.
This is in the stacked PR #1418 at 939d8cb:
update_cloud_connection_unsafenow hasinsiders_only=True.- The new private
_is_unsafe_connection_update_tool_enabled(ctx)checks insiders mode, destructive tools, pipeline changes and read-only mode. The unsafe-change error names the unsafe tool only when the helper returnsTrue. Otherwise it points to the Airbyte UI and gives the connection URL. - The
override_stream_configsdescription no longer names the unsafe tool. It now says the change is rejected and the error explains how to proceed.
To confirm your question: after this change, update_cloud_connection_unsafe appears in the airbyte/ code only in its own definition, the helper's docstring, and the gated guidance string. One gap remains: if a per-tool exclusion list names only this tool, the helper doesn't see it.


Overview
👉 TL;DR: Agents can change how several streams on a Cloud connection sync, all in one call. The default tool refuses changes that would invalidate a stream's saved progress, and a separate unsafe tool allows them.
Specifically, this adds
override_stream_configs: list[StreamConfigOverride]toupdate_cloud_connection(nowdestructive=False), the newupdate_cloud_connection_unsafe(destructive=True), andCloudConnection.update_stream_configs.Merges before (pairs with) #1392:
discover_cloud_source_catalog) #1392Replaces (with #1392):
Changes
StreamConfigOverride:stream_name,stream_namespace(None= any,""= unnamespaced),sync_mode,destination_sync_mode,cursor_field(top-levelstr). At least one setting is required.CloudConnection.update_stream_configs(overrides, *, allow_unsafe=False): the only public entry point. In one call it reads the catalog and state, evaluates every override on a typedConfiguredAirbyteCatalog(private_evaluate_stream_config_updates; API-only fields likeselected/mappersround-trip unchanged), and writes the catalog once. Helpers live in privateairbyte/cloud/_stream_config_overrides.py. No method takes a precomputed plan. ReturnsStreamConfigUpdateResult;reset_streamsis only filled when a reset was requested.AirbyteLibInputError): missing stream (the available streams are listed once per call), ambiguous name, unsupported sync mode, incremental with no cursor, and*_dedupwith no primary key. Any invalid override rejects the whole batch._find_unsafe_stream_changesflags changes tosyncMode,destinationSyncMode,cursorField, orprimaryKeyon streams with saved state, and the safe path raisesAirbyteUnsafeStreamConfigChangeError(naming each stream and changed field) without writing anything.update_cloud_connection: gainsoverride_stream_configs, drops todestructive=False, always sendsskipReset=true, and writes stream overrides beforeenabled/schedule.update_cloud_connection_unsafe: same inputs; sendsskipReset=falsewhen unsafe streams exist and lists every stream the platform will refresh or reset. The MCP layer only adds error guidance; arg validation and schedule setting live in privateairbyte/cloud/_connection_settings.py.api_util.replace_connection_catalogandimport_raw_catalogtakeskip_reset(defaultTrue).import_raw_catalogclears the cached_connection_info(shared with feat(mcp): add async source catalog discovery and comparison (discover_cloud_source_catalog) #1392).Review Spotlight
Reviewers with limited time, please review first:
_stream_config_overrides.py:_find_unsafe_stream_changes, the saved-state rule_stream_config_overrides.py:_apply_stream_override, the per-override validationmcp/cloud.py:update_cloud_connection_unsafeand the reset reportingRisks
update_cloud_connectionchanges fromdestructive=Truetodestructive=False, so clients that filter by that flag will now see it.web_backend/connections/updateacceptsselectedFields: [](reported in feat(mcp): Add tools to modify stream sync settings and refresh catalog on existing connections #994).Test plan
AIRBYTE_MCP_TRUSTED_EXECUTION=1 uv run pytest tests/unit_tests/test_cloud_connection_catalog_edits.py tests/unit_tests/test_mcp_connection_schedules.py tests/unit_tests/test_mcp_tool_errors.py tests/unit_tests/test_mcp_arg_trace_parity.pyruff check,ruff format --checkLink to Devin session: https://app.devin.ai/sessions/4d1ece6885c44c52a07034e025db11e6
Open in Devin Desktop: https://app.devin.ai/desktop/session/4d1ece6885c44c52a07034e025db11e6?variant=devin
Requested by: Aaron ("AJ") Steers (@aaronsteers)