Skip to content

[ENG-3549] Ask Databricks Genie over MCP with a service principal - #628

Open
tino097 wants to merge 21 commits into
martyna/notion-mcp-connectorfrom
tino097/eng-3549-databricks-genie-mcp-connector
Open

tino097 wants to merge 21 commits into
martyna/notion-mcp-connectorfrom
tino097/eng-3549-databricks-genie-mcp-connector

Conversation

@tino097

@tino097 tino097 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

https://linear.app/mindsdb/issue/ENG-3549

Why this matters

A Databricks connection can now ask Databricks Genie instead of running SQL from the scratchpad. Genie writes and runs the SQL inside Databricks under the service principal's Unity Catalog grants, and the agent gets a short answer plus the rows. Signing in uses a Databricks-managed service principal's client ID and secret, so it works for every desktop app user and on the self-hosted build, which has no browser sign-in. It ships to all desktop users once released. Building it also surfaced three bugs in the shared MCP client that hit HubSpot and Notion too: a 5 s HTTP timeout, a reconnect that crossed task scopes and could hang a turn for good, and a session that stayed dead after one failed reconnect.

What changed

  • Add the Databricks workspace host check for Genie connections: a token or MCP call only ever goes to *.cloud.databricks.com, *.azuredatabricks.net or *.gcp.databricks.com, with no port, path, user info or IP literal.
  • Leave hidden connection fields out of a published app's environment, so _client_secret never reaches a published app.
  • Build the Genie One or per-space MCP address from a workspace host; a space id must be 32 lowercase hex.
  • Mint a Genie access token from a service principal's client credentials: one POST to /oidc/v1/token, scope all-apis, no redirects, and no secret, token or body text in any error.
  • Treat a rejected client on any 4xx as refused credentials, since OAuth servers answer 400 as well as 401.
  • Discover Genie MCP connections with a token minted for the turn: genie-mcp joins mcp as an MCP method, the four genie_* tools are read-only, and genie_ask waits for the answer inside the call and returns only status, final answer, query item ids and the deep link.
  • Hold the Genie secret and token for redaction on every Genie call, inside the turn's own scrub scope.
  • Never hand Genie's raw reply text to the agent: a reply without structured fields becomes a fixed message, since that text is long reasoning that opens with instructions addressed to the agent.
  • Tell the agent to use Genie's tools for a Genie connection, with no DS_* variables listed for it.
  • Classify a Genie connection's fields from its own record, so the workspace host in Genie's links is no longer redacted.
  • Give MCP sessions the SDK's HTTP timeouts (30 s, 300 s read) instead of httpx's 5 s default, for every MCP connector.
  • Retry a dropped MCP call without crossing task scopes: each client lives in its own owner task, so a dropped call no longer leaves the turn waiting forever.
  • Recover a session after a failed reconnect and a cancelled owner: the next call reconnects, and a close from another event loop returns normally.
  • Close a client a reconnect opens after the session was closed, so nothing leaks at turn end.
  • Hide a connection's secret bookkeeping fields from the start of every turn, so _client_secret is redacted before the first Genie call too.
  • Log a Genie session's retry, reconnect and close errors by class name only; HubSpot and Notion keep their lines.
  • Name a Genie connection's tools in the prompt the way registration names them, including two Genie connections and access mode "none".
  • Label Genie's held secrets by connection, skip a Genie connection with tools off, and close the HTTP client when a transport fails to build.
  • Tidy the Genie connector tests: literal URLs pinned, one source for shared test values, imports at the top.
  • State why hidden secure fields are held at any length.
  • Do not log a reconnect stopped by close as a failure.

Notes for the reviewer

Ships with

Checks

Check Observed result
uv run pytest tests/core/mcp tests/test_scrubbing.py tests/test_datasources_prompt.py tests/test_publisher_state.py -q 602 passed
uv run pytest -q (full suite, at 34bda71) 7420 passed, 271 skipped
Live, desktop dev app against a Databricks trial workspace (this branch installed editable, at 31104b9 and e9178aa, before the review fixes) 5 test questions answered correctly through databricks__genie_ask; secret masked in the connection API and absent from the agent's environment. Not yet rerun on the final head

🤖 Generated with Claude Code

