Skip to content

Commit 61c1238

Browse files
authored
MCP: port "install" command from aitools (#3962)
## Changes Introduces `databricks experimental apps-mcp install` command to simplify installing the MCP into Coding agents. This is basically copied over from @lennartkats-db's `aitools` command. <img width="962" height="749" alt="Screenshot 2025-11-20 at 10 35 10" src="https://github.com/user-attachments/assets/955a330a-752d-499f-af37-76ad12b0962d" /> ## Why Make onboarding easier. ## Tests Manually ran the `install` command and validated that it works with Cursor and Claude Code. <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent a3e2e6f commit 61c1238

File tree

5 files changed

+279
-0
lines changed

5 files changed

+279
-0
lines changed

experimental/apps-mcp/cmd/apps_mcp.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,7 @@ The server communicates via stdio using the Model Context Protocol.`,
8282
cmd.Flags().BoolVar(&allowDeployment, "allow-deployment", false, "Enable deployment tools")
8383
cmd.Flags().BoolVar(&withWorkspaceTools, "with-workspace-tools", false, "Enable workspace tools (file operations, bash, grep, glob)")
8484

85+
cmd.AddCommand(newInstallCmd())
86+
8587
return cmd
8688
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package mcp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"time"
8+
9+
"github.com/databricks/cli/experimental/apps-mcp/lib/agents"
10+
"github.com/databricks/cli/libs/cmdio"
11+
"github.com/fatih/color"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
func newInstallCmd() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "install",
18+
Short: "Install the Apps MCP server in coding agents",
19+
Long: `Install the Databricks Apps MCP server in coding agents like Claude Code and Cursor.`,
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
return runInstall(cmd.Context())
22+
},
23+
}
24+
25+
return cmd
26+
}
27+
28+
func runInstall(ctx context.Context) error {
29+
cmdio.LogString(ctx, "")
30+
green := color.New(color.FgGreen).SprintFunc()
31+
cmdio.LogString(ctx, " "+green("[")+"████████"+green("]")+" Databricks Experimental Apps MCP")
32+
cmdio.LogString(ctx, " "+green("[")+"██▌ ▐██"+green("]"))
33+
cmdio.LogString(ctx, " "+green("[")+"████████"+green("]")+" AI-powered Databricks Apps development and exploration")
34+
cmdio.LogString(ctx, "")
35+
36+
yellow := color.New(color.FgYellow).SprintFunc()
37+
cmdio.LogString(ctx, yellow("╔════════════════════════════════════════════════════════════════╗"))
38+
cmdio.LogString(ctx, yellow("║ ⚠️ EXPERIMENTAL: This command may change in future versions ║"))
39+
cmdio.LogString(ctx, yellow("╚════════════════════════════════════════════════════════════════╝"))
40+
cmdio.LogString(ctx, "")
41+
42+
cmdio.LogString(ctx, "Which coding agents would you like to install the MCP server for?")
43+
cmdio.LogString(ctx, "")
44+
45+
anySuccess := false
46+
47+
ans, err := cmdio.AskSelect(ctx, "Install for Claude Code?", []string{"yes", "no"})
48+
if err != nil {
49+
return err
50+
}
51+
if ans == "yes" {
52+
fmt.Fprint(os.Stderr, "Installing MCP server for Claude Code...")
53+
if err := agents.InstallClaude(); err != nil {
54+
fmt.Fprint(os.Stderr, "\r"+color.YellowString("⊘ Skipped Claude Code: "+err.Error())+"\n")
55+
} else {
56+
fmt.Fprint(os.Stderr, "\r"+color.GreenString("✓ Installed for Claude Code")+" \n")
57+
anySuccess = true
58+
}
59+
cmdio.LogString(ctx, "")
60+
}
61+
62+
ans, err = cmdio.AskSelect(ctx, "Install for Cursor?", []string{"yes", "no"})
63+
if err != nil {
64+
return err
65+
}
66+
if ans == "yes" {
67+
fmt.Fprint(os.Stderr, "Installing MCP server for Cursor...")
68+
if err := agents.InstallCursor(); err != nil {
69+
fmt.Fprint(os.Stderr, "\r"+color.YellowString("⊘ Skipped Cursor: "+err.Error())+"\n")
70+
} else {
71+
// Brief delay so users see the "Installing..." message before it's replaced
72+
time.Sleep(1 * time.Second)
73+
fmt.Fprint(os.Stderr, "\r"+color.GreenString("✓ Installed for Cursor")+" \n")
74+
anySuccess = true
75+
}
76+
cmdio.LogString(ctx, "")
77+
}
78+
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 {
85+
return err
86+
}
87+
}
88+
89+
if anySuccess {
90+
cmdio.LogString(ctx, "")
91+
cmdio.LogString(ctx, "You can now use your coding agent to interact with Databricks.")
92+
cmdio.LogString(ctx, "")
93+
cmdio.LogString(ctx, "Try asking: "+color.YellowString("Create an app that calculates taxi trip metrics: average fare by distance bracket and time of day."))
94+
}
95+
96+
return nil
97+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package agents
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
)
9+
10+
// DetectClaude checks if Claude Code CLI is installed and available on PATH.
11+
func DetectClaude() bool {
12+
_, err := exec.LookPath("claude")
13+
return err == nil
14+
}
15+
16+
// InstallClaude installs the Databricks MCP server in Claude Code.
17+
func InstallClaude() error {
18+
if !DetectClaude() {
19+
return errors.New("claude Code CLI is not installed or not on PATH\n\nPlease install Claude Code and ensure 'claude' is available on your system PATH.\nFor installation instructions, visit: https://docs.anthropic.com/en/docs/claude-code")
20+
}
21+
22+
databricksPath, err := os.Executable()
23+
if err != nil {
24+
return err
25+
}
26+
27+
removeCmd := exec.Command("claude", "mcp", "remove", "--scope", "user", "databricks-mcp")
28+
_ = removeCmd.Run()
29+
30+
cmd := exec.Command("claude", "mcp", "add",
31+
"--scope", "user",
32+
"--transport", "stdio",
33+
"databricks-mcp",
34+
"--",
35+
databricksPath, "experimental", "apps-mcp")
36+
37+
output, err := cmd.CombinedOutput()
38+
if err != nil {
39+
return fmt.Errorf("failed to install MCP server in Claude Code: %w\nOutput: %s", err, string(output))
40+
}
41+
42+
return nil
43+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package agents
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"runtime"
9+
)
10+
11+
type cursorConfig struct {
12+
McpServers map[string]mcpServer `json:"mcpServers"`
13+
}
14+
15+
type mcpServer struct {
16+
Command string `json:"command"`
17+
Args []string `json:"args,omitempty"`
18+
Env map[string]string `json:"env,omitempty"`
19+
}
20+
21+
func getCursorConfigPath() (string, error) {
22+
if runtime.GOOS == "windows" {
23+
userProfile := os.Getenv("USERPROFILE")
24+
if userProfile == "" {
25+
return "", os.ErrNotExist
26+
}
27+
return filepath.Join(userProfile, ".cursor", "mcp.json"), nil
28+
}
29+
30+
home, err := os.UserHomeDir()
31+
if err != nil {
32+
return "", err
33+
}
34+
return filepath.Join(home, ".cursor", "mcp.json"), nil
35+
}
36+
37+
// DetectCursor checks if Cursor is installed by looking for its config directory.
38+
func DetectCursor() bool {
39+
configPath, err := getCursorConfigPath()
40+
if err != nil {
41+
return false
42+
}
43+
// Check if the .cursor directory exists (not just the mcp.json file)
44+
cursorDir := filepath.Dir(configPath)
45+
_, err = os.Stat(cursorDir)
46+
return err == nil
47+
}
48+
49+
// InstallCursor installs the Databricks MCP server in Cursor.
50+
func InstallCursor() error {
51+
configPath, err := getCursorConfigPath()
52+
if err != nil {
53+
return fmt.Errorf("failed to determine Cursor config path: %w", err)
54+
}
55+
56+
// Check if .cursor directory exists (not the file, we'll create that if needed)
57+
cursorDir := filepath.Dir(configPath)
58+
if _, err := os.Stat(cursorDir); err != nil {
59+
return fmt.Errorf("cursor directory not found at: %s\n\nPlease install Cursor from: https://cursor.sh", cursorDir)
60+
}
61+
62+
// Read existing config
63+
var config cursorConfig
64+
data, err := os.ReadFile(configPath)
65+
if err != nil {
66+
// If file doesn't exist or can't be read, start with empty config
67+
config = cursorConfig{
68+
McpServers: make(map[string]mcpServer),
69+
}
70+
} else {
71+
if err := json.Unmarshal(data, &config); err != nil {
72+
return fmt.Errorf("failed to parse Cursor config: %w", err)
73+
}
74+
if config.McpServers == nil {
75+
config.McpServers = make(map[string]mcpServer)
76+
}
77+
}
78+
79+
databricksPath, err := os.Executable()
80+
if err != nil {
81+
return fmt.Errorf("failed to determine Databricks path: %w", err)
82+
}
83+
84+
// Add or update the Databricks MCP server entry
85+
config.McpServers["databricks-mcp"] = mcpServer{
86+
Command: databricksPath,
87+
Args: []string{"experimental", "apps-mcp"},
88+
}
89+
90+
// Write back to file with pretty printing
91+
updatedData, err := json.MarshalIndent(config, "", " ")
92+
if err != nil {
93+
return fmt.Errorf("failed to marshal Cursor config: %w", err)
94+
}
95+
96+
if err := os.WriteFile(configPath, updatedData, 0o644); err != nil {
97+
return fmt.Errorf("failed to write Cursor config: %w", err)
98+
}
99+
100+
return nil
101+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package agents
2+
3+
import (
4+
"context"
5+
6+
"github.com/databricks/cli/libs/cmdio"
7+
)
8+
9+
// ShowCustomInstructions displays instructions for manually installing the MCP server.
10+
func ShowCustomInstructions(ctx context.Context) error {
11+
instructions := `
12+
To install the Databricks CLI MCP server in your coding agent:
13+
14+
1. Add a new MCP server to your coding agent's configuration
15+
2. Set the command to: "databricks experimental apps-mcp"
16+
3. No environment variables or additional configuration needed
17+
18+
Example MCP server configuration:
19+
{
20+
"mcpServers": {
21+
"databricks": {
22+
"command": "databricks",
23+
"args": ["experimental", "apps-mcp"]
24+
}
25+
}
26+
}
27+
`
28+
cmdio.LogString(ctx, instructions)
29+
30+
_, err := cmdio.Ask(ctx, "Press Enter to continue", "")
31+
if err != nil {
32+
return err
33+
}
34+
cmdio.LogString(ctx, "")
35+
return nil
36+
}

0 commit comments

Comments
 (0)