@databrainhq/mcp-server 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -218,7 +218,7 @@ var DatabrainClient = class {
218
218
  throw new DatabrainApiError(
219
219
  401,
220
220
  "MISSING_API_TOKEN",
221
- "A per-data-app API token is required for this embedding operation but none is configured. Use `setup_embed_interactive` with step `ensure_api_token` to create or set one, or call `create_api_token` / `set_api_token` directly. Do not ask the user for this token \u2014 create it via the MCP tools."
221
+ "A per-data-app API token is required for this embedding operation but none is configured. Call `create_api_token` to generate one, or `set_api_token` if you already have the value. Do not ask the user for this token \u2014 create it via the MCP tools."
222
222
  );
223
223
  }
224
224
  let url = `${this.config.apiUrl}${options.path}`;
@@ -1690,7 +1690,7 @@ Next: use \`generate_guest_token\` with the clientId and \`generate_embed_code\`
1690
1690
  - workspaceName: \`${body.workspaceName}\`
1691
1691
  - accessSettings.datamartName: \`${datamartName}\`
1692
1692
 
1693
- Next: run \`get_embed_details\` and \`diagnose_embed_health\` to verify update compatibility.`
1693
+ Next: run \`get_embed_details\` to verify the embed configuration.`
1694
1694
  );
1695
1695
  } catch (err) {
1696
1696
  if (err instanceof DatabrainApiError) return err.toToolResponse();
@@ -2377,612 +2377,71 @@ Use \`get_embed_details\` to view the full current theme configuration.` + GUEST
2377
2377
  );
2378
2378
  }
2379
2379
 
