Skip to content

Add Cursor as an MCP client#192

Open
sunishsheth2009 wants to merge 2 commits into
databricks:mainfrom
sunishsheth2009:sunish-sheth_data/cursor-mcp-support
Open

Add Cursor as an MCP client#192
sunishsheth2009 wants to merge 2 commits into
databricks:mainfrom
sunishsheth2009:sunish-sheth_data/cursor-mcp-support

Conversation

@sunishsheth2009

@sunishsheth2009 sunishsheth2009 commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Adds Cursor as an MCP client in ucode, so ucode configure mcp can register Databricks managed MCP servers into ~/.cursor/mcp.json, plus a ucode cursor launcher.

Cursor is MCP-only: cursor-agent runs models on the user's own Cursor account and exposes no gateway base URL, so ucode configures no models for it (it stays out of agents.__init__._MODULES). What ucode does is register Databricks MCP servers in Cursor's config via the existing ucode configure mcp flow.

Auth: the uniform ucode mcp-proxy bridge (no per-client logic)

This PR is reworked on top of the ucode mcp-proxy foundation (#201). Cursor uses the exact same mechanism as every other client: each Databricks MCP server is registered as a local stdio server that runs ucode mcp-proxy, which bridges to the Databricks streamable-HTTP endpoint and mints a fresh OAuth token per request. So there is no literal token in ~/.cursor/mcp.json, no ${env:...} expansion, and no background refresh thread — Cursor just spawns the proxy like any stdio server, and auth never expires mid-session.

This also sidesteps the earlier DCR failure: with no bearer in the config, cursor-agent doesn't attempt the dynamic-client-registration flow that Databricks managed MCP doesn't support.

Depends on #201 ("Route all MCP clients through a uniform ucode mcp-proxy stdio bridge"). This branch is built on top of that commit — merge #201 first. The proxy commit shows in this PR's diff until #201 lands.

Changes

  • src/ucode/agents/cursor.py (new): build_mcp_server_entry(argv){command, args} stdio entry; write_/remove_mcp_server_config merge surgically into the shared ~/.cursor/mcp.json (preserving the user's own entries); launch() is a thin exec of cursor-agent (no token wiring — the proxy authenticates itself).
  • src/ucode/mcp.py: register cursor in MCP_CLIENTS; configure/remove branches dispatch the proxy argv to cursor; MCP_ONLY_CLIENTS so cursor is eligible whenever cursor-agent is installed (it never lands in available_tools).
  • src/ucode/cli.py: ucode cursor launcher; accept cursor in --agents (partitioned out of the model-agent path, workspace-only configure).
  • src/ucode/agents/__init__.py: comment documenting why cursor is intentionally not a model-routing agent.
  • Tests (tests/test_agent_cursor.py, cursor coverage in tests/test_mcp.py) + README.

Verification

Verified end-to-end against a live workspace (uc-dogfood, e2-dogfood UC):

  • ucode mcp-proxy stdio handshake against .../api/2.0/mcp/functions/system/ai: initializeDatabricksMCPServer, tools/list → 4 live tools (system__ai__ai_classify, ai_extract, ai_parse_document, python_exec).
  • configure_client_mcp_server("cursor", ...) through the real code path writes the correct {command, args} proxy entry into ~/.cursor/mcp.json (merging, not clobbering).
  • cursor-agent is installed and reads ~/.cursor/mcp.json; the registered entry is the proxy argv, so auth is handled by ucode mcp-proxy per request.

uv run pytest → 876 passed (2 pre-existing test_e2e_user_agent network failures unrelated to this change); uv run ruff format --check and uv run ty check src/ clean.

This pull request and its description were written by Isaac.

@sunishsheth2009 sunishsheth2009 force-pushed the sunish-sheth_data/cursor-mcp-support branch from 7e10377 to a40057e Compare July 8, 2026 18:10
rohita5l
rohita5l previously approved these changes Jul 9, 2026
Every coding agent now registers Databricks MCP servers as one identical local
stdio command — `ucode mcp-proxy --url <endpoint> --host <ws> --profile <p>` —
instead of per-client HTTP-bearer entries. This replaces five divergent auth
paths (claude `${OAUTH_TOKEN}` header, codex `--bearer-token-env-var`, gemini
`--header`, opencode `{env:OAUTH_TOKEN}`, copilot `Bearer ${OAUTH_TOKEN}`), none
of which refreshed the token mid-session, with a single mechanism.

Why a proxy: MCP clients disagree on HTTP-auth config and none expose a
per-request auth hook (except Claude's headersHelper), so a static bearer froze
at launch and expired after ~1h. A local stdio proxy sidesteps all of that: the
client spawns it as a child process (and reaps it — ucode owns no long-lived
process or refresh thread), and the proxy mints a fresh OAuth token from the
Databricks CLI profile on every upstream request. The proxy is an invisible
implementation detail baked into each client's config; users never invoke it.

- src/ucode/mcp_proxy.py (new): stdio<->streamable-HTTP bridge built on the
  official `mcp` SDK. `httpx.Auth` injects a fresh `get_databricks_token(...)`
  bearer per request; a transport-level pump forwards messages both ways, so new
  MCP methods/capabilities pass through untouched.
- cli.py: hidden `ucode mcp-proxy --url --host --profile --use-pat` command.
- databricks.py: `build_mcp_proxy_argv(...)` (mirrors build_auth_token_argv).
- mcp.py: add_{claude,codex,gemini}_mcp_server + opencode/copilot writers now
  register the stdio proxy argv; removed build_mcp_http_entry / OAUTH_TOKEN
  header. add_claude_mcp_server stays polymorphic so the web_search stdio entry
  (a dict with its own env) still registers via add-json.
- pyproject: add `mcp>=1.28.0`.

Verified against a live workspace: `ucode mcp-proxy` handshakes stdio and returns
the endpoint's tools (initialize OK, tools/list OK); ucode's real codex
registration lists the proxy as an enabled stdio server. Tests: 864 pass
(2 pre-existing e2e network failures unrelated); ruff + ty clean.

Co-authored-by: Isaac
Cursor runs models on the user's own Cursor account, so ucode configures
no models for it. It registers Databricks MCP servers in ~/.cursor/mcp.json
using the same uniform mechanism as every other client: a local stdio
server that runs `ucode mcp-proxy`, which mints a fresh OAuth token per
request. So Cursor needs no token in its config and no launch-time token
export.

- agents/cursor.py: build/write/remove ~/.cursor/mcp.json entries + thin launch()
- mcp.py: register cursor in MCP_CLIENTS + MCP_ONLY_CLIENTS; configure/remove branches
- cli.py: `ucode cursor` launcher; accept `cursor` in `--agents`
- README + tests (test_agent_cursor.py, cursor coverage in test_mcp.py)

Co-authored-by: Isaac
@sunishsheth2009

Copy link
Copy Markdown
Author

Reworked on top of the uniform ucode mcp-proxy bridge.

Dependency: this PR is stacked on #201 — merge that first. Until #201 lands, its proxy commit appears in this PR's diff.

What changed from the earlier revision:

  • Dropped the literal-token-in-config + background-refresh-thread approach.
  • Cursor now registers each Databricks MCP server as a local stdio server running ucode mcp-proxy (same as every other client), so ~/.cursor/mcp.json carries no bearer token, there's no ${env:...} expansion, and auth refreshes per request with no per-client logic.

Re-verified end-to-end against uc-dogfood (proxy handshake → 4 live system.ai tools; real registration writes the correct stdio entry).

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