Skip to content

feat(mcp): always return snippets from execute_external_search_query - #1464

Merged
Caleb Evans (calebevans-ab) merged 5 commits into
release-candidate/v1from
devin/1794459230-fusion-snippet-search
Nov 12, 2026
Merged

Caleb Evans (calebevans-ab) merged 5 commits into
release-candidate/v1from
devin/1794459230-fusion-snippet-search

Conversation

@calebevans-ab

@calebevans-ab Caleb Evans (calebevans-ab) commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Overview

👉 TL;DR: Agents searching Fusion-indexed data now always get short text snippets and record IDs back, never every matched record in full. That cuts a typical search response from hundreds of thousands of tokens to a few thousand.

Specifically, execute_external_search_query always runs in snippet mode and has no option to return full records. CloudConnector.execute_search_query gains include_entity_data (default True, so existing SDK callers are unchanged); the MCP tool always passes False.

Requested by Caleb Evans. This is phase 1. Phase 2 adds a fetch-by-id tool that reads full records for chosen hits; it needs work in Sonar and the Cloud API first and is not in this PR.

Changes

  • Snippet mode (what the MCP tool always uses):
    • every streams entry with no fields is sent with fields: [], so the backend leaves entity_data out
    • with no streams, the request goes out unchanged (no status lookup) and each hit's entity_data is set to {} on the client
    • max_context_chars defaults to 512 when the caller leaves it unset; a value the caller passes is kept
  • The SDK's include_entity_data=True sends the request unchanged and returns full records, as today.
  • ExternalSearchHit.namespace is new. It is filled from metadata when only one namespace matches the hit's (source_id, stream_name).
  • ExternalSearchIndexMetadata.hits_omitted is new. A positive total adds a warning to narrow streams or lower limit.
  • The entity_id docs now say it is a search record key (a JSON array of typed primary key parts, or row-sha256:<hash>), not a source API ID.
  • The tool description tells agents to pick hits from the snippets and then read the full records through execute_external_api_query / execute_external_sql_query, until the phase 2 fetch-by-id tool lands.

Review Spotlight

Reviewers with limited time, please review first:

Risks

  • The MCP tool's output changes. Agents that read entity_data from a search now get {} unless they list fields in streams; full records must come from the API or SQL tools until phase 2.
  • No server-side projection without streams. That search still returns full records over the wire and drops them on the client, so agent tokens go down but response size and latency do not.
  • hits_omitted depends on the backend. PyAirbyte reads it when the backend sends it; until then it is 0 and no warning shows.

Evals: no Fusion scenario exists yet

cloud-mcp-evals main has no scenario that calls execute_external_search_query, and its airbyte_cloud twin has no search endpoint for one to call. Fusion scenarios and the twin are separate work in another session (not yet merged), and fusion_snippet_then_fetch there expects an include_entity_data=true call, which this PR no longer allows. The changed-tools comparison for this PR therefore cannot measure the snippet change, and its result is reported in a comment below.

Test plan

  • poe check (ruff, pyrefly, test collection, docstring lint): passes
  • poe test-unit-tests: 2486 passed, 1 skipped. The VM's AIRBYTE_CLIENT_ID / AIRBYTE_CLIENT_SECRET env vars were unset for the run, because they make test_agent_request_problem_message_and_guidance fail in this environment regardless of the change.
  • New tests in test_cloud_direct_execute.py and test_mcp_cloud.py cover:
    • fields: [] added to streams entries, while caller fields are kept
    • a search with no streams making one call and returning entity_data == {}
    • the SDK's include_entity_data=True forwarding the request unchanged
    • the 512 default and a caller value winning
    • destination searches still requiring source_id
    • namespace for one, none and two candidates
    • the hits_omitted warning
    • the MCP tool always passing include_entity_data=False

Link to Devin session: https://app.devin.ai/sessions/e0df147ccc894ea78fcbd53adadad8d3

