Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
264d296
mcp-proxy: drive per-connection login on a 401 (generic, all agents)
sunishsheth2009 Sep 10, 2026
6cfbb49
mcp-proxy: make the connection login non-blocking and surface its URL
sunishsheth2009 Sep 11, 2026
e2de85c
mcp-proxy: drive the connection login at connect-time (mcp-remote style)
sunishsheth2009 Sep 11, 2026
12c9061
mcp-proxy: connection login at connect (the mcp-remote pattern), drop…
sunishsheth2009 Sep 11, 2026
2dffbf1
mcp-proxy: clear error when the Databricks CLI lacks --resource
sunishsheth2009 Sep 15, 2026
c1e3f6d
mcp-proxy: only run the connection login when the credential is actua…
sunishsheth2009 Sep 22, 2026
e000015
mcp-proxy: drive the connection login lazily on a 401, not eagerly at…
sunishsheth2009 Sep 22, 2026
acb8ddc
mcp_connection_login: make the sign-in message reusable by `ug mcp lo…
sunishsheth2009 Sep 23, 2026
3403e58
mcp: one source of truth for the /ai-gateway/mcp-services/ path + FQN…
sunishsheth2009 Sep 23, 2026
c28a77b
mcp-proxy: rename _login_or_fail -> _connection_login_or_fail (review…
sunishsheth2009 Sep 23, 2026
b8a2b02
Add `ug mcp login` to sign in to connection-backed MCP services
sunishsheth2009 Sep 17, 2026
ebca9bb
Merge branch 'main' into mcp-login-command
sunishsheth2009 Sep 23, 2026
d197bc0
docs: simplify the MCP Servers README for non-technical users
sunishsheth2009 Sep 23, 2026
57af15b
docs: add a worked example (add -> list -> login) to MCP Servers
sunishsheth2009 Sep 23, 2026
1a9f001
docs: drop the worked example and internal-detail bullets from MCP Se…
sunishsheth2009 Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,36 +91,36 @@ Cursor models still run through your Cursor account.

## MCP Servers

Register Databricks MCP servers for configured MCP-capable agents. Cursor Agent
is MCP-only and is included when `cursor-agent` is installed:

Use `ug mcp add` to add servers without removing existing registrations:
MCP servers let your coding agents use Databricks-governed tools — like GitHub, Slack,
Vector Search, and Genie. `ug` sets them up for all your installed agents at once.

```bash
# Add tools — a whole catalog.schema, or specific ones by name.
ug mcp add --location system.ai
ug mcp add --names system.ai.slack,system.ai.github
ug mcp add --agents claude,codex --location system.ai
```
ug mcp add --names system.ai.github,system.ai.slack

Remove configured servers:
# See what's set up, and whether each is signed in.
ug mcp list

```bash
# Sign in to a tool that needs it (opens your browser). Plain form shows a picker.
ug mcp login
ug mcp login --names system.ai.github