tino097 and others added 21 commits October 20, 2026 21:54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h tools off, and close the HTTP client when a transport fails to build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move a test's imports to the top of the file and assert the exact keys of the published environment. Check the Genie addresses against literal URLs so a wrong path constant fails. Share the Genie stub's expected values through conftest fixtures so the copies cannot drift. Rewrap the mint_token docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T22:13:52.279549Z 34bda71 PR opened
ℹ️ About Codex in GitHub

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

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34bda71f96

ℹ️ About Codex in GitHub

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

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread anton/core/mcp/wiring.py
Comment on lines +158 to +160
endpoint = await resolve_mcp_endpoint(
engine, method, vault.load(engine, name) or {}, connection_name=name
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve Genie fields in the turn-key vault

Cloud turns pass a TurnKeyDataVault here, but its _fetch_uncached() still requires an access_token and only retains the existing OAuth allowlist plus _method; it drops workspace_url, client_id, _client_secret, and genie_space_id. Consequently this vault.load() can never provide the fields resolve_mcp_endpoint() requires, so a cloud Genie connection is either omitted before discovery or reported as incomplete and registers no tools. Extend the turn-key response handling and secure-key classification for the Genie service-principal fields.

Useful? React with 👍 / 👎.

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.

Non-blocking: I think this doesn't apply for now: cowork-server refuses Genie in org mode, so cloud never has a Genie connection. Can you confirm and resolve?

Comment thread anton/core/mcp/client.py
Comment on lines +417 to +419
client = self._client
if client is not None:
return client

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reopen clients whose owner loop has stopped

In the cloud path, discover_mcp_tools() opens sessions inside asyncio.run(), whose shutdown cancels the still-waiting _owner task and closes the SDK client, while _client remains non-None. The first tool call later runs on the turn's event loop and this fast path returns that closed client, guaranteeing a failed attempt and consuming the sole retry merely to establish a usable session; any transient failure on the retried call is then surfaced without its intended retry. Treat a done/cancelled owner as a missing client and reconnect before returning it.

Useful? React with 👍 / 👎.

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.

Non-blocking: agree with this one. On the cloud path, the first tool call gets a dead client and uses up its only retry reconnecting. Could we treat a finished _owner task as "no client" in _acquire_client and reconnect right away? This affects HubSpot and Notion on cloud too.

@martyna-mindsdb martyna-mindsdb 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.

Here are my comments:

  1. Blocking: The live test ran before the review fixes. Can you rerun it on the final code? Please include a couple of Genie questions plus one HubSpot or Notion chat, since client.py changed for all MCP connectors.
  2. Non-blocking: The PR mentions Genie One dropping and reconnecting several times in one turn. Can we open a ticket for it, so it's tracked after merge?
  3. Non-blocking: The docstrings are quite long across the PR. Could you trim them to just the non-obvious parts?
  4. Non-blocking: The PR description reads like a commit log. Could you shorten it to why, what changed and how it was tested? Also, please put the Linear link on a Linear: line.

Inline comments below.

_MAX_SCHEMA_DEPTH = 20

#: Rows a Genie query result may hand the model; the largest seen live was 12.
GENIE_RESULT_ROW_CAP = 200

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.

Non-blocking: this caps the number of rows but not their size, so a wide table could still be very large. Worth adding a size limit too?

return token


async def wait_for_genie_answer(

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.

Non-blocking: if the turn is cancelled during this wait, Genie keeps working on the question. Should we call genie_cancel_response on cancel?

Comment thread anton/core/mcp/servers.py
return None
host = canonical_workspace_host(workspace_url)
url = genie_mcp_url(host, fields.get("genie_space_id") or None)
token = await mint_token(host, client_id, client_secret)

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.

Non-blocking: this runs on every turn with a Genie connection, even when the user doesn't ask about Databricks. A failure can add about 40 s to the turn. Fine for now, but worth keeping in mind.

Comment thread anton/core/mcp/access.py
"notion-memory-search": "read",
"notion-get-file-download-urls": "read",
},
# Genie One, seen live 2026-10-20: it answers questions over the workspace's

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.

Non-blocking: can we drop the date from this comment? The reason is enough on its own.

This branch has not been deployed

No deployments
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.

2 participants