@@ -2,13 +2,24 @@ package mcp
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "os"
8+ "slices"
9+ "strings"
710 "time"
811
912 "github.com/databricks/cli/experimental/apps-mcp/lib/agents"
13+ "github.com/databricks/cli/experimental/apps-mcp/lib/middlewares"
1014 "github.com/databricks/cli/libs/cmdio"
15+ "github.com/databricks/cli/libs/databrickscfg/profile"
16+ "github.com/databricks/cli/libs/env"
17+ "github.com/databricks/databricks-sdk-go"
18+ "github.com/databricks/databricks-sdk-go/config"
19+ "github.com/databricks/databricks-sdk-go/httpclient"
20+ "github.com/databricks/databricks-sdk-go/service/sql"
1121 "github.com/fatih/color"
22+ "github.com/manifoldco/promptui"
1223 "github.com/spf13/cobra"
1324)
1425
@@ -18,14 +29,20 @@ func newInstallCmd() *cobra.Command {
1829 Short : "Install the Apps MCP server in coding agents" ,
1930 Long : `Install the Databricks Apps MCP server in coding agents like Claude Code and Cursor.` ,
2031 RunE : func (cmd * cobra.Command , args []string ) error {
21- return runInstall (cmd . Context () )
32+ return runInstall (cmd )
2233 },
2334 }
2435
36+ cmd .Flags ().StringP ("profile" , "p" , "" , "~/.databrickscfg profile" )
37+ cmd .RegisterFlagCompletionFunc ("profile" , profile .ProfileCompletion )
38+ cmd .Flags ().StringP ("warehouse-id" , "w" , "" , "Databricks SQL warehouse ID" )
39+ cmd .Flags ().StringSliceP ("agent" , "a" , []string {}, "Agents to install the MCP server for (valid values: claude, cursor)" )
40+
2541 return cmd
2642}
2743
28- func runInstall (ctx context.Context ) error {
44+ func runInstall (cmd * cobra.Command ) error {
45+ ctx := cmd .Context ()
2946 cmdio .LogString (ctx , "" )
3047 green := color .New (color .FgGreen ).SprintFunc ()
3148 cmdio .LogString (ctx , " " + green ("[" )+ "████████" + green ("]" )+ " Databricks Experimental Apps MCP" )
@@ -39,18 +56,57 @@ func runInstall(ctx context.Context) error {
3956 cmdio .LogString (ctx , yellow ("╚════════════════════════════════════════════════════════════════╝" ))
4057 cmdio .LogString (ctx , "" )
4158
42- cmdio .LogString (ctx , "Which coding agents would you like to install the MCP server for?" )
59+ // Check for profile configuration
60+ selectedProfile , err := selectProfile (cmd )
61+ if err != nil {
62+ return err
63+ }
64+
4365 cmdio .LogString (ctx , "" )
66+ cmdio .LogString (ctx , fmt .Sprintf ("Using profile: %s (%s)" , color .CyanString (selectedProfile .Name ), selectedProfile .Host ))
4467
45- anySuccess := false
68+ warehouse , err := selectAndValidateWarehouse (ctx , cmd .Flag ("warehouse-id" ).Value .String (), selectedProfile )
69+ if err != nil {
70+ return err
71+ }
72+ cmdio .LogString (ctx , fmt .Sprintf ("Using warehouse: %s (%s)" , color .CyanString (warehouse .Name ), warehouse .Id ))
73+ cmdio .LogString (ctx , "" )
4674
47- ans , err := cmdio .AskSelect (ctx , "Install for Claude Code?" , []string {"yes" , "no" })
75+ // Check if --agent flag is set
76+ requestedAgents , err := cmd .Flags ().GetStringSlice ("agent" )
4877 if err != nil {
4978 return err
5079 }
51- if ans == "yes" {
80+
81+ // Normalize and validate agent names
82+ for i , agent := range requestedAgents {
83+ agent = strings .TrimSpace (strings .ToLower (agent ))
84+ requestedAgents [i ] = agent
85+ if agent != "" && agent != "claude" && agent != "cursor" {
86+ return fmt .Errorf ("invalid agent %q. Valid agents are: claude, cursor" , agent )
87+ }
88+ }
89+
90+ anySuccess := false
91+
92+ // Install for Claude Code
93+ installClaude := false
94+ if len (requestedAgents ) > 0 {
95+ installClaude = slices .Contains (requestedAgents , "claude" )
96+ } else {
97+ // Prompt the user
98+ cmdio .LogString (ctx , "Which coding agents would you like to install the MCP server for?" )
99+ cmdio .LogString (ctx , "" )
100+ ans , err := cmdio .AskSelect (ctx , "Install for Claude Code?" , []string {"yes" , "no" })
101+ if err != nil {
102+ return err
103+ }
104+ installClaude = ans == "yes"
105+ }
106+
107+ if installClaude {
52108 fmt .Fprint (os .Stderr , "Installing MCP server for Claude Code..." )
53- if err := agents .InstallClaude (); err != nil {
109+ if err := agents .InstallClaude (selectedProfile , warehouse . Id ); err != nil {
54110 fmt .Fprint (os .Stderr , "\r " + color .YellowString ("⊘ Skipped Claude Code: " + err .Error ())+ "\n " )
55111 } else {
56112 fmt .Fprint (os .Stderr , "\r " + color .GreenString ("✓ Installed for Claude Code" )+ " \n " )
@@ -59,13 +115,22 @@ func runInstall(ctx context.Context) error {
59115 cmdio .LogString (ctx , "" )
60116 }
61117
62- ans , err = cmdio .AskSelect (ctx , "Install for Cursor?" , []string {"yes" , "no" })
63- if err != nil {
64- return err
118+ // Install for Cursor
119+ installCursor := false
120+ if len (requestedAgents ) > 0 {
121+ installCursor = slices .Contains (requestedAgents , "cursor" )
122+ } else {
123+ // Prompt the user
124+ ans , err := cmdio .AskSelect (ctx , "Install for Cursor?" , []string {"yes" , "no" })
125+ if err != nil {
126+ return err
127+ }
128+ installCursor = ans == "yes"
65129 }
66- if ans == "yes" {
130+
131+ if installCursor {
67132 fmt .Fprint (os .Stderr , "Installing MCP server for Cursor..." )
68- if err := agents .InstallCursor (); err != nil {
133+ if err := agents .InstallCursor (selectedProfile , warehouse . Id ); err != nil {
69134 fmt .Fprint (os .Stderr , "\r " + color .YellowString ("⊘ Skipped Cursor: " + err .Error ())+ "\n " )
70135 } else {
71136 // Brief delay so users see the "Installing..." message before it's replaced
@@ -76,14 +141,17 @@ func runInstall(ctx context.Context) error {
76141 cmdio .LogString (ctx , "" )
77142 }
78143
79- ans , err = cmdio .AskSelect (ctx , "Show manual installation instructions for other agents?" , []string {"yes" , "no" })
80- if err != nil {
81- return err
82- }
83- if ans == "yes" {
84- if err := agents .ShowCustomInstructions (ctx ); err != nil {
144+ // Only show custom instructions if no agents were specified or installed
145+ if len (requestedAgents ) == 0 {
146+ ans , err := cmdio .AskSelect (ctx , "Show manual installation instructions for other agents?" , []string {"yes" , "no" })
147+ if err != nil {
85148 return err
86149 }
150+ if ans == "yes" {
151+ if err := agents .ShowCustomInstructions (ctx , selectedProfile , warehouse .Id ); err != nil {
152+ return err
153+ }
154+ }
87155 }
88156
89157 if anySuccess {
@@ -95,3 +163,167 @@ func runInstall(ctx context.Context) error {
95163
96164 return nil
97165}
166+
167+ func selectAndValidateWarehouse (ctx context.Context , warehouseIdFlag string , selectedProfile * profile.Profile ) (* sql.EndpointInfo , error ) {
168+ w , err := databricks .NewWorkspaceClient (& databricks.Config {
169+ Profile : selectedProfile .Name ,
170+ })
171+ if err != nil {
172+ return nil , err
173+ }
174+
175+ var warehouse * sql.EndpointInfo
176+ if warehouseIdFlag != "" {
177+ warehouseResponse , err := w .Warehouses .Get (ctx , sql.GetWarehouseRequest {
178+ Id : warehouseIdFlag ,
179+ })
180+ if err != nil {
181+ return nil , fmt .Errorf ("get warehouse: %w" , err )
182+ }
183+ warehouse = & sql.EndpointInfo {
184+ Id : warehouseResponse .Id ,
185+ Name : warehouseResponse .Name ,
186+ State : warehouseResponse .State ,
187+ }
188+ } else {
189+ // Auto-detect warehouse
190+
191+ clientCfg , err := config .HTTPClientConfigFromConfig (w .Config )
192+ if err != nil {
193+ return nil , fmt .Errorf ("failed to create HTTP client config: %w" , err )
194+ }
195+ apiClient := httpclient .NewApiClient (clientCfg )
196+ warehouse , err = middlewares .GetDefaultWarehouse (ctx , apiClient )
197+ if err != nil {
198+ return nil , err
199+ }
200+ }
201+
202+ if warehouse == nil {
203+ return nil , errors .New ("no warehouse found" )
204+ }
205+
206+ // Validate warehouse connection with a simple query
207+ _ , err = w .StatementExecution .ExecuteAndWait (ctx , sql.ExecuteStatementRequest {
208+ WarehouseId : warehouse .Id ,
209+ Statement : "SELECT 1" ,
210+ WaitTimeout : "30s" ,
211+ })
212+ if err != nil {
213+ return nil , fmt .Errorf ("failed to validate warehouse connection: %w" , err )
214+ }
215+
216+ return warehouse , nil
217+ }
218+
219+ // selectProfile checks if a profile is available and prompts the user to select one if needed.
220+ func selectProfile (cmd * cobra.Command ) (* profile.Profile , error ) {
221+ ctx := cmd .Context ()
222+ profiler := profile .GetProfiler (ctx )
223+
224+ // Load all workspace profiles
225+ profiles , err := profiler .LoadProfiles (ctx , profile .MatchWorkspaceProfiles )
226+ if err != nil {
227+ return nil , fmt .Errorf ("failed to load profiles: %w" , err )
228+ }
229+
230+ // If no profiles are available, ask the user to login
231+ if len (profiles ) == 0 {
232+ cmdio .LogString (ctx , color .RedString ("No Databricks profiles found." ))
233+ cmdio .LogString (ctx , "" )
234+ cmdio .LogString (ctx , "To authenticate, please run:" )
235+ cmdio .LogString (ctx , " " + color .YellowString ("databricks auth login --host <workspace-url>" ))
236+ cmdio .LogString (ctx , "" )
237+ cmdio .LogString (ctx , "Then run this command again." )
238+ return nil , errors .New ("no profiles configured" )
239+ }
240+
241+ // Check if --profile flag is set
242+ profileFlag := cmd .Flag ("profile" )
243+ if profileFlag != nil && profileFlag .Value .String () != "" {
244+ requestedProfile := profileFlag .Value .String ()
245+
246+ // Find the requested profile
247+ var found * profile.Profile
248+ for i := range profiles {
249+ if profiles [i ].Name == requestedProfile {
250+ found = & profiles [i ]
251+ break
252+ }
253+ }
254+
255+ if found == nil {
256+ return nil , fmt .Errorf ("profile %q not found in ~/.databrickscfg. Run `databricks auth login <workspace-url> -p %s` to create this profile and then run this command again" , requestedProfile , requestedProfile )
257+ }
258+
259+ return found , nil
260+ }
261+
262+ // Get the current profile name from environment variable
263+ currentProfileName := env .Get (ctx , "DATABRICKS_CONFIG_PROFILE" )
264+ if currentProfileName == "" {
265+ currentProfileName = "DEFAULT"
266+ }
267+
268+ // Find the current profile in the list
269+ var currentProfile * profile.Profile
270+ for i := range profiles {
271+ if profiles [i ].Name == currentProfileName {
272+ currentProfile = & profiles [i ]
273+ break
274+ }
275+ }
276+
277+ // If a profile is already selected, show it and ask if they want to use it
278+ if currentProfile != nil {
279+ cmdio .LogString (ctx , "Current Databricks profile:" )
280+ cmdio .LogString (ctx , " Name: " + color .CyanString (currentProfile .Name ))
281+ cmdio .LogString (ctx , " Host: " + color .CyanString (currentProfile .Host ))
282+ cmdio .LogString (ctx , "" )
283+
284+ ans , err := cmdio .AskSelect (ctx , "Use this profile?" , []string {"yes" , "no" })
285+ if err != nil {
286+ return nil , err
287+ }
288+
289+ if ans == "yes" {
290+ return currentProfile , nil
291+ }
292+ }
293+
294+ // User wants to select a different profile, or no current profile set
295+ // Show all available profiles for selection
296+ if len (profiles ) == 1 {
297+ // Only one profile available, use it
298+ selectedProfile := profiles [0 ]
299+ cmdio .LogString (ctx , fmt .Sprintf ("Using profile: %s (%s)" , color .CyanString (selectedProfile .Name ), selectedProfile .Host ))
300+ cmdio .LogString (ctx , "" )
301+ cmdio .LogString (ctx , "Set this profile by running:" )
302+ cmdio .LogString (ctx , " " + color .YellowString ("export DATABRICKS_CONFIG_PROFILE=" + selectedProfile .Name ))
303+ return & selectedProfile , nil
304+ }
305+
306+ cmdio .LogString (ctx , "Which Databricks profile would you like to use with the MCP server?" )
307+ cmdio .LogString (ctx , "(You can change the profile later by running this install command again)" )
308+ cmdio .LogString (ctx , "" )
309+
310+ // Multiple profiles available, let the user select
311+ i , _ , err := cmdio .RunSelect (ctx , & promptui.Select {
312+ Label : "Select a Databricks profile" ,
313+ Items : profiles ,
314+ Searcher : profiles .SearchCaseInsensitive ,
315+ StartInSearchMode : true ,
316+ Templates : & promptui.SelectTemplates {
317+ Label : "{{ . | faint }}" ,
318+ Active : `{{.Name | bold}} ({{.Host|faint}})` ,
319+ Inactive : `{{.Name}} ({{.Host}})` ,
320+ Selected : `{{ "Selected profile" | faint }}: {{ .Name | bold }}` ,
321+ },
322+ })
323+ if err != nil {
324+ return nil , err
325+ }
326+
327+ selectedProfile := profiles [i ]
328+ return & selectedProfile , nil
329+ }
0 commit comments