Repository navigation
mcp-proxy: drive per-connection login on a 401 (generic, all agents) #557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sunishsheth2009
merged 10 commits into
databricks:main
from
sunishsheth2009:mcp-connection-login-proxy
Sep 23, 2026
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
264d296
mcp-proxy: drive per-connection login on a 401 (generic, all agents)
sunishsheth2009 6cfbb49
mcp-proxy: make the connection login non-blocking and surface its URL
sunishsheth2009 e2de85c
mcp-proxy: drive the connection login at connect-time (mcp-remote style)
sunishsheth2009 12c9061
mcp-proxy: connection login at connect (the mcp-remote pattern), drop…
sunishsheth2009 2dffbf1
mcp-proxy: clear error when the Databricks CLI lacks --resource
sunishsheth2009 c1e3f6d
mcp-proxy: only run the connection login when the credential is actua…
sunishsheth2009 e000015
mcp-proxy: drive the connection login lazily on a 401, not eagerly at…
sunishsheth2009 acb8ddc
mcp_connection_login: make the sign-in message reusable by `ug mcp lo…
sunishsheth2009 3403e58
mcp: one source of truth for the /ai-gateway/mcp-services/ path + FQN…
sunishsheth2009 c28a77b
mcp-proxy: rename _login_or_fail -> _connection_login_or_fail (review…
sunishsheth2009 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| """Per-connection login for AI Gateway MCP services, driven by the proxy on a 401. | ||
|
|
||
| A connection-backed AI Gateway MCP service (e.g. ``system.ai.github``) needs a | ||
| per-user connection credential before its tools can be used. Until the user has | ||
| logged in to the underlying SaaS, AI Gateway answers requests with an HTTP 401 | ||
| (RFC 9728 ``WWW-Authenticate``). | ||
|
|
||
| The ``ug mcp-proxy`` bridge (see ``mcp_proxy``) sees that 401 in its httpx auth | ||
| flow and, for a connection-backed service, runs the Databricks CLI U2M login | ||
| with an RFC 8707 ``resource`` indicator naming the service, then retries the | ||
| request. A resource-aware ``/oidc`` drives the connection's own SaaS login | ||
| before minting the token, so the credential exists on retry — transparently to | ||
| the coding agent, which just sees the connection authenticate and succeed. This | ||
| is the behaviour of a generic OAuth MCP bridge (e.g. ``mcp-remote``), done in | ||
| ucode with the Databricks CLI so no extra library or per-agent OAuth app is | ||
| needed. Requires the CLI ``--resource`` flag (databricks/cli#6621). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import subprocess | ||
| import sys | ||
|
|
||
| from ucode.databricks import AIGW_MCP_SERVICES_SEGMENT | ||
|
|
||
| # Login can pop a browser and wait for the user to complete the SaaS login, so | ||
| # allow generously more than a token refresh would take. | ||
| _LOGIN_TIMEOUT_SECONDS = 300 | ||
|
|
||
|
|
||
| def connection_from_url(url: str) -> str | None: | ||
| """Return the connection FQN of an AI Gateway MCP service URL, or ``None``. | ||
|
|
||
| ``https://ws/ai-gateway/mcp-services/system.ai.github`` -> ``system.ai.github``. | ||
| A URL that is not an mcp-services endpoint (or names no service) returns | ||
| ``None`` — only connection-backed services get the login-on-401 treatment. | ||
| """ | ||
| marker = url.find(AIGW_MCP_SERVICES_SEGMENT) | ||
| if marker == -1: | ||
| return None | ||
| tail = url[marker + len(AIGW_MCP_SERVICES_SEGMENT) :] | ||
| # Strip any trailing path (``/tools/list``), query, or fragment. | ||
| connection = tail.split("/")[0].split("?")[0].split("#")[0] | ||
| return connection or None | ||
|
|
||
|
|
||
| def _cli_supports_resource_flag(login_binary: str) -> bool: | ||
| """Whether ``<login_binary> auth login`` advertises the ``--resource`` flag. | ||
|
|
||
| The connection sign-in needs a Databricks CLI with ``--resource`` | ||
| (databricks/cli#6621). An older CLI rejects the flag and the login exits with | ||
| a cryptic parse error, so we check ``--help`` up front to give a clear message | ||
| instead. Fail-open (assume supported) if ``--help`` can't be run — the real | ||
| login attempt will surface any genuine failure.""" | ||
| try: | ||
| result = subprocess.run( | ||
| [login_binary, "auth", "login", "--help"], | ||
| check=False, | ||
| timeout=20, | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| except (OSError, subprocess.TimeoutExpired): | ||
| return True | ||
| return "--resource" in f"{result.stdout or ''}{result.stderr or ''}" | ||
|
|
||
|
|
||
| def run_connection_login( | ||
| resource_url: str, | ||
| workspace: str, | ||
| *, | ||
| profile: str | None = None, | ||
| login_binary: str = "databricks", | ||
| ) -> tuple[bool, str]: | ||
| """Run the CLI U2M login with an RFC 8707 resource indicator for this service. | ||
|
|
||
| ``resource_url`` is the MCP service endpoint (also the proxy's upstream URL); | ||
| it is sent as ``--resource`` so a resource-aware ``/oidc`` drives the | ||
| connection's SaaS login before issuing the token. Uses the Databricks CLI's | ||
| own default client, whose loopback redirect is already registered — no | ||
| ``--client-id`` needed. | ||
|
|
||
| The CLI opens the browser to complete the login and prints the authorize URL. | ||
| We route its output to **stderr** (never stdout — that is the proxy's MCP | ||
| JSON-RPC wire), so a coding agent surfaces it in the server's log and the URL | ||
| stays visible when the browser can't open (e.g. a headless remote). Returns | ||
| ``(ok, message)``; on failure ``message`` points at that log. | ||
| """ | ||
| connection = connection_from_url(resource_url) or resource_url | ||
| if not _cli_supports_resource_flag(login_binary): | ||
| return False, ( | ||
| f"the Databricks CLI ('{login_binary}') has no `--resource` flag, so the " | ||
| f"'{connection}' connection sign-in can't run. Upgrade the CLI " | ||
| "(databricks/cli#6621) and retry." | ||
| ) | ||
| argv = [ | ||
| login_binary, | ||
| "auth", | ||
| "login", | ||
| "--host", | ||
| workspace.rstrip("/"), | ||
| "--resource", | ||
| resource_url, | ||
| ] | ||
| if profile: | ||
| argv += ["--profile", profile] | ||
| print( | ||
| f"Signing in to '{connection}' — opening your browser to complete the connection login; " | ||
| "if it doesn't open, use the authorization URL printed below.", | ||
| file=sys.stderr, | ||
| flush=True, | ||
| ) | ||
| try: | ||
| # stdout -> stderr: the CLI's prompts and authorize URL reach the agent's | ||
| # MCP log (fd 2) without corrupting this process's stdout (fd 1, the MCP | ||
| # JSON-RPC stream). stdin is closed since the flow is browser-driven. | ||
| result = subprocess.run( | ||
| argv, | ||
| check=False, | ||
| timeout=_LOGIN_TIMEOUT_SECONDS, | ||
| stdin=subprocess.DEVNULL, | ||
| stdout=sys.stderr, | ||
| stderr=sys.stderr, | ||
| ) | ||
| except OSError as exc: | ||
| return False, f"could not run '{login_binary} auth login': {exc}" | ||
| except subprocess.TimeoutExpired: | ||
| return False, "connection sign-in timed out waiting for the browser flow to complete" | ||
| if result.returncode == 0: | ||
| return True, "signed in" | ||
| return ( | ||
| False, | ||
| f"connection sign-in did not complete (CLI exited {result.returncode}; see the log above)", | ||
| ) | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "connection_from_url", | ||
| "run_connection_login", | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.