Skip to content
Closed
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
29 changes: 25 additions & 4 deletions experimental/apps-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ extensive validation to ensure high-quality outputs.
1. **Explore your data** - Query Databricks catalogs, schemas, and tables to understand your data
2. **Generate the app** - Scaffold a full-stack TypeScript application (tRPC + React) with proper structure
3. **Customize with AI** - Use workspace tools to read, write, and edit files naturally through conversation
4. **Validate rigorously** - Run builds, type checks, and tests to ensure quality
4. **Validate rigorously** - Run builds, type checks, and tests in isolated containers (Dagger)
5. **Deploy confidently** - Push validated apps directly to Databricks Apps platform

**Why use it:**
- **Speed**: Go from concept to deployed Databricks app in minutes, not hours or days
- **Quality**: Extensive validation ensures your app builds, passes tests, and is production-ready
- **Safety**: Containerized validation prevents breaking changes from reaching production
- **Simplicity**: One natural language conversation handles the entire workflow

Perfect for data engineers and developers who want to build Databricks apps without the manual overhead of project setup, configuration, testing infrastructure, and deployment pipelines.
Expand Down Expand Up @@ -103,10 +104,11 @@ Create the application structure:

Ensure production-readiness before deployment:

- **`validate_data_app`** - Comprehensive validation
- **`validate_data_app`** - Comprehensive validation in isolated containers
- Build verification (npm build)
- Type checking (TypeScript compiler)
- Test execution (full test suite)
- Containerized with Dagger (Docker)

*This step guarantees your application is tested and ready for production before deployment.*

Expand Down Expand Up @@ -258,12 +260,17 @@ Deploy the app to Databricks as "orders-dashboard"
- Every change is validated before deployment
- No broken builds reach production

**2. Natural Language = Productivity**
**2. Containerized Validation = Safety**
- Dagger containers ensure reproducible builds
- Isolated testing prevents environment issues
- Consistent behavior from development to production

**3. Natural Language = Productivity**
- Describe what you want, not how to build it
- AI handles implementation details
- Focus on requirements, not configuration

**3. End-to-End Workflow = Simplicity**
**4. End-to-End Workflow = Simplicity**
- Single tool for entire lifecycle
- No context switching between tools
- Seamless progression from idea to deployment
Expand All @@ -276,6 +283,7 @@ The Databricks MCP server doesn't just generate code—it ensures quality:
- ✅ **Build verification** - Ensures code compiles
- ✅ **Test suite** - Validates functionality
- ✅ **Linting** - Enforces code quality
- ✅ **Containerization** - Reproducible environments
- ✅ **Databricks integration** - Native SDK usage

---
Expand All @@ -293,6 +301,15 @@ databricks experimental apps-mcp --warehouse-id <warehouse-id> --with-workspace-

# Enable deployment
databricks experimental apps-mcp --warehouse-id <warehouse-id> --allow-deployment

# With custom Docker image
databricks experimental apps-mcp --warehouse-id <warehouse-id> --docker-image node:20-alpine

# Without containerized validation
databricks experimental apps-mcp --warehouse-id <warehouse-id> --use-dagger=false

# Check configuration
databricks experimental apps-mcp check
```

### CLI Flags
Expand All @@ -302,6 +319,8 @@ databricks experimental apps-mcp --warehouse-id <warehouse-id> --allow-deploymen
| `--warehouse-id` | Databricks SQL Warehouse ID (required) | - |
| `--with-workspace-tools` | Enable workspace file operations | `false` |
| `--allow-deployment` | Enable deployment operations | `false` |
| `--docker-image` | Docker image for validation | `node:20-alpine` |
| `--use-dagger` | Use Dagger for containerized validation | `true` |
| `--help` | Show help | - |

### Environment Variables
Expand All @@ -314,6 +333,7 @@ databricks experimental apps-mcp --warehouse-id <warehouse-id> --allow-deploymen
| `DATABRICKS_WAREHOUSE_ID` | Alternative name for warehouse ID | `abc123def456` |
| `ALLOW_DEPLOYMENT` | Enable deployment operations | `true` or `false` |
| `WITH_WORKSPACE_TOOLS` | Enable workspace tools | `true` or `false` |
| `USE_DAGGER` | Enable Dagger sandbox for validation | `true` or `false` |

### Authentication

Expand All @@ -328,6 +348,7 @@ For more details, see the [Databricks authentication documentation](https://docs
### Requirements

- **Databricks CLI** (this package)
- **Docker** (optional, for Dagger-based validation)
- **Databricks workspace** with a SQL warehouse
- **MCP-compatible client** (Claude Desktop, Continue, etc.)

Expand Down
19 changes: 17 additions & 2 deletions experimental/apps-mcp/cmd/apps_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ func NewMcpCmd() *cobra.Command {
var warehouseID string
var allowDeployment bool
var withWorkspaceTools bool
var dockerImage string
var useDagger bool

cmd := &cobra.Command{
Use: "apps-mcp",
Expand All @@ -33,7 +35,13 @@ The server communicates via stdio using the Model Context Protocol.`,
databricks experimental apps-mcp --warehouse-id abc123 --with-workspace-tools

