Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ Options are shown in this order:
Discovered external MCP connections are listed directly. MCP auth uses a Databricks token that
`ucode` sets when launching each tool.

To set up an agent and its MCP server(s) in one command, pass `--mcp` with fully-qualified
service name(s) to `ucode configure`:

```bash
ucode configure --agents claude --mcp system.ai.slack
```

`--mcp` also works without `--agents` for MCP-only clients (it configures just the workspace,
then registers the servers); pass a comma-separated list to register several at once.

---

## Other Commands
Expand All @@ -107,6 +117,7 @@ Discovered external MCP connections are listed directly. MCP auth uses a Databri
| `ucode configure --profiles DEFAULT` | Configure using existing Databricks CLI profiles (hosts come from `~/.databrickscfg`) |
| `ucode configure --profiles DEFAULT --use-pat` | Authenticate with the profile's personal access token — no browser login |
| `ucode configure --skip-validate` | Write configs without sending a test message through each agent |
| `ucode configure --agents claude --mcp system.ai.slack` | Configure an agent and register its Databricks MCP server(s) in one command |

## Managed Local Files

Expand Down
44 changes: 44 additions & 0 deletions src/ucode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,17 @@ def configure(
"freshly discovered models.",
),
] = False,
mcp: Annotated[
str | None,
typer.Option(
"--mcp",
help="Also register the given Databricks MCP service(s) for the configured "
"coding agents, in one command. Pass a comma-separated list of fully-qualified "
"names like `system.ai.slack`. Combine with --agents to set up an agent and its "
"MCP servers together (e.g. `--agents claude --mcp system.ai.slack`); use without "
"--agents for MCP-only clients such as Cursor.",
),
] = None,
tracing: Annotated[
bool,
typer.Option(
Expand Down Expand Up @@ -1094,6 +1105,19 @@ def configure(
prompt_optional_updates=prompt_optional_updates,
**skip_kwargs,
)
elif mcp is not None:
# MCP-only: `--mcp` without --agent(s) (e.g. Cursor, which isn't a
# model agent, or adding MCP servers to an already-configured setup).
# Configure just the workspace — no interactive agent picker — so the
# `--mcp` registration below has a current workspace to target.
if workspace_entries is None:
workspace_entries = [_prompt_for_configuration(None)]
_configure_shared_workspace_states(
workspace_entries,
tools=[],
force_login=not use_pat,
use_pat=use_pat,
)
else:
# Tool binaries are installed after the user picks which agents
# they want, in configure_workspace_command.
Expand All @@ -1118,6 +1142,26 @@ def configure(
tracing_workspaces = [(current, None)] if current else None
if tracing_workspaces:
configure_tracing_command(workspaces=tracing_workspaces)
if mcp is not None:
# The workspace + agents were just configured above, so the current
# workspace state now lists the agents whose MCP configs we should
# write. `--mcp` takes fully-qualified service names, which
# `configure_mcp_command` locates and registers without a picker
# (bare short names would need --location, which we don't accept here).
services = {name.strip() for name in mcp.split(",") if name.strip()}
if not services:
raise RuntimeError(
"--mcp needs at least one fully-qualified MCP service name, e.g. "
"`--mcp system.ai.slack`."
)
bare = sorted(name for name in services if name.count(".") < 2)
if bare:
raise RuntimeError(
"--mcp names must be fully qualified `<catalog>.<schema>.<name>` "
f"(got: {', '.join(bare)}). Use `ucode configure mcp` for the "
"interactive picker."
)
configure_mcp_command(services=services)
except RuntimeError as exc:
print_err(str(exc))
raise typer.Exit(1) from None
Expand Down
62 changes: 62 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,68 @@ def test_workspaces_flag_rejects_empty_list(self):
mock_cfg.assert_not_called()


class TestConfigureMcpFlag:
def test_mcp_with_agents_configures_then_registers_services(self):
with (
patch("ucode.cli.install_databricks_cli"),
patch("ucode.cli.install_tool_binary"),
patch("ucode.cli.configure_workspace_command") as mock_cfg,
patch("ucode.cli.configure_mcp_command") as mock_mcp,
):
result = runner.invoke(
app,
["configure", "--agents", "claude", "--mcp", "system.ai.slack,system.ai.github"],
)
assert result.exit_code == 0, result.output
mock_cfg.assert_called_once_with(
selected_tools=["claude"],
prompt_optional_updates=True,
)
mock_mcp.assert_called_once_with(services={"system.ai.slack", "system.ai.github"})

def test_mcp_only_configures_workspace_without_agent_picker(self):
# `--mcp` with no --agents (e.g. Cursor): configure the workspace directly,
# never the interactive agent picker, then register the MCP service.
with (
patch("ucode.cli.install_databricks_cli"),
patch("ucode.cli.configure_workspace_command") as mock_cfg,
patch("ucode.cli._configure_shared_workspace_states") as mock_shared,
patch("ucode.cli.configure_mcp_command") as mock_mcp,
):
result = runner.invoke(
app,
[
"configure",
"--workspaces",
"https://ws.databricks.com",
"--mcp",
"system.ai.slack",
],
)
assert result.exit_code == 0, result.output
# Never the model-agent picker path.
mock_cfg.assert_not_called()
mock_shared.assert_called_once()
# Workspace-only: no model tools fetched.
assert (
mock_shared.call_args.kwargs.get("tools") == [] or mock_shared.call_args.args[1] == []
)
mock_mcp.assert_called_once_with(services={"system.ai.slack"})

def test_mcp_rejects_bare_short_name(self):
with (
patch("ucode.cli.install_databricks_cli"),
patch("ucode.cli.configure_workspace_command"),
patch("ucode.cli._configure_shared_workspace_states"),
patch("ucode.cli.configure_mcp_command") as mock_mcp,
):
result = runner.invoke(
app, ["configure", "--workspaces", "https://ws.databricks.com", "--mcp", "slack"]
)
assert result.exit_code != 0
mock_mcp.assert_not_called()


class TestConfigureAgentsSelection:
def test_selected_tools_skip_picker(self, monkeypatch):
import ucode.cli as cli_mod
Expand Down
Loading