2380
- // src/tools/embed/configure-embed.ts
2381
- import { z as z15 } from "zod";
2382
- var stringFilterSchema = z15.object({
2383
- filterName: z15.string().describe("Name of the filter as it appears in the dashboard"),
2384
- options: z15.array(z15.object({
2385
- value: z15.string(),
2386
- label: z15.string()
2387
- })).describe("Dropdown options"),
2388
- defaultOption: z15.union([z15.string(), z15.array(z15.string())]).optional().describe("Default selected value (string or array for multi-select)")
2389
- });
2390
- var numberFilterSchema = z15.object({
2391
- filterName: z15.string().describe("Name of the filter"),
2392
- min: z15.number().describe("Minimum value"),
2393
- max: z15.number().describe("Maximum value")
2394
- });
2395
- var datePresetSchema = z15.object({
2396
- type: z15.string().optional().describe('Preset type (e.g. "Last", "This")'),
2397
- interval: z15.string().optional().describe('Interval (e.g. "Days", "Months", "Years")'),
2398
- timeGrain: z15.string().optional().describe("Time grain"),
2399
- label: z15.string().describe('Display label (e.g. "Last 30 Days")'),
2400
- startDate: z15.string().optional().describe("Start date (YYYY-MM-DD)"),
2401
- endDate: z15.string().optional().describe("End date (YYYY-MM-DD)")
2402
- });
2403
- var dateFilterSchema = z15.object({
2404
- filterName: z15.string().describe("Name of the filter"),
2405
- startDate: z15.string().optional().describe("Default start date (YYYY-MM-DD)"),
2406
- endDate: z15.string().optional().describe("Default end date (YYYY-MM-DD)"),
2407
- datePresetOptions: z15.array(datePresetSchema).optional().describe("Custom date presets to show in the date picker")
2408
- });
2409
- var sqlFilterSchema = z15.object({
2410
- filterName: z15.string().describe("Name of the filter"),
2411
- sql: z15.string().describe("SQL query to fetch filter options"),
2412
- columnName: z15.string().describe("Column from the SQL result to use as option values")
2413
- });
2414
- var SUPPORTED_LANGUAGES = [
2415
- "en",
2416
- "fr",
2417
- "es",
2418
- "de",
2419
- "it",
2420
- "pt",
2421
- "zh",
2422
- "ja",
2423
- "ko",
2424
- "hi",
2425
- "ar",
2426
- "ru"
2427
- ];
2428
- function registerConfigureEmbed(server, client) {
2429
- const services = createServiceBundle(client);
2430
- server.tool(
2431
- "configure_embed",
2432
- "Configure embed options (UI toggles, downloads, layout), filters (string/date/number/SQL), and i18n (language, translations).",
2433
- {
2434
- embedId: z15.string().describe("ID of the embed to configure"),
2435
- body: z15.record(z15.unknown()).optional().describe(
2436
- "Raw API body for PUT /api/v2/data-app/embeds. If provided, sent directly and convenience fields are ignored."
2437
- ),
2438
- // ===== OPTIONS (from configure_embed_options) =====
2439
- // Metric Operations
2440
- disableMetricCreation: z15.boolean().optional().describe("Disable creating new metrics"),
2441
- disableMetricUpdation: z15.boolean().optional().describe("Disable updating existing metrics"),
2442
- disableMetricDeletion: z15.boolean().optional().describe("Disable deleting metrics"),
2443
- disableManageMetrics: z15.boolean().optional().describe("Hide the manage metrics interface"),
2444
- // Layout
2445
- disableLayoutCustomization: z15.boolean().optional().describe("Prevent layout changes"),
2446
- disableSaveLayout: z15.boolean().optional().describe("Prevent saving layout changes"),
2447
- // Downloads & Export
2448
- enableDownloadCsv: z15.boolean().optional().describe("Enable CSV download for individual metrics"),
2449
- enableEmailCsv: z15.boolean().optional().describe("Enable emailing metric data as CSV"),
2450
- disableDownloadPng: z15.boolean().optional().describe("Disable PNG download in fullscreen"),
2451
- enableDownloadAllMetrics: z15.boolean().optional().describe("Enable bulk download of all metrics as CSV"),
2452
- enableDownloadAllPdf: z15.boolean().optional().describe("Enable downloading entire dashboard as PDF"),
2453
- disableDownloadDataNoFilters: z15.boolean().optional().describe("Prevent data download when no filters"),
2454
- disableDownloadUnderlyingDataNoFilters: z15.boolean().optional().describe("Prevent underlying data download without filters"),
2455
- // UI Visibility
2456
- hideDashboardName: z15.boolean().optional().describe("Hide the dashboard title"),
2457
- hideMetricCardShadow: z15.boolean().optional().describe("Remove card shadow effects"),
2458
- disableMetricCardBorder: z15.boolean().optional().describe("Remove metric card borders"),
2459
- showDashboardActions: z15.boolean().optional().describe("Show/hide dashboard action buttons"),
2460
- disableFullscreen: z15.boolean().optional().describe("Disable fullscreen mode"),
2461
- isHideTablePreview: z15.boolean().optional().describe("Hide table preview in fullscreen"),
2462
- isHideChartSettings: z15.boolean().optional().describe("Hide chart settings in fullscreen"),
2463
- shouldFitFullScreen: z15.boolean().optional().describe("Make fullscreen modal fit the component space"),
2464
- // Loaders
2465
- disableMainLoader: z15.boolean().optional().describe("Hide the main loading indicator"),
2466
- disableMetricLoader: z15.boolean().optional().describe("Hide metric-level loading indicators"),
2467
- // Data Access
2468
- disableUnderlyingData: z15.boolean().optional().describe("Disable access to underlying data"),
2469
- isShowNoDataFoundScreen: z15.boolean().optional().describe('Show a "no data" screen when data unavailable'),
2470
- // Scheduling
2471
- disableScheduleEmailReports: z15.boolean().optional().describe("Disable email report scheduling"),
2472
- // Interactions
2473
- isStickyDashboardFilters: z15.boolean().optional().describe("Make filters sticky when scrolling"),
2474
- enableMultiMetricFilters: z15.boolean().optional().describe("Enable cross-metric filter interactions"),
2475
- optionsIcon: z15.enum(["kebab-menu-vertical", "download"]).optional().describe("Menu icon style"),
2476
- enableTitleClickFullscreen: z15.boolean().optional().describe("Click metric title to enter fullscreen"),
2477
- // Export
2478
- exportMsgPosition: z15.enum([
2479
- "bottom",
2480
- "bottom-left",
2481
- "bottom-right",
2482
- "center",
2483
- "top",
2484
- "top-left",
2485
- "top-right"
2486
- ]).optional().describe('Position of "exporting" notification message'),
2487
- isModifyUrl: z15.boolean().optional().describe("Whether navigating metrics modifies the browser URL"),
2488
- // No-data visuals
2489
- noDataImg: z15.string().optional().describe("URL to custom image when no data found"),
2490
- noDataFoundSvg: z15.string().optional().describe("Inline SVG when no data found"),
2491
- // Metric description
2492
- longDescriptionWidth: z15.string().optional().describe('Width of long-description tooltip (e.g. "300px")'),
2493
- longDescriptionFontColor: z15.string().optional().describe("Font color for long-description tooltip (hex)"),
2494
- // Settings icon
2495
- settingsIconName: z15.string().optional().describe("Custom name/label for the settings icon"),
2496
- settingsIconSvg: z15.string().optional().describe("Inline SVG for a custom settings icon"),
2497
- // Advanced: per-chart settings
2498
- customChartSettings: z15.record(z15.unknown()).optional().describe("Per-chart override settings (JSON)"),
2499
- // Custom Messages
2500
- tokenExpiryMessage: z15.string().optional().describe("Custom message when token expires"),
2501
- tokenAbsentMessage: z15.string().optional().describe("Custom message when token is missing"),
2502
- // ===== FILTERS (from configure_filters) =====
2503
- stringFilters: z15.array(stringFilterSchema).optional().describe("String filter configurations with dropdown options and defaults"),
2504
- numberFilters: z15.array(numberFilterSchema).optional().describe("Number filter configurations with min/max defaults"),
2505
- dateFilters: z15.array(dateFilterSchema).optional().describe("Date filter configurations with default ranges and presets"),
2506
- sqlFilters: z15.array(sqlFilterSchema).optional().describe("SQL-based filter options for large option sets"),
2507
- hideDashboardFilters: z15.array(z15.string()).optional().describe("Filter names to hide from the dashboard UI"),
2508
- hideDatePickerOptions: z15.array(z15.string()).optional().describe('Date picker options to hide (e.g. ["this month", "yesterday"])'),
2509
- metricFilterPosition: z15.enum(["inside", "outside"]).optional().describe("Where to display metric-level filters"),
2510
- // ===== INTERNATIONALIZATION (from configure_internationalization) =====
2511
- language: z15.enum(SUPPORTED_LANGUAGES).optional().describe('Primary language code (e.g. "fr", "es", "ja")'),
2512
- calendarType: z15.enum(["default", "ind"]).optional().describe('Calendar system: "default" or "ind" (Indian)'),
2513
- translations: z15.array(z15.object({
2514
- key: z15.string().describe("Original text to translate (must match exactly)"),
2515
- translations: z15.record(z15.string()).describe("Language code \u2192 translation mapping")
2516
- })).optional().describe("Translation dictionary entries")
2517
- },
2518
- WRITE_IDEMPOTENT,
2519
- async (params) => {
2520
- const { embedId, body, ...opts } = params;
2521
- if (body) {
2522
- try {
2523
- const rawBody = { ...body };
2524
- if (!rawBody.embedId) rawBody.embedId = embedId;
2525
- const result = await services.embedsApi.updateEmbed(rawBody);
2526
- if (result.error) {
2527
- return toolError(`Embed configuration failed: ${stringify(result.error)}`);
2528
- }
2529
- return toolSuccess(
2530
- `Embed configured for "${embedId}" (raw passthrough mode).` + GUEST_TOKEN_REMINDER
2531
- );
2532
- } catch (err) {
2533
- const guardrail = await maybeBuildCorruptedEmbedGuardrail(client, embedId, "configure embed", err);
2534
- if (guardrail) return guardrail;
2535
- if (err instanceof DatabrainApiError) return err.toToolResponse();
2536
- return toolError(`Unexpected error: ${err.message}`);
2537
- }
2538
- }
2539
- const options = {};
2540
- const attrs = {};
2541
- const configured = [];
2542
- if (opts.disableMetricCreation !== void 0) options.disableMetricCreation = opts.disableMetricCreation;
2543
- if (opts.disableMetricUpdation !== void 0) options.disableMetricUpdation = opts.disableMetricUpdation;
2544
- if (opts.disableMetricDeletion !== void 0) options.disableMetricDeletion = opts.disableMetricDeletion;
2545
- if (opts.disableManageMetrics !== void 0) options.disableManageMetrics = opts.disableManageMetrics;
2546
- if (opts.disableLayoutCustomization !== void 0) options.disableLayoutCustomization = opts.disableLayoutCustomization;
2547
- if (opts.disableSaveLayout !== void 0) options.disableSaveLayout = opts.disableSaveLayout;
2548
- if (opts.disableDownloadDataNoFilters !== void 0) options.disableDownloadDataNoFilters = opts.disableDownloadDataNoFilters;
2549
- if (opts.disableDownloadUnderlyingDataNoFilters !== void 0) options.disableDownloadUnderlyingDataNoFilters = opts.disableDownloadUnderlyingDataNoFilters;
2550
- if (opts.hideDashboardName !== void 0) options.hideDashboardName = opts.hideDashboardName;
2551
- if (opts.hideMetricCardShadow !== void 0) options.hideMetricCardShadow = opts.hideMetricCardShadow;
2552
- if (opts.disableMetricCardBorder !== void 0) options.disableMetricCardBorder = opts.disableMetricCardBorder;
2553
- if (opts.showDashboardActions !== void 0) options.showDashboardActions = opts.showDashboardActions;
2554
- if (opts.shouldFitFullScreen !== void 0) options.shouldFitFullScreen = opts.shouldFitFullScreen;
2555
- if (opts.isShowNoDataFoundScreen !== void 0) options.isShowNoDataFoundScreen = opts.isShowNoDataFoundScreen;
2556
- if (opts.disableMainLoader !== void 0) options.disableMainLoader = opts.disableMainLoader;
2557
- if (opts.disableMetricLoader !== void 0) options.disableMetricLoader = opts.disableMetricLoader;
2558
- if (opts.disableUnderlyingData !== void 0) options.disableUnderlyingData = opts.disableUnderlyingData;
2559
- if (opts.disableScheduleEmailReports !== void 0) options.disableScheduleEmailReports = opts.disableScheduleEmailReports;
2560
- if (opts.exportMsgPosition) options.exportMsgPosition = opts.exportMsgPosition;
2561
- if (opts.isModifyUrl !== void 0) options.isModifyUrl = opts.isModifyUrl;
2562
- if (opts.enableDownloadCsv !== void 0) attrs.enableDownloadCsv = opts.enableDownloadCsv;
2563
- if (opts.enableEmailCsv !== void 0) attrs.enableEmailCsv = opts.enableEmailCsv;
2564
- if (opts.disableDownloadPng !== void 0) attrs.disableDownloadPng = opts.disableDownloadPng;
2565
- if (opts.enableDownloadAllMetrics !== void 0) attrs.enableDownloadAllMetrics = opts.enableDownloadAllMetrics;
2566
- if (opts.enableDownloadAllPdf !== void 0) attrs.enableDownloadAllPdf = opts.enableDownloadAllPdf;
2567
- if (opts.disableFullscreen !== void 0) attrs.disableFullscreen = opts.disableFullscreen;
2568
- if (opts.isHideTablePreview !== void 0) attrs.isHideTablePreview = opts.isHideTablePreview;
2569
- if (opts.isHideChartSettings !== void 0) attrs.isHideChartSettings = opts.isHideChartSettings;
2570
- if (opts.isStickyDashboardFilters !== void 0) attrs.isStickyDashboardFilters = opts.isStickyDashboardFilters;
2571
- if (opts.enableMultiMetricFilters !== void 0) attrs.enableMultiMetricFilters = opts.enableMultiMetricFilters;
2572
- if (opts.optionsIcon) attrs.optionsIcon = opts.optionsIcon;
2573
- if (opts.enableTitleClickFullscreen !== void 0) attrs.enableTitleClickFullscreen = opts.enableTitleClickFullscreen;
2574
- if (opts.noDataImg) attrs.noDataImg = opts.noDataImg;
2575
- if (opts.noDataFoundSvg) attrs.noDataFoundSvg = opts.noDataFoundSvg;
2576
- if (opts.customChartSettings) attrs.customChartSettings = opts.customChartSettings;
2577
- if (opts.longDescriptionWidth || opts.longDescriptionFontColor) {
2578
- attrs.longDescriptionConfig = {
2579
- ...opts.longDescriptionWidth && { width: opts.longDescriptionWidth },
2580
- ...opts.longDescriptionFontColor && { fontColor: opts.longDescriptionFontColor }
2581
- };
2582
- }
2583
- if (opts.settingsIconName || opts.settingsIconSvg) {
2584
- attrs.settingsIcon = {
2585
- ...opts.settingsIconName && { name: opts.settingsIconName },
2586
- ...opts.settingsIconSvg && { iconSvg: opts.settingsIconSvg }
2587
- };
2588
- }
2589
- const customMessages = {};
2590
- if (opts.tokenExpiryMessage) customMessages.tokenExpiry = opts.tokenExpiryMessage;
2591
- if (opts.tokenAbsentMessage) customMessages.tokenAbsent = opts.tokenAbsentMessage;
2592
- if (Object.keys(customMessages).length) options.customMessages = customMessages;
2593
- const globalFilterOptions = {};
2594
- if (opts.stringFilters) {
2595
- for (const f of opts.stringFilters) {
2596
- globalFilterOptions[f.filterName] = {
2597
- options: f.options,
2598
- ...f.defaultOption !== void 0 && { defaultOption: f.defaultOption }
2599
- };
2600
- }
2601
- configured.push(`${opts.stringFilters.length} string filter(s)`);
2602
- }
2603
- if (opts.numberFilters) {
2604
- for (const f of opts.numberFilters) {
2605
- globalFilterOptions[f.filterName] = {
2606
- defaultOption: { min: f.min, max: f.max }
2607
- };
2608
- }
2609
- configured.push(`${opts.numberFilters.length} number filter(s)`);
2610
- }
2611
- if (opts.dateFilters) {
2612
- for (const f of opts.dateFilters) {
2613
- const config = {};
2614
- if (f.startDate || f.endDate) {
2615
- config.defaultOption = {
2616
- ...f.startDate && { startDate: f.startDate },
2617
- ...f.endDate && { endDate: f.endDate }
2618
- };
2619
- }
2620
- if (f.datePresetOptions) config.datePresetOptions = f.datePresetOptions;
2621
- globalFilterOptions[f.filterName] = config;
2622
- }
2623
- configured.push(`${opts.dateFilters.length} date filter(s)`);
2624
- }
2625
- if (opts.sqlFilters) {
2626
- for (const f of opts.sqlFilters) {
2627
- globalFilterOptions[f.filterName] = {
2628
- sql: f.sql,
2629
- columnName: f.columnName
2630
- };
2631
- }
2632
- configured.push(`${opts.sqlFilters.length} SQL filter(s)`);
2633
- }
2634
- if (Object.keys(globalFilterOptions).length) options.globalFilterOptions = globalFilterOptions;
2635
- if (opts.hideDashboardFilters) {
2636
- attrs.hideDashboardFilters = opts.hideDashboardFilters;
2637
- configured.push(`hiding ${opts.hideDashboardFilters.length} filter(s)`);
2638
- }
2639
- if (opts.hideDatePickerOptions) {
2640
- options.hideDatePickerOptions = opts.hideDatePickerOptions;
2641
- configured.push(`hiding ${opts.hideDatePickerOptions.length} date picker option(s)`);
2642
- }
2643
- if (opts.metricFilterPosition) {
2644
- attrs.metricFilterPosition = opts.metricFilterPosition;
2645
- configured.push(`metric filter position: ${opts.metricFilterPosition}`);
2646
- }
2647
- if (opts.language) {
2648
- attrs.language = opts.language;
2649
- configured.push(`language: ${opts.language}`);
2650
- }
2651
- if (opts.calendarType) {
2652
- attrs.calendarType = opts.calendarType;
2653
- configured.push(`calendar: ${opts.calendarType}`);
2654
- }
2655
- if (opts.translations?.length) {
2656
- const dict = {};
2657
- for (const entry of opts.translations) {
2658
- dict[entry.key] = entry.translations;
2659
- }
2660
- attrs.translationDictionary = dict;
2661
- configured.push(`${opts.translations.length} translation(s)`);
2662
- }
2663
- const optionKeys = Object.keys(options).filter((k) => k !== "globalFilterOptions" && k !== "customMessages" && k !== "hideDatePickerOptions");
2664
- const attrKeys = Object.keys(attrs).filter(
2665
- (k) => k !== "hideDashboardFilters" && k !== "metricFilterPosition" && k !== "language" && k !== "calendarType" && k !== "translationDictionary"
2666
- );
2667
- if (optionKeys.length || attrKeys.length) {
2668
- configured.push(`${optionKeys.length + attrKeys.length} display option(s)`);
2669
- }
2670
- if (Object.keys(options).length === 0 && Object.keys(attrs).length === 0) {
2671
- return toolError("No configuration provided. Specify at least one option, filter, or i18n setting.");
2672
- }
2673
- try {
2674
- const patch = {};
2675
- if (Object.keys(options).length) patch.options = options;
2676
- if (Object.keys(attrs).length) patch.componentAttributes = attrs;
2677
- const result = await services.embedService.updateEmbed(embedId, patch);
2678
- if (result.error) {
2679
- return toolError(`Embed configuration failed: ${stringify(result.error)}`);
2680
- }
2681
- return toolSuccess(
2682
- `Embed configured for "${embedId}".
2683
-
2684
- **Configured:** ${configured.join(", ")}
2685
-
2686
- Use \`get_embed_details\` to view the full configuration.` + GUEST_TOKEN_REMINDER
2687
- );
2688
- } catch (err) {
2689
- const guardrail = await maybeBuildCorruptedEmbedGuardrail(client, embedId, "configure embed", err);
2690
- if (guardrail) return guardrail;
2691
- if (err instanceof DatabrainApiError) return err.toToolResponse();
2692
- return toolError(`Unexpected error: ${err.message}`);
2693
- }
2694
- }
2695
- );
2696
- }
2697
-
2698
- // src/tools/embed/diagnose-embed-health.ts
2699
- import { z as z16 } from "zod";
2700
- function asRecord2(value) {
2701
- return value && typeof value === "object" ? value : null;
2702
- }
2703
- function firstString(...values) {
2704
- for (const v of values) {
2705
- if (typeof v === "string" && v.trim().length > 0) return v;
2706
- }
2707
- return null;
2708
- }
2709
- function runRuntimeReadinessChecks(params) {
2710
- const payload = params.guestTokenBody ?? {
2711
- clientId: params.clientId,
2712
- dataAppName: params.dataAppName,
2713
- permissions: params.permissions
2714
- };
2715
- const checks = [];
2716
- const topLevelPermissions = payload.permissions;
2717
- const paramsObj = payload.params;
2718
- const nestedPermissions = paramsObj?.permissions;
2719
- checks.push({
2720
- check: "Top-level dataAppName",
2721
- status: typeof payload.dataAppName === "string" && payload.dataAppName.length > 0 ? "pass" : "fail",
2722
- details: typeof payload.dataAppName === "string" && payload.dataAppName.length > 0 ? "dataAppName is present at top level." : "Missing top-level dataAppName in guest token body."
2723
- });
2724
- checks.push({
2725
- check: "Top-level permissions object",
2726
- status: typeof topLevelPermissions === "object" && topLevelPermissions !== null ? "pass" : "fail",
2727
- details: typeof topLevelPermissions === "object" && topLevelPermissions !== null ? "permissions is present at top level." : "Missing top-level permissions object."
2728
- });
2729
- checks.push({
2730
- check: "No nested permissions in params",
2731
- status: nestedPermissions ? "fail" : "pass",
2732
- details: nestedPermissions ? "Found params.permissions. Move permissions to top level." : "No nested permissions found."
2733
- });
2734
- const disableMetricCreationFromToken = typeof topLevelPermissions === "object" && topLevelPermissions !== null ? topLevelPermissions.isDisableMetricCreation : void 0;
2735
- checks.push({
2736
- check: "Guest token metric creation gate",
2737
- status: disableMetricCreationFromToken === true ? "fail" : "pass",
2738
- details: disableMetricCreationFromToken === true ? "permissions.isDisableMetricCreation=true blocks + Create Metric." : "permissions.isDisableMetricCreation is not forcing metric creation off."
2739
- });
2740
- if (params.embedOptions) {
2741
- checks.push({
2742
- check: "Embed option metric creation gate",
2743
- status: params.embedOptions.disableMetricCreation === true ? "fail" : "pass",
2744
- details: params.embedOptions.disableMetricCreation === true ? "embed option disableMetricCreation=true blocks + Create Metric." : "Embed options are not forcing metric creation off."
2745
- });
2746
- }
2747
- if (params.dashboardRuntime) {
2748
- checks.push({
2749
- check: "Dashboard runtime: isAllowedToCreateMetrics",
2750
- status: params.dashboardRuntime.isAllowedToCreateMetrics === true ? "pass" : "fail",
2751
- details: params.dashboardRuntime.isAllowedToCreateMetrics === true ? "Dashboard runtime allows metric creation." : "Dashboard runtime says metric creation is not allowed."
2752
- });
2753
- checks.push({
2754
- check: "Dashboard runtime: metricCreationMode",
2755
- status: params.dashboardRuntime.metricCreationMode === "DRAG_DROP" ? "pass" : "warn",
2756
- details: `metricCreationMode is "${params.dashboardRuntime.metricCreationMode ?? "unknown"}".`
2757
- });
2758
- }
2759
- return checks;
2760
- }
2761
- function registerDiagnoseEmbedHealth(server, client) {
2762
- server.tool(
2763
- "diagnose_embed_health",
2764
- "Diagnose embed shape and required fields. Optionally validates runtime readiness if guestTokenBody or permissions are provided.",
2765
- {
2766
- embedId: z16.string().describe("Embed ID to diagnose"),
2767
- dataAppName: z16.string().optional().describe("Optional data app name to scope dashboard checks"),
2768
- // Runtime readiness params (absorbed from check_runtime_readiness)
2769
- guestTokenBody: z16.record(z16.unknown()).optional().describe(
2770
- "Exact request body sent to POST /api/v2/guest-token/create (for runtime validation)."
2771
- ),
2772
- permissions: z16.record(z16.unknown()).optional().describe(
2773
- "Guest token permissions (used when guestTokenBody is not provided)."
2774
- ),
2775
- embedOptions: z16.object({
2776
- disableMetricCreation: z16.boolean().optional()
2777
- }).optional().describe("Embed web component options (if known)."),
2778
- dashboardRuntime: z16.object({
2779
- isAllowedToCreateMetrics: z16.boolean().optional(),
2780
- metricCreationMode: z16.string().optional()
2781
- }).optional().describe("Runtime flags from dashboard response payload.")
2782
- },
2783
- READ_ONLY,
2784
- async (params) => {
2785
- try {
2786
- const raw = await client.request({
2787
- method: "GET",
2788
- path: "/api/v2/data-app/embeds",
2789
- auth: "api",
2790
- query: { embedId: params.embedId }
2791
- });
2792
- const embedList = Array.isArray(raw) ? raw : [raw];
2793
- const embed = embedList.find((e) => (e?.embedId || e?._id || e?.id) === params.embedId) || embedList[0];
2794
- const embedObj = asRecord2(embed);
2795
- if (!embedObj) {
2796
- return toolError(
2797
- `Embed "${params.embedId}" is not returned as an object by GET /embeds. This indicates malformed API data for this record.`
2798
- );
2799
- }
2800
- const embedMetadata = asRecord2(embedObj.embedMetadata);
2801
- const accessSettings = asRecord2(embedObj.accessSettings) || (embedMetadata ? asRecord2(embedMetadata.accessSettings) : null);
2802
- const dashboardIdCandidates = [
2803
- firstString(embedObj.dashboardId),
2804
- firstString(embedObj.externalDashboardId),
2805
- embedMetadata ? firstString(embedMetadata.dashboardId) : null
2806
- ].filter(Boolean);
2807
- const workspaceCandidates = [
2808
- firstString(embedObj.workspaceName),
2809
- firstString(embedObj.workspace),
2810
- firstString(embedObj.workspaceId),
2811
- embedMetadata ? firstString(embedMetadata.workspaceName) : null,
2812
- embedMetadata ? firstString(embedMetadata.workspaceId) : null
2813
- ].filter(Boolean);
2814
- const datamartName = firstString(
2815
- accessSettings?.datamartName,
2816
- embedObj.datamartName,
2817
- embedMetadata?.datamartName
2818
- );
2819
- let datamartsCount = 0;
2820
- let datamartNames = [];
2821
- try {
2822
- const datamarts = await client.request({
2823
- method: "GET",
2824
- path: "/api/v2/data-app/datamarts",
2825
- auth: "service",
2826
- cacheKey: "list_datamarts",
2827
- cacheTtlMs: 5 * 60 * 1e3
2828
- });
2829
- if (Array.isArray(datamarts)) {
2830
- datamartsCount = datamarts.length;
2831
- datamartNames = datamarts.map((d) => firstString(d?.name, d?.datamartName)).filter(Boolean).slice(0, 10);
2832
- }
2833
- } catch {
2834
- }
2835
- let dashboardsCount = 0;
2836
- let dashboardIdSample = [];
2837
- try {
2838
- const dashboardQuery = params.dataAppName && params.dataAppName.trim().length > 0 ? { dataAppName: params.dataAppName } : void 0;
2839
- const dashboards = await client.request({
2840
- method: "GET",
2841
- path: "/api/v2/data-app/dashboards",
2842
- auth: "api",
2843
- query: dashboardQuery,
2844
- cacheKey: "list_dashboards",
2845
- cacheTtlMs: 5 * 60 * 1e3
2846
- });
2847
- if (Array.isArray(dashboards)) {
2848
- dashboardsCount = dashboards.length;
2849
- dashboardIdSample = dashboards.map(
2850
- (d) => firstString(
2851
- d?.dashboardId,
2852
- d?.externalDashboardId,
2853
- d?._id,
2854
- d?.id,
2855
- d?.uid
2856
- )
2857
- ).filter(Boolean).slice(0, 10);
2858
- }
2859
- } catch {
2860
- }
2861
- const issues = [];
2862
- if (!firstString(embedObj.embedId, embedObj.id, embedObj._id)) {
2863
- issues.push("No stable embed identifier found in record");
2864
- }
2865
- if (!accessSettings) {
2866
- issues.push("Missing accessSettings");
2867
- }
2868
- if (!datamartName) {
2869
- issues.push("Missing datamartName (in accessSettings or embed metadata)");
2870
- }
2871
- if (dashboardIdCandidates.length === 0) {
2872
- issues.push("No dashboard identifier found on embed");
2873
- }
2874
- if (workspaceCandidates.length === 0) {
2875
- issues.push("No workspace identifier/name found on embed");
2876
- }
2877
- const health = issues.length === 0 ? "healthy-shape" : "inconsistent-shape";
2878
- let report = `Embed Health Report: \`${params.embedId}\`
2879
-
2880
- - **Status:** ${health}
2881
- - **Issues:** ${issues.length ? issues.join("; ") : "none detected in shape checks"}
2882
- - **Dashboard ID candidates:** ${dashboardIdCandidates.length ? dashboardIdCandidates.map((id) => `\`${id}\``).join(", ") : "none"}
2883
- - **Workspace candidates:** ${workspaceCandidates.length ? workspaceCandidates.map((id) => `\`${id}\``).join(", ") : "none"}
2884
- - **Resolved datamartName:** ${datamartName ? `\`${datamartName}\`` : "none"}
2885
- - **Datamarts visible:** ${datamartsCount}${datamartNames.length ? ` (sample: ${datamartNames.join(", ")})` : ""}
2886
- - **Dashboards visible:** ${dashboardsCount}${dashboardIdSample.length ? ` (ID sample: ${dashboardIdSample.map((id) => `\`${id}\``).join(", ")})` : ""}
2887
- `;
2888
- if (params.guestTokenBody || params.permissions || params.embedOptions || params.dashboardRuntime) {
2889
- const runtimeChecks = runRuntimeReadinessChecks({
2890
- guestTokenBody: params.guestTokenBody,
2891
- clientId: void 0,
2892
- dataAppName: params.dataAppName,
2893
- permissions: params.permissions,
2894
- embedOptions: params.embedOptions,
2895
- dashboardRuntime: params.dashboardRuntime
2896
- });
2897
- const hasFail = runtimeChecks.some((c) => c.status === "fail");
2898
- report += `
2899
- ### Runtime Readiness: ${hasFail ? "NOT_READY" : "READY"}
2900
-
2901
- `;
2902
- for (const c of runtimeChecks) {
2903
- const icon = c.status === "pass" ? "PASS" : c.status === "fail" ? "FAIL" : "WARN";
2904
- report += `- **[${icon}]** ${c.check}: ${c.details}
2905
- `;
2906
- }
2907
- }
2908
- report += `
2909
- Raw embed payload:
2910
- \`\`\`json
2911
- ` + JSON.stringify(embedObj, null, 2) + "\n```";
2912
- return toolSuccess(report);
2913
- } catch (err) {
2914
- if (err instanceof DatabrainApiError) return err.toToolResponse();
2915
- return toolError(`Unexpected error: ${err.message}`);
2916
- }
2917
- }
2918
- );
2919
- }
2920
-
2921
2380
  // src/tools/frontend/generate-guest-token.ts