# Start with deployment tools enabled
databricks experimental apps-mcp --warehouse-id abc123 --allow-deployment`,
databricks experimental apps-mcp --warehouse-id abc123 --allow-deployment

# Start with custom Docker image for validation
databricks experimental apps-mcp --warehouse-id abc123 --docker-image node:20-alpine

# Start without containerized validation
databricks experimental apps-mcp --warehouse-id abc123 --use-dagger=false`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this the default mode?

PreRunE: root.MustWorkspaceClient,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
Expand All @@ -47,7 +55,10 @@ The server communicates via stdio using the Model Context Protocol.`,
WarehouseID: warehouseID,
DatabricksHost: w.Config.Host,
IoConfig: &mcplib.IoConfig{
Validation: &mcplib.ValidationConfig{},
Validation: &mcplib.ValidationConfig{
DockerImage: dockerImage,
UseDagger: useDagger,
},
},
}

Expand All @@ -71,6 +82,10 @@ The server communicates via stdio using the Model Context Protocol.`,
cmd.Flags().StringVar(&warehouseID, "warehouse-id", "", "Databricks SQL Warehouse ID")
cmd.Flags().BoolVar(&allowDeployment, "allow-deployment", false, "Enable deployment tools")
cmd.Flags().BoolVar(&withWorkspaceTools, "with-workspace-tools", false, "Enable workspace tools (file operations, bash, grep, glob)")
cmd.Flags().StringVar(&dockerImage, "docker-image", "node:20-alpine", "Docker image for validation")
cmd.Flags().BoolVar(&useDagger, "use-dagger", true, "Use Dagger for containerized validation")

cmd.AddCommand(newCheckCmd())

return cmd
}
51 changes: 51 additions & 0 deletions experimental/apps-mcp/cmd/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package mcp

import (
"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/log"
"github.com/spf13/cobra"
)

func newCheckCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "check",
Short: "Check MCP server environment",
Long: `Verify that the environment is correctly configured for running the MCP server.
This command checks:
- Databricks authentication (API token, profile, or other auth methods)
- Workspace connectivity
Use this command to troubleshoot connection issues before starting the MCP server.`,
Example: ` # Check environment configuration
databricks experimental apps-mcp check
# Check with specific profile
databricks experimental apps-mcp check --profile production`,
PreRunE: root.MustWorkspaceClient,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

log.Info(ctx, "Checking MCP server environment")

// Check Databricks authentication
w := cmdctx.WorkspaceClient(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDK template requires much more env vars in .env file - does it make sense to detect if there is SDK template installed and enforce checks for .env?

me, err := w.CurrentUser.Me(ctx)
if err != nil {
return err
}

cmdio.LogString(ctx, "✓ Databricks authentication: OK")
cmdio.LogString(ctx, " User: "+me.UserName)
cmdio.LogString(ctx, " Host: "+w.Config.Host)

cmdio.LogString(ctx, "\nEnvironment is ready for MCP server")

return nil
},
}

return cmd
}
22 changes: 19 additions & 3 deletions experimental/apps-mcp/lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Config struct {
type IoConfig struct {
Template *TemplateConfig
Validation *ValidationConfig
Dagger *DaggerConfig
}

// TemplateConfig specifies which template to use for scaffolding new projects.
Expand All @@ -24,19 +25,30 @@ type TemplateConfig struct {
Path string
}

// ValidationConfig defines custom validation commands for project validation.
// ValidationConfig defines custom validation commands and docker images for project validation.
type ValidationConfig struct {
Command string
Timeout int
Command string
DockerImage string
UseDagger bool
Timeout int
}

// SetDefaults applies default values to ValidationConfig if not explicitly set.
func (v *ValidationConfig) SetDefaults() {
if v.DockerImage == "" {
v.DockerImage = "node:20-alpine"
}
if v.Timeout == 0 {
v.Timeout = 600
}
}

// DaggerConfig configures the Dagger sandbox when use_dagger is enabled.
type DaggerConfig struct {
Image string
ExecuteTimeout int
}

// DefaultConfig returns a Config with sensible default values.
func DefaultConfig() *Config {
validationCfg := &ValidationConfig{}
Expand All @@ -51,6 +63,10 @@ func DefaultConfig() *Config {
Path: "",
},
Validation: validationCfg,
Dagger: &DaggerConfig{
Image: "node:20-alpine",
ExecuteTimeout: 600,
},
},
WarehouseID: "",
}
Expand Down
105 changes: 98 additions & 7 deletions experimental/apps-mcp/lib/providers/io/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"os"
"path/filepath"

