@databrainhq/mcp-server 0.2.3 → 0.2.5
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 +25 -15
- package/dist/index.js +624 -458
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -206,8 +206,8 @@ var DatabrainClient = class {
|
|
|
206
206
|
return cached;
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
-
const token = this.getToken(options.auth);
|
|
210
|
-
if (options.auth !== "none" && !token) {
|
|
209
|
+
const token = options.token ?? this.getToken(options.auth);
|
|
210
|
+
if (options.auth !== "none" && !options.token && !token) {
|
|
211
211
|
if (options.auth === "service") {
|
|
212
212
|
throw new DatabrainApiError(
|
|
213
213
|
401,
|
|
@@ -236,7 +236,12 @@ var DatabrainClient = class {
|
|
|
236
236
|
"X-Databrain-Client": MCP_USER_AGENT
|
|
237
237
|
};
|
|
238
238
|
if (token) {
|
|
239
|
-
|
|
239
|
+
if (options.auth === "none") {
|
|
240
|
+
headers["x-plugin-token"] = token;
|
|
241
|
+
headers["Origin"] = "https://mcp.usedatabrain.com";
|
|
242
|
+
} else {
|
|
243
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
244
|
+
}
|
|
240
245
|
}
|
|
241
246
|
const response = await fetch(url, {
|
|
242
247
|
method: options.method,
|
|
@@ -585,6 +590,68 @@ var QueryApi = class {
|
|
|
585
590
|
}
|
|
586
591
|
};
|
|
587
592
|
|
|
593
|
+
// src/api/widgets-api.ts
|
|
594
|
+
var WidgetsApi = class {
|
|
595
|
+
constructor(client) {
|
|
596
|
+
this.client = client;
|
|
597
|
+
}
|
|
598
|
+
async listWidgets(embedId, clientId) {
|
|
599
|
+
return this.client.request({
|
|
600
|
+
method: "GET",
|
|
601
|
+
path: "/api/v2/data-app/metrics",
|
|
602
|
+
auth: "api",
|
|
603
|
+
query: { embedId, clientId }
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
async createMetric(guestToken, body) {
|
|
607
|
+
return this.client.request({
|
|
608
|
+
method: "POST",
|
|
609
|
+
path: "/api/v2/dashboard/createMetric",
|
|
610
|
+
auth: "none",
|
|
611
|
+
token: guestToken,
|
|
612
|
+
body
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
async updateMetric(guestToken, body) {
|
|
616
|
+
return this.client.request({
|
|
617
|
+
method: "POST",
|
|
618
|
+
path: "/api/v2/dashboard/updateMetric",
|
|
619
|
+
auth: "none",
|
|
620
|
+
token: guestToken,
|
|
621
|
+
body
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
async markArchived(guestToken, body) {
|
|
625
|
+
return this.client.request({
|
|
626
|
+
method: "POST",
|
|
627
|
+
path: "/api/v2/dashboard/markArchived",
|
|
628
|
+
auth: "none",
|
|
629
|
+
token: guestToken,
|
|
630
|
+
body
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
async generateMetric(guestToken, body) {
|
|
634
|
+
return this.client.request({
|
|
635
|
+
method: "POST",
|
|
636
|
+
path: "/api/v2/dashboard/generateMetric",
|
|
637
|
+
auth: "none",
|
|
638
|
+
token: guestToken,
|
|
639
|
+
body,
|
|
640
|
+
timeoutMs: 12e4,
|
|
641
|
+
noRetryOnTimeout: true
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async fetchEmbeddedMetric(guestToken, metricId) {
|
|
645
|
+
return this.client.request({
|
|
646
|
+
method: "GET",
|
|
647
|
+
path: "/api/v2/dashboard/embeddedMetric",
|
|
648
|
+
auth: "none",
|
|
649
|
+
token: guestToken,
|
|
650
|
+
query: { metricId }
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
588
655
|
// src/api/workspaces-api.ts
|
|
589
656
|
var WorkspacesApi = class {
|
|
590
657
|
constructor(client) {
|
|
@@ -863,6 +930,126 @@ var GuestTokenService = class {
|
|
|
863
930
|
}
|
|
864
931
|
};
|
|
865
932
|
|
|
933
|
+
// src/services/widget-service.ts
|
|
934
|
+
var isNonEmptyString3 = (v) => typeof v === "string" && v.trim().length > 0;
|
|
935
|
+
var asRecord2 = (v) => {
|
|
936
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return null;
|
|
937
|
+
return v;
|
|
938
|
+
};
|
|
939
|
+
var WidgetService = class {
|
|
940
|
+
constructor(widgetsApi, embedsApi, dataAppsApi, guestTokenService, discoveryService) {
|
|
941
|
+
this.widgetsApi = widgetsApi;
|
|
942
|
+
this.embedsApi = embedsApi;
|
|
943
|
+
this.dataAppsApi = dataAppsApi;
|
|
944
|
+
this.guestTokenService = guestTokenService;
|
|
945
|
+
this.discoveryService = discoveryService;
|
|
946
|
+
}
|
|
947
|
+
async resolveEmbedContext(embedId) {
|
|
948
|
+
const response = await this.embedsApi.getByEmbedId(embedId);
|
|
949
|
+
const records = Array.isArray(response) ? response : response ? [response] : [];
|
|
950
|
+
const exact = records.find((r) => r && r.embedId === embedId) || records[0];
|
|
951
|
+
if (!exact) {
|
|
952
|
+
throw new Error(`Embed "${embedId}" not found. Run \`list_embeds\` to verify.`);
|
|
953
|
+
}
|
|
954
|
+
const metadata = asRecord2(exact.embedMetadata);
|
|
955
|
+
const externalDashboard = asRecord2(exact.externalDashboard);
|
|
956
|
+
const dataAppId = isNonEmptyString3(exact.dataAppId) ? exact.dataAppId : null;
|
|
957
|
+
const externalDashboardId = isNonEmptyString3(exact.externalDashboardId) && exact.externalDashboardId || (externalDashboard && isNonEmptyString3(externalDashboard.externalDashboardId) ? externalDashboard.externalDashboardId : null) || (metadata && isNonEmptyString3(metadata.externalDashboardId) ? metadata.externalDashboardId : null);
|
|
958
|
+
const dashboardId = isNonEmptyString3(exact.dashboardId) && exact.dashboardId || (metadata && isNonEmptyString3(metadata.dashboardId) ? metadata.dashboardId : null);
|
|
959
|
+
let dataAppName = metadata && isNonEmptyString3(metadata.dataAppName) ? metadata.dataAppName : null;
|
|
960
|
+
if (!dataAppName && dataAppId) {
|
|
961
|
+
try {
|
|
962
|
+
const apps = await this.dataAppsApi.listDataApps();
|
|
963
|
+
const match = apps.find((a) => {
|
|
964
|
+
const n = this.discoveryService.normalizeDataApp(a);
|
|
965
|
+
return n.dataAppId === dataAppId;
|
|
966
|
+
});
|
|
967
|
+
if (match) {
|
|
968
|
+
dataAppName = this.discoveryService.normalizeDataApp(match).name;
|
|
969
|
+
}
|
|
970
|
+
} catch {
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
embedId,
|
|
975
|
+
dataAppId,
|
|
976
|
+
dataAppName,
|
|
977
|
+
externalDashboardId,
|
|
978
|
+
dashboardId
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
async mintGuestToken(clientId, dataAppName, allowedEmbeds) {
|
|
982
|
+
const { token } = await this.guestTokenService.generateToken({
|
|
983
|
+
clientId,
|
|
984
|
+
dataAppName,
|
|
985
|
+
...allowedEmbeds && allowedEmbeds.length > 0 ? { allowedEmbeds } : {},
|
|
986
|
+
permissions: {
|
|
987
|
+
isDisableMetricCreation: false,
|
|
988
|
+
isEnableManageMetrics: true,
|
|
989
|
+
isEnableMetricUpdation: true,
|
|
990
|
+
isEnableArchiveMetrics: true
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
return token;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Build a create body by cloning a template metric. Fetches the full metric
|
|
997
|
+
* via the embeddedMetric endpoint and copies structural fields, then lets the
|
|
998
|
+
* caller override `name`/`metricId`/etc.
|
|
999
|
+
*/
|
|
1000
|
+
async buildCloneBody(guestToken, templateMetricId, overrides, externalDashboardId) {
|
|
1001
|
+
const response = await this.widgetsApi.fetchEmbeddedMetric(
|
|
1002
|
+
guestToken,
|
|
1003
|
+
templateMetricId
|
|
1004
|
+
);
|
|
1005
|
+
const metric = asRecord2(response.externalMetric);
|
|
1006
|
+
if (!metric) {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
`Template metric "${templateMetricId}" not found or could not be loaded. Response keys: ${Object.keys(
|
|
1009
|
+
response || {}
|
|
1010
|
+
).join(", ")}. Error: ${JSON.stringify(response?.error ?? null)}`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
const clone = {
|
|
1014
|
+
// Required identity — caller must override or we derive.
|
|
1015
|
+
name: overrides.name || (isNonEmptyString3(metric.name) ? `${metric.name} (copy)` : "Widget"),
|
|
1016
|
+
metricId: overrides.metricId || `${isNonEmptyString3(metric.metricId) && metric.metricId || "widget"}-${Date.now()}`,
|
|
1017
|
+
// Structural fields copied from template.
|
|
1018
|
+
query: metric.query || void 0,
|
|
1019
|
+
metricQuery: metric.metricQuery || void 0,
|
|
1020
|
+
description: metric.description || "",
|
|
1021
|
+
chartOptions: asRecord2(metric.chartOptions) || {},
|
|
1022
|
+
companyId: metric.companyId || void 0,
|
|
1023
|
+
companyIntegrationId: metric.companyIntegrationId || void 0,
|
|
1024
|
+
integrationName: metric.integrationName || void 0,
|
|
1025
|
+
outputColumns: metric.outputColumns || void 0,
|
|
1026
|
+
inputFields: metric.inputFields,
|
|
1027
|
+
isLive: metric.isLive ?? void 0,
|
|
1028
|
+
timeGrain: metric.timeGrain || void 0,
|
|
1029
|
+
selectedGroupBy: metric.selectedGroupBy,
|
|
1030
|
+
isEnableGroupBy: metric.isEnableGroupBy ?? void 0,
|
|
1031
|
+
datasetMetricSettings: metric.datasetMetricSettings,
|
|
1032
|
+
groupBy: metric.groupBy,
|
|
1033
|
+
drillDownSettings: metric.drillDownSettings,
|
|
1034
|
+
limit: metric.limit ?? void 0,
|
|
1035
|
+
// Dashboard attachment — always for the target embed's dashboard.
|
|
1036
|
+
externalDashboardIds: [{ externalDashboardId }],
|
|
1037
|
+
// Defaults that shouldn't be inherited from the template.
|
|
1038
|
+
isCreatedByClient: false,
|
|
1039
|
+
isPublished: true
|
|
1040
|
+
};
|
|
1041
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1042
|
+
if (value !== void 0) {
|
|
1043
|
+
clone[key] = value;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (!clone.externalDashboardIds || clone.externalDashboardIds.length === 0) {
|
|
1047
|
+
clone.externalDashboardIds = [{ externalDashboardId }];
|
|
1048
|
+
}
|
|
1049
|
+
return clone;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
866
1053
|
// src/services/factory.ts
|
|
867
1054
|
function createServiceBundle(client) {
|
|
868
1055
|
const embedsApi = new EmbedsApi(client);
|
|
@@ -872,7 +1059,10 @@ function createServiceBundle(client) {
|
|
|
872
1059
|
const datasourcesApi = new DatasourcesApi(client);
|
|
873
1060
|
const guestTokenApi = new GuestTokenApi(client);
|
|
874
1061
|
const queryApi = new QueryApi(client);
|
|
1062
|
+
const widgetsApi = new WidgetsApi(client);
|
|
875
1063
|
const workspacesApi = new WorkspacesApi(client);
|
|
1064
|
+
const discoveryService = new DiscoveryService();
|
|
1065
|
+
const guestTokenService = new GuestTokenService(guestTokenApi);
|
|
876
1066
|
return {
|
|
877
1067
|
embedsApi,
|
|
878
1068
|
dashboardsApi,
|
|
@@ -881,9 +1071,17 @@ function createServiceBundle(client) {
|
|
|
881
1071
|
datasourcesApi,
|
|
882
1072
|
workspacesApi,
|
|
883
1073
|
queryApi,
|
|
1074
|
+
widgetsApi,
|
|
884
1075
|
embedService: new EmbedService(embedsApi, datamartsApi),
|
|
885
|
-
discoveryService
|
|
886
|
-
guestTokenService
|
|
1076
|
+
discoveryService,
|
|
1077
|
+
guestTokenService,
|
|
1078
|
+
widgetService: new WidgetService(
|
|
1079
|
+
widgetsApi,
|
|
1080
|
+
embedsApi,
|
|
1081
|
+
dataAppsApi,
|
|
1082
|
+
guestTokenService,
|
|
1083
|
+
discoveryService
|
|
1084
|
+
)
|
|
887
1085
|
};
|
|
888
1086
|
}
|
|
889
1087
|
|
|
@@ -1281,6 +1479,7 @@ function registerListDatasources(server, client) {
|
|
|
1281
1479
|
return toolSuccess(JSON.stringify({
|
|
1282
1480
|
count: active.length,
|
|
1283
1481
|
datasources: active.map((ds) => ({
|
|
1482
|
+
id: ds.id,
|
|
1284
1483
|
name: ds.name,
|
|
1285
1484
|
type: ds.dbName || ds.datasourceType || "unknown"
|
|
1286
1485
|
})),
|
|
@@ -4012,6 +4211,324 @@ You can now:
|
|
|
4012
4211
|
);
|
|
4013
4212
|
}
|
|
4014
4213
|
|
|
4214
|
+
// src/tools/widgets/list-widgets.ts
|
|
4215
|
+
import { z as z21 } from "zod";
|
|
4216
|
+
function registerListWidgets(server, client) {
|
|
4217
|
+
const services = createServiceBundle(client);
|
|
4218
|
+
server.tool(
|
|
4219
|
+
"list_widgets",
|
|
4220
|
+
"List widgets (metrics) on a customer dashboard embed. Returns each widget's metricId, name, published state, and creator identifier.",
|
|
4221
|
+
{
|
|
4222
|
+
embedId: z21.string().describe("Embed ID (customer dashboard instantiation)"),
|
|
4223
|
+
clientId: z21.string().describe("Client/tenant identifier for this embed")
|
|
4224
|
+
},
|
|
4225
|
+
READ_ONLY,
|
|
4226
|
+
async (params) => {
|
|
4227
|
+
try {
|
|
4228
|
+
const widgets = await services.widgetsApi.listWidgets(
|
|
4229
|
+
params.embedId,
|
|
4230
|
+
params.clientId
|
|
4231
|
+
);
|
|
4232
|
+
if (!Array.isArray(widgets) || widgets.length === 0) {
|
|
4233
|
+
return toolSuccess(
|
|
4234
|
+
`No widgets found on embed "${params.embedId}" for client "${params.clientId}".`
|
|
4235
|
+
);
|
|
4236
|
+
}
|
|
4237
|
+
return toolSuccess(
|
|
4238
|
+
JSON.stringify({
|
|
4239
|
+
count: widgets.length,
|
|
4240
|
+
embedId: params.embedId,
|
|
4241
|
+
clientId: params.clientId,
|
|
4242
|
+
widgets: widgets.map((w) => ({
|
|
4243
|
+
metricId: w.metricId || null,
|
|
4244
|
+
name: w.name || null,
|
|
4245
|
+
isPublished: w.isPublished ?? null,
|
|
4246
|
+
createdByUser: w.createdByUser || null
|
|
4247
|
+
}))
|
|
4248
|
+
})
|
|
4249
|
+
);
|
|
4250
|
+
} catch (err) {
|
|
4251
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
4252
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
);
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4258
|
+
// src/tools/widgets/add-widget.ts
|
|
4259
|
+
import { z as z22 } from "zod";
|
|
4260
|
+
function registerAddWidget(server, client) {
|
|
4261
|
+
const services = createServiceBundle(client);
|
|
4262
|
+
server.tool(
|
|
4263
|
+
"add_widget",
|
|
4264
|
+
"Add a widget (metric) to a customer dashboard embed. Either clone a template via `templateMetricId` (recommended \u2014 inherits query, chartOptions, datasource), or supply a full spec (name + query + chartOptions + companyIntegrationId). Mints a guest token for the clientId internally.",
|
|
4265
|
+
{
|
|
4266
|
+
embedId: z22.string().describe("Embed ID the widget will be added to"),
|
|
4267
|
+
clientId: z22.string().describe("Client/tenant identifier (widget is created in their scope)"),
|
|
4268
|
+
dataAppName: z22.string().optional().describe("Data app name (required for guest token). Auto-resolved from embedId if omitted."),
|
|
4269
|
+
templateMetricId: z22.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
|
|
4270
|
+
name: z22.string().optional().describe("Widget name (overrides template)"),
|
|
4271
|
+
metricId: z22.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
|
|
4272
|
+
query: z22.string().optional().describe("SQL query body (overrides template)"),
|
|
4273
|
+
chartOptions: z22.record(z22.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
|
|
4274
|
+
description: z22.string().optional(),
|
|
4275
|
+
companyIntegrationId: z22.string().optional().describe("Datasource ID. Required when not cloning a template."),
|
|
4276
|
+
integrationName: z22.string().optional(),
|
|
4277
|
+
isPublished: z22.boolean().optional().describe("Publish immediately (default true)"),
|
|
4278
|
+
body: z22.record(z22.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params.")
|
|
4279
|
+
},
|
|
4280
|
+
WRITE_CREATE,
|
|
4281
|
+
async (params) => {
|
|
4282
|
+
try {
|
|
4283
|
+
const ctx = await services.widgetService.resolveEmbedContext(params.embedId);
|
|
4284
|
+
const dataAppName = params.dataAppName || ctx.dataAppName;
|
|
4285
|
+
if (!dataAppName) {
|
|
4286
|
+
return toolError(
|
|
4287
|
+
`Could not resolve dataAppName for embed "${params.embedId}". Pass \`dataAppName\` explicitly or verify the embed exists via \`list_embeds\`.`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
if (!ctx.externalDashboardId) {
|
|
4291
|
+
return toolError(
|
|
4292
|
+
`Embed "${params.embedId}" has no externalDashboardId \u2014 cannot attach a widget. Verify via \`get_embed_details\`.`
|
|
4293
|
+
);
|
|
4294
|
+
}
|
|
4295
|
+
const guestToken = await services.widgetService.mintGuestToken(
|
|
4296
|
+
params.clientId,
|
|
4297
|
+
dataAppName,
|
|
4298
|
+
[params.embedId]
|
|
4299
|
+
);
|
|
4300
|
+
let createBody;
|
|
4301
|
+
if (params.body) {
|
|
4302
|
+
createBody = { ...params.body };
|
|
4303
|
+
if (!createBody.externalDashboardIds || createBody.externalDashboardIds.length === 0) {
|
|
4304
|
+
createBody.externalDashboardIds = [
|
|
4305
|
+
{ externalDashboardId: ctx.externalDashboardId }
|
|
4306
|
+
];
|
|
4307
|
+
}
|
|
4308
|
+
} else if (params.templateMetricId) {
|
|
4309
|
+
const overrides = {};
|
|
4310
|
+
if (params.name !== void 0) overrides.name = params.name;
|
|
4311
|
+
if (params.metricId !== void 0) overrides.metricId = params.metricId;
|
|
4312
|
+
if (params.query !== void 0) overrides.query = params.query;
|
|
4313
|
+
if (params.chartOptions !== void 0) overrides.chartOptions = params.chartOptions;
|
|
4314
|
+
if (params.description !== void 0) overrides.description = params.description;
|
|
4315
|
+
if (params.companyIntegrationId !== void 0) {
|
|
4316
|
+
overrides.companyIntegrationId = params.companyIntegrationId;
|
|
4317
|
+
}
|
|
4318
|
+
if (params.integrationName !== void 0) overrides.integrationName = params.integrationName;
|
|
4319
|
+
if (params.isPublished !== void 0) overrides.isPublished = params.isPublished;
|
|
4320
|
+
createBody = await services.widgetService.buildCloneBody(
|
|
4321
|
+
guestToken,
|
|
4322
|
+
params.templateMetricId,
|
|
4323
|
+
overrides,
|
|
4324
|
+
ctx.externalDashboardId
|
|
4325
|
+
);
|
|
4326
|
+
} else {
|
|
4327
|
+
if (!params.name) return toolError("`name` is required when not cloning a template.");
|
|
4328
|
+
if (!params.query) return toolError("`query` is required when not cloning a template.");
|
|
4329
|
+
if (!params.companyIntegrationId) {
|
|
4330
|
+
return toolError(
|
|
4331
|
+
"`companyIntegrationId` is required when not cloning a template. Use `list_datasources` to find the datasource ID, or clone from an existing widget via `templateMetricId`."
|
|
4332
|
+
);
|
|
4333
|
+
}
|
|
4334
|
+
createBody = {
|
|
4335
|
+
name: params.name,
|
|
4336
|
+
metricId: params.metricId || `widget-${Date.now()}`,
|
|
4337
|
+
query: params.query,
|
|
4338
|
+
chartOptions: params.chartOptions || {},
|
|
4339
|
+
description: params.description || "",
|
|
4340
|
+
companyIntegrationId: params.companyIntegrationId,
|
|
4341
|
+
integrationName: params.integrationName,
|
|
4342
|
+
isPublished: params.isPublished ?? true,
|
|
4343
|
+
isCreatedByClient: false,
|
|
4344
|
+
externalDashboardIds: [{ externalDashboardId: ctx.externalDashboardId }]
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
const result = await services.widgetsApi.createMetric(guestToken, createBody);
|
|
4348
|
+
if (result.error) {
|
|
4349
|
+
return toolError(
|
|
4350
|
+
`Widget creation failed: ${stringify(result.error)}
|
|
4351
|
+
|
|
4352
|
+
Request body sent:
|
|
4353
|
+
${JSON.stringify(createBody, null, 2).slice(0, 3e3)}`
|
|
4354
|
+
);
|
|
4355
|
+
}
|
|
4356
|
+
const inserted = result.insert_externalMetrics_one;
|
|
4357
|
+
const newId = inserted?.id || inserted?.metricId || "UNKNOWN_ID";
|
|
4358
|
+
const newMetricId = inserted?.metricId || createBody.metricId;
|
|
4359
|
+
return toolSuccess(
|
|
4360
|
+
`Widget added successfully.
|
|
4361
|
+
|
|
4362
|
+
- externalMetricId: \`${newId}\`
|
|
4363
|
+
- metricId: \`${newMetricId}\`
|
|
4364
|
+
- name: \`${createBody.name}\`
|
|
4365
|
+
- embedId: \`${params.embedId}\`
|
|
4366
|
+
- clientId: \`${params.clientId}\`
|
|
4367
|
+
|
|
4368
|
+
Use \`list_widgets\` to verify, or \`update_widget\`/\`remove_widget\` to adjust.`
|
|
4369
|
+
);
|
|
4370
|
+
} catch (err) {
|
|
4371
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
4372
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
);
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
// src/tools/widgets/update-widget.ts
|
|
4379
|
+
import { z as z23 } from "zod";
|
|
4380
|
+
function registerUpdateWidget(server, client) {
|
|
4381
|
+
const services = createServiceBundle(client);
|
|
4382
|
+
server.tool(
|
|
4383
|
+
"update_widget",
|
|
4384
|
+
"Update a widget on a customer dashboard embed. Applies a partial patch to the externalMetrics row (name, query, chartOptions, isPublished, etc.). Mints a guest token internally.",
|
|
4385
|
+
{
|
|
4386
|
+
externalMetricId: z23.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
|
|
4387
|
+
clientId: z23.string().describe("Client/tenant identifier"),
|
|
4388
|
+
dataAppName: z23.string().describe("Data app name the widget lives in (required to mint guest token)"),
|
|
4389
|
+
embedId: z23.string().optional().describe("Optional \u2014 if provided, the guest token is scoped to just this embed."),
|
|
4390
|
+
set: z23.record(z23.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
|
|
4391
|
+
},
|
|
4392
|
+
WRITE_IDEMPOTENT,
|
|
4393
|
+
async (params) => {
|
|
4394
|
+
try {
|
|
4395
|
+
if (!params.set || Object.keys(params.set).length === 0) {
|
|
4396
|
+
return toolError("`set` must contain at least one field to update.");
|
|
4397
|
+
}
|
|
4398
|
+
const guestToken = await services.widgetService.mintGuestToken(
|
|
4399
|
+
params.clientId,
|
|
4400
|
+
params.dataAppName,
|
|
4401
|
+
params.embedId ? [params.embedId] : void 0
|
|
4402
|
+
);
|
|
4403
|
+
const result = await services.widgetsApi.updateMetric(guestToken, {
|
|
4404
|
+
externalMetricId: params.externalMetricId,
|
|
4405
|
+
set: params.set
|
|
4406
|
+
});
|
|
4407
|
+
if (result.error) {
|
|
4408
|
+
return toolError(
|
|
4409
|
+
`Widget update failed: ${stringify(result.error)}`
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4412
|
+
const updated = result.update_externalMetrics_by_pk;
|
|
4413
|
+
const id = updated?.id || params.externalMetricId;
|
|
4414
|
+
const metricId = updated?.metricId || null;
|
|
4415
|
+
return toolSuccess(
|
|
4416
|
+
`Widget updated successfully.
|
|
4417
|
+
|
|
4418
|
+
- externalMetricId: \`${id}\`
|
|
4419
|
+
` + (metricId ? `- metricId: \`${metricId}\`
|
|
4420
|
+
` : "") + `- fields changed: ${Object.keys(params.set).join(", ")}
|
|
4421
|
+
|
|
4422
|
+
Use \`list_widgets\` to verify.`
|
|
4423
|
+
);
|
|
4424
|
+
} catch (err) {
|
|
4425
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
4426
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
// src/tools/widgets/remove-widget.ts
|
|
4433
|
+
import { z as z24 } from "zod";
|
|
4434
|
+
function registerRemoveWidget(server, client) {
|
|
4435
|
+
const services = createServiceBundle(client);
|
|
4436
|
+
server.tool(
|
|
4437
|
+
"remove_widget",
|
|
4438
|
+
"Remove a widget from a customer dashboard embed. This is a per-client soft delete (writes to clientDeletedMetrics) \u2014 the widget stays on the template dashboard and on other customers' embeds.",
|
|
4439
|
+
{
|
|
4440
|
+
externalMetricId: z24.string().describe("Internal uuid of the widget (externalMetrics.id)"),
|
|
4441
|
+
clientId: z24.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
|
|
4442
|
+
dataAppName: z24.string().describe("Data app name the widget lives in (required to mint guest token)"),
|
|
4443
|
+
embedId: z24.string().optional().describe("Optional \u2014 scopes the guest token to this embed.")
|
|
4444
|
+
},
|
|
4445
|
+
DESTRUCTIVE,
|
|
4446
|
+
async (params) => {
|
|
4447
|
+
try {
|
|
4448
|
+
const guestToken = await services.widgetService.mintGuestToken(
|
|
4449
|
+
params.clientId,
|
|
4450
|
+
params.dataAppName,
|
|
4451
|
+
params.embedId ? [params.embedId] : void 0
|
|
4452
|
+
);
|
|
4453
|
+
const result = await services.widgetsApi.markArchived(guestToken, {
|
|
4454
|
+
id: params.externalMetricId,
|
|
4455
|
+
clientId: params.clientId
|
|
4456
|
+
});
|
|
4457
|
+
if (result.error) {
|
|
4458
|
+
return toolError(
|
|
4459
|
+
`Widget removal failed: ${stringify(result.error)}`
|
|
4460
|
+
);
|
|
4461
|
+
}
|
|
4462
|
+
return toolSuccess(
|
|
4463
|
+
`Widget removed for client "${params.clientId}".
|
|
4464
|
+
|
|
4465
|
+
- externalMetricId: \`${params.externalMetricId}\`
|
|
4466
|
+
- clientId: \`${params.clientId}\`
|
|
4467
|
+
|
|
4468
|
+
Note: this is a per-client soft delete. The widget remains available for other clients and on the source dashboard.`
|
|
4469
|
+
);
|
|
4470
|
+
} catch (err) {
|
|
4471
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
4472
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
);
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
// src/tools/widgets/generate-widget.ts
|
|
4479
|
+
import { z as z25 } from "zod";
|
|
4480
|
+
function registerGenerateWidget(server, client) {
|
|
4481
|
+
const services = createServiceBundle(client);
|
|
4482
|
+
server.tool(
|
|
4483
|
+
"generate_widget",
|
|
4484
|
+
"AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft (query, metadata) the caller can review and pass to `add_widget`. Mints a guest token internally.",
|
|
4485
|
+
{
|
|
4486
|
+
clientId: z25.string().describe("Client/tenant identifier"),
|
|
4487
|
+
dataAppName: z25.string().describe("Data app name (required to mint guest token)"),
|
|
4488
|
+
integrationId: z25.string().describe("Datasource ID to generate the widget against. Use `list_datasources` to find."),
|
|
4489
|
+
integrationName: z25.string().describe("Datasource name (matches integrationId)"),
|
|
4490
|
+
userInputs: z25.record(z25.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
|
|
4491
|
+
embedId: z25.string().optional().describe("Optional \u2014 scopes the guest token to this embed.")
|
|
4492
|
+
},
|
|
4493
|
+
READ_ONLY,
|
|
4494
|
+
async (params) => {
|
|
4495
|
+
try {
|
|
4496
|
+
const guestToken = await services.widgetService.mintGuestToken(
|
|
4497
|
+
params.clientId,
|
|
4498
|
+
params.dataAppName,
|
|
4499
|
+
params.embedId ? [params.embedId] : void 0
|
|
4500
|
+
);
|
|
4501
|
+
const result = await services.widgetsApi.generateMetric(guestToken, {
|
|
4502
|
+
integrationId: params.integrationId,
|
|
4503
|
+
integrationName: params.integrationName,
|
|
4504
|
+
userInputs: params.userInputs
|
|
4505
|
+
});
|
|
4506
|
+
const resultRec = result;
|
|
4507
|
+
if (resultRec.error) {
|
|
4508
|
+
return toolError(
|
|
4509
|
+
`Widget generation failed: ${stringify(resultRec.error)}`
|
|
4510
|
+
);
|
|
4511
|
+
}
|
|
4512
|
+
return toolSuccess(
|
|
4513
|
+
JSON.stringify({
|
|
4514
|
+
message: "Draft generated. Review the `query` and `result` fields below, then pass to `add_widget` (using raw `body` passthrough) to persist.",
|
|
4515
|
+
draft: {
|
|
4516
|
+
query: resultRec.query ?? null,
|
|
4517
|
+
result: resultRec.result ?? null,
|
|
4518
|
+
metaData: resultRec.metaData ?? null,
|
|
4519
|
+
timeTaken: resultRec.timeTaken ?? null,
|
|
4520
|
+
error: resultRec.error ?? null
|
|
4521
|
+
}
|
|
4522
|
+
})
|
|
4523
|
+
);
|
|
4524
|
+
} catch (err) {
|
|
4525
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
4526
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
);
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4015
4532
|
// src/tools/registry.ts
|
|
4016
4533
|
function registerAllTools(server, client) {
|
|
4017
4534
|
registerListDatasources(server, client);
|
|
@@ -4035,6 +4552,11 @@ function registerAllTools(server, client) {
|
|
|
4035
4552
|
registerAskQuestion(server, client);
|
|
4036
4553
|
registerGetSemanticLayer(server, client);
|
|
4037
4554
|
registerUpdateSemanticLayer(server, client);
|
|
4555
|
+
registerListWidgets(server, client);
|
|
4556
|
+
registerAddWidget(server, client);
|
|
4557
|
+
registerUpdateWidget(server, client);
|
|
4558
|
+
registerRemoveWidget(server, client);
|
|
4559
|
+
registerGenerateWidget(server, client);
|
|
4038
4560
|
}
|
|
4039
4561
|
|
|
4040
4562
|
// src/resources/content/getting-started.ts
|
|
@@ -6484,7 +7006,7 @@ tenant. For example, filter by department or user role within an organization.
|
|
|
6484
7006
|
// src/resources/content/use-case-guides.ts
|
|
6485
7007
|
var useCaseGuidesContent = `# Databrain Use Case Guides
|
|
6486
7008
|
|
|
6487
|
-
Databrain supports
|
|
7009
|
+
Databrain supports 3 core use cases for embedding analytics. Each maps to a specific MCP prompt and tool sequence.
|
|
6488
7010
|
|
|
6489
7011
|
---
|
|
6490
7012
|
|
|
@@ -6501,15 +7023,7 @@ Databrain supports 4 core use cases for embedding analytics. Each maps to a spec
|
|
|
6501
7023
|
**Tool sequence:** \`list_data_apps\` \u2192 \`list_datamarts\` \u2192 \`create_embed\` with \`clientId\` (per client) \u2192 \`generate_guest_token\` \u2192 \`generate_embed_code\`
|
|
6502
7024
|
**Key concepts:** \`clientId\` for tenant isolation, \`templateDashboardId\` for cloning, \`tableTenancySettings\` for row-level security.
|
|
6503
7025
|
|
|
6504
|
-
## Use Case 3:
|
|
6505
|
-
**When to use:** You want end-users to build their own metrics/charts on embedded dashboards.
|
|
6506
|
-
**Prompt:** \`enable-metric-creation\`
|
|
6507
|
-
**Two modes:**
|
|
6508
|
-
- **DRAG_DROP:** Visual metric builder \u2014 users drag columns, pick chart types. No AI needed.
|
|
6509
|
-
- **CHAT:** AI-powered natural language \u2014 users type questions. Requires LLM connector in Databrain admin.
|
|
6510
|
-
**Tool sequence:** \`list_embeds\` \u2192 \`update_embed\` (set accessSettings and options) \u2192 \`generate_guest_token\` (with permissions)
|
|
6511
|
-
|
|
6512
|
-
## Use Case 4: Explore and Query Data
|
|
7026
|
+
## Use Case 3: Explore and Query Data
|
|
6513
7027
|
**When to use:** You want to explore available data and ask questions using natural language.
|
|
6514
7028
|
**Prompt:** \`query-data\` (or \`populate-semantic-layer\` if starting from an empty semantic layer)
|
|
6515
7029
|
**Path:** Exploration + querying (datasources, datamarts, and workspaces are created in the Databrain UI). The semantic layer is populated via MCP tools.
|
|
@@ -6520,17 +7034,17 @@ Databrain supports 4 core use cases for embedding analytics. Each maps to a spec
|
|
|
6520
7034
|
|
|
6521
7035
|
## Customization (After Any Setup)
|
|
6522
7036
|
|
|
6523
|
-
Once an embed exists, use
|
|
7037
|
+
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:
|
|
6524
7038
|
|
|
6525
|
-
|
|
|
6526
|
-
|
|
6527
|
-
|
|
|
6528
|
-
|
|
|
6529
|
-
| \`
|
|
6530
|
-
|
|
|
6531
|
-
|
|
|
6532
|
-
|
|
|
6533
|
-
| \`populate-semantic-layer\`
|
|
7039
|
+
| Task | How |
|
|
7040
|
+
|------|-----|
|
|
7041
|
+
| Colors, fonts, chart styles | Prompt: \`brand-my-embed\` |
|
|
7042
|
+
| Filters (string, date, SQL) | \`update_embed\` with \`options.globalFilterOptions\` via body passthrough |
|
|
7043
|
+
| Localization / translations | \`update_embed\` with \`componentAttributes\` via body passthrough |
|
|
7044
|
+
| Permissions / access controls | \`update_embed\` + \`generate_guest_token\` with permissions |
|
|
7045
|
+
| Metric creation modes | \`update_embed\` accessSettings + \`generate_guest_token\` |
|
|
7046
|
+
| Manage / audit embeds | \`list_embeds\` \u2192 \`get_embed_details\` |
|
|
7047
|
+
| Semantic layer | Prompt: \`populate-semantic-layer\` or \`get_semantic_layer\` / \`update_semantic_layer\` |
|
|
6534
7048
|
|
|
6535
7049
|
---
|
|
6536
7050
|
|
|
@@ -6540,9 +7054,8 @@ Once an embed exists, use these prompts to customize:
|
|
|
6540
7054
|
|----------|-------------------|
|
|
6541
7055
|
| Do you have dashboards in Databrain UI? | Yes \u2192 Use Case 1 |
|
|
6542
7056
|
| Does each client need their own dashboard? | Yes \u2192 Use Case 2 |
|
|
6543
|
-
|
|
|
6544
|
-
|
|
|
6545
|
-
| Just need to brand/filter/secure? | Use customization prompts |
|
|
7057
|
+
| Want to explore or query your data? | Yes \u2192 Use Case 3 |
|
|
7058
|
+
| Just need to brand/customize? | Use \`brand-my-embed\` prompt or describe what you need |
|
|
6546
7059
|
`;
|
|
6547
7060
|
|
|
6548
7061
|
// src/resources/content/permission-schema.ts
|
|
@@ -6665,7 +7178,7 @@ var resources = [
|
|
|
6665
7178
|
{
|
|
6666
7179
|
uri: "databrain://use-case-guides",
|
|
6667
7180
|
name: "Use Case Guides",
|
|
6668
|
-
description: "Summary of
|
|
7181
|
+
description: "Summary of 3 core use cases with tool sequences, prompt names, and decision guide",
|
|
6669
7182
|
content: useCaseGuidesContent
|
|
6670
7183
|
},
|
|
6671
7184
|
{
|
|
@@ -6747,7 +7260,7 @@ Only after they answer, call \`generate_embed_code\` with the \`embedId\`.
|
|
|
6747
7260
|
- Prefer existing embeds when the user already has one for their target dashboard.
|
|
6748
7261
|
- NEVER ask for service or API tokens \u2014 both are handled automatically.
|
|
6749
7262
|
- \`workspaceName\` comes from dashboard data, not from the data app name.
|
|
6750
|
-
- After setup, suggest: \`brand-my-embed
|
|
7263
|
+
- 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.`;
|
|
6751
7264
|
|
|
6752
7265
|
// src/prompts/embed-blank-dashboard.ts
|
|
6753
7266
|
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.
|
|
@@ -6811,83 +7324,7 @@ The frontend code is the same for all clients \u2014 the \`clientId\` in the gue
|
|
|
6811
7324
|
- STOP after each step. Present results and wait for user input before proceeding.
|
|
6812
7325
|
- When calling \`create_embed\` with \`clientId\`, only pass \`accessSettings: { datamartName }\` \u2014 the tool fills in all required defaults automatically. Do NOT enumerate isAllow* fields.
|
|
6813
7326
|
- \`clientId\` must be consistent between embed creation and guest token generation.
|
|
6814
|
-
- After setup, suggest: \`brand-my-embed
|
|
6815
|
-
|
|
6816
|
-
// src/prompts/enable-metric-creation.ts
|
|
6817
|
-
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).
|
|
6818
|
-
|
|
6819
|
-
**CRITICAL: This is an interactive guide. You MUST stop and ask the user at each decision point before proceeding. Never assume answers.**
|
|
6820
|
-
|
|
6821
|
-
**Two Metric Creation Modes:**
|
|
6822
|
-
- **DRAG_DROP**: Visual metric builder where users drag columns, choose chart types, and configure metrics. No AI needed.
|
|
6823
|
-
- **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.
|
|
6824
|
-
|
|
6825
|
-
**Step 1 \u2014 Identify the Embed**
|
|
6826
|
-
Call \`list_embeds\` to show available embeds.
|
|
6827
|
-
STOP and ASK the user: "Which embed do you want to enable metric creation on?"
|
|
6828
|
-
Only after they answer, call \`get_embed_details\` to see current access settings.
|
|
6829
|
-
|
|
6830
|
-
**Step 2 \u2014 Choose the Mode**
|
|
6831
|
-
STOP and ASK the user:
|
|
6832
|
-
- "Which metric creation mode do you want?"
|
|
6833
|
-
- **DRAG_DROP**: Visual builder, no AI dependency
|
|
6834
|
-
- **CHAT**: AI-powered, requires LLM connector setup in Databrain admin
|
|
6835
|
-
Do NOT proceed until the user picks a mode.
|
|
6836
|
-
|
|
6837
|
-
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?"
|
|
6838
|
-
|
|
6839
|
-
**Step 3 \u2014 Update Embed Access Settings**
|
|
6840
|
-
Call \`update_embed\` with the embed ID and updated \`accessSettings\`:
|
|
6841
|
-
|
|
6842
|
-
For DRAG_DROP mode:
|
|
6843
|
-
\`\`\`json
|
|
6844
|
-
{
|
|
6845
|
-
"accessSettings": {
|
|
6846
|
-
"isAllowMetricCreation": true,
|
|
6847
|
-
"metricCreationMode": "DRAG_DROP"
|
|
6848
|
-
}
|
|
6849
|
-
}
|
|
6850
|
-
\`\`\`
|
|
6851
|
-
|
|
6852
|
-
For CHAT mode:
|
|
6853
|
-
\`\`\`json
|
|
6854
|
-
{
|
|
6855
|
-
"accessSettings": {
|
|
6856
|
-
"isAllowMetricCreation": true,
|
|
6857
|
-
"metricCreationMode": "CHAT"
|
|
6858
|
-
}
|
|
6859
|
-
}
|
|
6860
|
-
\`\`\`
|
|
6861
|
-
|
|
6862
|
-
**Step 4 \u2014 Configure Embed Options**
|
|
6863
|
-
Call \`update_embed\` (body passthrough with options) to ensure the UI supports metric creation:
|
|
6864
|
-
- \`disableMetricCreation\`: false
|
|
6865
|
-
- \`hideCreateMetricButton\`: false
|
|
6866
|
-
- \`hideChartSettings\`: false (so users can customize their created metrics)
|
|
6867
|
-
|
|
6868
|
-
**Step 5 \u2014 Set Guest Token Permissions**
|
|
6869
|
-
Remind the user: guest token \`permissions\` override embed \`accessSettings\` at runtime. When generating guest tokens via \`generate_guest_token\`, include:
|
|
6870
|
-
\`\`\`json
|
|
6871
|
-
{
|
|
6872
|
-
"permissions": {
|
|
6873
|
-
"isDisableMetricCreation": false
|
|
6874
|
-
}
|
|
6875
|
-
}
|
|
6876
|
-
\`\`\`
|
|
6877
|
-
|
|
6878
|
-
**Step 6 \u2014 Verify and Test**
|
|
6879
|
-
Call \`get_embed_details\` to verify the settings were applied correctly.
|
|
6880
|
-
Tell the user to refresh their embed and look for the "+ Create Metric" button.
|
|
6881
|
-
|
|
6882
|
-
If using CHAT mode, remind the user:
|
|
6883
|
-
- An LLM connector must be configured in Databrain admin (Settings > AI Configuration)
|
|
6884
|
-
- 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)
|
|
6885
|
-
|
|
6886
|
-
**Important rules:**
|
|
6887
|
-
- STOP and ASK at each decision point. Never skip ahead.
|
|
6888
|
-
- Guest token \`permissions\` take precedence over embed \`accessSettings\` \u2014 both must allow metric creation.
|
|
6889
|
-
- CHAT mode requires external LLM connector setup (not available via MCP).
|
|
6890
|
-
- After enabling, suggest: \`secure-my-embed\` to fine-tune which users get metric creation access.`;
|
|
7327
|
+
- 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.`;
|
|
6891
7328
|
|
|
6892
7329
|
// src/prompts/brand-my-embed.ts
|
|
6893
7330
|
var brandMyEmbedPrompt = `You are helping the user brand and visually customize their embedded Databrain dashboard. Follow these steps.
|
|
@@ -6964,327 +7401,6 @@ Iterate by calling the relevant tools again with adjusted values. Remember: all
|
|
|
6964
7401
|
|
|
6965
7402
|
**Reference**: Use \`databrain://theme-reference\` for the full theme schema and \`databrain://web-component-reference\` for all component attributes.`;
|
|
6966
7403
|
|
|
6967
|
-
// src/prompts/localize-embed.ts
|
|
6968
|
-
var localizeEmbedPrompt = `You are helping the user set up multi-language support for their embedded Databrain dashboard. Follow these steps.
|
|
6969
|
-
|
|
6970
|
-
**Step 1 \u2014 Identify the Embed and Target Languages**
|
|
6971
|
-
Call \`list_embeds\` and ask the user which embed to localize. Then ask:
|
|
6972
|
-
- Which languages do they need? (Supported: English, French, Spanish, German, Italian, Portuguese, Chinese, Japanese, Korean, Hindi, Arabic, Russian)
|
|
6973
|
-
- What is the default/primary language?
|
|
6974
|
-
|
|
6975
|
-
**Step 2 \u2014 Set the Primary Language**
|
|
6976
|
-
Use \`update_embed\` (body passthrough with componentAttributes) with the \`language\` parameter set to their primary language code (e.g. "fr" for French).
|
|
6977
|
-
|
|
6978
|
-
**Step 3 \u2014 Gather Translations**
|
|
6979
|
-
Call \`get_embed_details\` to understand what text is in the dashboard. Then help the user build translations:
|
|
6980
|
-
|
|
6981
|
-
Ask the user to list the key text elements in their dashboard:
|
|
6982
|
-
- Dashboard title
|
|
6983
|
-
- Metric titles and descriptions
|
|
6984
|
-
- Filter labels
|
|
6985
|
-
- Any footnotes or annotations
|
|
6986
|
-
|
|
6987
|
-
For each text element, ask for translations in their target languages. Build translation entries like:
|
|
6988
|
-
\`\`\`
|
|
6989
|
-
{ key: "Revenue", translations: { fr: "Revenus", es: "Ingresos", de: "Einnahmen" } }
|
|
6990
|
-
\`\`\`
|
|
6991
|
-
|
|
6992
|
-
**Important rules for translation keys:**
|
|
6993
|
-
- Keys must match the dashboard text EXACTLY (case-sensitive)
|
|
6994
|
-
- Define longer keys before shorter ones to avoid partial matches
|
|
6995
|
-
- If a translation is missing for a language, the original key text is shown as fallback
|
|
6996
|
-
|
|
6997
|
-
**Step 4 \u2014 Apply Translations**
|
|
6998
|
-
Use \`update_embed\` (body passthrough with componentAttributes.translationDictionary) with the translations.
|
|
6999
|
-
|
|
7000
|
-
**Step 5 \u2014 Configure Calendar (if needed)**
|
|
7001
|
-
Ask if they need a different calendar system:
|
|
7002
|
-
- \`"default"\` \u2014 standard Gregorian calendar
|
|
7003
|
-
- \`"ind"\` \u2014 Indian calendar format
|
|
7004
|
-
|
|
7005
|
-
Set via \`calendarType\` in \`update_embed\` (body passthrough with componentAttributes).
|
|
7006
|
-
|
|
7007
|
-
**Step 6 \u2014 Set Custom Messages**
|
|
7008
|
-
Ask if they want to translate system messages:
|
|
7009
|
-
- Token expiry message (shown when session expires)
|
|
7010
|
-
- Token absent message (shown when no auth token)
|
|
7011
|
-
|
|
7012
|
-
Use \`update_embed\` (body passthrough with options.customMessages) with \`tokenExpiry\` and \`tokenAbsent\` in their primary language.
|
|
7013
|
-
|
|
7014
|
-
**Step 7 \u2014 Test**
|
|
7015
|
-
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.
|
|
7016
|
-
|
|
7017
|
-
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.
|
|
7018
|
-
|
|
7019
|
-
**Tip:** The \`language\`, \`calendarType\`, \`hideDashboardFilters\`, and filter settings are all configurable via \`update_embed\` body passthrough.
|
|
7020
|
-
|
|
7021
|
-
**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.`;
|
|
7022
|
-
|
|
7023
|
-
// src/prompts/secure-my-embed.ts
|
|
7024
|
-
var secureMyEmbedPrompt = `You are helping the user secure and configure permissions for their embedded Databrain dashboard. Follow these steps.
|
|
7025
|
-
|
|
7026
|
-
**Step 1 \u2014 Understand User Roles**
|
|
7027
|
-
Ask the user about their end-user roles. Common patterns:
|
|
7028
|
-
- **Viewer** \u2014 can see data, maybe filter, but can't modify anything
|
|
7029
|
-
- **Analyst** \u2014 can export data, drill down, use filters
|
|
7030
|
-
- **Builder/Power User** \u2014 can create/edit metrics, change layouts
|
|
7031
|
-
- **Admin** \u2014 full access including AI features
|
|
7032
|
-
|
|
7033
|
-
Ask: "What roles do your end-users have? What should each role be allowed to do?"
|
|
7034
|
-
|
|
7035
|
-
**Step 2 \u2014 Lock Down the Embed Defaults**
|
|
7036
|
-
Use \`update_embed\` (body passthrough with options) to set restrictive defaults (principle of least privilege):
|
|
7037
|
-
|
|
7038
|
-
\`\`\`
|
|
7039
|
-
update_embed(embedId, body: { options: {
|
|
7040
|
-
disableMetricCreation: true,
|
|
7041
|
-
disableMetricUpdation: true,
|
|
7042
|
-
disableMetricDeletion: true,
|
|
7043
|
-
disableManageMetrics: true,
|
|
7044
|
-
disableLayoutCustomization: true,
|
|
7045
|
-
disableSaveLayout: true,
|
|
7046
|
-
disableUnderlyingData: true,
|
|
7047
|
-
disableScheduleEmailReports: true,
|
|
7048
|
-
enableDownloadCsv: false,
|
|
7049
|
-
enableDownloadAllPdf: false)
|
|
7050
|
-
\`\`\`
|
|
7051
|
-
|
|
7052
|
-
Explain: "We start with everything locked down, then use guest token permissions to grant access per role."
|
|
7053
|
-
|
|
7054
|
-
**Step 3 \u2014 Create Role-Based Guest Token Examples**
|
|
7055
|
-
Show the user how to generate tokens for each role:
|
|
7056
|
-
|
|
7057
|
-
**Viewer (default \u2014 no overrides needed):**
|
|
7058
|
-
\`\`\`
|
|
7059
|
-
generate_guest_token(clientId, dataAppName)
|
|
7060
|
-
\`\`\`
|
|
7061
|
-
|
|
7062
|
-
**Analyst (can export and drill down):**
|
|
7063
|
-
\`\`\`
|
|
7064
|
-
generate_guest_token(clientId, dataAppName, {
|
|
7065
|
-
permissions: {
|
|
7066
|
-
isEnableDownloadMetrics: true,
|
|
7067
|
-
isEnableUnderlyingData: true
|
|
7068
|
-
}
|
|
7069
|
-
})
|
|
7070
|
-
\`\`\`
|
|
7071
|
-
|
|
7072
|
-
**Builder (can create and edit):**
|
|
7073
|
-
\`\`\`
|
|
7074
|
-
generate_guest_token(clientId, dataAppName, {
|
|
7075
|
-
permissions: {
|
|
7076
|
-
isEnableManageMetrics: true,
|
|
7077
|
-
isEnableMetricUpdation: true,
|
|
7078
|
-
isEnableCustomizeLayout: true
|
|
7079
|
-
}
|
|
7080
|
-
})
|
|
7081
|
-
\`\`\`
|
|
7082
|
-
|
|
7083
|
-
**Admin (full access with AI):**
|
|
7084
|
-
\`\`\`
|
|
7085
|
-
generate_guest_token(clientId, dataAppName, {
|
|
7086
|
-
permissions: {
|
|
7087
|
-
isEnableManageMetrics: true,
|
|
7088
|
-
isEnableMetricUpdation: true,
|
|
7089
|
-
isEnableCustomizeLayout: true,
|
|
7090
|
-
isEnableDownloadMetrics: true,
|
|
7091
|
-
isEnableUnderlyingData: true
|
|
7092
|
-
}
|
|
7093
|
-
})
|
|
7094
|
-
\`\`\`
|
|
7095
|
-
|
|
7096
|
-
**Step 4 \u2014 Multi-Tenancy Check**
|
|
7097
|
-
Ask: "Do different customers/organizations see different data?"
|
|
7098
|
-
|
|
7099
|
-
If yes:
|
|
7100
|
-
- Explain that \`clientId\` in the guest token controls row-level security
|
|
7101
|
-
- The \`clientId\` must match values in the datamart's client column
|
|
7102
|
-
- Suggest using \`params.appFilters\` / \`params.dashboardAppFilters\` in \`generate_guest_token\` for server-side row-level filtering
|
|
7103
|
-
|
|
7104
|
-
**Step 5 \u2014 Security Checklist**
|
|
7105
|
-
Walk through this checklist with the user:
|
|
7106
|
-
|
|
7107
|
-
1. \u2705 API token is stored server-side only (never in frontend code)
|
|
7108
|
-
2. \u2705 Guest tokens are generated server-side per request
|
|
7109
|
-
3. \u2705 Guest tokens have appropriate expiry time
|
|
7110
|
-
4. \u2705 Embed defaults are restrictive (locked down in Step 2)
|
|
7111
|
-
5. \u2705 Role-based permissions are granted via guest token overrides
|
|
7112
|
-
6. \u2705 Multi-tenancy is configured if serving multiple customers
|
|
7113
|
-
7. \u2705 Download restrictions are set (\`disableDownloadDataNoFilters\` if needed)
|
|
7114
|
-
|
|
7115
|
-
**Step 6 \u2014 Generate Secure Frontend Code**
|
|
7116
|
-
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.
|
|
7117
|
-
|
|
7118
|
-
When reviewing runtime behavior (for example missing \`+ Create Metric\`), verify contract shape explicitly:
|
|
7119
|
-
- Guest token request body must send \`permissions\` at top level (not nested inside \`params\`)
|
|
7120
|
-
- Include \`dataAppName\` at top level
|
|
7121
|
-
- For metric creation flows, ensure \`permissions.isDisableMetricCreation=false\` and \`permissions.isEnableManageMetrics=true\`
|
|
7122
|
-
- Confirm embed options do not set \`disableMetricCreation=true\`
|
|
7123
|
-
- In browser network response for \`/api/v2/dashboard\`, confirm \`isAllowedToCreateMetrics=true\` and \`metricCreationMode=DRAG_DROP\`
|
|
7124
|
-
|
|
7125
|
-
**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.
|
|
7126
|
-
|
|
7127
|
-
**Reference**: Use \`databrain://self-serve-reference\` for all permission flags and \`databrain://multi-tenancy-guide\` for row-level security details.`;
|
|
7128
|
-
|
|
7129
|
-
// src/prompts/setup-filters.ts
|
|
7130
|
-
var setupFiltersPrompt = `You are helping the user configure dashboard filters for their embedded Databrain dashboard. Follow these steps.
|
|
7131
|
-
|
|
7132
|
-
**Step 1 \u2014 Understand the Data**
|
|
7133
|
-
Call \`list_embeds\` and ask which embed to configure. Then call \`get_embed_details\` to see the current filter configuration.
|
|
7134
|
-
|
|
7135
|
-
**Step 2 \u2014 Identify Filter Needs**
|
|
7136
|
-
Ask the user:
|
|
7137
|
-
- "What filters do your end-users need?" (e.g. date range, region, product category, status)
|
|
7138
|
-
- "Should any filters have default values?"
|
|
7139
|
-
- "Are there filters you want to hide from end-users?" (e.g. internal IDs)
|
|
7140
|
-
- "Do any filters have very large option lists?" (100+ options \u2014 these benefit from SQL-based filters)
|
|
7141
|
-
|
|
7142
|
-
**Step 3 \u2014 Configure String Filters**
|
|
7143
|
-
For text/category filters, use \`update_embed\` (body passthrough with options.globalFilterOptions) for \`stringFilters\`:
|
|
7144
|
-
|
|
7145
|
-
\`\`\`
|
|
7146
|
-
stringFilters: [{
|
|
7147
|
-
filterName: "Region",
|
|
7148
|
-
options: [
|
|
7149
|
-
{ value: "US", label: "United States" },
|
|
7150
|
-
{ value: "EU", label: "Europe" },
|
|
7151
|
-
{ value: "APAC", label: "Asia Pacific" }
|
|
7152
|
-
],
|
|
7153
|
-
defaultOption: "US"
|
|
7154
|
-
}]
|
|
7155
|
-
\`\`\`
|
|
7156
|
-
|
|
7157
|
-
For multi-select defaults, use an array: \`defaultOption: ["US", "EU"]\`
|
|
7158
|
-
|
|
7159
|
-
**Step 4 \u2014 Configure Date Filters**
|
|
7160
|
-
For date range filters, use \`dateFilters\`:
|
|
7161
|
-
|
|
7162
|
-
\`\`\`
|
|
7163
|
-
dateFilters: [{
|
|
7164
|
-
filterName: "Date Range",
|
|
7165
|
-
startDate: "2024-01-01",
|
|
7166
|
-
endDate: "2024-12-31",
|
|
7167
|
-
datePresetOptions: [
|
|
7168
|
-
{ label: "Last 30 Days", type: "Last", interval: "Days" },
|
|
7169
|
-
{ label: "This Quarter", type: "This", interval: "Quarter" },
|
|
7170
|
-
{ label: "This Year", type: "This", interval: "Year" }
|
|
7171
|
-
]
|
|
7172
|
-
}]
|
|
7173
|
-
\`\`\`
|
|
7174
|
-
|
|
7175
|
-
**Step 5 \u2014 Configure Number Filters**
|
|
7176
|
-
For numeric range filters, use \`numberFilters\`:
|
|
7177
|
-
|
|
7178
|
-
\`\`\`
|
|
7179
|
-
numberFilters: [{
|
|
7180
|
-
filterName: "Revenue",
|
|
7181
|
-
min: 0,
|
|
7182
|
-
max: 1000000
|
|
7183
|
-
}]
|
|
7184
|
-
\`\`\`
|
|
7185
|
-
|
|
7186
|
-
**Step 6 \u2014 SQL-Based Filters (for large datasets)**
|
|
7187
|
-
If a filter has 100+ options (e.g. company names, product SKUs), use \`sqlFilters\`:
|
|
7188
|
-
|
|
7189
|
-
\`\`\`
|
|
7190
|
-
sqlFilters: [{
|
|
7191
|
-
filterName: "Company",
|
|
7192
|
-
sql: "SELECT DISTINCT \\"company_name\\" FROM \\"companies\\" ORDER BY \\"company_name\\"",
|
|
7193
|
-
columnName: "company_name"
|
|
7194
|
-
}]
|
|
7195
|
-
\`\`\`
|
|
7196
|
-
|
|
7197
|
-
Explain: "SQL filters dynamically fetch options from the database, so they're always up-to-date and don't bloat the embed configuration."
|
|
7198
|
-
|
|
7199
|
-
**Step 7 \u2014 Hide Unnecessary Filters**
|
|
7200
|
-
If the user has filters that should be hidden (e.g. internal IDs, tenant columns already handled by clientId):
|
|
7201
|
-
|
|
7202
|
-
\`\`\`
|
|
7203
|
-
hideDashboardFilters: ["internal_id", "tenant_id"]
|
|
7204
|
-
\`\`\`
|
|
7205
|
-
|
|
7206
|
-
**Step 8 \u2014 Configure Date Picker Options**
|
|
7207
|
-
Ask if they want to hide certain date picker presets:
|
|
7208
|
-
|
|
7209
|
-
\`\`\`
|
|
7210
|
-
hideDatePickerOptions: ["yesterday", "last week"]
|
|
7211
|
-
\`\`\`
|
|
7212
|
-
|
|
7213
|
-
**Step 9 \u2014 Metric Filter Position**
|
|
7214
|
-
Ask: "Do you want metric-level filters inside the card or outside?"
|
|
7215
|
-
- \`"inside"\` \u2014 compact, within each metric card (default)
|
|
7216
|
-
- \`"outside"\` \u2014 above the card, more prominent
|
|
7217
|
-
|
|
7218
|
-
Also ask if they want cross-metric filtering (\`enableMultiMetricFilters\`).
|
|
7219
|
-
|
|
7220
|
-
**Step 10 \u2014 Server-Side Filters (via Guest Token)**
|
|
7221
|
-
If the user needs filters that end-users cannot remove (e.g. for security):
|
|
7222
|
-
- Explain that \`appFilters\` in \`generate_guest_token\` are server-side enforced
|
|
7223
|
-
- These are invisible to the end-user and cannot be overridden
|
|
7224
|
-
- Perfect for tenant filtering, role-based data access, or compliance requirements
|
|
7225
|
-
|
|
7226
|
-
Show an example:
|
|
7227
|
-
\`\`\`
|
|
7228
|
-
generate_guest_token(clientId, dataAppName, {
|
|
7229
|
-
params: {
|
|
7230
|
-
appFilters: [{
|
|
7231
|
-
metricId: "metric_123",
|
|
7232
|
-
values: {
|
|
7233
|
-
region: ["US", "CA"]
|
|
7234
|
-
}
|
|
7235
|
-
}]
|
|
7236
|
-
}
|
|
7237
|
-
})
|
|
7238
|
-
\`\`\`
|
|
7239
|
-
|
|
7240
|
-
**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.
|
|
7241
|
-
|
|
7242
|
-
**Reference**: Use \`databrain://filter-reference\` for all filter types, operators, and combination rules.`;
|
|
7243
|
-
|
|
7244
|
-
// src/prompts/manage-embeds.ts
|
|
7245
|
-
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.
|
|
7246
|
-
|
|
7247
|
-
**Step 1 \u2014 Understand the Task**
|
|
7248
|
-
Ask the user what they want to do:
|
|
7249
|
-
1. **List and compare embeds** \u2014 see all embeds and their configurations
|
|
7250
|
-
2. **Review embed settings** \u2014 audit theme, permissions, and options
|
|
7251
|
-
|
|
7252
|
-
**For Listing and Comparing Embeds:**
|
|
7253
|
-
1. Call \`list_embeds\` to show all embed configurations
|
|
7254
|
-
2. For detailed comparison, call \`get_embed_details\` on each embed the user wants to compare
|
|
7255
|
-
3. Present a comparison table showing:
|
|
7256
|
-
- Embed name and ID
|
|
7257
|
-
- Linked dashboard
|
|
7258
|
-
- Theme (preset or custom)
|
|
7259
|
-
- Key permissions (metric creation, downloads, AI pilot)
|
|
7260
|
-
- Multi-tenancy settings
|
|
7261
|
-
|
|
7262
|
-
**For Reviewing Embed Settings:**
|
|
7263
|
-
1. Call \`get_embed_details\` for the embed
|
|
7264
|
-
2. Present a clear summary organized by category:
|
|
7265
|
-
|
|
7266
|
-
**Theme:**
|
|
7267
|
-
- Current preset or custom theme values
|
|
7268
|
-
- Suggest \`customize_embed_theme\` if they want changes
|
|
7269
|
-
|
|
7270
|
-
**Permissions / Access Settings:**
|
|
7271
|
-
- What's enabled vs disabled
|
|
7272
|
-
- Suggest \`update_embed\` for changes
|
|
7273
|
-
|
|
7274
|
-
**Filters:**
|
|
7275
|
-
- Current filter configuration
|
|
7276
|
-
- Suggest \`update_embed\` (body passthrough with options.globalFilterOptions) for changes
|
|
7277
|
-
|
|
7278
|
-
**Internationalization:**
|
|
7279
|
-
- Current language and translations
|
|
7280
|
-
- Suggest \`update_embed\` (body passthrough with componentAttributes) for changes
|
|
7281
|
-
|
|
7282
|
-
3. Ask: "Would you like to modify any of these settings?"
|
|
7283
|
-
|
|
7284
|
-
**Tips:**
|
|
7285
|
-
- Embed IDs are permanent \u2014 even if you rename an embed, the ID stays the same
|
|
7286
|
-
- After any embed setting changes, existing guest tokens still carry the old configuration \u2014 **regenerate guest tokens** for changes to take effect`;
|
|
7287
|
-
|
|
7288
7404
|
// src/prompts/query-data.ts
|
|
7289
7405
|
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.
|
|
7290
7406
|
|
|
@@ -7441,6 +7557,77 @@ Suggest a sample question based on the tables you described and run it with \`as
|
|
|
7441
7557
|
- For very large schemas (50+ tables), start with the most important tables and expand as needed
|
|
7442
7558
|
`;
|
|
7443
7559
|
|
|
7560
|
+
// src/prompts/customize-customer-dashboard.ts
|
|
7561
|
+
var customizeCustomerDashboardPrompt = `You are guiding widget CRUD on a specific customer's dashboard embed. This is the "tweak per customer" flow: add, modify, or remove individual widgets (metrics) on a customer's embedded dashboard without touching the template or other customers.
|
|
7562
|
+
|
|
7563
|
+
**CRITICAL: This is an interactive, step-by-step guide. STOP after each step, show the user what you found, and wait for their input. Never batch multiple steps. Never assume the customer/embed.**
|
|
7564
|
+
|
|
7565
|
+
**Key concepts (read before starting):**
|
|
7566
|
+
- **Embed** = a customer's instantiation of a dashboard. Identified by \`embedId\`. Each embed is scoped to one \`clientId\` (the customer/tenant).
|
|
7567
|
+
- **Widget** = a single visualization on a dashboard. Stored as \`externalMetrics\` internally. Has two ids: \`metricId\` (user-facing slug, shown in lists) and \`externalMetricId\` (internal uuid, used for updates/deletes).
|
|
7568
|
+
- **Per-client scope**: \`remove_widget\` is a per-client soft delete \u2014 it hides the widget for one client only. The widget stays on the template dashboard and on other customers' embeds.
|
|
7569
|
+
- **Guest tokens auto-minted**: \`add_widget\`, \`update_widget\`, \`remove_widget\`, and \`generate_widget\` all mint a guest token per call. You do not need to call \`generate_guest_token\` manually for widget operations.
|
|
7570
|
+
|
|
7571
|
+
**Step 1 \u2014 Identify the target embed**
|
|
7572
|
+
STOP and ASK the user:
|
|
7573
|
+
- "Which customer/client are we customizing for?" (the \`clientId\`)
|
|
7574
|
+
- "Which embed/dashboard?" (the \`embedId\`)
|
|
7575
|
+
|
|
7576
|
+
If the user only knows the customer name, call \`list_data_apps\` then \`list_embeds\` (filtered by \`dataAppName\`) and present the matching embeds. Do NOT proceed until you have both \`embedId\` and \`clientId\` confirmed.
|
|
7577
|
+
|
|
7578
|
+
**Step 2 \u2014 Survey the current widgets**
|
|
7579
|
+
Call \`list_widgets({ embedId, clientId })\`.
|
|
7580
|
+
STOP and present the list: \`{metricId, name, isPublished, createdByUser}\` per widget.
|
|
7581
|
+
ASK: "What would you like to change? (add / modify / remove)" \u2014 or more specifically, "Which widgets should I add, update, or remove?"
|
|
7582
|
+
|
|
7583
|
+
**Step 3a \u2014 ADD a widget**
|
|
7584
|
+
If the user wants to add a widget, STOP and ASK which sourcing mode:
|
|
7585
|
+
|
|
7586
|
+
1. **Clone from template** (cheapest, most common):
|
|
7587
|
+
- ASK for the source \`templateMetricId\` \u2014 or call \`list_widgets\` on the template embed / another customer's embed to help pick.
|
|
7588
|
+
- Call \`add_widget({ embedId, clientId, templateMetricId, name? })\`. The tool inherits query, chartOptions, and datasource from the template.
|
|
7589
|
+
|
|
7590
|
+
2. **Fresh spec** (SQL + chart config):
|
|
7591
|
+
- ASK for: \`name\`, \`query\` (SQL), \`chartOptions\` (chart type + config), \`companyIntegrationId\` (datasource ID \u2014 use \`list_datasources\` if needed).
|
|
7592
|
+
- Call \`add_widget({ embedId, clientId, name, query, chartOptions, companyIntegrationId, ... })\`.
|
|
7593
|
+
|
|
7594
|
+
3. **AI generate** (NL prompt):
|
|
7595
|
+
- ASK for a prompt describing the widget (e.g., "calls per day over last 30 days") and the \`integrationId\` / \`integrationName\` to query against.
|
|
7596
|
+
- Call \`generate_widget({ clientId, dataAppName, integrationId, integrationName, userInputs: { prompt: "..." } })\`.
|
|
7597
|
+
- Review the returned \`draft\` with the user. If they approve, call \`add_widget({ embedId, clientId, body: <draft + externalDashboardIds> })\` to persist.
|
|
7598
|
+
- If they want changes, re-run \`generate_widget\` with a refined prompt.
|
|
7599
|
+
|
|
7600
|
+
Show the user the new widget's \`externalMetricId\` and \`metricId\` after each add.
|
|
7601
|
+
|
|
7602
|
+
**Step 3b \u2014 UPDATE a widget**
|
|
7603
|
+
If the user wants to modify a widget, STOP and ASK:
|
|
7604
|
+
- Which widget? (look up its \`externalMetricId\` from Step 2 \u2014 remember, this is the uuid, NOT the \`metricId\` slug)
|
|
7605
|
+
- What to change? (name, query, chartOptions, isPublished, etc.)
|
|
7606
|
+
|
|
7607
|
+
Call \`update_widget({ externalMetricId, clientId, dataAppName, set: {<partial patch>} })\`.
|
|
7608
|
+
Show the user the confirmation and list the changed fields.
|
|
7609
|
+
|
|
7610
|
+
**Step 3c \u2014 REMOVE a widget**
|
|
7611
|
+
If the user wants to remove a widget, STOP and CONFIRM:
|
|
7612
|
+
- "This is a per-client soft delete. The widget will be hidden for \`<clientId>\` only \u2014 other customers and the template dashboard are unaffected. Proceed?"
|
|
7613
|
+
|
|
7614
|
+
Wait for explicit confirmation before calling \`remove_widget({ externalMetricId, clientId, dataAppName })\`.
|
|
7615
|
+
|
|
7616
|
+
**Step 4 \u2014 Verify**
|
|
7617
|
+
Call \`list_widgets({ embedId, clientId })\` again. Present the updated list and confirm the changes match what the user asked for.
|
|
7618
|
+
|
|
7619
|
+
**Step 5 \u2014 (Optional) regenerate guest token for live preview**
|
|
7620
|
+
If the user wants to see the changes in their running app:
|
|
7621
|
+
- Remind them: "Existing guest tokens carry the old embed config. Regenerate via \`generate_guest_token({ clientId, dataAppName })\` and reload the frontend to see the changes."
|
|
7622
|
+
|
|
7623
|
+
**Important rules:**
|
|
7624
|
+
- STOP after each step. Present results and wait for user input before proceeding.
|
|
7625
|
+
- Always pass \`externalMetricId\` (uuid) to \`update_widget\` / \`remove_widget\`, NOT the \`metricId\` slug. When in doubt, use \`list_widgets\` output \u2014 but note that \`list_widgets\` shows \`metricId\`, so you may need \`get_embed_details\` or context from \`add_widget\`'s response to find the uuid.
|
|
7626
|
+
- Never batch add + update + remove without confirming each one.
|
|
7627
|
+
- If the customer-facing effect is not immediate (e.g., guest token cached), say so explicitly in your handoff message.
|
|
7628
|
+
|
|
7629
|
+
After completion, suggest: \`brand-my-embed\` if they want to adjust theme, or \`manage-embeds\` if they want to tweak access settings / options.`;
|
|
7630
|
+
|
|
7444
7631
|
// src/prompts/registry.ts
|
|
7445
7632
|
var prompts = [
|
|
7446
7633
|
{
|
|
@@ -7453,36 +7640,11 @@ var prompts = [
|
|
|
7453
7640
|
description: "Multi-tenant blank dashboard setup: each client gets their own dashboard copy via create_embed with clientId",
|
|
7454
7641
|
content: embedBlankDashboardPrompt
|
|
7455
7642
|
},
|
|
7456
|
-
{
|
|
7457
|
-
name: "enable-metric-creation",
|
|
7458
|
-
description: "Enable end-user metric creation on embeds: DRAG_DROP (visual) or CHAT (AI-powered) modes",
|
|
7459
|
-
content: enableMetricCreationPrompt
|
|
7460
|
-
},
|
|
7461
7643
|
{
|
|
7462
7644
|
name: "brand-my-embed",
|
|
7463
7645
|
description: "Customize colors, fonts, chart styles, and component theme to match your brand",
|
|
7464
7646
|
content: brandMyEmbedPrompt
|
|
7465
7647
|
},
|
|
7466
|
-
{
|
|
7467
|
-
name: "localize-embed",
|
|
7468
|
-
description: "Set up multi-language support with translations and locale settings",
|
|
7469
|
-
content: localizeEmbedPrompt
|
|
7470
|
-
},
|
|
7471
|
-
{
|
|
7472
|
-
name: "secure-my-embed",
|
|
7473
|
-
description: "Configure permissions, access controls, and role-based guest tokens",
|
|
7474
|
-
content: secureMyEmbedPrompt
|
|
7475
|
-
},
|
|
7476
|
-
{
|
|
7477
|
-
name: "setup-filters",
|
|
7478
|
-
description: "Configure dashboard filters: string, date, number, SQL-based, and server-side filters",
|
|
7479
|
-
content: setupFiltersPrompt
|
|
7480
|
-
},
|
|
7481
|
-
{
|
|
7482
|
-
name: "manage-embeds",
|
|
7483
|
-
description: "List, compare, rename, delete embeds and rotate API tokens",
|
|
7484
|
-
content: manageEmbedsPrompt
|
|
7485
|
-
},
|
|
7486
7648
|
{
|
|
7487
7649
|
name: "query-data",
|
|
7488
7650
|
description: "Query your data with natural language: ask questions, get results with chart suggestions, manage the semantic layer",
|
|
@@ -7492,6 +7654,11 @@ var prompts = [
|
|
|
7492
7654
|
name: "populate-semantic-layer",
|
|
7493
7655
|
description: "Auto-generate and push table/column descriptions to enable natural language querying on a datamart",
|
|
7494
7656
|
content: populateSemanticLayerPrompt
|
|
7657
|
+
},
|
|
7658
|
+
{
|
|
7659
|
+
name: "customize-customer-dashboard",
|
|
7660
|
+
description: "Add, modify, or remove widgets on a specific customer's dashboard embed. Per-client scope \u2014 changes only affect that customer.",
|
|
7661
|
+
content: customizeCustomerDashboardPrompt
|
|
7495
7662
|
}
|
|
7496
7663
|
];
|
|
7497
7664
|
function registerAllPrompts(server) {
|
|
@@ -7523,16 +7690,15 @@ function createServer(config) {
|
|
|
7523
7690
|
\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.
|
|
7524
7691
|
\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.
|
|
7525
7692
|
|
|
7526
|
-
|
|
7693
|
+
3 core use cases \u2014 match the user's scenario to the right prompt:
|
|
7527
7694
|
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)
|
|
7528
7695
|
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)
|
|
7529
|
-
3.
|
|
7530
|
-
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)
|
|
7696
|
+
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)
|
|
7531
7697
|
|
|
7532
7698
|
Infrastructure CREATION (datasources, datamarts, workspaces) is done in the Databrain UI.
|
|
7533
7699
|
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.
|
|
7534
7700
|
|
|
7535
|
-
After embed setup, customize with: brand-my-embed
|
|
7701
|
+
After embed setup, customize with: brand-my-embed prompt for theming. Use update_embed (body passthrough) for filters, localization, and permissions.
|
|
7536
7702
|
Use generate_guest_token for frontend auth and generate_embed_code for framework snippets.
|
|
7537
7703
|
For advanced embed configuration (UI toggles, filters, i18n), use update_embed with a body passthrough.
|
|
7538
7704
|
To diagnose embed issues, use get_embed_details and reason about the response.
|
|
@@ -7566,7 +7732,7 @@ async function main() {
|
|
|
7566
7732
|
const server = createServer(config);
|
|
7567
7733
|
const transport = new StdioServerTransport();
|
|
7568
7734
|
await server.connect(transport);
|
|
7569
|
-
banner(`v${VERSION} ready (
|
|
7735
|
+
banner(`v${VERSION} ready (21 tools, 11 resources, 5 prompts)`);
|
|
7570
7736
|
} catch (error) {
|
|
7571
7737
|
logger.error("Failed to start server", error);
|
|
7572
7738
|
process.exit(1);
|