…uery`

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

Copy link
Copy Markdown

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This PyAirbyte Version

You 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/1794459230-fusion-snippet-search' pyairbyte --help

# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1794459230-fusion-snippet-search'

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /fix-pr - Fixes most formatting and linting issues
  • /uv-lock - Updates uv.lock file
  • /test-pr - Runs tests with the updated PyAirbyte
  • /prerelease - Builds and publishes a prerelease version to PyPI
  • /evals - Runs the Cloud MCP evals that use the MCP tools this PR changed (/evals full=true runs the full suite)
📚 Show Repo Guidance

Helpful Resources

Community Support

Questions? Join the #pyairbyte channel in our Slack workspace.

📝 Edit this welcome message.

@github-code-quality

github-code-quality Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Code coverage overview

Languages: Python

Python / code-coverage/pytest-no-creds

release-candidate/v1 d9f652f devin/1794459230-fus... 09890db +/-
65% 81% +16%
View coverage of the 10 most impacted files.
File release-candidate/v1 d9f652f devin/1794459230-fus... 09890db +/-
airbyte/_util/api_util.py 36% 66% +30%
airbyte/mcp/cloud.py 52% 87% +35%
airbyte/cloud/connectors.py 34% 81% +47%
airbyte/cloud/connections.py 26% 76% +50%
airbyte/mcp/_datadog.py 0% 50% +50%
airbyte/mcp/_telemetry.py 0% 94% +94%
airbyte/cloud/c...lder/project.py 0% 94% +94%
airbyte/_direct...ctors/models.py 0% 95% +95%
airbyte/mcp/_sso_auth.py 0% 96% +96%
airbyte/cloud/models.py 0% 96% +96%

Python / code-coverage/pytest

release-candidate/v1 d9f652f devin/1794459230-fus... 09890db +/-
71% 83% +12%
View coverage of the 10 most impacted files.
File release-candidate/v1 d9f652f devin/1794459230-fus... 09890db +/-
airbyte/cloud/connectors.py 51% 84% +33%
airbyte/mcp/cloud.py 52% 87% +35%
airbyte/cloud/connections.py 34% 77% +43%
airbyte/mcp/_datadog.py 0% 50% +50%
airbyte/mcp/_telemetry.py 0% 94% +94%
airbyte/cloud/c...lder/project.py 0% 94% +94%
airbyte/_direct...ctors/models.py 0% 95% +95%
airbyte/mcp/_sso_auth.py 0% 96% +96%
airbyte/cloud/models.py 0% 96% +96%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%

Python / code-coverage/pytest-unit

release-candidate/v1 devin/1794459230-fus... 09890db +/-
— 78% —

Line coverage data for the release-candidate/v1 branch is not yet available.

View coverage of the 10 most covered files.
File release-candidate/v1 devin/1794459230-fus... 09890db +/-
airbyte/mcp/_sso_auth.py — 96% —
airbyte/cloud/models.py — 96% —
airbyte/_direct...ctors/models.py — 95% —
airbyte/cloud/client.py — 91% —
airbyte/mcp/cloud.py — 87% —
airbyte/cloud/connectors.py — 81% —
airbyte/progress.py — 80% —
airbyte/cloud/connections.py — 76% —
airbyte/shared/...ql_processor.py — 68% —
airbyte/_util/api_util.py — 66% —

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.


Updated November 12, 2026 18:46 UTC

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat(mcp): return snippets by default from execute_external_search_query feat(mcp): always return snippets from execute_external_search_query Nov 12, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Cloud MCP eval comparison (run 43159059561, dispatched at 073095f, before include_entity_data was removed in 98129d1)

  • 073095f: 122 of 130 passed. Base 4c62940: 119 of 130 passed. Net change +3.
  • 2 trials passed on base and failed on head. In both, the failing checks are on the answer text and on which tools were called; neither trial called execute_external_search_query:
    • narrow-health-check-before-describing (claude-sonnet-4-6)
    • smoke-workspace-resources (gpt-6-luna)
  • 5 trials flipped the other way, from fail to pass.
  • Each scenario ran once per side, so a single flip may be model variance. The comparison job is red because any flip counts as a regression.
  • cloud-mcp-evals main has no Fusion search scenario, so this run cannot measure the snippet change. The Fusion scenarios are in progress in a separate PR.

Posted by Devin, on behalf of Caleb Evans (@calebevans-ab).

@aaronsteers
Aaron ("AJ") Steers (aaronsteers) marked this pull request as ready for review November 12, 2026 18:12
Copilot AI balanced review requested due to automatic review settings November 12, 2026 18:12
@aaronsteers

Copy link
Copy Markdown
Member

Devin, is this ready to go? Did you do e2e test?

@calebevans-ab

Copy link
Copy Markdown
Contributor Author

Aaron ("AJ") Steers (@aaronsteers) this is a bit of a PoC at the moment, sorry meant to mark this as a draft while I work on this.

Devin, please mark this as a draft and add DO NOT MERGE to the title while we work on proving if this actually helps or hurts our results.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

An empty streams selection bypasses client-side stripping despite include_entity_data=False, retaining entity_data and undermining response-size reduction; this case needs a fix and test coverage.

1 open issue
What changed in this PR

This PR makes MCP searches use snippet mode to reduce agent response size while preserving full-record behavior for existing SDK callers.

Changes:

  • Adds SDK snippet mode with stream field projection, client-side data stripping when streams are omitted, and a default 512-character context limit.
  • Adds hit namespaces and warnings for hits omitted due to response-size limits.
  • Updates agent guidance and tests for snippet behavior and SDK compatibility.
File Description
tests/​unit_tests/​test_mcp_cloud.py Verifies the MCP tool always requests snippet mode.
tests/​unit_tests/​test_cloud_direct_execute.py Tests projections, context limits, SDK compatibility, namespaces, and omission warnings.
airbyte/​mcp/​cloud.py Forces snippet mode and updates agent-facing search guidance.
airbyte/​cloud/​connectors.py Adds optional snippet-mode behavior while retaining SDK defaults.
airbyte/​_direct_connectors/​models.py Adds namespace enrichment, omitted-hit warnings, and search-key documentation.

Review effort: Balanced

Comment thread airbyte/cloud/connectors.py Resolved
@devin-ai-integration

Copy link
Copy Markdown
Contributor

AJ, not yet. I haven't run an e2e test against a live Fusion index.

What has been verified:

  • poe check and the unit tests pass locally, and all CI checks are green on 98129d1b.
  • The Cloud MCP eval comparison ran on the previous commit. No scenario on cloud-mcp-evals main calls search, so it doesn't cover this change.

Still open before merge:

  1. A live search through the MCP tool against a Fusion-enabled source (e.g. the Fusion test org), confirming hits come back with context snippets and an empty entity_data.
  2. Caleb's review, and his call on the follow-up fetch-by-id tool. Until that lands, agents read full records through execute_external_api_query / execute_external_sql_query.

I can run item 1 next if you want it before merge.


Posted by Devin, on behalf of Caleb Evans (@calebevans-ab).

@devin-ai-integration
devin-ai-integration Bot marked this pull request as draft November 12, 2026 18:23
@devin-ai-integration devin-ai-integration Bot changed the title feat(mcp): always return snippets from execute_external_search_query DO NOT MERGE: feat(mcp): always return snippets from execute_external_search_query Nov 12, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Done, Caleb: the PR is back to draft and the title now starts with DO NOT MERGE:. I'm still running the read-only live search against the Fusion test org and will post what it shows here.


Posted by Devin, on behalf of Caleb Evans (@calebevans-ab).

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

AJ, the read-only live test passed. I ran execute_external_search_query locally from this branch (bb25f643) against a keyword-indexed GitHub issues eval source in your Fusion test org, with prompt="fix" and limit=5. This tests the code, not a deployed preview server. Read-only throughout, nothing was written.

Call Hits entity_data Response size
MCP, no streams 5 {} on every hit 3,155 chars
MCP, streams=[{stream_name}] 5 {} on every hit 3,156 chars
MCP, streams[].fields=["title"] 5 only title 3,302 chars
MCP, streams=[] 0 n/a the server matches no index and returns its "No index matched" warning
SDK, include_entity_data=True (unchanged default) 5 full record, 6 fields 4,750 bytes
SDK, include_entity_data=False 5 {} 3,110 bytes
  • Each hit carries context (282–313 characters, under the 512 default), stream_name, namespace and an entity_id such as [["str","810001"]].
  • These eval records are small, so dropping entity_data saves only about 35% here. Sources with large records should save much more, but this test doesn't show that.

Posted by Devin, on behalf of Caleb Evans (@calebevans-ab).

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@calebevans-ab Caleb Evans (calebevans-ab) changed the title DO NOT MERGE: feat(mcp): always return snippets from execute_external_search_query feat(mcp): always return snippets from execute_external_search_query Nov 12, 2026
@calebevans-ab

Copy link
Copy Markdown
Contributor Author

Devin please run e2e tests on this

@calebevans-ab
Caleb Evans (calebevans-ab) marked this pull request as ready for review November 12, 2026 19:52
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T19:54:16.467349Z 09890db Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

On it, Caleb. I'm running a read-only end-to-end test of the full flow from this branch against the Fusion test org: search for snippets, pick hits, then read their full records through execute_external_api_query and execute_external_sql_query. I'll post the results here. There's no Fusion eval scenario on cloud-mcp-evals main yet, so the evals can't cover this change.


Posted by Devin, on behalf of Caleb Evans (@calebevans-ab).

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Caleb, the end-to-end flow passed. I ran the MCP tools from this branch locally (head 09890db2) against AJ's Fusion test org, read-only. This tests the branch's code, not a deployed preview server.

  1. Search returns snippets. execute_external_search_query with prompt="memory leak" and keyword search, run on the GitHub eval source c5c6d2a0, returned 5 hits. Each had a context of 242 to 295 characters, an entity_id such as [["str","810112"]] and an empty entity_data. The response was 2,949 bytes. The same search through the SDK with full records was 4,530 bytes; these 6-field records are small.
  2. No full-record option. Passing include_entity_data=true is rejected with Unexpected keyword argument.
  3. Field projection works. With streams[].fields=["title","state"], only those two fields came back.
  4. Fetch by SQL works. I picked the open issue from the snippets, 810112, and execute_external_sql_query on the BigQuery destination 88253e59 returned its full row (SELECT * ... WHERE id = '810112').
  5. The API path is untested. execute_external_api_query on this source returns "Direct access is currently disabled". I didn't turn it on.

One gap for agents: a hit doesn't tell them where its full record lives. Here namespace is null, so I had to look up the BigQuery dataset (sql_passthrough_test) in INFORMATION_SCHEMA and decode the key out of entity_id myself. A fetch-by-id tool would remove both steps.


Posted by Devin, on behalf of Caleb Evans (@calebevans-ab).

@calebevans-ab
Caleb Evans (calebevans-ab) merged commit 7de8652 into release-candidate/v1 Nov 12, 2026
30 checks passed
@calebevans-ab
Caleb Evans (calebevans-ab) deleted the devin/1794459230-fusion-snippet-search branch November 12, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants