@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/README.md +6 -14
- package/dist/index.js +117 -1700
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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\`
|
|
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
|
|
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:
|
|
2930
|
-
dataAppName:
|
|
2931
|
-
body:
|
|
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:
|
|
2935
|
-
allowedEmbeds:
|
|
2936
|
-
rlsSettings:
|
|
2937
|
-
metricId:
|
|
2938
|
-
values:
|
|
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:
|
|
2941
|
-
metricId:
|
|
2942
|
-
values:
|
|
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:
|
|
2945
|
-
dashboardId:
|
|
2946
|
-
values:
|
|
2947
|
-
isShowOnUrl:
|
|
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:
|
|
2950
|
-
userIdentifier:
|
|
2951
|
-
timezone:
|
|
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:
|
|
2955
|
-
allowedEmbeds:
|
|
2956
|
-
rlsSettings:
|
|
2957
|
-
metricId:
|
|
2958
|
-
values:
|
|
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:
|
|
2961
|
-
metricId:
|
|
2962
|
-
values:
|
|
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:
|
|
2965
|
-
dashboardId:
|
|
2966
|
-
values:
|
|
2967
|
-
isShowOnUrl:
|
|
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:
|
|
2970
|
-
isEnableArchiveMetrics:
|
|
2971
|
-
isEnableManageMetrics:
|
|
2972
|
-
isEnableCreateDashboardView:
|
|
2973
|
-
isEnableMetricUpdation:
|
|
2974
|
-
isEnableCustomizeLayout:
|
|
2975
|
-
isEnableUnderlyingData:
|
|
2976
|
-
isEnableDownloadMetrics:
|
|
2977
|
-
isShowSideBar:
|
|
2978
|
-
isShowDashboardName:
|
|
2979
|
-
isDisableMetricCreation:
|
|
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:
|
|
2982
|
-
hideDashboardFilters:
|
|
2983
|
-
userIdentifier:
|
|
2984
|
-
expiryTime:
|
|
2985
|
-
datasourceName:
|
|
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
|
|
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:
|
|
4035
|
-
framework:
|
|
4036
|
-
embedType:
|
|
4037
|
-
options:
|
|
4038
|
-
enableDownloadCsv:
|
|
4039
|
-
enableEmailCsv:
|
|
4040
|
-
enableDownloadAllMetrics:
|
|
4041
|
-
enableDownloadAllPdf:
|
|
4042
|
-
disableFullscreen:
|
|
4043
|
-
isHideTablePreview:
|
|
4044
|
-
isHideChartSettings:
|
|
4045
|
-
isStickyDashboardFilters:
|
|
4046
|
-
enableMultiMetricFilters:
|
|
4047
|
-
themeName:
|
|
4048
|
-
customTheme:
|
|
4049
|
-
optionsIcon:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
3629
|
+
sessionId: z18.string().optional().describe(
|
|
4780
3630
|
"Session ID from a previous ask_question response, for follow-up questions."
|
|
4781
3631
|
),
|
|
4782
|
-
timezone:
|
|
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
|
|
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:
|
|
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
|
|
5065
|
-
var columnSchema =
|
|
5066
|
-
name:
|
|
5067
|
-
description:
|
|
5068
|
-
synonyms:
|
|
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:
|
|
5072
|
-
columnType:
|
|
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 =
|
|
5086
|
-
name:
|
|
5087
|
-
description:
|
|
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:
|
|
5091
|
-
miscellaneousInfo:
|
|
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:
|
|
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:
|
|
3952
|
+
datamartName: z20.string().optional().describe(
|
|
5103
3953
|
"Name of the datamart to update. Omit to list available datamarts."
|
|
5104
3954
|
),
|
|
5105
|
-
tables:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
|
7679
|
-
|
|
7680
|
-
|
|
|
7681
|
-
|
|
|
7682
|
-
| \`
|
|
7683
|
-
|
|
|
7684
|
-
|
|
|
7685
|
-
|
|
|
7686
|
-
| \`populate-semantic-layer\`
|
|
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
|
-
|
|
|
7697
|
-
|
|
|
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
|
|
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\`
|
|
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
|
|
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
|
|
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 \`
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 (
|
|
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);
|