Skip to content

Commit 0a5225a

Browse files
Add ucode configure --mcp one-shot flag (#199)
Register Databricks MCP service(s) for configured agents in a single command: ucode configure --agents claude --mcp system.ai.slack `--mcp` takes a comma-separated list of fully-qualified `<catalog>.<schema>.<name>` service names. It runs after the workspace + agents are configured (alongside the existing `--tracing` hook) and dispatches to the already-tested `configure_mcp_command(services=...)`. Without `--agents` (e.g. for MCP-only clients like Cursor) it configures just the workspace — no interactive agent picker — then registers the servers. Bare short names are rejected with a pointer to `ucode configure mcp` for the picker. Co-authored-by: Isaac
1 parent 86491e2 commit 0a5225a

3 files changed

Lines changed: 117 additions & 0 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ Options are shown in this order:
9292
Discovered external MCP connections are listed directly. MCP auth uses a Databricks token that
9393
`ucode` sets when launching each tool.
9494

95+
To set up an agent and its MCP server(s) in one command, pass `--mcp` with fully-qualified
96+
service name(s) to `ucode configure`:
97+
98+
```bash
99+
ucode configure --agents claude --mcp system.ai.slack
100+
```
101+
102+
`--mcp` also works without `--agents` for MCP-only clients (it configures just the workspace,
103+
then registers the servers); pass a comma-separated list to register several at once.
104+
95105
---
96106

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

111122
## Managed Local Files
112123

src/ucode/cli.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,17 @@ def configure(
11371137
"--disable-databricks-ai-tools to opt out.",
11381138
),
11391139
] = None,
1140+
mcp: Annotated[
1141+
str | None,
1142+
typer.Option(
1143+
"--mcp",
1144+
help="Also register the given Databricks MCP service(s) for the configured "
1145+
"coding agents, in one command. Pass a comma-separated list of fully-qualified "
1146+
"names like `system.ai.slack`. Combine with --agents to set up an agent and its "
1147+
"MCP servers together (e.g. `--agents claude --mcp system.ai.slack`); use without "
1148+
"--agents for MCP-only clients such as Cursor.",
1149+
),
1150+
] = None,
11401151
tracing: Annotated[
11411152
bool,
11421153
typer.Option(
@@ -1235,6 +1246,19 @@ def configure(
12351246
prompt_optional_updates=prompt_optional_updates,
12361247
**skip_kwargs,
12371248
)
1249+
elif mcp is not None:
1250+
# MCP-only: `--mcp` without --agent(s) (e.g. Cursor, which isn't a
1251+
# model agent, or adding MCP servers to an already-configured setup).
1252+
# Configure just the workspace — no interactive agent picker — so the
1253+
# `--mcp` registration below has a current workspace to target.
1254+
if workspace_entries is None:
1255+
workspace_entries = [_prompt_for_configuration(None)]
1256+
_configure_shared_workspace_states(
1257+
workspace_entries,
1258+
tools=[],
1259+
force_login=not use_pat,
1260+
use_pat=use_pat,
1261+
)
12381262
else:
12391263
# Tool binaries are installed after the user picks which agents
12401264
# they want, in configure_workspace_command.
@@ -1259,6 +1283,26 @@ def configure(
12591283
tracing_workspaces = [(current, None)] if current else None
12601284
if tracing_workspaces:
12611285
configure_tracing_command(workspaces=tracing_workspaces)
1286+
if mcp is not None:
1287+
# The workspace + agents were just configured above, so the current
1288+
# workspace state now lists the agents whose MCP configs we should
1289+
# write. `--mcp` takes fully-qualified service names, which
1290+
# `configure_mcp_command` locates and registers without a picker
1291+
# (bare short names would need --location, which we don't accept here).
1292+
services = {name.strip() for name in mcp.split(",") if name.strip()}
1293+
if not services:
1294+
raise RuntimeError(
1295+
"--mcp needs at least one fully-qualified MCP service name, e.g. "
1296+
"`--mcp system.ai.slack`."
1297+
)
1298+
bare = sorted(name for name in services if name.count(".") < 2)
1299+
if bare:
1300+
raise RuntimeError(
1301+
"--mcp names must be fully qualified `<catalog>.<schema>.<name>` "
1302+
f"(got: {', '.join(bare)}). Use `ucode configure mcp` for the "
1303+
"interactive picker."
1304+
)
1305+
configure_mcp_command(services=services)
12621306
except RuntimeError as exc:
12631307
print_err(str(exc))
12641308
raise typer.Exit(1) from None

tests/test_cli.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,68 @@ def test_workspaces_flag_rejects_empty_list(self):
839839
mock_cfg.assert_not_called()
840840

841841

842+
class TestConfigureMcpFlag:
843+
def test_mcp_with_agents_configures_then_registers_services(self):
844+
with (
845+
patch("ucode.cli.install_databricks_cli"),
846+
patch("ucode.cli.install_tool_binary"),
847+
patch("ucode.cli.configure_workspace_command") as mock_cfg,
848+
patch("ucode.cli.configure_mcp_command") as mock_mcp,
849+
):
850+
result = runner.invoke(
851+
app,
852+
["configure", "--agents", "claude", "--mcp", "system.ai.slack,system.ai.github"],
853+
)
854+
assert result.exit_code == 0, result.output
855+
mock_cfg.assert_called_once_with(
856+
selected_tools=["claude"],
857+
prompt_optional_updates=True,
858+
)
859+
mock_mcp.assert_called_once_with(services={"system.ai.slack", "system.ai.github"})
860+
861+
def test_mcp_only_configures_workspace_without_agent_picker(self):
862+
# `--mcp` with no --agents (e.g. Cursor): configure the workspace directly,
863+
# never the interactive agent picker, then register the MCP service.
864+
with (
865+
patch("ucode.cli.install_databricks_cli"),
866+
patch("ucode.cli.configure_workspace_command") as mock_cfg,
867+
patch("ucode.cli._configure_shared_workspace_states") as mock_shared,
868+
patch("ucode.cli.configure_mcp_command") as mock_mcp,
869+
):
870+
result = runner.invoke(
871+
app,
872+
[
873+
"configure",
874+
"--workspaces",
875+
"https://ws.databricks.com",
876+
"--mcp",
877+
"system.ai.slack",
878+
],
879+
)
880+
assert result.exit_code == 0, result.output
881+
# Never the model-agent picker path.
882+
mock_cfg.assert_not_called()
883+
mock_shared.assert_called_once()
884+
# Workspace-only: no model tools fetched.
885+
assert (
886+
mock_shared.call_args.kwargs.get("tools") == [] or mock_shared.call_args.args[1] == []
887+
)
888+
mock_mcp.assert_called_once_with(services={"system.ai.slack"})
889+
890+
def test_mcp_rejects_bare_short_name(self):
891+
with (
892+
patch("ucode.cli.install_databricks_cli"),
893+
patch("ucode.cli.configure_workspace_command"),
894+
patch("ucode.cli._configure_shared_workspace_states"),
895+
patch("ucode.cli.configure_mcp_command") as mock_mcp,
896+
):
897+
result = runner.invoke(
898+
app, ["configure", "--workspaces", "https://ws.databricks.com", "--mcp", "slack"]
899+
)
900+
assert result.exit_code != 0
901+
mock_mcp.assert_not_called()
902+
903+
842904
class TestConfigureAgentsSelection:
843905
def test_selected_tools_skip_picker(self, monkeypatch):
844906
import ucode.cli as cli_mod

0 commit comments

Comments
 (0)