# Remove tools (plain form shows a picker).
ug mcp remove
ug mcp remove --agents codex
```

List configured servers and their connection status:
**You only sign in once.** Some tools (like `system.ai.github`) ask you to sign in to the
underlying service the first time. Do it once — through any agent or `ug mcp login` — and the
tool works everywhere: Claude, Cursor, Codex, and the rest.

```bash
ug mcp list
ug mcp list --agents claude,codex
```
<details><summary>Advanced options</summary>

- Limit any command to certain agents: add `--agents claude,codex`.
- Add other AI Gateway tools by typed name: `vector-search:main.docs`,
`uc-functions:main.tools`, `external:<name>`, `genie-space:<space-id>`, `app:<name>`.

Every Databricks MCP server is registered as a local stdio server that runs
`ug mcp-proxy`; the proxy refreshes Databricks OAuth tokens from your CLI
profile. V2 AI Gateway servers can be added with typed selectors such as
`vector-search:main.docs`, `uc-functions:main.tools`, `external:<name>`,
`genie-space:<space-id>`, or `app:<name>`.
</details>

## Skills

Expand Down Expand Up @@ -163,6 +163,7 @@ ug skills remove --location main.default --via mcp
| `ug mcp add` | Add MCP servers without removing existing registrations |
| `ug mcp remove` | Unregister configured MCP servers |
| `ug mcp list` | List configured MCP servers and connection status |
| `ug mcp login` | Sign in to connection-backed MCP services (interactive, or `--names`) |
| `ug skills` | Set up the Databricks skills MCP so agents can create and manage skills |
| `ug skills list` | List configured skills and how each was configured |
| `ug skills add` | Add skill MCP scopes or download skills |
Expand Down
44 changes: 44 additions & 0 deletions src/ucode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
remove_skills_locations_command,
revert_mcp_configs,
)
from ucode.mcp_login import login_mcp_command
from ucode.skills_download import (
configure_location_skills_download_command,
configure_selected_skills_download_command,
Expand Down Expand Up @@ -1552,6 +1553,49 @@ def mcp_list(
raise typer.Exit(130) from None


@mcp_app.command("login")
def mcp_login(
names: Annotated[
str | None,
typer.Option(
"--names",
help="Sign in to this comma-separated subset of MCP services non-interactively. "
"Full names like `system.ai.github` or bare short names like `github` both work. "
"Omit --names to show the interactive picker with each service's sign-in status.",
),
] = None,
agents: Annotated[
str | None,
typer.Option(
"--agents",
help="Comma-separated coding agents to scope to (e.g. claude,codex). Without "
"--agents, considers the MCP services configured for every agent.",
),
] = None,
) -> None:
"""Sign in to the connection-backed MCP services your agents use.

Shows which configured MCP services are already signed in vs. need a
connection sign-in, and runs the sign-in for the ones you pick (or all named
with --names). Sign-in uses `databricks auth login --resource`, so it
works for any connection-backed MCP service (not just `system.ai.*`).
"""
selected = None if names is None else {s.strip() for s in names.split(",") if s.strip()}
requested_agents = (
None
if agents is None
else ({a.strip().lower() for a in agents.split(",") if a.strip()} or None)
)
try:
login_mcp_command(names=selected, agents=requested_agents)
except RuntimeError as exc:
print_err(str(exc))
raise typer.Exit(1) from None
except KeyboardInterrupt:
print_err("Interrupted.")
raise typer.Exit(130) from None


@mcp_app.command("web-search")
def mcp_web_search_cmd() -> None:
"""Run the web_search MCP server over stdio. Invoked as a subprocess by Claude Code."""
Expand Down
74 changes: 45 additions & 29 deletions src/ucode/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2238,6 +2238,47 @@ def _row_status(
)


def configured_mcp_servers_by_name(
state: dict, agents: set[str] | None = None
) -> dict[str, dict[str, Any]]:
"""Merge the developer- and workspace-managed MCP servers ug has configured, keyed by
registered name, unioning the agents each is on. Skills connections are excluded (they are
reported/handled separately). ``agents`` drops agents outside that scope, and a server left
with no in-scope agent is omitted. Each value is ``{"server", "clients", "managed"}``.

Managed servers can be delivered two ways: to fallback state (``managed_mcp_servers``) or, for
Claude/Codex, into the agents' OS-managed files — the latter is the source of truth, so it is
read directly here. Shared by ``ug mcp list`` and ``ug mcp login`` so both (and ``ug status``)
see the same configured-server set regardless of how a managed server was delivered."""
configured: dict[str, dict[str, Any]] = {}

def _collect(server: dict, *, managed: bool) -> None:
name = _server_name(server)
if not name or server.get("kind") == SKILLS_MCP_KIND:
return
clients = [
client for client in _mcp_server_clients(server) if agents is None or client in agents
]
if not clients:
return
entry = configured.setdefault(name, {"server": server, "clients": [], "managed": managed})
entry["clients"] = _merge_clients(entry["clients"], clients)
entry["managed"] = entry["managed"] or managed

for server in state.get("mcp_servers") or []:
_collect(server, managed=False)
for server in state.get("managed_mcp_servers") or []:
_collect(server, managed=True)
# Managed servers delivered through the agents' OS-managed files (Claude/Codex) live in those
# files, not in state, so read them too — otherwise `ug mcp login` would miss them.
for agent, module in (("claude", claude), ("codex", codex)):
if agents is not None and agent not in agents:
continue
for name, url in module.read_managed_mcp_urls().items():
_collect({"name": name, "url": url, "clients": [agent]}, managed=True)
return configured


def list_mcp_command(agents: set[str] | None = None) -> int:
"""`ug mcp list`: show the Databricks MCP servers ug has configured and their live
connection status in each coding agent, one row per server.
Expand Down Expand Up @@ -2269,35 +2310,10 @@ def list_mcp_command(agents: set[str] | None = None) -> int:

live = _query_live_statuses(probe_clients)

# Merge developer- and workspace-managed servers by registered name, unioning their agents.
# ``--agents`` drops agents outside the scope, and a server left with no in-scope agent is
# omitted. The skills connection is intentionally excluded — it's reported by the skill commands.
configured: dict[str, dict[str, Any]] = {}

def _collect(server: dict, *, managed: bool) -> None:
name = _server_name(server)
if not name or server.get("kind") == SKILLS_MCP_KIND:
return
clients = [
client for client in _mcp_server_clients(server) if agents is None or client in agents
]
if not clients:
return
entry = configured.setdefault(name, {"server": server, "clients": [], "managed": managed})
entry["clients"] = _merge_clients(entry["clients"], clients)
entry["managed"] = entry["managed"] or managed

for server in state.get("mcp_servers") or []:
_collect(server, managed=False)
# Managed servers also live in the OS-managed files (source of truth), not just fallback state.
for server in state.get("managed_mcp_servers") or []:
_collect(server, managed=True)
if agents is None or "claude" in agents:
for name, url in claude.read_managed_mcp_urls().items():
_collect({"name": name, "url": url, "clients": ["claude"]}, managed=True)
if agents is None or "codex" in agents:
for name, url in codex.read_managed_mcp_urls().items():
_collect({"name": name, "url": url, "clients": ["codex"]}, managed=True)
# Merge developer- and workspace-managed servers by registered name, unioning their agents
# (shared with `ug mcp login` so both see the same configured-server set, including the servers
# delivered through the agents' OS-managed files).
configured = configured_mcp_servers_by_name(state, agents)

if configured:
table = Table(box=None, pad_edge=False, header_style="bold")
Expand Down
Loading
Loading