2922
- import { z as z17 } from "zod";
2381
+ import { z as z15 } from "zod";
2923
2382
  function registerGenerateGuestToken(server, client) {
2924
2383
  const services = createServiceBundle(client);
2925
2384
  server.tool(
2926
2385
  "generate_guest_token",
2927
2386
  "Generate a guest token for frontend embedding. Requires clientId. Omit dataAppName to auto-resolve.",
2928
2387
  {
2929
- clientId: z17.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
2930
- dataAppName: z17.string().optional().describe("Name of the data app. Omit to list available data apps."),
2931
- body: z17.record(z17.unknown()).optional().describe(
2388
+ clientId: z15.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
2389
+ dataAppName: z15.string().optional().describe("Name of the data app. Omit to list available data apps."),
2390
+ body: z15.record(z15.unknown()).optional().describe(
2932
2391
  "Raw API body for POST /api/v2/guest-token/create. If provided, sent directly and convenience fields are ignored."
2933
2392
  ),
2934
- params: z17.object({
2935
- allowedEmbeds: z17.array(z17.string()).optional().describe("Restrict token to specific embed IDs"),
2936
- rlsSettings: z17.array(z17.object({
2937
- metricId: z17.string().describe("Metric ID"),
2938
- values: z17.record(z17.unknown()).describe("RLS values by column name")
2393
+ params: z15.object({
2394
+ allowedEmbeds: z15.array(z15.string()).optional().describe("Restrict token to specific embed IDs"),
2395
+ rlsSettings: z15.array(z15.object({
2396
+ metricId: z15.string().describe("Metric ID"),
2397
+ values: z15.record(z15.unknown()).describe("RLS values by column name")
2939
2398
  })).optional().describe("Legacy alias used in docs/examples; forwarded as params.rlsSettings"),
2940
- appFilters: z17.array(z17.object({
2941
- metricId: z17.string().describe("Metric ID"),
2942
- values: z17.record(z17.unknown()).describe("Filter values by column name")
2399
+ appFilters: z15.array(z15.object({
2400
+ metricId: z15.string().describe("Metric ID"),
2401
+ values: z15.record(z15.unknown()).describe("Filter values by column name")
2943
2402
  })).optional().describe("Metric-level app filters"),
2944
- dashboardAppFilters: z17.array(z17.object({
2945
- dashboardId: z17.string().describe("Dashboard ID"),
2946
- values: z17.record(z17.unknown()).describe("Filter values by dashboard filter name"),
2947
- isShowOnUrl: z17.boolean().optional().describe("Whether filters are shown in URL params")
2403
+ dashboardAppFilters: z15.array(z15.object({
2404
+ dashboardId: z15.string().describe("Dashboard ID"),
2405
+ values: z15.record(z15.unknown()).describe("Filter values by dashboard filter name"),
2406
+ isShowOnUrl: z15.boolean().optional().describe("Whether filters are shown in URL params")
2948
2407
  })).optional().describe("Dashboard-level app filters"),
2949
- hideDashboardFilters: z17.array(z17.string()).optional().describe("Dashboard filter names to hide"),
2950
- userIdentifier: z17.string().optional().describe("End-user identifier for private metrics support"),
2951
- timezone: z17.string().optional().describe('IANA timezone, e.g. "America/New_York"')
2408
+ hideDashboardFilters: z15.array(z15.string()).optional().describe("Dashboard filter names to hide"),
2409
+ userIdentifier: z15.string().optional().describe("End-user identifier for private metrics support"),
2410
+ timezone: z15.string().optional().describe('IANA timezone, e.g. "America/New_York"')
2952
2411
  }).optional().describe("Advanced token params"),
2953
2412
  // Legacy convenience fields (auto-mapped into params for backward compatibility)
2954
- timezone: z17.string().optional().describe("Legacy: moved under params.timezone"),
2955
- allowedEmbeds: z17.array(z17.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
2956
- rlsSettings: z17.array(z17.object({
2957
- metricId: z17.string().describe("Metric ID"),
2958
- values: z17.record(z17.unknown()).describe("RLS values by column name")
2413
+ timezone: z15.string().optional().describe("Legacy: moved under params.timezone"),
2414
+ allowedEmbeds: z15.array(z15.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
2415
+ rlsSettings: z15.array(z15.object({
2416
+ metricId: z15.string().describe("Metric ID"),
2417
+ values: z15.record(z15.unknown()).describe("RLS values by column name")
2959
2418
  })).optional().describe("Legacy: moved under params.rlsSettings"),
2960
- appFilters: z17.array(z17.object({
2961
- metricId: z17.string().describe("Metric ID"),
2962
- values: z17.record(z17.unknown()).describe("Filter values by column name")
2419
+ appFilters: z15.array(z15.object({
2420
+ metricId: z15.string().describe("Metric ID"),
2421
+ values: z15.record(z15.unknown()).describe("Filter values by column name")
2963
2422
  })).optional().describe("Legacy: moved under params.appFilters"),
2964
- dashboardAppFilters: z17.array(z17.object({
2965
- dashboardId: z17.string().describe("Dashboard ID to apply filters to"),
2966
- values: z17.record(z17.unknown()).describe("Filter values for this dashboard"),
2967
- isShowOnUrl: z17.boolean().optional().describe("Whether filters are shown in URL params")
2423
+ dashboardAppFilters: z15.array(z15.object({
2424
+ dashboardId: z15.string().describe("Dashboard ID to apply filters to"),
2425
+ values: z15.record(z15.unknown()).describe("Filter values for this dashboard"),
2426
+ isShowOnUrl: z15.boolean().optional().describe("Whether filters are shown in URL params")
2968
2427
  })).optional().describe("Legacy: moved under params.dashboardAppFilters"),
2969
- permissions: z17.object({
2970
- isEnableArchiveMetrics: z17.boolean().optional(),
2971
- isEnableManageMetrics: z17.boolean().optional(),
2972
- isEnableCreateDashboardView: z17.boolean().optional(),
2973
- isEnableMetricUpdation: z17.boolean().optional(),
2974
- isEnableCustomizeLayout: z17.boolean().optional(),
2975
- isEnableUnderlyingData: z17.boolean().optional(),
2976
- isEnableDownloadMetrics: z17.boolean().optional(),
2977
- isShowSideBar: z17.boolean().optional(),
2978
- isShowDashboardName: z17.boolean().optional(),
2979
- isDisableMetricCreation: z17.boolean().optional()
2428
+ permissions: z15.object({
2429
+ isEnableArchiveMetrics: z15.boolean().optional(),
2430
+ isEnableManageMetrics: z15.boolean().optional(),
2431
+ isEnableCreateDashboardView: z15.boolean().optional(),
2432
+ isEnableMetricUpdation: z15.boolean().optional(),
2433
+ isEnableCustomizeLayout: z15.boolean().optional(),
2434
+ isEnableUnderlyingData: z15.boolean().optional(),
2435
+ isEnableDownloadMetrics: z15.boolean().optional(),
2436
+ isShowSideBar: z15.boolean().optional(),
2437
+ isShowDashboardName: z15.boolean().optional(),
2438
+ isDisableMetricCreation: z15.boolean().optional()
2980
2439
  }).optional().describe("Runtime permission overrides (merge with and override embed-level settings)"),
2981
- theme: z17.record(z17.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
2982
- hideDashboardFilters: z17.array(z17.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
2983
- userIdentifier: z17.string().optional().describe("Legacy: moved under params.userIdentifier"),
2984
- expiryTime: z17.number().optional().describe("Token TTL in milliseconds"),
2985
- datasourceName: z17.string().optional().describe("Datasource name for multi-datasource routing")
2440
+ theme: z15.record(z15.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
2441
+ hideDashboardFilters: z15.array(z15.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
2442
+ userIdentifier: z15.string().optional().describe("Legacy: moved under params.userIdentifier"),
2443
+ expiryTime: z15.number().optional().describe("Token TTL in milliseconds"),
2444
+ datasourceName: z15.string().optional().describe("Datasource name for multi-datasource routing")
2986
2445
  },
2987
2446
  WRITE_CREATE,
2988
2447
  async (params) => {
@@ -3035,7 +2494,7 @@ Next step: Use \`generate_embed_code\` to get copy-paste-ready frontend code.`
3035
2494
  }
3036
2495
 
3037
2496
  // src/tools/frontend/generate-embed-code.ts
3038
- import { z as z18 } from "zod";
2497
+ import { z as z16 } from "zod";
3039
2498
 
3040
2499
  // src/templates/react.ts
3041
2500
  function kebabCase(str) {
@@ -4031,22 +3490,22 @@ function registerGenerateEmbedCode(server, _client) {
4031
3490
  "generate_embed_code",
4032
3491
  "Generate framework-specific embed code (React, Next.js, Vue, Angular, Svelte, SolidJS, vanilla HTML).",
4033
3492
  {
4034
- embedId: z18.string().describe("The embed ID to generate code for"),
4035
- framework: z18.enum(FRAMEWORKS).describe("Frontend framework"),
4036
- embedType: z18.enum(["dashboard", "metric"]).optional().default("dashboard"),
4037
- options: z18.object({
4038
- enableDownloadCsv: z18.boolean().optional(),
4039
- enableEmailCsv: z18.boolean().optional(),
4040
- enableDownloadAllMetrics: z18.boolean().optional(),
4041
- enableDownloadAllPdf: z18.boolean().optional(),
4042
- disableFullscreen: z18.boolean().optional(),
4043
- isHideTablePreview: z18.boolean().optional(),
4044
- isHideChartSettings: z18.boolean().optional(),
4045
- isStickyDashboardFilters: z18.boolean().optional(),
4046
- enableMultiMetricFilters: z18.boolean().optional(),
4047
- themeName: z18.string().optional(),
4048
- customTheme: z18.record(z18.unknown()).optional(),
4049
- optionsIcon: z18.enum(["kebab-menu-vertical", "download"]).optional()
3493
+ embedId: z16.string().describe("The embed ID to generate code for"),
3494
+ framework: z16.enum(FRAMEWORKS).describe("Frontend framework"),
3495
+ embedType: z16.enum(["dashboard", "metric"]).optional().default("dashboard"),
3496
+ options: z16.object({
3497
+ enableDownloadCsv: z16.boolean().optional(),
3498
+ enableEmailCsv: z16.boolean().optional(),
3499
+ enableDownloadAllMetrics: z16.boolean().optional(),
3500
+ enableDownloadAllPdf: z16.boolean().optional(),
3501
+ disableFullscreen: z16.boolean().optional(),
3502
+ isHideTablePreview: z16.boolean().optional(),
3503
+ isHideChartSettings: z16.boolean().optional(),
3504
+ isStickyDashboardFilters: z16.boolean().optional(),
3505
+ enableMultiMetricFilters: z16.boolean().optional(),
3506
+ themeName: z16.string().optional(),
3507
+ customTheme: z16.record(z16.unknown()).optional(),
3508
+ optionsIcon: z16.enum(["kebab-menu-vertical", "download"]).optional()
4050
3509
  }).optional()
4051
3510
  },
4052
3511
  READ_ONLY,
@@ -4086,17 +3545,17 @@ ${generated.instructions}`;
4086
3545
  }
4087
3546
 
4088
3547
  // src/tools/analytics/list-dashboards.ts
4089
- import { z as z19 } from "zod";
3548
+ import { z as z17 } from "zod";
4090
3549
  function registerListDashboards(server, client) {
4091
3550
  const services = createServiceBundle(client);
4092
3551
  server.tool(
4093
3552
  "list_dashboards",
4094
3553
  "List dashboards globally or for a specific data app.",
4095
3554
  {
4096
- scope: z19.enum(["auto", "global", "data_app"]).optional().describe(
3555
+ scope: z17.enum(["auto", "global", "data_app"]).optional().describe(
4097
3556
  "Dashboard listing scope. auto: use data_app when dataAppName + API token are available, otherwise global."
4098
3557
  ),
4099
- dataAppName: z19.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
3558
+ dataAppName: z17.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
4100
3559
  },
4101
3560
  READ_ONLY,
4102
3561
  async (params) => {
@@ -4130,617 +3589,8 @@ function registerListDashboards(server, client) {
4130
3589
  );
4131
3590
  }
4132
3591
 
4133
- // src/tools/orchestration/setup-embed-interactive.ts
4134
- import { z as z20 } from "zod";
4135
- var STEPS = [
4136
- "discover_data_apps",
4137
- "choose_data_app",
4138
- "create_data_app",
4139
- "ensure_api_token",
4140
- "discover_workspaces",
4141
- "choose_workspace",
4142
- "list_dashboards",
4143
- "choose_dashboard",
4144
- "create_embed",
4145
- "runtime_readiness",
4146
- "complete"
4147
- ];
4148
- var stateSchema = z20.object({
4149
- dataAppName: z20.string().optional(),
4150
- dataAppId: z20.string().optional(),
4151
- workspaceName: z20.string().optional(),
4152
- embedType: z20.enum(["dashboard", "metric"]).optional(),
4153
- dashboardId: z20.string().optional(),
4154
- metricId: z20.string().optional(),
4155
- datamartName: z20.string().optional(),
4156
- embedId: z20.string().optional(),
4157
- embedName: z20.string().optional(),
4158
- clientId: z20.string().optional(),
4159
- runtimeValidated: z20.boolean().optional()
4160
- });
4161
- function emit(status, step, state, message, options, nextStep) {
4162
- const payload = {
4163
- status,
4164
- step,
4165
- message,
4166
- state
4167
- };
4168
- if (options) payload.options = options;
4169
- if (nextStep) payload.nextStep = nextStep;
4170
- return toolSuccess(JSON.stringify(payload, null, 2));
4171
- }
4172
- function parseUserChoiceObject(userChoice) {
4173
- if (!userChoice) return null;
4174
- try {
4175
- const parsed = JSON.parse(userChoice);
4176
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4177
- return parsed;
4178
- }
4179
- return null;
4180
- } catch {
4181
- return null;
4182
- }
4183
- }
4184
- function parseNameFromUserChoice(userChoice) {
4185
- if (!userChoice) return null;
4186
- const choiceObj = parseUserChoiceObject(userChoice);
4187
- if (choiceObj) {
4188
- const rawName = choiceObj.name;
4189
- if (typeof rawName === "string" && rawName.trim().length > 0) return rawName.trim();
4190
- return null;
4191
- }
4192
- const trimmed = userChoice.trim();
4193
- return trimmed.length > 0 ? trimmed : null;
4194
- }
4195
- function registerSetupEmbedInteractive(server, client) {
4196
- const services = createServiceBundle(client);
4197
- const listDashboardsForContext = async (currentState) => {
4198
- const dataAppName = currentState.dataAppName;
4199
- const workspaceName = currentState.workspaceName;
4200
- if (dataAppName && dataAppName.trim().length > 0 && client.hasApiToken()) {
4201
- const dashboards2 = await services.dashboardsApi.listDashboards(dataAppName);
4202
- if (Array.isArray(dashboards2) && dashboards2.length > 0) {
4203
- return { dashboards: dashboards2, scope: "data_app" };
4204
- }
4205
- }
4206
- if (workspaceName && workspaceName.trim().length > 0) {
4207
- const dashboards2 = await services.dashboardsApi.listDashboardsForWorkspace(workspaceName);
4208
- return { dashboards: dashboards2, scope: "workspace" };
4209
- }
4210
- const dashboards = await services.dashboardsApi.listDashboardsAcrossWorkspaces();
4211
- return { dashboards, scope: "global" };
4212
- };
4213
- server.tool(
4214
- "setup_embed_interactive",
4215
- "Step-by-step embed setup wizard. Pass step and state from previous call.",
4216
- {
4217
- step: z20.enum(STEPS).describe("Current orchestration step"),
4218
- state: stateSchema.optional().describe("State from previous step \u2014 pass forward unchanged"),
4219
- userChoice: z20.string().optional().describe("User response for CHOICE_REQUIRED or CONFIRM_REQUIRED")
4220
- },
4221
- WRITE_CREATE,
4222
- async (params) => {
4223
- const state = params.state ?? {};
4224
- const step = params.step;
4225
- try {
4226
- if (step === "discover_data_apps") {
4227
- const apps = await services.dataAppsApi.listDataApps();
4228
- if (!Array.isArray(apps) || apps.length === 0) {
4229
- return emit(
4230
- "CHOICE_REQUIRED",
4231
- step,
4232
- state,
4233
- "No data apps found. Ask user for a data app name to create one.",
4234
- [
4235
- { id: "create", label: "Create a new data app" },
4236
- { id: "stop", label: "Stop setup" }
4237
- ],
4238
- "create_data_app"
4239
- );
4240
- }
4241
- if (apps.length === 1) {
4242
- const app = services.discoveryService.normalizeDataApp(apps[0]);
4243
- return emit(
4244
- "CONFIRM_REQUIRED",
4245
- step,
4246
- {
4247
- ...state,
4248
- dataAppName: app.name,
4249
- dataAppId: app.dataAppId || void 0
4250
- },
4251
- `Found one data app: "${app.name}". Confirm to continue.`,
4252
- [{ id: app.name, label: `${app.name} (${app.dataAppId || "no-id"})` }],
4253
- "ensure_api_token"
4254
- );
4255
- }
4256
- const options = apps.map((a) => {
4257
- const n = services.discoveryService.normalizeDataApp(a);
4258
- return {
4259
- id: n.dataAppId || n.name,
4260
- label: `${n.name} (${n.dataAppId || "no-id"})`,
4261
- dataAppName: n.name,
4262
- dataAppId: n.dataAppId
4263
- };
4264
- });
4265
- return emit(
4266
- "CHOICE_REQUIRED",
4267
- step,
4268
- state,
4269
- `Found ${apps.length} data app(s). Ask the user which one to embed from.`,
4270
- options,
4271
- "choose_data_app"
4272
- );
4273
- }
4274
- if (step === "choose_data_app") {
4275
- if (!params.userChoice) {
4276
- return toolError("userChoice is required for choose_data_app.");
4277
- }
4278
- const apps = await services.dataAppsApi.listDataApps();
4279
- const q = params.userChoice.toLowerCase();
4280
- const match = apps.find((a) => {
4281
- const n2 = services.discoveryService.normalizeDataApp(a);
4282
- return n2.name.toLowerCase() === q || (n2.dataAppId || "").toLowerCase() === q || n2.name.toLowerCase().includes(q);
4283
- });
4284
- if (!match) {
4285
- return toolError(
4286
- `No data app matched "${params.userChoice}". Re-run discover_data_apps.`
4287
- );
4288
- }
4289
- const n = services.discoveryService.normalizeDataApp(match);
4290
- return emit(
4291
- "READY",
4292
- step,
4293
- {
4294
- ...state,
4295
- dataAppName: n.name,
4296
- dataAppId: n.dataAppId || void 0
4297
- },
4298
- `Selected data app: "${n.name}".`,
4299
- void 0,
4300
- "ensure_api_token"
4301
- );
4302
- }
4303
- if (step === "create_data_app") {
4304
- const name = parseNameFromUserChoice(params.userChoice);
4305
- if (!name) {
4306
- return toolError(
4307
- 'Provide data app name in userChoice. Accepts plain string or JSON: {"name":"my-data-app"}.'
4308
- );
4309
- }
4310
- const created = await client.request({
4311
- method: "POST",
4312
- path: "/api/v2/data-app/",
4313
- auth: "service",
4314
- body: { name },
4315
- invalidateCache: ["list_data_apps"]
4316
- });
4317
- return emit(
4318
- "READY",
4319
- step,
4320
- {
4321
- ...state,
4322
- dataAppName: name,
4323
- dataAppId: created.id || void 0
4324
- },
4325
- `Created data app "${name}".`,
4326
- void 0,
4327
- "ensure_api_token"
4328
- );
4329
- }
4330
- if (step === "ensure_api_token") {
4331
- const appName = state.dataAppName;
4332
- if (!appName) {
4333
- return toolError("state.dataAppName is required. Run discover_data_apps first.");
4334
- }
4335
- const tokens = await client.request({
4336
- method: "GET",
4337
- path: "/api/v2/data-app/api-tokens",
4338
- auth: "service",
4339
- query: { dataAppName: appName }
4340
- });
4341
- if (Array.isArray(tokens) && tokens.length > 0) {
4342
- const tokenValue = tokens[0].token || tokens[0].apiToken || tokens[0].key;
4343
- if (tokenValue) {
4344
- client.setRuntimeApiToken(tokenValue);
4345
- return emit(
4346
- "READY",
4347
- step,
4348
- { ...state, dataAppName: appName },
4349
- `API token found and activated for "${appName}".`,
4350
- void 0,
4351
- "discover_workspaces"
4352
- );
4353
- }
4354
- return emit(
4355
- "CHOICE_REQUIRED",
4356
- step,
4357
- { ...state, dataAppName: appName },
4358
- `"${appName}" has API token(s) but the value was not returned. Call \`create_api_token\` to generate a new one, or \`set_api_token\` if you have the value. Then re-run discover_workspaces.`,
4359
- [
4360
- { id: "create", label: "Create new API token (call create_api_token)" },
4361
- { id: "set", label: "Set existing token (call set_api_token)" }
4362
- ],
4363
- "discover_workspaces"
4364
- );
4365
- }
4366
- return emit(
4367
- "CHOICE_REQUIRED",
4368
- step,
4369
- { ...state, dataAppName: appName },
4370
- `No API token for "${appName}". Call \`create_api_token\` to generate one. Then proceed to discover_workspaces.`,
4371
- [
4372
- { id: "create", label: "Create API token now (call create_api_token)" }
4373
- ],
4374
- "discover_workspaces"
4375
- );
4376
- }
4377
- if (step === "discover_workspaces") {
4378
- const workspaces = await services.workspacesApi.listWorkspaces();
4379
- if (!Array.isArray(workspaces) || workspaces.length === 0) {
4380
- return emit(
4381
- "READY",
4382
- step,
4383
- state,
4384
- "No workspaces found. Create a workspace in Databrain UI first, then re-run this step."
4385
- );
4386
- }
4387
- if (workspaces.length === 1) {
4388
- const ws = workspaces[0];
4389
- const wsName = (typeof ws.name === "string" ? ws.name.trim() : "") || "Unnamed";
4390
- return emit(
4391
- "READY",
4392
- step,
4393
- { ...state, workspaceName: wsName },
4394
- `Found one workspace: "${wsName}". Auto-selected.`,
4395
- void 0,
4396
- "list_dashboards"
4397
- );
4398
- }
4399
- const options = workspaces.filter((ws) => typeof ws.name === "string" && ws.name.trim().length > 0).map((ws) => ({
4400
- id: ws.name,
4401
- label: `${ws.name} (${ws._id || ws.id || "no-id"})`,
4402
- workspaceName: ws.name
4403
- }));
4404
- return emit(
4405
- "CHOICE_REQUIRED",
4406
- step,
4407
- state,
4408
- `Found ${workspaces.length} workspace(s). Ask the user which workspace to use.`,
4409
- options,
4410
- "choose_workspace"
4411
- );
4412
- }
4413
- if (step === "choose_workspace") {
4414
- if (!params.userChoice) {
4415
- return toolError("userChoice is required for choose_workspace.");
4416
- }
4417
- const workspaces = await services.workspacesApi.listWorkspaces();
4418
- const q = params.userChoice.toLowerCase();
4419
- const match = workspaces.find((ws) => {
4420
- const name = (typeof ws.name === "string" ? ws.name.trim() : "").toLowerCase();
4421
- const wsId = (ws._id || ws.id || "").toLowerCase();
4422
- return name === q || wsId === q || name.includes(q);
4423
- });
4424
- if (!match) {
4425
- return toolError(
4426
- `No workspace matched "${params.userChoice}". Re-run discover_workspaces.`
4427
- );
4428
- }
4429
- const wsName = (typeof match.name === "string" ? match.name.trim() : "") || "Unnamed";
4430
- return emit(
4431
- "READY",
4432
- step,
4433
- { ...state, workspaceName: wsName },
4434
- `Selected workspace: "${wsName}".`,
4435
- void 0,
4436
- "list_dashboards"
4437
- );
4438
- }
4439
- if (step === "list_dashboards") {
4440
- const { dashboards, scope } = await listDashboardsForContext(state);
4441
- if (!Array.isArray(dashboards) || dashboards.length === 0) {
4442
- const scopeMsg = scope === "data_app" ? `No dashboards found for data app "${state.dataAppName || "unknown"}".` : scope === "workspace" ? `No dashboards found in workspace "${state.workspaceName || "unknown"}". Create a dashboard in Databrain UI first, then re-run this step.` : "No dashboards found in available workspaces. Create a dashboard in Databrain UI first, then re-run this step.";
4443
- return emit("READY", step, state, scopeMsg);
4444
- }
4445
- const options = dashboards.map((d) => {
4446
- const n = services.discoveryService.normalizeDashboard(d);
4447
- return {
4448
- id: n.dashboardId || n.name,
4449
- label: `${n.name} (${n.dashboardId || "no-id"})${n.workspaceName ? ` \u2014 workspace: ${n.workspaceName}` : ""}`,
4450
- dashboardId: n.dashboardId,
4451
- workspaceName: n.workspaceName
4452
- };
4453
- });
4454
- const foundMsg = scope === "data_app" ? `Found ${dashboards.length} dashboard(s) for data app "${state.dataAppName || "unknown"}". Ask the user which dashboard to embed.` : scope === "workspace" ? `Found ${dashboards.length} dashboard(s) in workspace "${state.workspaceName || "unknown"}". Ask the user which dashboard to embed.` : `Found ${dashboards.length} dashboard(s) across workspaces. Ask the user which dashboard to embed.`;
4455
- return emit(
4456
- "CHOICE_REQUIRED",
4457
- step,
4458
- state,
4459
- foundMsg,
4460
- options,
4461
- "choose_dashboard"
4462
- );
4463
- }
4464
- if (step === "choose_dashboard") {
4465
- if (!params.userChoice) {
4466
- return toolError("userChoice is required for choose_dashboard. Pass dashboardId or name.");
4467
- }
4468
- const { dashboards } = await listDashboardsForContext(state);
4469
- const q = params.userChoice.toLowerCase();
4470
- const match = dashboards.find((d) => {
4471
- const n2 = services.discoveryService.normalizeDashboard(d);
4472
- return (n2.dashboardId || "").toLowerCase() === q || (n2.name || "").toLowerCase() === q || (n2.name || "").toLowerCase().includes(q);
4473
- });
4474
- if (!match) {
4475
- return toolError(
4476
- `No dashboard matched "${params.userChoice}". Re-run list_dashboards.`
4477
- );
4478
- }
4479
- const n = services.discoveryService.normalizeDashboard(match);
4480
- let resolvedDatamartName = state.datamartName;
4481
- const resolveAppId = state.dataAppId || n.workspaceName;
4482
- if (!resolvedDatamartName && resolveAppId) {
4483
- try {
4484
- const datamarts = await services.datamartsApi.listDatamartsByDataAppId(resolveAppId);
4485
- const names = Array.from(
4486
- new Set(
4487
- datamarts.map((dm) => dm.name || dm.datamartName).filter((v) => typeof v === "string" && v.trim().length > 0)
4488
- )
4489
- );
4490
- if (names.length === 1) resolvedDatamartName = names[0];
4491
- } catch {
4492
- }
4493
- }
4494
- return emit(
4495
- "READY",
4496
- step,
4497
- {
4498
- ...state,
4499
- dashboardId: n.dashboardId,
4500
- embedType: "dashboard",
4501
- ...n.workspaceName ? { workspaceName: n.workspaceName } : {},
4502
- ...resolvedDatamartName ? { datamartName: resolvedDatamartName } : {}
4503
- },
4504
- `Selected dashboard: "${n.name}" (${n.dashboardId}).${n.workspaceName ? ` workspaceName=${n.workspaceName}.` : ""}${resolvedDatamartName ? ` datamartName=${resolvedDatamartName}.` : ""} Proceeding to create embed.`,
4505
- void 0,
4506
- "create_embed"
4507
- );
4508
- }
4509
- if (step === "create_embed") {
4510
- const choiceObj = parseUserChoiceObject(params.userChoice);
4511
- const merged = {
4512
- ...state,
4513
- ...choiceObj || {}
4514
- };
4515
- const dashboardId = merged.dashboardId;
4516
- const metricId = merged.metricId;
4517
- let resolvedWorkspaceName = merged.workspaceName;
4518
- const embedType = merged.embedType?.toLowerCase() === "metric" ? "metric" : "dashboard";
4519
- const datamartName = merged.datamartName;
4520
- const embedName = merged.embedName || (embedType === "dashboard" && dashboardId ? `embed-${dashboardId}` : embedType === "metric" && metricId ? `embed-metric-${metricId}` : void 0);
4521
- let resolvedDatamartName = datamartName;
4522
- let resolveAppId = merged.dataAppId;
4523
- let availableDatamartNames = [];
4524
- if (!resolveAppId) {
4525
- const appName = merged.dataAppName;
4526
- if (appName) {
4527
- try {
4528
- const apps = await services.dataAppsApi.listDataApps();
4529
- const app = apps.find((a) => {
4530
- const n = services.discoveryService.normalizeDataApp(a);
4531
- return n.name.toLowerCase() === appName.toLowerCase() || (n.dataAppId || "").toLowerCase() === appName.toLowerCase();
4532
- });
4533
- if (app) {
4534
- resolveAppId = services.discoveryService.normalizeDataApp(app).dataAppId || void 0;
4535
- }
4536
- } catch {
4537
- }
4538
- }
4539
- }
4540
- if (embedType === "dashboard" && dashboardId && merged.dataAppName && client.hasApiToken()) {
4541
- try {
4542
- const scopedDashboards = await services.dashboardsApi.listDashboards(
4543
- merged.dataAppName
4544
- );
4545
- const dashboardMatch = scopedDashboards.find((d) => {
4546
- const n = services.discoveryService.normalizeDashboard(d);
4547
- return (n.dashboardId || "").toLowerCase() === dashboardId.toLowerCase();
4548
- });
4549
- if (!dashboardMatch) {
4550
- return emit(
4551
- "CHOICE_REQUIRED",
4552
- step,
4553
- merged,
4554
- `dashboardId "${dashboardId}" is not available under data app "${merged.dataAppName || "unknown"}". Re-run list_dashboards and choose from the current data app scope.`,
4555
- [],
4556
- "list_dashboards"
4557
- );
4558
- }
4559
- const dashboardNormalized = services.discoveryService.normalizeDashboard(dashboardMatch);
4560
- if (dashboardNormalized.workspaceName) {
4561
- if (resolvedWorkspaceName && resolvedWorkspaceName !== dashboardNormalized.workspaceName) {
4562
- return emit(
4563
- "CHOICE_REQUIRED",
4564
- step,
4565
- merged,
4566
- `workspaceName "${resolvedWorkspaceName}" does not match dashboard workspace "${dashboardNormalized.workspaceName}". Use the dashboard workspace.`,
4567
- [
4568
- {
4569
- id: "use_dashboard_workspace",
4570
- label: dashboardNormalized.workspaceName,
4571
- workspaceName: dashboardNormalized.workspaceName
4572
- }
4573
- ],
4574
- "create_embed"
4575
- );
4576
- }
4577
- resolvedWorkspaceName = dashboardNormalized.workspaceName;
4578
- }
4579
- } catch {
4580
- }
4581
- }
4582
- if (embedType === "dashboard" && !resolvedDatamartName) {
4583
- if (resolveAppId) {
4584
- try {
4585
- const datamarts = await services.datamartsApi.listDatamartsByDataAppId(
4586
- resolveAppId
4587
- );
4588
- const names = Array.from(
4589
- new Set(
4590
- datamarts.map((dm) => dm.name || dm.datamartName).filter(
4591
- (v) => typeof v === "string" && v.trim().length > 0
4592
- )
4593
- )
4594
- );
4595
- availableDatamartNames = names;
4596
- if (names.length === 1) {
4597
- resolvedDatamartName = names[0];
4598
- } else if (names.length > 1) {
4599
- return emit(
4600
- "CHOICE_REQUIRED",
4601
- step,
4602
- merged,
4603
- "Multiple datamarts are available for this data app. Ask the user to select one for accessSettings.datamartName.",
4604
- names.map((name) => ({
4605
- id: name,
4606
- label: name,
4607
- datamartName: name
4608
- })),
4609
- "create_embed"
4610
- );
4611
- }
4612
- } catch {
4613
- }
4614
- }
4615
- }
4616
- if (embedType === "dashboard" && resolvedDatamartName && resolveAppId && availableDatamartNames.length === 0) {
4617
- try {
4618
- const datamarts = await services.datamartsApi.listDatamartsByDataAppId(resolveAppId);
4619
- availableDatamartNames = Array.from(
4620
- new Set(
4621
- datamarts.map((dm) => dm.name || dm.datamartName).filter((v) => typeof v === "string" && v.trim().length > 0)
4622
- )
4623
- );
4624
- } catch {
4625
- }
4626
- }
4627
- if (embedType === "dashboard" && resolvedDatamartName && availableDatamartNames.length > 0 && !availableDatamartNames.includes(resolvedDatamartName)) {
4628
- return emit(
4629
- "CHOICE_REQUIRED",
4630
- step,
4631
- merged,
4632
- `datamartName "${resolvedDatamartName}" is invalid for this data app. Choose one of the available datamarts.`,
4633
- availableDatamartNames.map((name) => ({
4634
- id: name,
4635
- label: name,
4636
- datamartName: name
4637
- })),
4638
- "create_embed"
4639
- );
4640
- }
4641
- const missing = [];
4642
- if (!resolvedWorkspaceName) missing.push("workspaceName");
4643
- if (!dashboardId) missing.push("dashboardId");
4644
- if (embedType === "metric" && !metricId) missing.push("metricId");
4645
- if (embedType === "dashboard" && !resolvedDatamartName) missing.push("datamartName");
4646
- if (missing.length > 0) {
4647
- return emit(
4648
- "CHOICE_REQUIRED",
4649
- step,
4650
- {
4651
- ...merged,
4652
- ...resolvedDatamartName ? { datamartName: resolvedDatamartName } : {}
4653
- },
4654
- `Missing required fields for embed creation: ${missing.join(", ")}. Provide via userChoice JSON.`,
4655
- [
4656
- {
4657
- id: "provide_fields",
4658
- label: 'Provide JSON: {"dashboardId":"...","workspaceName":"...","datamartName":"..."}'
4659
- }
4660
- ],
4661
- "create_embed"
4662
- );
4663
- }
4664
- const created = await services.embedService.createEmbedStrict({
4665
- dashboardId,
4666
- metricId,
4667
- workspaceName: resolvedWorkspaceName,
4668
- embedType,
4669
- name: embedName,
4670
- accessSettings: {
4671
- datamartName: resolvedDatamartName,
4672
- isAllowMetricCreation: false,
4673
- isAllowMetricDeletion: false,
4674
- isAllowMetricUpdate: false,
4675
- isAllowMetricLayoutChange: false,
4676
- isAllowManageMetrics: false,
4677
- isAllowUnderlyingData: false,
4678
- isAllowAiPilot: false,
4679
- isAllowEmailReports: false,
4680
- isAllowCreateDashboardView: false,
4681
- metricCreationMode: "DRAG_DROP"
4682
- }
4683
- });
4684
- const embedId = created.embedId || created.id || created._id || void 0;
4685
- return emit(
4686
- "READY",
4687
- step,
4688
- { ...merged, embedType, embedId },
4689
- `Embed created successfully${embedId ? ` (embedId: ${embedId})` : ""}. Next: run runtime readiness validation before finishing setup.`,
4690
- void 0,
4691
- "runtime_readiness"
4692
- );
4693
- }
4694
- if (step === "runtime_readiness") {
4695
- const appName = state.dataAppName;
4696
- if (!appName) {
4697
- return toolError("state.dataAppName is required. Run discover_data_apps first.");
4698
- }
4699
- const choiceObj = parseUserChoiceObject(params.userChoice);
4700
- const clientId = choiceObj?.clientId || state.clientId || "None";
4701
- const permissions = {
4702
- isEnableManageMetrics: true,
4703
- isEnableMetricUpdation: true,
4704
- isEnableCustomizeLayout: true,
4705
- isEnableCreateDashboardView: true,
4706
- isDisableMetricCreation: false,
4707
- ...choiceObj?.permissions || {}
4708
- };
4709
- await services.guestTokenService.generateToken({
4710
- clientId,
4711
- dataAppName: appName,
4712
- permissions
4713
- });
4714
- return emit(
4715
- "DONE",
4716
- step,
4717
- {
4718
- ...state,
4719
- clientId,
4720
- runtimeValidated: true
4721
- },
4722
- "Runtime contract validated by generating a guest token with top-level clientId/dataAppName/permissions. Next: run generate_embed_code and then continue with brand-my-embed or secure-my-embed as needed."
4723
- );
4724
- }
4725
- if (step === "complete") {
4726
- return emit(
4727
- "DONE",
4728
- step,
4729
- state,
4730
- "Setup complete. Recommended before handoff: run runtime_readiness. Then use brand-my-embed, secure-my-embed, setup-filters, or localize-embed prompts for further customization."
4731
- );
4732
- }
4733
- return toolError(`Unknown step: ${step}`);
4734
- } catch (err) {
4735
- if (err instanceof DatabrainApiError) return err.toToolResponse();
4736
- return toolError(`Unexpected error: ${err.message}`);
4737
- }
4738
- }
4739
- );
4740
- }
4741
-
4742
3592
  // src/tools/query/ask-question.ts
4743
- import { z as z21 } from "zod";
3593
+ import { z as z18 } from "zod";
4744
3594
  var MAX_DISPLAY_ROWS = 50;
4745
3595
  function formatDataTable(data, columns) {
4746
3596
  if (!Array.isArray(data) || data.length === 0) return "_No data returned._";
@@ -4770,16 +3620,16 @@ function registerAskQuestion(server, client) {
4770
3620
  "ask_question",
4771
3621
  "Ask a natural language question \u2014 converts to SQL, returns data + chart suggestion. Requires datamartName with a populated semantic layer.",
4772
3622
  {
4773
- question: z21.string().describe(
3623
+ question: z18.string().describe(
4774
3624
  'The natural language question to ask (e.g. "What was total revenue last month?")'
4775
3625
  ),
4776
- datamartName: z21.string().optional().describe(
3626
+ datamartName: z18.string().optional().describe(
4777
3627
  "Name of the datamart to query. Use `list_datamarts` to find available names. REQUIRED unless exactly one datamart exists (auto-resolved). Do NOT pass a workspace name here."
4778
3628
  ),
4779
- sessionId: z21.string().optional().describe(
3629
+ sessionId: z18.string().optional().describe(
4780
3630
  "Session ID from a previous ask_question response, for follow-up questions."
4781
3631
  ),
4782
- timezone: z21.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
3632
+ timezone: z18.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
4783
3633
  },
4784
3634
  WRITE_CREATE,
4785
3635
  async (params) => {
@@ -4944,14 +3794,14 @@ To fix this:
4944
3794
  }
4945
3795
 
4946
3796
  // src/tools/query/get-semantic-layer.ts
4947
- import { z as z22 } from "zod";
3797
+ import { z as z19 } from "zod";
4948
3798
  function registerGetSemanticLayer(server, client) {
4949
3799
  const services = createServiceBundle(client);
4950
3800
  server.tool(
4951
3801
  "get_semantic_layer",
4952
3802
  "Get semantic layer metadata for a datamart: table/column descriptions, synonyms, completion score. Omit datamartName to auto-resolve.",
4953
3803
  {
4954
- datamartName: z22.string().optional().describe(
3804
+ datamartName: z19.string().optional().describe(
4955
3805
  "Name of the datamart to inspect. Omit to list available datamarts."
4956
3806
  )
4957
3807
  },
@@ -5061,15 +3911,15 @@ ${result.feedback}`);
5061
3911
  }
5062
3912
 
5063
3913
  // src/tools/query/update-semantic-layer.ts
5064
- import { z as z23 } from "zod";
5065
- var columnSchema = z23.object({
5066
- name: z23.string().describe("Column name (must exist in the datamart)"),
5067
- description: z23.string().optional().describe("Business-friendly description of what this column represents"),
5068
- synonyms: z23.array(z23.string()).optional().describe(
3914
+ import { z as z20 } from "zod";
3915
+ var columnSchema = z20.object({
3916
+ name: z20.string().describe("Column name (must exist in the datamart)"),
3917
+ description: z20.string().optional().describe("Business-friendly description of what this column represents"),
3918
+ synonyms: z20.array(z20.string()).optional().describe(
5069
3919
  'Alternative names users might use for this column (e.g. ["revenue", "sales amount"])'
5070
3920
  ),
5071
- miscellaneousInfo: z23.string().optional().describe("Additional context such as valid values, units, or caveats"),
5072
- columnType: z23.enum([
3921
+ miscellaneousInfo: z20.string().optional().describe("Additional context such as valid values, units, or caveats"),
3922
+ columnType: z20.enum([
5073
3923
  "String",
5074
3924
  "Long String",
5075
3925
  "String (Custom)",
@@ -5082,16 +3932,16 @@ var columnSchema = z23.object({
5082
3932
  "JSON"
5083
3933
  ]).nullable().optional().describe('Semantic column type. Use "Identifier" for IDs/foreign keys, "Number" for numeric values, "String" for text. Omit for date columns.')
5084
3934
  });
5085
- var tableSchema = z23.object({
5086
- name: z23.string().describe("Table name (must exist in the datamart)"),
5087
- description: z23.string().optional().describe(
3935
+ var tableSchema = z20.object({
3936
+ name: z20.string().describe("Table name (must exist in the datamart)"),
3937
+ description: z20.string().optional().describe(
5088
3938
  "Business-friendly description of what this table represents"
5089
3939
  ),
5090
- synonyms: z23.array(z23.string()).optional().describe("Alternative names for this table"),
5091
- miscellaneousInfo: z23.string().optional().describe(
3940
+ synonyms: z20.array(z20.string()).optional().describe("Alternative names for this table"),
3941
+ miscellaneousInfo: z20.string().optional().describe(
5092
3942
  "Additional context such as how the table relates to other tables"
5093
3943
  ),
5094
- columns: z23.array(columnSchema).optional().describe("Column-level semantic definitions to update")
3944
+ columns: z20.array(columnSchema).optional().describe("Column-level semantic definitions to update")
5095
3945
  });
5096
3946
  function registerUpdateSemanticLayer(server, client) {
5097
3947
  const services = createServiceBundle(client);
@@ -5099,13 +3949,13 @@ function registerUpdateSemanticLayer(server, client) {
5099
3949
  "update_semantic_layer",
5100
3950
  "Populate or update the semantic layer for a datamart. Add table/column descriptions, synonyms, context. Required before ask_question works. Auto-generate descriptions from column names when empty.",
5101
3951
  {
5102
- datamartName: z23.string().optional().describe(
3952
+ datamartName: z20.string().optional().describe(
5103
3953
  "Name of the datamart to update. Omit to list available datamarts."
5104
3954
  ),
5105
- tables: z23.array(tableSchema).optional().describe(
3955
+ tables: z20.array(tableSchema).optional().describe(
5106
3956
  "Table and column definitions to update. Only include tables/columns you want to change."
5107
3957
  ),
5108
- feedback: z23.string().optional().describe(
3958
+ feedback: z20.string().optional().describe(
5109
3959
  'General feedback or context about the datamart (e.g. "This datamart contains e-commerce data for the US market").'
5110
3960
  )
5111
3961
  },
@@ -5179,12 +4029,9 @@ function registerAllTools(server, client) {
5179
4029
  registerGetEmbedDetails(server, client);
5180
4030
  registerUpdateEmbed(server, client);
5181
4031
  registerCustomizeTheme(server, client);
5182
- registerConfigureEmbed(server, client);
5183
- registerDiagnoseEmbedHealth(server, client);
5184
4032
  registerGenerateGuestToken(server, client);
5185
4033
  registerGenerateEmbedCode(server, client);
5186
4034
  registerListDashboards(server, client);
5187
- registerSetupEmbedInteractive(server, client);
5188
4035
  registerAskQuestion(server, client);
5189
4036
  registerGetSemanticLayer(server, client);
5190
4037
  registerUpdateSemanticLayer(server, client);
@@ -7637,7 +6484,7 @@ tenant. For example, filter by department or user role within an organization.
7637
6484
  // src/resources/content/use-case-guides.ts
7638
6485
  var useCaseGuidesContent = `# Databrain Use Case Guides
7639
6486
 
7640
- Databrain supports 4 core use cases for embedding analytics. Each maps to a specific MCP prompt and tool sequence.
6487
+ Databrain supports 3 core use cases for embedding analytics. Each maps to a specific MCP prompt and tool sequence.
7641
6488
 
7642
6489
  ---
7643
6490
 
@@ -7654,15 +6501,7 @@ Databrain supports 4 core use cases for embedding analytics. Each maps to a spec
7654
6501
  **Tool sequence:** \`list_data_apps\` \u2192 \`list_datamarts\` \u2192 \`create_embed\` with \`clientId\` (per client) \u2192 \`generate_guest_token\` \u2192 \`generate_embed_code\`
7655
6502
  **Key concepts:** \`clientId\` for tenant isolation, \`templateDashboardId\` for cloning, \`tableTenancySettings\` for row-level security.
7656
6503
 
7657
- ## Use Case 3: Enable Metric Creation
7658
- **When to use:** You want end-users to build their own metrics/charts on embedded dashboards.
7659
- **Prompt:** \`enable-metric-creation\`
7660
- **Two modes:**
7661
- - **DRAG_DROP:** Visual metric builder \u2014 users drag columns, pick chart types. No AI needed.
7662
- - **CHAT:** AI-powered natural language \u2014 users type questions. Requires LLM connector in Databrain admin.
7663
- **Tool sequence:** \`list_embeds\` \u2192 \`update_embed\` (set accessSettings) \u2192 \`configure_embed\` \u2192 \`generate_guest_token\` (with permissions)
7664
-
7665
- ## Use Case 4: Explore and Query Data
6504
+ ## Use Case 3: Explore and Query Data
7666
6505
  **When to use:** You want to explore available data and ask questions using natural language.
7667
6506
  **Prompt:** \`query-data\` (or \`populate-semantic-layer\` if starting from an empty semantic layer)
7668
6507
  **Path:** Exploration + querying (datasources, datamarts, and workspaces are created in the Databrain UI). The semantic layer is populated via MCP tools.
@@ -7673,17 +6512,17 @@ Databrain supports 4 core use cases for embedding analytics. Each maps to a spec
7673
6512
 
7674
6513
  ## Customization (After Any Setup)
7675
6514
 
7676
- Once an embed exists, use these prompts to customize:
6515
+ Once an embed exists, use the \`brand-my-embed\` prompt for theming. For everything else, describe what you need and the assistant uses tools directly:
7677
6516
 
7678
- | Prompt | Purpose |
7679
- |--------|---------|
7680
- | \`brand-my-embed\` | Colors, fonts, chart styles, theme presets |
7681
- | \`setup-filters\` | String, date, number, SQL, and server-side filters |
7682
- | \`localize-embed\` | Multi-language support and locale settings |
7683
- | \`secure-my-embed\` | Permissions, access controls, role-based tokens |
7684
- | \`manage-embeds\` | List, compare, and manage existing embeds |
7685
- | \`query-data\` | Natural language querying and semantic layer management |
7686
- | \`populate-semantic-layer\` | Auto-generate table/column descriptions for AI features |
6517
+ | Task | How |
6518
+ |------|-----|
6519
+ | Colors, fonts, chart styles | Prompt: \`brand-my-embed\` |
6520
+ | Filters (string, date, SQL) | \`update_embed\` with \`options.globalFilterOptions\` via body passthrough |
6521
+ | Localization / translations | \`update_embed\` with \`componentAttributes\` via body passthrough |
6522
+ | Permissions / access controls | \`update_embed\` + \`generate_guest_token\` with permissions |
6523
+ | Metric creation modes | \`update_embed\` accessSettings + \`generate_guest_token\` |
6524
+ | Manage / audit embeds | \`list_embeds\` \u2192 \`get_embed_details\` |
6525
+ | Semantic layer | Prompt: \`populate-semantic-layer\` or \`get_semantic_layer\` / \`update_semantic_layer\` |
7687
6526
 
7688
6527
  ---
7689
6528
 
@@ -7693,9 +6532,8 @@ Once an embed exists, use these prompts to customize:
7693
6532
  |----------|-------------------|
7694
6533
  | Do you have dashboards in Databrain UI? | Yes \u2192 Use Case 1 |
7695
6534
  | Does each client need their own dashboard? | Yes \u2192 Use Case 2 |
7696
- | Do you want end-users to create metrics? | Yes \u2192 Use Case 3 |
7697
- | Want to explore or query your data? | Yes \u2192 Use Case 4 |
7698
- | Just need to brand/filter/secure? | Use customization prompts |
6535
+ | Want to explore or query your data? | Yes \u2192 Use Case 3 |
6536
+ | Just need to brand/customize? | Use \`brand-my-embed\` prompt or describe what you need |
7699
6537
  `;
7700
6538
 
7701
6539
  // src/resources/content/permission-schema.ts
@@ -7818,7 +6656,7 @@ var resources = [
7818
6656
  {
7819
6657
  uri: "databrain://use-case-guides",
7820
6658
  name: "Use Case Guides",
7821
- description: "Summary of 4 core use cases with tool sequences, prompt names, and decision guide",
6659
+ description: "Summary of 3 core use cases with tool sequences, prompt names, and decision guide",
7822
6660
  content: useCaseGuidesContent
7823
6661
  },
7824
6662
  {
@@ -7883,7 +6721,7 @@ If creating new: resolve datamartName by calling \`list_datamarts\`, then call \
7883
6721
  - \`accessSettings: { datamartName: "<resolved>" }\` \u2014 only datamartName is needed, the tool fills in all other required defaults automatically. Do NOT add isAllow* fields yourself.
7884
6722
 
7885
6723
  **Step 6 \u2014 Verify Embed**
7886
- Call \`get_embed_details\` and \`diagnose_embed_health\` to verify the embed is properly configured.
6724
+ Call \`get_embed_details\` to verify the embed is properly configured.
7887
6725
  Show the user the embed ID and status.
7888
6726
 
7889
6727
  **Step 7 \u2014 Generate Guest Token**
@@ -7900,7 +6738,7 @@ Only after they answer, call \`generate_embed_code\` with the \`embedId\`.
7900
6738
  - Prefer existing embeds when the user already has one for their target dashboard.
7901
6739
  - NEVER ask for service or API tokens \u2014 both are handled automatically.
7902
6740
  - \`workspaceName\` comes from dashboard data, not from the data app name.
7903
- - After setup, suggest: \`brand-my-embed\`, \`setup-filters\`, \`localize-embed\`, \`secure-my-embed\`.`;
6741
+ - After setup, suggest: \`brand-my-embed\` for theming. For filters, localization, or permissions, just describe what you need and use \`update_embed\` / \`generate_guest_token\` directly.`;
7904
6742
 
7905
6743
  // src/prompts/embed-blank-dashboard.ts
7906
6744
  var embedBlankDashboardPrompt = `You are guiding a multi-tenant setup where each client gets their own blank (or template-based) dashboard. This is ideal for SaaS platforms where each customer sees their own analytics.
@@ -7964,83 +6802,7 @@ The frontend code is the same for all clients \u2014 the \`clientId\` in the gue
7964
6802
  - STOP after each step. Present results and wait for user input before proceeding.
7965
6803
  - When calling \`create_embed\` with \`clientId\`, only pass \`accessSettings: { datamartName }\` \u2014 the tool fills in all required defaults automatically. Do NOT enumerate isAllow* fields.
7966
6804
  - \`clientId\` must be consistent between embed creation and guest token generation.
7967
- - After setup, suggest: \`brand-my-embed\`, \`setup-filters\`, \`enable-metric-creation\`, \`secure-my-embed\`.`;
7968
-
7969
- // src/prompts/enable-metric-creation.ts
7970
- var enableMetricCreationPrompt = `You are helping the user enable end-user metric creation on their embedded dashboards. Databrain supports two modes: DRAG_DROP (visual builder) and CHAT (AI-powered natural language).
7971
-
7972
- **CRITICAL: This is an interactive guide. You MUST stop and ask the user at each decision point before proceeding. Never assume answers.**
7973
-
7974
- **Two Metric Creation Modes:**
7975
- - **DRAG_DROP**: Visual metric builder where users drag columns, choose chart types, and configure metrics. No AI needed.
7976
- - **CHAT**: AI-powered natural language metric creation. Users type questions like "Show me revenue by month" and Databrain generates the metric. Requires an LLM connector configured in the Databrain admin panel.
7977
-
7978
- **Step 1 \u2014 Identify the Embed**
7979
- Call \`list_embeds\` to show available embeds.
7980
- STOP and ASK the user: "Which embed do you want to enable metric creation on?"
7981
- Only after they answer, call \`get_embed_details\` to see current access settings.
7982
-
7983
- **Step 2 \u2014 Choose the Mode**
7984
- STOP and ASK the user:
7985
- - "Which metric creation mode do you want?"
7986
- - **DRAG_DROP**: Visual builder, no AI dependency
7987
- - **CHAT**: AI-powered, requires LLM connector setup in Databrain admin
7988
- Do NOT proceed until the user picks a mode.
7989
-
7990
- If they choose CHAT, tell them: "CHAT mode requires an LLM connector configured in the Databrain admin panel (Settings > AI Configuration). Have you set this up?"
7991
-
7992
- **Step 3 \u2014 Update Embed Access Settings**
7993
- Call \`update_embed\` with the embed ID and updated \`accessSettings\`:
7994
-
7995
- For DRAG_DROP mode:
7996
- \`\`\`json
7997
- {
7998
- "accessSettings": {
7999
- "isAllowMetricCreation": true,
8000
- "metricCreationMode": "DRAG_DROP"
8001
- }
8002
- }
8003
- \`\`\`
8004
-
8005
- For CHAT mode:
8006
- \`\`\`json
8007
- {
8008
- "accessSettings": {
8009
- "isAllowMetricCreation": true,
8010
- "metricCreationMode": "CHAT"
8011
- }
8012
- }
8013
- \`\`\`
8014
-
8015
- **Step 4 \u2014 Configure Embed Options**
8016
- Call \`configure_embed\` to ensure the UI supports metric creation:
8017
- - \`disableMetricCreation\`: false
8018
- - \`hideCreateMetricButton\`: false
8019
- - \`hideChartSettings\`: false (so users can customize their created metrics)
8020
-
8021
- **Step 5 \u2014 Set Guest Token Permissions**
8022
- Remind the user: guest token \`permissions\` override embed \`accessSettings\` at runtime. When generating guest tokens via \`generate_guest_token\`, include:
8023
- \`\`\`json
8024
- {
8025
- "permissions": {
8026
- "isDisableMetricCreation": false
8027
- }
8028
- }
8029
- \`\`\`
8030
-
8031
- **Step 6 \u2014 Verify and Test**
8032
- Call \`get_embed_details\` to verify the settings were applied correctly.
8033
- Tell the user to refresh their embed and look for the "+ Create Metric" button.
8034
-
8035
- If using CHAT mode, remind the user:
8036
- - An LLM connector must be configured in Databrain admin (Settings > AI Configuration)
8037
- - The datamart should have a populated semantic layer for best AI results (use \`get_semantic_layer\` to check, and \`update_semantic_layer\` or the \`populate-semantic-layer\` prompt to populate it)
8038
-
8039
- **Important rules:**
8040
- - STOP and ASK at each decision point. Never skip ahead.
8041
- - Guest token \`permissions\` take precedence over embed \`accessSettings\` \u2014 both must allow metric creation.
8042
- - CHAT mode requires external LLM connector setup (not available via MCP).
8043
- - After enabling, suggest: \`secure-my-embed\` to fine-tune which users get metric creation access.`;
6805
+ - After setup, suggest: \`brand-my-embed\` for theming. For filters, metric creation, or permissions, just describe what you need and use \`update_embed\` / \`generate_guest_token\` directly.`;
8044
6806
 
8045
6807
  // src/prompts/brand-my-embed.ts
8046
6808
  var brandMyEmbedPrompt = `You are helping the user brand and visually customize their embedded Databrain dashboard. Follow these steps.
@@ -8084,7 +6846,7 @@ Use \`customize_embed_theme\` with the chart appearance params to fine-tune char
8084
6846
  Ask if they want to customize tooltip fonts, axis styling, or label positions.
8085
6847
 
8086
6848
  **Step 5 \u2014 Configure Display Options**
8087
- Use \`configure_embed\` to clean up the UI:
6849
+ Use \`update_embed\` (body passthrough) to clean up the UI:
8088
6850
  - Ask if they want to hide the dashboard name (\`hideDashboardName\`)
8089
6851
  - Ask about card shadows and borders
8090
6852
  - Ask about download/export options they want to show
@@ -8117,327 +6879,6 @@ Iterate by calling the relevant tools again with adjusted values. Remember: all
8117
6879
 
8118
6880
  **Reference**: Use \`databrain://theme-reference\` for the full theme schema and \`databrain://web-component-reference\` for all component attributes.`;
8119
6881
 
8120
- // src/prompts/localize-embed.ts
8121
- var localizeEmbedPrompt = `You are helping the user set up multi-language support for their embedded Databrain dashboard. Follow these steps.
8122
-
8123
- **Step 1 \u2014 Identify the Embed and Target Languages**
8124
- Call \`list_embeds\` and ask the user which embed to localize. Then ask:
8125
- - Which languages do they need? (Supported: English, French, Spanish, German, Italian, Portuguese, Chinese, Japanese, Korean, Hindi, Arabic, Russian)
8126
- - What is the default/primary language?
8127
-
8128
- **Step 2 \u2014 Set the Primary Language**
8129
- Use \`configure_embed\` with the \`language\` parameter set to their primary language code (e.g. "fr" for French).
8130
-
8131
- **Step 3 \u2014 Gather Translations**
8132
- Call \`get_embed_details\` to understand what text is in the dashboard. Then help the user build translations:
8133
-
8134
- Ask the user to list the key text elements in their dashboard:
8135
- - Dashboard title
8136
- - Metric titles and descriptions
8137
- - Filter labels
8138
- - Any footnotes or annotations
8139
-
8140
- For each text element, ask for translations in their target languages. Build translation entries like:
8141
- \`\`\`
8142
- { key: "Revenue", translations: { fr: "Revenus", es: "Ingresos", de: "Einnahmen" } }
8143
- \`\`\`
8144
-
8145
- **Important rules for translation keys:**
8146
- - Keys must match the dashboard text EXACTLY (case-sensitive)
8147
- - Define longer keys before shorter ones to avoid partial matches
8148
- - If a translation is missing for a language, the original key text is shown as fallback
8149
-
8150
- **Step 4 \u2014 Apply Translations**
8151
- Use \`configure_embed\` with the \`translations\` array containing all entries.
8152
-
8153
- **Step 5 \u2014 Configure Calendar (if needed)**
8154
- Ask if they need a different calendar system:
8155
- - \`"default"\` \u2014 standard Gregorian calendar
8156
- - \`"ind"\` \u2014 Indian calendar format
8157
-
8158
- Set via \`calendarType\` in \`configure_embed\` (\`language\` and \`calendarType\` params).
8159
-
8160
- **Step 6 \u2014 Set Custom Messages**
8161
- Ask if they want to translate system messages:
8162
- - Token expiry message (shown when session expires)
8163
- - Token absent message (shown when no auth token)
8164
-
8165
- Use \`configure_embed\` with \`tokenExpiryMessage\` and \`tokenAbsentMessage\` in their primary language.
8166
-
8167
- **Step 7 \u2014 Test**
8168
- Tell the user to refresh the embed and verify translations appear correctly. If any text isn't translating, double-check that the key matches exactly.
8169
-
8170
- Remind the user: translations can also be applied per-user at the web component level using the \`translation-dictionary\` attribute, which is useful for dynamically switching languages based on user preferences.
8171
-
8172
- **Tip:** The \`language\`, \`calendarType\`, \`hideDashboardFilters\`, and filter settings are all available in the same \`configure_embed\` tool.
8173
-
8174
- **Important:** After changing language or translation settings, existing guest tokens still carry the old configuration. Remind the user to **regenerate guest tokens** (or reload their app) for changes to take effect.`;
8175
-
8176
- // src/prompts/secure-my-embed.ts
8177
- var secureMyEmbedPrompt = `You are helping the user secure and configure permissions for their embedded Databrain dashboard. Follow these steps.
8178
-
8179
- **Step 1 \u2014 Understand User Roles**
8180
- Ask the user about their end-user roles. Common patterns:
8181
- - **Viewer** \u2014 can see data, maybe filter, but can't modify anything
8182
- - **Analyst** \u2014 can export data, drill down, use filters
8183
- - **Builder/Power User** \u2014 can create/edit metrics, change layouts
8184
- - **Admin** \u2014 full access including AI features
8185
-
8186
- Ask: "What roles do your end-users have? What should each role be allowed to do?"
8187
-
8188
- **Step 2 \u2014 Lock Down the Embed Defaults**
8189
- Use \`configure_embed\` to set restrictive defaults (principle of least privilege):
8190
-
8191
- \`\`\`
8192
- configure_embed(embedId,
8193
- disableMetricCreation: true,
8194
- disableMetricUpdation: true,
8195
- disableMetricDeletion: true,
8196
- disableManageMetrics: true,
8197
- disableLayoutCustomization: true,
8198
- disableSaveLayout: true,
8199
- disableUnderlyingData: true,
8200
- disableScheduleEmailReports: true,
8201
- enableDownloadCsv: false,
8202
- enableDownloadAllPdf: false)
8203
- \`\`\`
8204
-
8205
- Explain: "We start with everything locked down, then use guest token permissions to grant access per role."
8206
-
8207
- **Step 3 \u2014 Create Role-Based Guest Token Examples**
8208
- Show the user how to generate tokens for each role:
8209
-
8210
- **Viewer (default \u2014 no overrides needed):**
8211
- \`\`\`
8212
- generate_guest_token(clientId, dataAppName)
8213
- \`\`\`
8214
-
8215
- **Analyst (can export and drill down):**
8216
- \`\`\`
8217
- generate_guest_token(clientId, dataAppName, {
8218
- permissions: {
8219
- isEnableDownloadMetrics: true,
8220
- isEnableUnderlyingData: true
8221
- }
8222
- })
8223
- \`\`\`
8224
-
8225
- **Builder (can create and edit):**
8226
- \`\`\`
8227
- generate_guest_token(clientId, dataAppName, {
8228
- permissions: {
8229
- isEnableManageMetrics: true,
8230
- isEnableMetricUpdation: true,
8231
- isEnableCustomizeLayout: true
8232
- }
8233
- })
8234
- \`\`\`
8235
-
8236
- **Admin (full access with AI):**
8237
- \`\`\`
8238
- generate_guest_token(clientId, dataAppName, {
8239
- permissions: {
8240
- isEnableManageMetrics: true,
8241
- isEnableMetricUpdation: true,
8242
- isEnableCustomizeLayout: true,
8243
- isEnableDownloadMetrics: true,
8244
- isEnableUnderlyingData: true
8245
- }
8246
- })
8247
- \`\`\`
8248
-
8249
- **Step 4 \u2014 Multi-Tenancy Check**
8250
- Ask: "Do different customers/organizations see different data?"
8251
-
8252
- If yes:
8253
- - Explain that \`clientId\` in the guest token controls row-level security
8254
- - The \`clientId\` must match values in the datamart's client column
8255
- - Suggest using \`params.appFilters\` / \`params.dashboardAppFilters\` in \`generate_guest_token\` for server-side row-level filtering
8256
-
8257
- **Step 5 \u2014 Security Checklist**
8258
- Walk through this checklist with the user:
8259
-
8260
- 1. \u2705 API token is stored server-side only (never in frontend code)
8261
- 2. \u2705 Guest tokens are generated server-side per request
8262
- 3. \u2705 Guest tokens have appropriate expiry time
8263
- 4. \u2705 Embed defaults are restrictive (locked down in Step 2)
8264
- 5. \u2705 Role-based permissions are granted via guest token overrides
8265
- 6. \u2705 Multi-tenancy is configured if serving multiple customers
8266
- 7. \u2705 Download restrictions are set (\`disableDownloadDataNoFilters\` if needed)
8267
-
8268
- **Step 6 \u2014 Generate Secure Frontend Code**
8269
- Call \`generate_embed_code\` for their framework. Emphasize that the generated code includes a backend route for token generation \u2014 this is the secure pattern.
8270
-
8271
- When reviewing runtime behavior (for example missing \`+ Create Metric\`), verify contract shape explicitly:
8272
- - Guest token request body must send \`permissions\` at top level (not nested inside \`params\`)
8273
- - Include \`dataAppName\` at top level
8274
- - For metric creation flows, ensure \`permissions.isDisableMetricCreation=false\` and \`permissions.isEnableManageMetrics=true\`
8275
- - Confirm embed options do not set \`disableMetricCreation=true\`
8276
- - In browser network response for \`/api/v2/dashboard\`, confirm \`isAllowedToCreateMetrics=true\` and \`metricCreationMode=DRAG_DROP\`
8277
-
8278
- **Important:** After changing embed permissions or access settings, existing guest tokens still carry the old configuration. Remind the user to **regenerate guest tokens** (or reload their app) for changes to take effect.
8279
-
8280
- **Reference**: Use \`databrain://self-serve-reference\` for all permission flags and \`databrain://multi-tenancy-guide\` for row-level security details.`;
8281
-
8282
- // src/prompts/setup-filters.ts
8283
- var setupFiltersPrompt = `You are helping the user configure dashboard filters for their embedded Databrain dashboard. Follow these steps.
8284
-
8285
- **Step 1 \u2014 Understand the Data**
8286
- Call \`list_embeds\` and ask which embed to configure. Then call \`get_embed_details\` to see the current filter configuration.
8287
-
8288
- **Step 2 \u2014 Identify Filter Needs**
8289
- Ask the user:
8290
- - "What filters do your end-users need?" (e.g. date range, region, product category, status)
8291
- - "Should any filters have default values?"
8292
- - "Are there filters you want to hide from end-users?" (e.g. internal IDs)
8293
- - "Do any filters have very large option lists?" (100+ options \u2014 these benefit from SQL-based filters)
8294
-
8295
- **Step 3 \u2014 Configure String Filters**
8296
- For text/category filters, use \`configure_embed\` with \`stringFilters\`:
8297
-
8298
- \`\`\`
8299
- stringFilters: [{
8300
- filterName: "Region",
8301
- options: [
8302
- { value: "US", label: "United States" },
8303
- { value: "EU", label: "Europe" },
8304
- { value: "APAC", label: "Asia Pacific" }
8305
- ],
8306
- defaultOption: "US"
8307
- }]
8308
- \`\`\`
8309
-
8310
- For multi-select defaults, use an array: \`defaultOption: ["US", "EU"]\`
8311
-
8312
- **Step 4 \u2014 Configure Date Filters**
8313
- For date range filters, use \`dateFilters\`:
8314
-
8315
- \`\`\`
8316
- dateFilters: [{
8317
- filterName: "Date Range",
8318
- startDate: "2024-01-01",
8319
- endDate: "2024-12-31",
8320
- datePresetOptions: [
8321
- { label: "Last 30 Days", type: "Last", interval: "Days" },
8322
- { label: "This Quarter", type: "This", interval: "Quarter" },
8323
- { label: "This Year", type: "This", interval: "Year" }
8324
- ]
8325
- }]
8326
- \`\`\`
8327
-
8328
- **Step 5 \u2014 Configure Number Filters**
8329
- For numeric range filters, use \`numberFilters\`:
8330
-
8331
- \`\`\`
8332
- numberFilters: [{
8333
- filterName: "Revenue",
8334
- min: 0,
8335
- max: 1000000
8336
- }]
8337
- \`\`\`
8338
-
8339
- **Step 6 \u2014 SQL-Based Filters (for large datasets)**
8340
- If a filter has 100+ options (e.g. company names, product SKUs), use \`sqlFilters\`:
8341
-
8342
- \`\`\`
8343
- sqlFilters: [{
8344
- filterName: "Company",
8345
- sql: "SELECT DISTINCT \\"company_name\\" FROM \\"companies\\" ORDER BY \\"company_name\\"",
8346
- columnName: "company_name"
8347
- }]
8348
- \`\`\`
8349
-
8350
- Explain: "SQL filters dynamically fetch options from the database, so they're always up-to-date and don't bloat the embed configuration."
8351
-
8352
- **Step 7 \u2014 Hide Unnecessary Filters**
8353
- If the user has filters that should be hidden (e.g. internal IDs, tenant columns already handled by clientId):
8354
-
8355
- \`\`\`
8356
- hideDashboardFilters: ["internal_id", "tenant_id"]
8357
- \`\`\`
8358
-
8359
- **Step 8 \u2014 Configure Date Picker Options**
8360
- Ask if they want to hide certain date picker presets:
8361
-
8362
- \`\`\`
8363
- hideDatePickerOptions: ["yesterday", "last week"]
8364
- \`\`\`
8365
-
8366
- **Step 9 \u2014 Metric Filter Position**
8367
- Ask: "Do you want metric-level filters inside the card or outside?"
8368
- - \`"inside"\` \u2014 compact, within each metric card (default)
8369
- - \`"outside"\` \u2014 above the card, more prominent
8370
-
8371
- Also ask if they want cross-metric filtering (\`enableMultiMetricFilters\`).
8372
-
8373
- **Step 10 \u2014 Server-Side Filters (via Guest Token)**
8374
- If the user needs filters that end-users cannot remove (e.g. for security):
8375
- - Explain that \`appFilters\` in \`generate_guest_token\` are server-side enforced
8376
- - These are invisible to the end-user and cannot be overridden
8377
- - Perfect for tenant filtering, role-based data access, or compliance requirements
8378
-
8379
- Show an example:
8380
- \`\`\`
8381
- generate_guest_token(clientId, dataAppName, {
8382
- params: {
8383
- appFilters: [{
8384
- metricId: "metric_123",
8385
- values: {
8386
- region: ["US", "CA"]
8387
- }
8388
- }]
8389
- }
8390
- })
8391
- \`\`\`
8392
-
8393
- **Important:** After changing filter settings, existing guest tokens still carry the old configuration. Remind the user to **regenerate guest tokens** (or reload their app) for changes to take effect.
8394
-
8395
- **Reference**: Use \`databrain://filter-reference\` for all filter types, operators, and combination rules.`;
8396
-
8397
- // src/prompts/manage-embeds.ts
8398
- var manageEmbedsPrompt = `You are helping the user manage Databrain embed configurations \u2014 list, compare, and review settings. Follow these steps based on what the user needs.
8399
-
8400
- **Step 1 \u2014 Understand the Task**
8401
- Ask the user what they want to do:
8402
- 1. **List and compare embeds** \u2014 see all embeds and their configurations
8403
- 2. **Review embed settings** \u2014 audit theme, permissions, and options
8404
-
8405
- **For Listing and Comparing Embeds:**
8406
- 1. Call \`list_embeds\` to show all embed configurations
8407
- 2. For detailed comparison, call \`get_embed_details\` on each embed the user wants to compare
8408
- 3. Present a comparison table showing:
8409
- - Embed name and ID
8410
- - Linked dashboard
8411
- - Theme (preset or custom)
8412
- - Key permissions (metric creation, downloads, AI pilot)
8413
- - Multi-tenancy settings
8414
-
8415
- **For Reviewing Embed Settings:**
8416
- 1. Call \`get_embed_details\` for the embed
8417
- 2. Present a clear summary organized by category:
8418
-
8419
- **Theme:**
8420
- - Current preset or custom theme values
8421
- - Suggest \`customize_embed_theme\` if they want changes
8422
-
8423
- **Permissions / Access Settings:**
8424
- - What's enabled vs disabled
8425
- - Suggest \`configure_embed\` for changes
8426
-
8427
- **Filters:**
8428
- - Current filter configuration
8429
- - Suggest \`configure_embed\` with filter params for changes
8430
-
8431
- **Internationalization:**
8432
- - Current language and translations
8433
- - Suggest \`configure_embed\` with \`language\` and \`translations\` params for changes
8434
-
8435
- 3. Ask: "Would you like to modify any of these settings?"
8436
-
8437
- **Tips:**
8438
- - Embed IDs are permanent \u2014 even if you rename an embed, the ID stays the same
8439
- - After any embed setting changes, existing guest tokens still carry the old configuration \u2014 **regenerate guest tokens** for changes to take effect`;
8440
-
8441
6882
  // src/prompts/query-data.ts
8442
6883
  var queryDataPrompt = `You are helping the user query their data using natural language. Databrain's AI converts questions into SQL, runs them against the database, and returns results with chart type suggestions.
8443
6884
 
@@ -8606,36 +7047,11 @@ var prompts = [
8606
7047
  description: "Multi-tenant blank dashboard setup: each client gets their own dashboard copy via create_embed with clientId",
8607
7048
  content: embedBlankDashboardPrompt
8608
7049
  },
8609
- {
8610
- name: "enable-metric-creation",
8611
- description: "Enable end-user metric creation on embeds: DRAG_DROP (visual) or CHAT (AI-powered) modes",
8612
- content: enableMetricCreationPrompt
8613
- },
8614
7050
  {
8615
7051
  name: "brand-my-embed",
8616
7052
  description: "Customize colors, fonts, chart styles, and component theme to match your brand",
8617
7053
  content: brandMyEmbedPrompt
8618
7054
  },
8619
- {
8620
- name: "localize-embed",
8621
- description: "Set up multi-language support with translations and locale settings",
8622
- content: localizeEmbedPrompt
8623
- },
8624
- {
8625
- name: "secure-my-embed",
8626
- description: "Configure permissions, access controls, and role-based guest tokens",
8627
- content: secureMyEmbedPrompt
8628
- },
8629
- {
8630
- name: "setup-filters",
8631
- description: "Configure dashboard filters: string, date, number, SQL-based, and server-side filters",
8632
- content: setupFiltersPrompt
8633
- },
8634
- {
8635
- name: "manage-embeds",
8636
- description: "List, compare, rename, delete embeds and rotate API tokens",
8637
- content: manageEmbedsPrompt
8638
- },
8639
7055
  {
8640
7056
  name: "query-data",
8641
7057
  description: "Query your data with natural language: ask questions, get results with chart suggestions, manage the semantic layer",
@@ -8676,17 +7092,18 @@ function createServer(config) {
8676
7092
  \u2022 SERVICE TOKEN (DATABRAIN_SERVICE_TOKEN): org-level, pre-configured in MCP server config. Used for: list_datasources, discover_datasource_schema, list_datamarts, list_workspaces, link_datamart_to_workspace, list_data_apps, create_data_app, list_api_tokens, create_api_token, rotate_api_token, list_dashboards (workspace scope), ask_question, get_semantic_layer, update_semantic_layer. You already have this \u2014 never ask the user for it.
8677
7093
  \u2022 API TOKEN (per data app): scoped to one data app, used for embedding operations (list_embeds, get_embed_details, create_embed, update_embed, generate_guest_token) and data-app scoped dashboard listing. Created/rotated via create_api_token or rotate_api_token during setup.
8678
7094
 
8679
- 4 core use cases \u2014 match the user's scenario to the right prompt:
7095
+ 3 core use cases \u2014 match the user's scenario to the right prompt:
8680
7096
  1. Embed existing dashboard \u2192 prompt: embed-existing-dashboard (list_data_apps \u2192 create_api_token \u2192 list_dashboards \u2192 create_embed \u2192 generate_guest_token \u2192 generate_embed_code)
8681
7097
  2. Blank dashboard per client \u2192 prompt: embed-blank-dashboard (list_data_apps \u2192 list_datamarts \u2192 create_embed with clientId per client \u2192 generate_guest_token \u2192 generate_embed_code)
8682
- 3. Enable metric creation \u2192 prompt: enable-metric-creation (list_embeds \u2192 update_embed accessSettings \u2192 configure_embed \u2192 generate_guest_token with permissions)
8683
- 4. Explore & query data \u2192 prompt: query-data (list_datamarts \u2192 get_semantic_layer \u2192 if empty, auto-generate descriptions with update_semantic_layer or use populate-semantic-layer prompt \u2192 ask_question)
7098
+ 3. Explore & query data \u2192 prompt: query-data (list_datamarts \u2192 get_semantic_layer \u2192 if empty, auto-generate descriptions with update_semantic_layer or use populate-semantic-layer prompt \u2192 ask_question)
8684
7099
 
8685
7100
  Infrastructure CREATION (datasources, datamarts, workspaces) is done in the Databrain UI.
8686
7101
  However, the **semantic layer** is populated and maintained entirely via MCP tools (get_semantic_layer, update_semantic_layer). When the semantic layer is empty, YOU (the assistant) should auto-generate descriptions from table/column names and push them with update_semantic_layer \u2014 do NOT send the user to the UI for this.
8687
7102
 
8688
- After embed setup, customize with: brand-my-embed, setup-filters, localize-embed, secure-my-embed.
7103
+ After embed setup, customize with: brand-my-embed prompt for theming. Use update_embed (body passthrough) for filters, localization, and permissions.
8689
7104
  Use generate_guest_token for frontend auth and generate_embed_code for framework snippets.
7105
+ For advanced embed configuration (UI toggles, filters, i18n), use update_embed with a body passthrough.
7106
+ To diagnose embed issues, use get_embed_details and reason about the response.
8690
7107
  Do NOT ask the user for any tokens \u2014 manage them via MCP tools.
8691
7108
 
8692
7109
  IMPORTANT \u2014 accessSettings defaults:
@@ -8717,7 +7134,7 @@ async function main() {
8717
7134
  const server = createServer(config);
8718
7135
  const transport = new StdioServerTransport();
8719
7136
  await server.connect(transport);
8720
- banner(`v${VERSION} ready (24 tools, 11 resources, 10 prompts)`);
7137
+ banner(`v${VERSION} ready (21 tools, 11 resources, 5 prompts)`);
8721
7138
  } catch (error) {
8722
7139
  logger.error("Failed to start server", error);
8723
7140
  process.exit(1);