Skip to content

Commit 73515a5

Browse files
fjakobspietern
andauthored
MCP: Create AppKit Template (#4007)
## Changes * Added AppKit template * Removed tRPC template * Introduced go templates for interpolating files from the template * Removed `--allow-deployment` configuration. It's defaulting to `true` now. * The new template features * AppKit support (bundled dependency) * Playwright for E2E smoke test validation * DABs pre-configured (not currently used but will be in the future) ## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. For example, were there any decisions behind the change that are not reflected in the code itself? --> ## Tests Creating dozens of NYC taxi apps <!-- 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. --> --------- Co-authored-by: Pieter Noordhuis <[email protected]>
1 parent 691538b commit 73515a5

File tree

157 files changed

+13940
-14798
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

157 files changed

+13940
-14798
lines changed

.wsignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ python/docs/images/databricks-logo.svg
1010
# Binary files
1111
**/*.zip
1212
**/*.whl
13+
**/*.png
14+
**/*.tgz
1315

1416
# new lines are recorded differently on windows and unix.
1517
# In unix: "raw_body": "hello, world\n"

experimental/apps-mcp/README.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extensive validation to ensure high-quality outputs.
1010

1111
**How it works:**
1212
1. **Explore your data** - Query Databricks catalogs, schemas, and tables to understand your data
13-
2. **Generate the app** - Scaffold a full-stack TypeScript application (tRPC + React) with proper structure
13+
2. **Generate the app** - Scaffold a full-stack TypeScript application with proper structure
1414
3. **Customize with AI** - Use workspace tools to read, write, and edit files naturally through conversation
1515
4. **Validate rigorously** - Run builds, type checks, and tests to ensure quality
1616
5. **Deploy confidently** - Push validated apps directly to Databricks Apps platform
@@ -91,7 +91,7 @@ Understand your Databricks data before building:
9191

9292
Create the application structure:
9393

94-
- **`scaffold_data_app`** - Generate a full-stack TypeScript application
94+
- **`scaffold_databricks_app`** - Generate a full-stack TypeScript application
9595
- Modern stack: Node.js, TypeScript, React, tRPC
9696
- Pre-configured build system, linting, and testing
9797
- Production-ready project structure
@@ -103,7 +103,7 @@ Create the application structure:
103103

104104
Ensure production-readiness before deployment:
105105

106-
- **`validate_data_app`** - Comprehensive validation
106+
- **`validate_databricks_app`** - Comprehensive validation
107107
- Build verification (npm build)
108108
- Type checking (TypeScript compiler)
109109
- Test execution (full test suite)
@@ -112,7 +112,7 @@ Ensure production-readiness before deployment:
112112

113113
### 4. Deployment (Production Release)
114114

115-
Deploy validated applications to Databricks (enable with `--allow-deployment`):
115+
Deploy validated applications to Databricks:
116116

117117
- **`deploy_databricks_app`** - Push to Databricks Apps platform
118118
- Automatic deployment configuration
@@ -290,9 +290,6 @@ databricks experimental apps-mcp --warehouse-id <warehouse-id>
290290

291291
# Enable workspace tools
292292
databricks experimental apps-mcp --warehouse-id <warehouse-id> --with-workspace-tools
293-
294-
# Enable deployment
295-
databricks experimental apps-mcp --warehouse-id <warehouse-id> --allow-deployment
296293
```
297294

298295
### CLI Flags
@@ -301,7 +298,6 @@ databricks experimental apps-mcp --warehouse-id <warehouse-id> --allow-deploymen
301298
|------|-------------|---------|
302299
| `--warehouse-id` | Databricks SQL Warehouse ID (required) | - |
303300
| `--with-workspace-tools` | Enable workspace file operations | `false` |
304-
| `--allow-deployment` | Enable deployment operations | `false` |
305301
| `--help` | Show help | - |
306302

307303
### Environment Variables
@@ -312,7 +308,6 @@ databricks experimental apps-mcp --warehouse-id <warehouse-id> --allow-deploymen
312308
| `DATABRICKS_TOKEN` | Databricks personal access token | `dapi...` |
313309
| `WAREHOUSE_ID` | Databricks SQL warehouse ID (preferred) | `abc123def456` |
314310
| `DATABRICKS_WAREHOUSE_ID` | Alternative name for warehouse ID | `abc123def456` |
315-
| `ALLOW_DEPLOYMENT` | Enable deployment operations | `true` or `false` |
316311
| `WITH_WORKSPACE_TOOLS` | Enable workspace tools | `true` or `false` |
317312

318313
### Authentication

experimental/apps-mcp/cmd/apps_mcp.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
func NewMcpCmd() *cobra.Command {
1111
var warehouseID string
12-
var allowDeployment bool
1312
var withWorkspaceTools bool
1413

1514
cmd := &cobra.Command{
@@ -21,23 +20,19 @@ func NewMcpCmd() *cobra.Command {
2120
The MCP server exposes the following capabilities:
2221
- Databricks integration (query catalogs, schemas, tables, execute SQL)
2322
- Project scaffolding (generate full-stack TypeScript applications)
24-
- Sandboxed execution (isolated file/command execution)
23+
- Deployment to Databricks Apps
2524
2625
The server communicates via stdio using the Model Context Protocol.`,
2726
Example: ` # Start MCP server with required warehouse
2827
databricks experimental apps-mcp --warehouse-id abc123
2928
3029
# Start with workspace tools enabled
31-
databricks experimental apps-mcp --warehouse-id abc123 --with-workspace-tools
32-
33-
# Start with deployment tools enabled
34-
databricks experimental apps-mcp --warehouse-id abc123 --allow-deployment`,
30+
databricks experimental apps-mcp --warehouse-id abc123 --with-workspace-tools`,
3531
RunE: func(cmd *cobra.Command, args []string) error {
3632
ctx := cmd.Context()
3733

3834
// Build MCP config from flags
3935
cfg := &mcplib.Config{
40-
AllowDeployment: allowDeployment,
4136
WithWorkspaceTools: withWorkspaceTools,
4237
IoConfig: &mcplib.IoConfig{
4338
Validation: &mcplib.ValidationConfig{},
@@ -62,7 +57,6 @@ The server communicates via stdio using the Model Context Protocol.`,
6257

6358
// Define flags
6459
cmd.Flags().StringVar(&warehouseID, "warehouse-id", "", "Databricks SQL Warehouse ID")
65-
cmd.Flags().BoolVar(&allowDeployment, "allow-deployment", false, "Enable deployment tools")
6660
cmd.Flags().BoolVar(&withWorkspaceTools, "with-workspace-tools", false, "Enable workspace tools (file operations, bash, grep, glob)")
6761

6862
cmd.AddCommand(newInstallCmd())

experimental/apps-mcp/lib/config.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package mcp
55
// Config holds MCP server configuration.
66
// Configuration is populated from CLI flags and Databricks client context.
77
type Config struct {
8-
AllowDeployment bool
98
WithWorkspaceTools bool
109
IoConfig *IoConfig
1110
}
@@ -41,7 +40,6 @@ func DefaultConfig() *Config {
4140
validationCfg.SetDefaults()
4241

4342
return &Config{
44-
AllowDeployment: false,
4543
WithWorkspaceTools: false,
4644
IoConfig: &IoConfig{
4745
Template: &TemplateConfig{
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Your session in Databricks MCP has been successfully initialized. Here are the guidelines to follow while working on projects using databricks_mcp tools:
22

33
## Project State Management:
4-
This project uses a state file (`.edda_state`) managed by edda MCP to enforce the correct workflow order:
5-
1. **Scaffolded**: `scaffold_data_app` creates project structure from template (starts in this state)
6-
2. **Validated**: `validate_data_app` runs build + tests, computes BLAKE3 checksum of package.json and all core source files
4+
This project uses a state file (`.edda_state`) managed by Databricks MCP to enforce the correct workflow order:
5+
1. **Scaffolded**: `scaffold_databricks_app` creates project structure from template (starts in this state)
6+
2. **Validated**: `validate_databricks_app` runs build + tests, computes a checksum of package.json and all core source files
77
3. **Deployed**: `deploy_databricks_app` deploys to Databricks Apps, but ONLY if checksum hasn't changed since validation
88

99
Re-validation is allowed (Deployed → Validated) to update the checksum after intentional changes. The databricks_mcp tools enforce these state transitions and prevent invalid state changes.
@@ -15,4 +15,4 @@ Re-validation is allowed (Deployed → Validated) to update the checksum after i
1515
- When working with Databricks or other services, use real API calls in tests (no mocks) to verify end-to-end functionality, unless explicitly instructed otherwise. It can be done on subset of data if applicable.
1616
- Do NOT create summary files, reports, or README unless explicitly requested
1717
- When not sure about the user's intent, ask clarifying questions before proceeding. For example, if user asks for "a data app to analyze sales data", ask for more details on data sources and analysis goals. Do not make assumptions regarding their needs and data sources.
18-
- However, stick to the technical stack initialized by the `scaffold_data_app` as it has been approved by the management and battle-tested in production.
18+
- However, stick to the technical stack initialized by the `scaffold_databricks_app` as it has been approved by the management and battle-tested in production.

experimental/apps-mcp/lib/providers/databricks/deployment.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package databricks
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"os/exec"
78
"time"
89

910
mcp "github.com/databricks/cli/experimental/apps-mcp/lib"
1011
"github.com/databricks/cli/experimental/apps-mcp/lib/middlewares"
11-
"github.com/databricks/cli/libs/cmdctx"
1212
"github.com/databricks/databricks-sdk-go/service/apps"
1313
"github.com/databricks/databricks-sdk-go/service/iam"
1414
)
@@ -20,18 +20,13 @@ func GetSourcePath(app *apps.App) string {
2020
return app.DefaultSourceCodePath
2121
}
2222

23-
func GetAppInfo(ctx context.Context, cfg *mcp.Config, name string) (*apps.App, error) {
24-
w := cmdctx.WorkspaceClient(ctx)
25-
app, err := w.Apps.GetByName(ctx, name)
26-
if err != nil {
27-
return nil, fmt.Errorf("failed to get app info: %w", err)
28-
}
29-
30-
return app, nil
23+
func GetAppInfo(ctx context.Context, name string) (*apps.App, error) {
24+
w := middlewares.MustGetDatabricksClient(ctx)
25+
return w.Apps.GetByName(ctx, name)
3126
}
3227

33-
func CreateApp(ctx context.Context, cfg *mcp.Config, createAppRequest *apps.CreateAppRequest) (*apps.App, error) {
34-
w := cmdctx.WorkspaceClient(ctx)
28+
func CreateApp(ctx context.Context, createAppRequest *apps.CreateAppRequest) (*apps.App, error) {
29+
w := middlewares.MustGetDatabricksClient(ctx)
3530

3631
wait, err := w.Apps.Create(ctx, *createAppRequest)
3732
if err != nil {
@@ -47,7 +42,10 @@ func CreateApp(ctx context.Context, cfg *mcp.Config, createAppRequest *apps.Crea
4742
}
4843

4944
func GetUserInfo(ctx context.Context, cfg *mcp.Config) (*iam.User, error) {
50-
w := cmdctx.WorkspaceClient(ctx)
45+
w, err := middlewares.GetDatabricksClient(ctx)
46+
if err != nil {
47+
return nil, fmt.Errorf("failed to get databricks client: %w", err)
48+
}
5149
user, err := w.CurrentUser.Me(ctx)
5250
if err != nil {
5351
return nil, fmt.Errorf("failed to get user info: %w", err)
@@ -56,8 +54,9 @@ func GetUserInfo(ctx context.Context, cfg *mcp.Config) (*iam.User, error) {
5654
return user, nil
5755
}
5856

59-
func SyncWorkspace(appInfo *apps.App, sourceDir string) error {
57+
func SyncWorkspace(ctx context.Context, appInfo *apps.App, sourceDir string) error {
6058
targetPath := GetSourcePath(appInfo)
59+
host := middlewares.MustGetDatabricksClient(ctx).Config.Host
6160

6261
cmd := exec.Command(
6362
"databricks",
@@ -68,6 +67,9 @@ func SyncWorkspace(appInfo *apps.App, sourceDir string) error {
6867
targetPath,
6968
)
7069
cmd.Dir = sourceDir
70+
env := os.Environ()
71+
env = append(env, "DATABRICKS_HOST="+host)
72+
cmd.Env = env
7173

7274
output, err := cmd.CombinedOutput()
7375
if err != nil {
@@ -78,7 +80,10 @@ func SyncWorkspace(appInfo *apps.App, sourceDir string) error {
7880
}
7981

8082
func DeployApp(ctx context.Context, cfg *mcp.Config, appInfo *apps.App) error {
81-
w := cmdctx.WorkspaceClient(ctx)
83+
w, err := middlewares.GetDatabricksClient(ctx)
84+
if err != nil {
85+
return fmt.Errorf("failed to get databricks client: %w", err)
86+
}
8287
sourcePath := GetSourcePath(appInfo)
8388

8489
req := apps.CreateAppDeploymentRequest{
@@ -109,8 +114,8 @@ func ResourcesFromEnv(ctx context.Context, cfg *mcp.Config) (*apps.AppResource,
109114
}
110115

111116
return &apps.AppResource{
112-
Name: "base",
113-
Description: "template resources",
117+
Name: "warehouse",
118+
Description: "Warehouse to use for the app",
114119
SqlWarehouse: &apps.AppResourceSqlWarehouse{
115120
Id: warehouseID,
116121
Permission: apps.AppResourceSqlWarehouseSqlWarehousePermissionCanUse,

experimental/apps-mcp/lib/providers/deployment/provider.go

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"os/exec"
87
"path/filepath"
98
"time"
109

@@ -19,14 +18,10 @@ import (
1918
)
2019

2120
func init() {
22-
// Register deployment provider with conditional enablement based on AllowDeployment
21+
// Register deployment provider
2322
providers.Register("deployment", func(ctx context.Context, cfg *mcp.Config, sess *session.Session) (providers.Provider, error) {
2423
return NewProvider(ctx, cfg, sess)
25-
}, providers.ProviderConfig{
26-
EnabledWhen: func(cfg *mcp.Config) bool {
27-
return cfg.AllowDeployment
28-
},
29-
})
24+
}, providers.ProviderConfig{})
3025
}
3126

3227
const deployRetries = 3
@@ -150,7 +145,7 @@ func (p *Provider) deployDatabricksApp(ctx context.Context, args *DeployDatabric
150145
if !hasChecksum {
151146
return &DeployResult{
152147
Success: false,
153-
Message: "Project must be validated before deployment. Run validate_data_app first.",
148+
Message: "Project must be validated before deployment. Run validate_databricks_app first.",
154149
AppName: args.Name,
155150
}, nil
156151
}
@@ -167,25 +162,7 @@ func (p *Provider) deployDatabricksApp(ctx context.Context, args *DeployDatabric
167162
if !checksumValid {
168163
return &DeployResult{
169164
Success: false,
170-
Message: "Project files changed since validation. Re-run validate_data_app before deployment.",
171-
AppName: args.Name,
172-
}, nil
173-
}
174-
175-
log.Infof(ctx, "Installing dependencies: work_dir=%s", workPath)
176-
if err := p.runCommand(workPath, "npm", "install"); err != nil {
177-
return &DeployResult{
178-
Success: false,
179-
Message: fmt.Sprintf("Failed to install dependencies: %v", err),
180-
AppName: args.Name,
181-
}, nil
182-
}
183-
184-
log.Infof(ctx, "Building frontend: work_dir=%s", workPath)
185-
if err := p.runCommand(workPath, "npm", "run", "build"); err != nil {
186-
return &DeployResult{
187-
Success: false,
188-
Message: fmt.Sprintf("Failed to build frontend: %v", err),
165+
Message: "Project files changed since validation. Re-run validate_databricks_app before deployment.",
189166
AppName: args.Name,
190167
}, nil
191168
}
@@ -199,11 +176,10 @@ func (p *Provider) deployDatabricksApp(ctx context.Context, args *DeployDatabric
199176
}, nil
200177
}
201178

202-
serverDir := filepath.Join(workPath, "server")
203179
syncStart := time.Now()
204-
log.Infof(ctx, "Syncing workspace: source=%s, target=%s", serverDir, databricks.GetSourcePath(appInfo))
180+
log.Infof(ctx, "Syncing workspace: source=%s, target=%s", workPath, databricks.GetSourcePath(appInfo))
205181

206-
if err := databricks.SyncWorkspace(appInfo, serverDir); err != nil {
182+
if err := databricks.SyncWorkspace(ctx, appInfo, workPath); err != nil {
207183
return &DeployResult{
208184
Success: false,
209185
Message: fmt.Sprintf("Failed to sync workspace: %v", err),
@@ -265,7 +241,7 @@ func (p *Provider) deployDatabricksApp(ctx context.Context, args *DeployDatabric
265241
}
266242

267243
func (p *Provider) getOrCreateApp(ctx context.Context, name, description string, force bool) (*apps.App, error) {
268-
appInfo, err := databricks.GetAppInfo(ctx, p.config, name)
244+
appInfo, err := databricks.GetAppInfo(ctx, name)
269245
if err == nil {
270246
log.Infof(ctx, "Found existing app: name=%s", name)
271247

@@ -302,19 +278,7 @@ func (p *Provider) getOrCreateApp(ctx context.Context, name, description string,
302278
},
303279
}
304280

305-
return databricks.CreateApp(ctx, p.config, createApp)
306-
}
307-
308-
func (p *Provider) runCommand(dir, name string, args ...string) error {
309-
cmd := exec.Command(name, args...)
310-
cmd.Dir = dir
311-
312-
output, err := cmd.CombinedOutput()
313-
if err != nil {
314-
return fmt.Errorf("%s failed: %w (output: %s)", name, err, string(output))
315-
}
316-
317-
return nil
281+
return databricks.CreateApp(ctx, createApp)
318282
}
319283

320284
func formatDeployResult(result *DeployResult) string {

experimental/apps-mcp/lib/providers/io/format.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package io
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
)
47

58
// formatScaffoldResult formats a ScaffoldResult for display
69
func formatScaffoldResult(result *ScaffoldResult) string {
710
return fmt.Sprintf(
811
"Successfully scaffolded %s template to %s\n\n"+
912
"Files copied: %d\n\n"+
1013
"Template: %s\n\n"+
11-
"%s",
14+
"It is recomended to run the app in the background immediately after scaffolding using `npm install && npm run dev`. Then directly open http://localhost:8000 in the browser so the user can follow the progress.\n\n"+
15+
"IMPORTANT: Make sure to read %s before proceeding with the project!!!\n\n",
1216
result.TemplateName,
1317
result.WorkDir,
1418
result.FilesCopied,
1519
result.TemplateName,
16-
result.TemplateDescription,
20+
filepath.Join(result.WorkDir, "CLAUDE.md"),
1721
)
1822
}
1923

0 commit comments

Comments
 (0)