mcp-proxy: fail fast on dead Databricks auth instead of hanging - #259
Merged
sunishsheth2009 merged 1 commit intoAug 4, 2026
Conversation
When the Databricks CLI can't mint a token (expired refresh token, logged-out
profile), `get_databricks_token` raised a RuntimeError from inside
`httpx.Auth.auth_flow` — i.e. inside the streamable-HTTP transport's anyio task
group. Rather than surfacing as an error, that stalled the process until the MCP
client's startup timeout fired (~30s in Codex/Claude), so the user saw:
MCP client for `foo` timed out after 30 seconds.
Add or adjust `startup_timeout_sec` in your config.toml
with no hint that the real problem was expired auth. Because every registered
server shares the profile, all of them failed at once — and raising the timeout
never helps, since the proxy never answers.
Measured on a workspace whose refresh token had expired: the proxy hung >60s
(15s token fetch + 30s non-interactive re-auth + 15s refetch, all inside the
client's 30s budget). It now exits in ~1s with the CLI's own message.
- Pre-flight the token in `serve()` before opening the bridge, so a dead profile
is diagnosed up front instead of from inside the transport.
- Translate token failures in `auth_flow` into a terminal `ProxyAuthError`, and
unwrap it from anyio ExceptionGroups so a mid-session expiry reports too.
- Report on stderr (stdout is the MCP wire) and exit AUTH_FAILURE_EXIT_CODE=2;
MCP clients surface a child's stderr and non-zero exit far better than a hang.
- Non-auth failures still propagate with their traceback, and KeyboardInterrupt
still works (verified).
Co-authored-by: Isaac
sunishsheth2009
marked this pull request as ready for review
August 3, 2026 22:13
AarushiShah-db
approved these changes
Aug 3, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
When the Databricks CLI can't mint a token (expired refresh token, logged-out profile),
ucode mcp-proxyhung until the MCP client's startup timeout instead of reporting the problem. Users saw this, with no hint that the real cause was expired auth:Because every registered MCP server shares the same Databricks profile, all of them fail at once in that state — and raising
startup_timeout_secnever helps, because the proxy never answers at all.Root cause
get_databricks_tokenraisesRuntimeErrorwhen auth is dead. The proxy called it insidehttpx.Auth.auth_flow, i.e. inside the streamable-HTTP transport's anyio task group. The exception tore through the task group instead of surfacing as a clean failure, and the process stalled.Worse, the failure path is slow by construction:
get_databricks_tokendoes a 15s token fetch → 30s non-interactiveauth loginretry → 15s refetch. That's up to 60s, which structurally cannot fit inside a client's 30s startup budget.Measured before/after
Reproduced against a workspace whose OAuth refresh token had expired:
AUTH_FAILURE_EXIT_CODE)databricks auth logincommandsActual stderr now:
Changes
serve()before opening the bridge, so a dead profile is diagnosed up front rather than from inside the transport.auth_flowinto a terminalProxyAuthError, and unwrap it from anyioExceptionGroups so a mid-session expiry is reported the same way.AUTH_FAILURE_EXIT_CODE(2) — stdout is the MCP wire, and MCP clients surface a child's stderr + non-zero exit far more usefully than a hang.KeyboardInterruptstill works (both covered by tests).How do you know it works?
ml-inferenceOAuth profile): previously >60s hang, now exits in ~1s with code 2, stdout empty, actionable stderr.tests/test_mcp_proxy.py(16 pass, up from 8) covering: dead auth →ProxyAuthError; preflight runs before the bridge; dead auth exits fast without opening the bridge and keeps stdout clean; mid-session expiry inside anExceptionGroupstill reports; non-auth errors still propagate; preflight validates the same workspace/profile the bridge uses.TestServetests only passed incidentally (theiranyio.runstub no-op'd the token call); they now stub_preflight_tokenexplicitly.test_e2e_user_agentlive-gateway network tests, which fail identically onmain.ruff format,ruff check, andty checkall clean.This is a follow-up fix to #201 (the uniform
mcp-proxybridge).This pull request and its description were written by Isaac.