mcp "github.com/databricks/cli/experimental/apps-mcp/lib"
"github.com/databricks/cli/experimental/apps-mcp/lib/sandbox"
"github.com/databricks/cli/experimental/apps-mcp/lib/sandbox/dagger"
"github.com/databricks/cli/experimental/apps-mcp/lib/sandbox/local"
"github.com/databricks/cli/libs/log"
)
Expand Down Expand Up @@ -44,7 +47,7 @@ func (p *Provider) Validate(ctx context.Context, args *ValidateArgs) (*ValidateR
valConfig := p.config.Validation
if valConfig.Command != "" {
log.Infof(ctx, "using custom validation command: command=%s", valConfig.Command)
validation = NewValidationCmd(valConfig.Command, "")
validation = NewValidationCmd(valConfig.Command, valConfig.DockerImage)
}
}

Expand All @@ -53,12 +56,45 @@ func (p *Provider) Validate(ctx context.Context, args *ValidateArgs) (*ValidateR
validation = NewValidationTRPC()
}

log.Info(ctx, "using local sandbox for validation")
sb, err := p.createLocalSandbox(workDir)
if err != nil {
return nil, fmt.Errorf("failed to create local sandbox: %w", err)
validationCfg := p.config.Validation
if validationCfg == nil {
validationCfg = &mcp.ValidationConfig{}
validationCfg.SetDefaults()
} else {
validationCfg.SetDefaults()
}

var sb sandbox.Sandbox
var sandboxType string
if validationCfg.UseDagger {
log.Info(ctx, "attempting to create Dagger sandbox")
daggerSb, err := p.createDaggerSandbox(ctx, workDir, validationCfg)
if err != nil {
log.Warnf(ctx, "failed to create Dagger sandbox, falling back to local: error=%s", err.Error())
sb, err = p.createLocalSandbox(workDir)
if err != nil {
return nil, fmt.Errorf("failed to create local sandbox: %w", err)
}
sandboxType = "local"
} else {
sb = daggerSb
sandboxType = "dagger"
}
} else {
log.Info(ctx, "using local sandbox")
sb, err = p.createLocalSandbox(workDir)
if err != nil {
return nil, fmt.Errorf("failed to create local sandbox: %w", err)
}
sandboxType = "local"
}

// Log which sandbox is being used for transparency
if sandboxType == "dagger" {
log.Info(ctx, "✓ Using Dagger sandbox for validation (containerized, isolated environment)")
} else {
log.Info(ctx, "Using local sandbox for validation (host filesystem)")
}
sandboxType := "local"

defer func() {
if closeErr := sb.Close(); closeErr != nil {
Expand Down Expand Up @@ -101,7 +137,62 @@ func (p *Provider) Validate(ctx context.Context, args *ValidateArgs) (*ValidateR
return result, nil
}

func (p *Provider) createLocalSandbox(workDir string) (*local.LocalSandbox, error) {
func (p *Provider) createDaggerSandbox(ctx context.Context, workDir string, cfg *mcp.ValidationConfig) (sandbox.Sandbox, error) {
log.Infof(ctx, "creating Dagger sandbox: image=%s, timeout=%d, workDir=%s",
cfg.DockerImage, cfg.Timeout, workDir)

sb, err := dagger.NewDaggerSandbox(ctx, dagger.Config{
Image: cfg.DockerImage,
ExecuteTimeout: cfg.Timeout,
BaseDir: "/workspace",
})
if err != nil {
log.Errorf(ctx, "failed to create Dagger sandbox: error=%s, image=%s",
err.Error(), cfg.DockerImage)
return nil, err
}

log.Debug(ctx, "propagating environment variables")
if err := p.propagateEnvironment(sb); err != nil {
log.Errorf(ctx, "failed to propagate environment: error=%s", err.Error())
sb.Close()
return nil, fmt.Errorf("failed to set environment: %w", err)
}

log.Debugf(ctx, "syncing files from host to container: workDir=%s", workDir)
if err := sb.RefreshFromHost(ctx, workDir, "/workspace"); err != nil {
log.Errorf(ctx, "failed to sync files: error=%s", err.Error())
sb.Close()
return nil, fmt.Errorf("failed to sync files: %w", err)
}

log.Info(ctx, "Dagger sandbox created successfully")
return sb, nil
}

func (p *Provider) createLocalSandbox(workDir string) (sandbox.Sandbox, error) {
log.Infof(p.ctx, "creating local sandbox: workDir=%s", workDir)
return local.NewLocalSandbox(workDir)
}

func (p *Provider) propagateEnvironment(sb sandbox.Sandbox) error {
daggerSb, ok := sb.(*dagger.DaggerSandbox)
if !ok {
return nil
}

envVars := []string{
"DATABRICKS_HOST",
"DATABRICKS_TOKEN",
"DATABRICKS_WAREHOUSE_ID",
}

for _, key := range envVars {
if value := os.Getenv(key); value != "" {
daggerSb.WithEnv(key, value)
log.Debugf(p.ctx, "propagated environment variable: key=%s", key)
}
}

return nil
}
Loading