Cap mcp below 2.0 so ucode mcp-proxy imports - #311
Merged
Conversation
`uv tool install` resolves deps fresh and ignores uv.lock, so the unbounded `mcp>=1.28.0` picked up mcp 2.0.0 once it published. That breaks `ucode.mcp_proxy` at import: mcp 2.x moved from httpx to httpx2 and renamed `streamablehttp_client` to `streamable_http_client`, so the proxy died with `ModuleNotFoundError: No module named 'httpx'` and every MCP client saw `-32000: Connection closed`. Lockfile-based runs stayed on 1.28.1, which is why CI never caught it. Only the uv.lock `requires-dist` specifier changes -- the resolved pin is already 1.28.1 and satisfies the cap, so no dependency versions move. Unblocks bugbash. The real follow-up is porting the proxy to mcp 2.x. Co-authored-by: Isaac
tt-le
approved these changes
Aug 11, 2026
sunishsheth2009
added a commit
to sunishsheth2009/ucode
that referenced
this pull request
Aug 14, 2026
) A fresh `ucode` install resolved mcp 2.0.0 off `mcp>=1.28.0` and `ucode mcp-proxy` crashed on import, because 2.x renamed `streamablehttp_client` to `streamable_http_client` and moved from httpx to httpx2. databricks#311 had capped `mcp<2` to stop the bad resolution; this instead makes the proxy work against both majors so the cap can be removed and a clean install on mcp 2.x just works. Key realization: both mcp 1.28+ and 2.x export the 2.x-native `streamable_http_client(url, http_client=<AsyncClient>)` (1.x's `streamablehttp_client` is a deprecated shim over it). So a single code path works if we (a) build the AsyncClient/Auth from the httpx flavor the installed SDK uses, and (b) tolerate the yield arity change. - `_httpx()` resolves httpx2 (mcp 2.x) else httpx (mcp 1.x), via importlib so the type checker/CI (pinned to 1.x, no httpx2) don't choke on a static import. - `_build_token_auth()` subclasses that flavor's `Auth` — its `auth_flow` generator contract is identical in httpx and httpx2. - `_run` unpacks `streams[0], streams[1]`: mcp 1.x yields (read, write, get_session_id), mcp 2.x yields (read, write); we don't use the trailing callback. - pyproject: `mcp>=1.28.0,<2` -> `mcp>=1.28.0`; httpx stays a direct dep for the 1.x path, httpx2 comes in transitively with mcp 2.x (no direct dep needed). Verified against BOTH majors in a real venv: proxy tests (21) pass and `ty check src/` is clean under mcp 1.28.1 AND mcp 2.0.0; under 2.x the proxy imports (the databricks#307 crash point), selects httpx2, composes httpx2.AsyncClient(auth=...) + streamable_http_client(http_client=...), and reaches the auth preflight. Full suite: 1788 passed (2 pre-existing live-gateway e2e failures, unrelated). ruff clean. Fixes databricks#307 Co-authored-by: Isaac
sunishsheth2009
added a commit
to sunishsheth2009/ucode
that referenced
this pull request
Aug 14, 2026
…atabricks#307) A fresh `ucode` install used to resolve `mcp 2.0.0` off an unconstrained `mcp>=1.28.0`, and `ucode mcp-proxy` then crashed on import because 2.x renamed `streamablehttp_client` (and swapped httpx for httpx2). Every MCP server configured through the proxy failed to connect, surfaced to the coding agent as a generic connection failure with no hint at the real cause. The dependency cap (`mcp>=1.28.0,<2`) already landed in databricks#311. This adds the remaining two acceptance criteria from the issue: - **Actionable compatibility error.** `ucode mcp-proxy` catches the ImportError at the proxy import site and prints a reinstall instruction naming the installed version and the supported range, then exits non-zero — instead of an opaque traceback. The message is built in cli.py (not mcp_proxy) because mcp_proxy is exactly the module that fails to import. - **Compat tests.** Assert the pin stays `<2`; assert the *resolved* mcp SDK satisfies it and that `mcp_proxy` imports against it (the clean-install guard, at test time); and cover the cli guard end-to-end (exit 1 + guidance). Verified: simulated an mcp-2.x-style import failure and confirmed the CLI exits 1 with the reinstall hint and no traceback. Full suite: 1788 passed (2 pre-existing live-gateway e2e failures unrelated); ruff + ty clean. Fixes databricks#307 Co-authored-by: Isaac
sunishsheth2009
added a commit
to sunishsheth2009/ucode
that referenced
this pull request
Aug 14, 2026
) A fresh `ucode` install resolved mcp 2.0.0 off `mcp>=1.28.0` and `ucode mcp-proxy` crashed on import, because 2.x renamed `streamablehttp_client` to `streamable_http_client` and moved from httpx to httpx2. databricks#311 had capped `mcp<2` to stop the bad resolution; this instead makes the proxy work against both majors so the cap can be removed and a clean install on mcp 2.x just works. Key realization: both mcp 1.28+ and 2.x export the 2.x-native `streamable_http_client(url, http_client=<AsyncClient>)` (1.x's `streamablehttp_client` is a deprecated shim over it). So a single code path works if we (a) build the AsyncClient/Auth from the httpx flavor the installed SDK uses, and (b) tolerate the yield arity change. - `_httpx()` resolves httpx2 (mcp 2.x) else httpx (mcp 1.x), via importlib so the type checker/CI (pinned to 1.x, no httpx2) don't choke on a static import. - `_build_token_auth()` subclasses that flavor's `Auth` — its `auth_flow` generator contract is identical in httpx and httpx2. - `_run` unpacks `streams[0], streams[1]`: mcp 1.x yields (read, write, get_session_id), mcp 2.x yields (read, write); we don't use the trailing callback. - pyproject: `mcp>=1.28.0,<2` -> `mcp>=1.28.0`; httpx stays a direct dep for the 1.x path, httpx2 comes in transitively with mcp 2.x (no direct dep needed). Verified against BOTH majors in a real venv: proxy tests (21) pass and `ty check src/` is clean under mcp 1.28.1 AND mcp 2.0.0; under 2.x the proxy imports (the databricks#307 crash point), selects httpx2, composes httpx2.AsyncClient(auth=...) + streamable_http_client(http_client=...), and reaches the auth preflight. Full suite: 1788 passed (2 pre-existing live-gateway e2e failures, unrelated). ruff clean. Fixes databricks#307 Co-authored-by: Isaac
sunishsheth2009
added a commit
that referenced
this pull request
Aug 14, 2026
* mcp-proxy: actionable error on incompatible mcp SDK + compat tests (#307) A fresh `ucode` install used to resolve `mcp 2.0.0` off an unconstrained `mcp>=1.28.0`, and `ucode mcp-proxy` then crashed on import because 2.x renamed `streamablehttp_client` (and swapped httpx for httpx2). Every MCP server configured through the proxy failed to connect, surfaced to the coding agent as a generic connection failure with no hint at the real cause. The dependency cap (`mcp>=1.28.0,<2`) already landed in #311. This adds the remaining two acceptance criteria from the issue: - **Actionable compatibility error.** `ucode mcp-proxy` catches the ImportError at the proxy import site and prints a reinstall instruction naming the installed version and the supported range, then exits non-zero — instead of an opaque traceback. The message is built in cli.py (not mcp_proxy) because mcp_proxy is exactly the module that fails to import. - **Compat tests.** Assert the pin stays `<2`; assert the *resolved* mcp SDK satisfies it and that `mcp_proxy` imports against it (the clean-install guard, at test time); and cover the cli guard end-to-end (exit 1 + guidance). Verified: simulated an mcp-2.x-style import failure and confirmed the CLI exits 1 with the reinstall hint and no traceback. Full suite: 1788 passed (2 pre-existing live-gateway e2e failures unrelated); ruff + ty clean. Fixes #307 Co-authored-by: Isaac * mcp-proxy: support both mcp 1.x and 2.x; drop the <2 cap (#307) A fresh `ucode` install resolved mcp 2.0.0 off `mcp>=1.28.0` and `ucode mcp-proxy` crashed on import, because 2.x renamed `streamablehttp_client` to `streamable_http_client` and moved from httpx to httpx2. #311 had capped `mcp<2` to stop the bad resolution; this instead makes the proxy work against both majors so the cap can be removed and a clean install on mcp 2.x just works. Key realization: both mcp 1.28+ and 2.x export the 2.x-native `streamable_http_client(url, http_client=<AsyncClient>)` (1.x's `streamablehttp_client` is a deprecated shim over it). So a single code path works if we (a) build the AsyncClient/Auth from the httpx flavor the installed SDK uses, and (b) tolerate the yield arity change. - `_httpx()` resolves httpx2 (mcp 2.x) else httpx (mcp 1.x), via importlib so the type checker/CI (pinned to 1.x, no httpx2) don't choke on a static import. - `_build_token_auth()` subclasses that flavor's `Auth` — its `auth_flow` generator contract is identical in httpx and httpx2. - `_run` unpacks `streams[0], streams[1]`: mcp 1.x yields (read, write, get_session_id), mcp 2.x yields (read, write); we don't use the trailing callback. - pyproject: `mcp>=1.28.0,<2` -> `mcp>=1.28.0`; httpx stays a direct dep for the 1.x path, httpx2 comes in transitively with mcp 2.x (no direct dep needed). Verified against BOTH majors in a real venv: proxy tests (21) pass and `ty check src/` is clean under mcp 1.28.1 AND mcp 2.0.0; under 2.x the proxy imports (the #307 crash point), selects httpx2, composes httpx2.AsyncClient(auth=...) + streamable_http_client(http_client=...), and reaches the auth preflight. Full suite: 1788 passed (2 pre-existing live-gateway e2e failures, unrelated). ruff clean. Fixes #307 Co-authored-by: Isaac * mcp-proxy: resolve static PAT auth in serve() (--use-pat) `ucode mcp-proxy --use-pat` didn't actually work: `databricks auth token` only reads OAuth caches, so a static-PAT profile is never resolved by the per-request token mint on its own — the PAT has to be exported as DATABRICKS_BEARER first (ensure_pat_bearer). Nothing in the proxy did that, so PAT — a previously supported auth path — silently failed in the mcp-2 rework. `serve` now calls `ensure_pat_bearer(profile)` when `use_pat` is set (before the token preflight), exporting the profile's PAT so every per-request mint takes the DATABRICKS_BEARER short-circuit; it fails fast with an actionable message if no PAT is found. This lives in `serve` (which already owns the proxy's auth preflight + fail-fast), not the CLI, so the command stays a thin forwarder: serve(url, workspace, profile, use_pat=use_pat or bool(state.get("use_pat"))) `_run` takes no use_pat — once the PAT is in the env, the transport path is identical to OAuth. Tests: test_mcp_proxy covers serve's PAT handling (exports the bearer before serving; missing PAT exits before the bridge opens; OAuth path never consults ensure_pat_bearer); test_cli asserts the flag/saved-state is forwarded to serve. Full suite 1794 passed (2 pre-existing live-gateway e2e failures). ruff + ty clean. Co-authored-by: Isaac
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.
Problem
ucode mcp-proxy— the stdio bridge every Databricks MCP server ucode configures points at — crashes on startup:The dependency was pinned
mcp>=1.28.0with no upper bound, so oncemcp2.0.0 published, freshuv tool installs resolved to it. mcp 2.x renamedstreamablehttp_client->streamable_http_clientand swappedhttpxforhttpx2, soucode.mcp_proxyfails at import, before the MCP handshake. Because the crash is at module import, every Databricks MCP server routed through the proxy fails identically; clients surface it as the genericFailed to reconnect ... -32000.Fix
Cap the constraint at
mcp>=1.28.0,<2. The resolved version stays1.28.1, so this is a pure constraint change with no code or transitive-resolution change (only the specifier line inuv.lock).Porting the proxy to mcp 2.x is deliberately not in scope: 2.0 dropped the
auth=kwarg the proxy relies on, requiring a rewrite of the per-request token-refresh path ontohttpx2.AsyncClient, plus a newhttpx2dependency. The proxy is transport-only and gains nothing functional from 2.0, so that port should be a separate, deliberately-tested change (and should land as>=2,<3, keeping an upper bound).Testing
mcp==2.0.0; confirmedmcp==1.28.1importsucode.mcp_proxycleanly.This pull request and its description were written by Isaac.