@databrainhq/mcp-server 0.3.0-rc.11 → 0.3.0-rc.12
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 +8 -5
- package/dist/apps/server.d.ts +35 -7
- package/dist/apps/server.js +4 -4
- package/dist/apps/stdio.js +5 -5
- package/dist/{chunk-RHK62ZJ6.js → chunk-347U6GXR.js} +3 -3
- package/dist/chunk-347U6GXR.js.map +1 -0
- package/dist/{chunk-IBYBK5ZE.js → chunk-LACJHQYH.js} +4 -4
- package/dist/{chunk-ES2LUP4U.js → chunk-OLOC2MTA.js} +245 -89
- package/dist/chunk-OLOC2MTA.js.map +1 -0
- package/dist/{chunk-DM7S4R57.js → chunk-V3UW3B6U.js} +2 -2
- package/dist/{chunk-NBFUKVWP.js → chunk-ZTHGF27R.js} +99 -15
- package/dist/chunk-ZTHGF27R.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +4 -4
- package/dist/{main-JEYYFGJM.js → main-IUV24DMP.js} +28 -20
- package/dist/main-IUV24DMP.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-ES2LUP4U.js.map +0 -1
- package/dist/chunk-NBFUKVWP.js.map +0 -1
- package/dist/chunk-RHK62ZJ6.js.map +0 -1
- package/dist/main-JEYYFGJM.js.map +0 -1
- /package/dist/{chunk-IBYBK5ZE.js.map → chunk-LACJHQYH.js.map} +0 -0
- /package/dist/{chunk-DM7S4R57.js.map → chunk-V3UW3B6U.js.map} +0 -0
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
toolContext,
|
|
14
14
|
toolError,
|
|
15
15
|
toolSuccess
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-347U6GXR.js";
|
|
17
17
|
import {
|
|
18
18
|
MAX_BUNDLE_CSS_BYTES,
|
|
19
19
|
MAX_BUNDLE_JS_BYTES,
|
|
@@ -42,7 +42,10 @@ var CUSTOM_BUNDLE_CHUNK_CHARS = 32e3;
|
|
|
42
42
|
var id = z.string().trim().min(1).max(256);
|
|
43
43
|
var cell = z.union([z.string().max(2e3), z.number().finite(), z.boolean(), z.null()]);
|
|
44
44
|
var rowsSchema = z.array(z.record(cell)).max(PAGE_SIZE);
|
|
45
|
-
var referenceSchema = z.
|
|
45
|
+
var referenceSchema = z.union([
|
|
46
|
+
z.object({ embedId: id, clientId: id, widgetId: id, dataAppName: id.optional() }).strict(),
|
|
47
|
+
z.object({ dashboardId: id, widgetId: id, clientId: id.optional(), display: z.enum(["chart", "kpi"]).optional() }).strict()
|
|
48
|
+
]);
|
|
46
49
|
var point = z.object({
|
|
47
50
|
key: id,
|
|
48
51
|
label: z.string().max(2e3),
|
|
@@ -63,8 +66,10 @@ var viewSchema = z.object({
|
|
|
63
66
|
drillId: id.optional()
|
|
64
67
|
}).strict();
|
|
65
68
|
var pageSchema = z.object({
|
|
66
|
-
kind: z.enum(["chart", "kpi"]),
|
|
69
|
+
kind: z.enum(["chart", "kpi", "table"]),
|
|
67
70
|
title: z.string().max(500),
|
|
71
|
+
description: z.string().max(2e3).optional(),
|
|
72
|
+
rowsTotal: z.number().int().nonnegative().optional(),
|
|
68
73
|
chartSpec: z.object({ type: z.string().max(80) }).strict(),
|
|
69
74
|
rows: rowsSchema,
|
|
70
75
|
columns: z.array(z.string().max(128)).max(20),
|
|
@@ -85,6 +90,9 @@ var pageSchema = z.object({
|
|
|
85
90
|
if (page.kind === "kpi" && (page.svg || page.points.length || page.value === void 0)) {
|
|
86
91
|
ctx.addIssue({ code: "custom", message: "KPI requires a value and no SVG/points" });
|
|
87
92
|
}
|
|
93
|
+
if (page.kind === "table" && (page.svg || page.points.length || page.value !== void 0)) {
|
|
94
|
+
ctx.addIssue({ code: "custom", message: "Table requires rows without SVG/points/value" });
|
|
95
|
+
}
|
|
88
96
|
if (new Set(page.points.map((p) => p.key)).size !== page.points.length || page.points.some((p) => p.x + p.width > page.width || p.y + p.height > page.height)) {
|
|
89
97
|
ctx.addIssue({ code: "custom", message: "Invalid point geometry" });
|
|
90
98
|
}
|
|
@@ -105,8 +113,8 @@ function firstRows(value) {
|
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
// src/apps/feature.ts
|
|
108
|
-
function appMeta(visibility) {
|
|
109
|
-
return { ui: { resourceUri:
|
|
116
|
+
function appMeta(visibility, uri = CHART_URI) {
|
|
117
|
+
return { ui: { resourceUri: uri, visibility }, [RESOURCE_URI_META_KEY]: uri };
|
|
110
118
|
}
|
|
111
119
|
var enabled = /* @__PURE__ */ new WeakSet();
|
|
112
120
|
function enableChartApps(server) {
|
|
@@ -122,9 +130,14 @@ function chartResult(summary, payload) {
|
|
|
122
130
|
return bounded({
|
|
123
131
|
content: [{ type: "text", text: summary }],
|
|
124
132
|
structuredContent: payload,
|
|
125
|
-
_meta: appMeta(["model", "app"])
|
|
133
|
+
_meta: appMeta(["model", "app"], payload.kind === "kpi" ? KPI_URI : CHART_URI)
|
|
126
134
|
});
|
|
127
135
|
}
|
|
136
|
+
function chartIndex(dashboardId, charts) {
|
|
137
|
+
return `To draw one, call render_metric_tile with dashboardId ${JSON.stringify(dashboardId)} and metricId set to a widgetId below.
|
|
138
|
+
` + JSON.stringify(charts.slice(0, 50).map((c) => ({ widgetId: c.widgetId, name: (c.name || "Untitled widget").slice(0, 200), chartType: c.chartType || null }))) + (charts.length > 50 ? `
|
|
139
|
+
Showing the first 50 of ${charts.length}.` : "");
|
|
140
|
+
}
|
|
128
141
|
|
|
129
142
|
// src/tools/custom-chart/author.ts
|
|
130
143
|
import { z as z3 } from "zod";
|
|
@@ -381,6 +394,7 @@ var TOOL_METADATA = {
|
|
|
381
394
|
preview_chart: { category: "internal_analytics", workflowIntent: "query_data", effect: "view" },
|
|
382
395
|
save_chart_as_dashboard: { category: "internal_analytics", workflowIntent: "author_dashboard", effect: "commit" },
|
|
383
396
|
// Read-only to the user, but each interaction mints an embed-scoped guest credential.
|
|
397
|
+
render_metric_tile: { category: "internal_analytics", workflowIntent: "chart_app", effect: "read" },
|
|
384
398
|
render_chart: { category: "embedding", workflowIntent: "chart_app", effect: "commit" },
|
|
385
399
|
refresh_widget: { category: "embedding", workflowIntent: "chart_app", effect: "commit" },
|
|
386
400
|
drill_down: { category: "embedding", workflowIntent: "chart_app", effect: "commit" },
|
|
@@ -602,10 +616,12 @@ var EmbedsApi = class {
|
|
|
602
616
|
this.client = client;
|
|
603
617
|
}
|
|
604
618
|
async createEmbed(body) {
|
|
619
|
+
const { dataAppName, ...payload } = body;
|
|
605
620
|
return this.client.request({
|
|
606
621
|
method: "POST",
|
|
607
622
|
path: "/api/v2/data-app/embeds",
|
|
608
|
-
|
|
623
|
+
query: typeof dataAppName === "string" ? { dataAppName } : void 0,
|
|
624
|
+
body: payload,
|
|
609
625
|
auth: "api",
|
|
610
626
|
invalidateCache: ["list_embeds"]
|
|
611
627
|
});
|
|
@@ -627,12 +643,12 @@ var EmbedsApi = class {
|
|
|
627
643
|
cacheTtlMs: 5 * 60 * 1e3
|
|
628
644
|
});
|
|
629
645
|
}
|
|
630
|
-
async getByEmbedId(embedId) {
|
|
646
|
+
async getByEmbedId(embedId, dataAppName) {
|
|
631
647
|
return this.client.request({
|
|
632
648
|
method: "GET",
|
|
633
649
|
path: "/api/v2/data-app/embeds",
|
|
634
650
|
auth: "api",
|
|
635
|
-
query: { embedId }
|
|
651
|
+
query: { embedId, ...dataAppName ? { dataAppName } : {} }
|
|
636
652
|
});
|
|
637
653
|
}
|
|
638
654
|
async updateEmbed(body) {
|
|
@@ -712,7 +728,7 @@ var QueryApi = class {
|
|
|
712
728
|
async startSemanticLayerGeneration(datamartId2) {
|
|
713
729
|
return this.client.request({
|
|
714
730
|
method: "POST",
|
|
715
|
-
path: `/
|
|
731
|
+
path: `/schema/datamart/${encodeURIComponent(datamartId2)}/semantic-layer/auto-generate`,
|
|
716
732
|
auth: "service",
|
|
717
733
|
invalidateCache: ["semantic_layer_"]
|
|
718
734
|
});
|
|
@@ -720,7 +736,7 @@ var QueryApi = class {
|
|
|
720
736
|
async getSemanticLayerGenerationStatus(datamartId2) {
|
|
721
737
|
return this.client.request({
|
|
722
738
|
method: "GET",
|
|
723
|
-
path: `/
|
|
739
|
+
path: `/schema/datamart/${encodeURIComponent(datamartId2)}/semantic-layer/generation-status`,
|
|
724
740
|
auth: "service"
|
|
725
741
|
});
|
|
726
742
|
}
|
|
@@ -759,6 +775,7 @@ var ScheduledReportsApi = class {
|
|
|
759
775
|
embedId: params.embedId,
|
|
760
776
|
clientId: params.clientId
|
|
761
777
|
};
|
|
778
|
+
if (params.dataAppName) query.dataAppName = params.dataAppName;
|
|
762
779
|
if (params.userIdentifier) query.userIdentifier = params.userIdentifier;
|
|
763
780
|
if (params.pageNumber !== void 0) query.pageNumber = String(params.pageNumber);
|
|
764
781
|
if (params.isPagination !== void 0) {
|
|
@@ -782,15 +799,15 @@ var SemanticModelApi = class {
|
|
|
782
799
|
path(id2, verb) {
|
|
783
800
|
return `/api/v2/semantic/${encodeURIComponent(id2)}/${verb}`;
|
|
784
801
|
}
|
|
785
|
-
pull(id2) {
|
|
786
|
-
return this.client.request({ method: "GET", path: this.path(id2, "pull"), auth: "service" });
|
|
802
|
+
pull(id2, mode) {
|
|
803
|
+
return this.client.request({ method: "GET", path: this.path(id2, "pull"), auth: "service", ...mode ? { query: { mode } } : {} });
|
|
787
804
|
}
|
|
788
805
|
retrieve(id2, q, k) {
|
|
789
806
|
return this.client.request({
|
|
790
|
-
method: "
|
|
807
|
+
method: "POST",
|
|
791
808
|
path: this.path(id2, "retrieve"),
|
|
792
809
|
auth: "service",
|
|
793
|
-
|
|
810
|
+
body: { q, k }
|
|
794
811
|
});
|
|
795
812
|
}
|
|
796
813
|
validate(id2, source2) {
|
|
@@ -866,12 +883,12 @@ var WidgetsApi = class {
|
|
|
866
883
|
constructor(client) {
|
|
867
884
|
this.client = client;
|
|
868
885
|
}
|
|
869
|
-
async listWidgets(embedId, clientId) {
|
|
886
|
+
async listWidgets(embedId, clientId, dataAppName) {
|
|
870
887
|
return this.client.request({
|
|
871
888
|
method: "GET",
|
|
872
889
|
path: "/api/v2/data-app/metrics",
|
|
873
890
|
auth: "api",
|
|
874
|
-
query: { embedId, clientId }
|
|
891
|
+
query: { embedId, clientId, ...dataAppName ? { dataAppName } : {} }
|
|
875
892
|
});
|
|
876
893
|
}
|
|
877
894
|
async createMetric(guestToken, body) {
|
|
@@ -880,7 +897,7 @@ var WidgetsApi = class {
|
|
|
880
897
|
path: "/api/v2/dashboard/createMetric",
|
|
881
898
|
auth: "none",
|
|
882
899
|
token: guestToken,
|
|
883
|
-
body
|
|
900
|
+
body: { ...body, isLive: body.isLive ?? false }
|
|
884
901
|
});
|
|
885
902
|
}
|
|
886
903
|
async updateMetric(guestToken, body) {
|
|
@@ -895,7 +912,7 @@ var WidgetsApi = class {
|
|
|
895
912
|
async markArchived(guestToken, body) {
|
|
896
913
|
return this.client.request({
|
|
897
914
|
method: "POST",
|
|
898
|
-
path: "/api/
|
|
915
|
+
path: "/api/v3/metric/markArchived",
|
|
899
916
|
auth: "none",
|
|
900
917
|
token: guestToken,
|
|
901
918
|
body
|
|
@@ -961,7 +978,7 @@ var DiscoveryService = class {
|
|
|
961
978
|
normalizeDashboard(dashboard) {
|
|
962
979
|
return {
|
|
963
980
|
name: isNonEmptyString(dashboard.name) && dashboard.name || isNonEmptyString(dashboard.dashboardName) && dashboard.dashboardName || "Unnamed Dashboard",
|
|
964
|
-
dashboardId: isNonEmptyString(dashboard.dashboardId) && dashboard.dashboardId || isNonEmptyString(dashboard.
|
|
981
|
+
dashboardId: isNonEmptyString(dashboard.dashboardId) && dashboard.dashboardId || isNonEmptyString(dashboard._id) && dashboard._id || isNonEmptyString(dashboard.id) && dashboard.id || isNonEmptyString(dashboard.uid) && dashboard.uid || isNonEmptyString(dashboard.externalDashboardId) && dashboard.externalDashboardId || null,
|
|
965
982
|
externalDashboardId: isNonEmptyString(dashboard.externalDashboardId) && dashboard.externalDashboardId || null,
|
|
966
983
|
workspaceName: isNonEmptyString(dashboard.workspaceName) && dashboard.workspaceName || isNonEmptyString(dashboard.workspace) && dashboard.workspace || isNonEmptyString(dashboard.workspaceId) && dashboard.workspaceId || null,
|
|
967
984
|
metricCount: typeof dashboard.metricCount === "number" ? dashboard.metricCount : null
|
|
@@ -1077,8 +1094,8 @@ var EmbedService = class {
|
|
|
1077
1094
|
};
|
|
1078
1095
|
return this.embedsApi.createEmbed(body);
|
|
1079
1096
|
}
|
|
1080
|
-
async resolveEmbedStrict(embedId) {
|
|
1081
|
-
const response = await this.embedsApi.getByEmbedId(embedId);
|
|
1097
|
+
async resolveEmbedStrict(embedId, dataAppName) {
|
|
1098
|
+
const response = await this.embedsApi.getByEmbedId(embedId, dataAppName);
|
|
1082
1099
|
const records = Array.isArray(response) ? response.filter((r) => !!r && typeof r === "object") : [response];
|
|
1083
1100
|
const exact = records.filter((r) => r.embedId === embedId);
|
|
1084
1101
|
if (exact.length === 1) return exact[0];
|
|
@@ -1093,8 +1110,8 @@ var EmbedService = class {
|
|
|
1093
1110
|
Received records (sample): ${sample || "none"}`
|
|
1094
1111
|
);
|
|
1095
1112
|
}
|
|
1096
|
-
async buildUpdateBase(embedId) {
|
|
1097
|
-
const embed = await this.resolveEmbedStrict(embedId);
|
|
1113
|
+
async buildUpdateBase(embedId, dataAppName) {
|
|
1114
|
+
const embed = await this.resolveEmbedStrict(embedId, dataAppName);
|
|
1098
1115
|
const existingAccess = extractAccessSettings(embed);
|
|
1099
1116
|
if (existingAccess) {
|
|
1100
1117
|
return { embedId, accessSettings: existingAccess };
|
|
@@ -1142,8 +1159,8 @@ Run \`update_embed\` with explicit \`accessSettings.datamartName\`, or recreate
|
|
|
1142
1159
|
}
|
|
1143
1160
|
};
|
|
1144
1161
|
}
|
|
1145
|
-
async updateEmbed(embedId, updatePatch) {
|
|
1146
|
-
const body = await this.buildUpdateBase(embedId);
|
|
1162
|
+
async updateEmbed(embedId, updatePatch, dataAppName) {
|
|
1163
|
+
const body = await this.buildUpdateBase(embedId, dataAppName);
|
|
1147
1164
|
const baseAccessSettings = asRecord2(body.accessSettings);
|
|
1148
1165
|
const patchAccessSettings = asRecord2(updatePatch.accessSettings);
|
|
1149
1166
|
for (const [key, value] of Object.entries(updatePatch)) {
|
|
@@ -1158,7 +1175,7 @@ Run \`update_embed\` with explicit \`accessSettings.datamartName\`, or recreate
|
|
|
1158
1175
|
} else if (patchAccessSettings) {
|
|
1159
1176
|
body.accessSettings = patchAccessSettings;
|
|
1160
1177
|
}
|
|
1161
|
-
return this.embedsApi.updateEmbed(body);
|
|
1178
|
+
return this.embedsApi.updateEmbed({ ...body, ...dataAppName ? { dataAppName } : {} });
|
|
1162
1179
|
}
|
|
1163
1180
|
};
|
|
1164
1181
|
|
|
@@ -1226,7 +1243,7 @@ var WidgetService = class {
|
|
|
1226
1243
|
* where the user only knows the dashboard + workspace, not the embedId or
|
|
1227
1244
|
* data app name. Returns a business-user-friendly error on ambiguity.
|
|
1228
1245
|
*/
|
|
1229
|
-
async resolveEmbedFromDashboard(dashboardName, workspaceName) {
|
|
1246
|
+
async resolveEmbedFromDashboard(dashboardName, workspaceName, dataAppName) {
|
|
1230
1247
|
const wsProvided = workspaceName && workspaceName.trim().length > 0 ? workspaceName.trim() : void 0;
|
|
1231
1248
|
let dashboards;
|
|
1232
1249
|
try {
|
|
@@ -1268,8 +1285,23 @@ var WidgetService = class {
|
|
|
1268
1285
|
};
|
|
1269
1286
|
}
|
|
1270
1287
|
let embeds;
|
|
1288
|
+
let dataAppSelectionAvailable = false;
|
|
1271
1289
|
try {
|
|
1272
|
-
|
|
1290
|
+
try {
|
|
1291
|
+
embeds = await this.embedsApi.listEmbeds(dataAppName);
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
if (!(error instanceof DatabrainApiError) || error.code !== "DATA_APP_REQUIRED") throw error;
|
|
1294
|
+
dataAppSelectionAvailable = true;
|
|
1295
|
+
const apps = await this.dataAppsApi.listDataApps(dashboard.workspaceName || wsProvided);
|
|
1296
|
+
embeds = [];
|
|
1297
|
+
for (const app of apps) {
|
|
1298
|
+
const name = app.name || app.dataAppName;
|
|
1299
|
+
if (typeof name === "string" && name.trim()) {
|
|
1300
|
+
const scoped = await this.embedsApi.listEmbeds(name);
|
|
1301
|
+
embeds.push(...scoped.map((e) => ({ ...e, embedMetadata: { ...e.embedMetadata, dataAppName: name } })));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1273
1305
|
} catch (err) {
|
|
1274
1306
|
return {
|
|
1275
1307
|
ok: false,
|
|
@@ -1295,10 +1327,11 @@ var WidgetService = class {
|
|
|
1295
1327
|
};
|
|
1296
1328
|
}
|
|
1297
1329
|
if (embedMatches.length > 1) {
|
|
1298
|
-
const names = embedMatches.map((x) => `"${x.n.name}"`).join(", ");
|
|
1330
|
+
const names = embedMatches.map((x) => `"${x.n.name}" (embedId: ${x.n.embedId})`).join(", ");
|
|
1331
|
+
const apps = [...new Set(embedMatches.map((x) => x.n.dataAppName).filter(Boolean))];
|
|
1299
1332
|
return {
|
|
1300
1333
|
ok: false,
|
|
1301
|
-
error: `Dashboard "${dashboard.name}" has multiple embeds (${names}).
|
|
1334
|
+
error: `Dashboard "${dashboard.name}" has multiple embeds (${names}). ` + (dataAppSelectionAvailable && apps.length > 1 ? `Pass dataAppName to choose one of: ${apps.join(", ")}, or select an embedId. (DATA_APP_REQUIRED)` : "Select an embedId and retry.")
|
|
1302
1335
|
};
|
|
1303
1336
|
}
|
|
1304
1337
|
return {
|
|
@@ -1324,7 +1357,8 @@ var WidgetService = class {
|
|
|
1324
1357
|
if (params.dashboardName && params.dashboardName.trim().length > 0) {
|
|
1325
1358
|
const res = await this.resolveEmbedFromDashboard(
|
|
1326
1359
|
params.dashboardName,
|
|
1327
|
-
params.workspaceName
|
|
1360
|
+
params.workspaceName,
|
|
1361
|
+
params.dataAppName
|
|
1328
1362
|
);
|
|
1329
1363
|
if (!res.ok) return res;
|
|
1330
1364
|
return {
|
|
@@ -1374,7 +1408,7 @@ var WidgetService = class {
|
|
|
1374
1408
|
error: "`clientId` cannot be empty. Pick a tenant/client first."
|
|
1375
1409
|
};
|
|
1376
1410
|
}
|
|
1377
|
-
const ctx = await this.resolveEmbedContext(params.embedId);
|
|
1411
|
+
const ctx = await this.resolveEmbedContext(params.embedId, params.dataAppName);
|
|
1378
1412
|
if (ctx.externalDashboardCreatedBy === clientId) {
|
|
1379
1413
|
return {
|
|
1380
1414
|
ok: true,
|
|
@@ -1449,7 +1483,7 @@ var WidgetService = class {
|
|
|
1449
1483
|
}
|
|
1450
1484
|
let ctx;
|
|
1451
1485
|
try {
|
|
1452
|
-
ctx = await this.resolveEmbedContext(params.embedId);
|
|
1486
|
+
ctx = await this.resolveEmbedContext(params.embedId, params.dataAppName);
|
|
1453
1487
|
} catch (err) {
|
|
1454
1488
|
return { ok: false, error: err.message };
|
|
1455
1489
|
}
|
|
@@ -1557,7 +1591,17 @@ Do not guess between the two \u2014 wait for the user to choose.`
|
|
|
1557
1591
|
}
|
|
1558
1592
|
return false;
|
|
1559
1593
|
};
|
|
1560
|
-
|
|
1594
|
+
let target = datasources.find(matchPredicate);
|
|
1595
|
+
if (!target && provided.integrationId) {
|
|
1596
|
+
for (const datasource of datasources) {
|
|
1597
|
+
if (datasource._id || datasource.id || typeof datasource.name !== "string") continue;
|
|
1598
|
+
const id3 = await this.datasourcesApi.resolveIdByName(datasource.name);
|
|
1599
|
+
if (id3 === provided.integrationId) {
|
|
1600
|
+
target = { ...datasource, id: id3 };
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1561
1605
|
if (!target) {
|
|
1562
1606
|
const available = datasources.filter((ds) => typeof ds.name === "string" && ds.name.trim().length > 0).map((ds) => `"${ds.name}"`).join(", ");
|
|
1563
1607
|
const inactiveMatch = allDatasources.find(
|
|
@@ -1627,8 +1671,8 @@ Do not guess between the two \u2014 wait for the user to choose.`
|
|
|
1627
1671
|
}
|
|
1628
1672
|
return { kind: "no_metric_fields", widgetsTried: toProbe.length, fetchErrors };
|
|
1629
1673
|
}
|
|
1630
|
-
async resolveEmbedContext(embedId) {
|
|
1631
|
-
const response = await this.embedsApi.getByEmbedId(embedId);
|
|
1674
|
+
async resolveEmbedContext(embedId, dataAppNameHint) {
|
|
1675
|
+
const response = await this.embedsApi.getByEmbedId(embedId, dataAppNameHint);
|
|
1632
1676
|
const records = Array.isArray(response) ? response : response ? [response] : [];
|
|
1633
1677
|
const exact = records.find((r) => r && r.embedId === embedId) || records[0];
|
|
1634
1678
|
if (!exact) {
|
|
@@ -2171,12 +2215,13 @@ Save this in backend secrets immediately. This value may not be returned again.`
|
|
|
2171
2215
|
}
|
|
2172
2216
|
|
|
2173
2217
|
// src/tools/data-app/rotate-api-token.ts
|
|
2218
|
+
import { randomUUID } from "crypto";
|
|
2174
2219
|
import { z as z9 } from "zod";
|
|
2175
2220
|
function registerRotateApiToken(server, client) {
|
|
2176
2221
|
const services = createServiceBundle(client);
|
|
2177
2222
|
server.tool(
|
|
2178
2223
|
"rotate_api_token",
|
|
2179
|
-
"Rotate
|
|
2224
|
+
"Rotate through the raw token-authenticated endpoint, or issue a replacement token for a data app. The replacement fallback does not revoke existing tokens.",
|
|
2180
2225
|
{
|
|
2181
2226
|
dataAppName: z9.string().optional().describe("Data app name. Omit to list available data apps."),
|
|
2182
2227
|
tokenId: z9.string().optional().describe("Optional token ID to rotate"),
|
|
@@ -2216,13 +2261,14 @@ function registerRotateApiToken(server, client) {
|
|
|
2216
2261
|
` : "") + "Update backend secrets immediately."
|
|
2217
2262
|
);
|
|
2218
2263
|
}
|
|
2264
|
+
if (params.tokenId) return toolError("Rotating a named token requires the token-authenticated raw body endpoint. No replacement was issued. Use the API token settings to retire a specific token.");
|
|
2219
2265
|
const createRes = await client.request({
|
|
2220
2266
|
method: "POST",
|
|
2221
2267
|
path: "/api/v2/data-app/api-tokens",
|
|
2222
2268
|
auth: "service",
|
|
2223
2269
|
body: {
|
|
2224
2270
|
dataAppName,
|
|
2225
|
-
name: `${dataAppName}-
|
|
2271
|
+
name: `${dataAppName}-rotated-${randomUUID()}`
|
|
2226
2272
|
}
|
|
2227
2273
|
});
|
|
2228
2274
|
if (createRes.error) {
|
|
@@ -2235,7 +2281,7 @@ function registerRotateApiToken(server, client) {
|
|
|
2235
2281
|
|
|
2236
2282
|
` + (key ? `**New Token:** \`${key}\`
|
|
2237
2283
|
|
|
2238
|
-
` : "") + "
|
|
2284
|
+
` : "") + "Existing tokens remain active. Update backend secrets, then retire the old token in API token settings."
|
|
2239
2285
|
);
|
|
2240
2286
|
} catch (err) {
|
|
2241
2287
|
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
@@ -2745,7 +2791,8 @@ function registerCreateEmbed(server, client) {
|
|
|
2745
2791
|
body: z14.record(z14.unknown()).optional().describe(
|
|
2746
2792
|
"Raw API body passthrough. If provided, all other fields are ignored."
|
|
2747
2793
|
),
|
|
2748
|
-
|
|
2794
|
+
dataAppName: z14.string().optional().describe("Data app name; required when the workspace has more than one"),
|
|
2795
|
+
dashboardId: z14.string().optional().describe("Existing dashboard UUID or externalDashboardId slug for a standard embed; a new unique slug for a client dashboard copy"),
|
|
2749
2796
|
workspaceName: z14.string().optional().describe("Workspace name. Omit to auto-resolve if only one exists."),
|
|
2750
2797
|
embedType: z14.enum(["dashboard", "metric"]).optional().describe("Embed type"),
|
|
2751
2798
|
metricId: z14.string().optional().describe("Required for metric embed type"),
|
|
@@ -2755,15 +2802,23 @@ function registerCreateEmbed(server, client) {
|
|
|
2755
2802
|
clientId: z14.string().optional().describe(
|
|
2756
2803
|
"Client identifier for multi-tenant isolation. When provided, creates a dashboard embed (each client gets their own dashboard copy)."
|
|
2757
2804
|
),
|
|
2758
|
-
templateDashboardId: z14.string().optional().describe("Template dashboard
|
|
2805
|
+
templateDashboardId: z14.string().optional().describe("Template dashboard UUID or externalDashboardId slug to clone metrics from"),
|
|
2759
2806
|
isImportMetrics: z14.boolean().optional().describe("Import metrics from template dashboard"),
|
|
2760
|
-
metricImportMode: z14.enum(["
|
|
2807
|
+
metricImportMode: z14.enum(["GALLERY", "METRIC"]).optional().describe("GALLERY imports dashboard gallery metrics; METRIC imports selected metrics"),
|
|
2761
2808
|
isRenameDashboard: z14.boolean().optional().describe("Rename the dashboard for this client")
|
|
2762
2809
|
},
|
|
2763
2810
|
WRITE_CREATE,
|
|
2764
2811
|
async (params) => {
|
|
2765
2812
|
try {
|
|
2766
2813
|
const isMultiTenant = !!params.clientId;
|
|
2814
|
+
const dashboardSlug = async (value, workspace) => {
|
|
2815
|
+
if (typeof value !== "string" || !z14.string().uuid().safeParse(value).success) return value;
|
|
2816
|
+
if (typeof workspace !== "string" || !workspace) throw new Error("workspaceName is required to resolve a dashboard UUID.");
|
|
2817
|
+
const boards = await services.dashboardsApi.listDashboardsForWorkspace(workspace);
|
|
2818
|
+
const board = boards.find((item) => item.id === value || item.dashboardId === value);
|
|
2819
|
+
if (!board?.externalDashboardId) throw new Error("Dashboard not found in this workspace. Use list_dashboards to choose one.");
|
|
2820
|
+
return board.externalDashboardId;
|
|
2821
|
+
};
|
|
2767
2822
|
let resolvedWorkspaceName = params.workspaceName;
|
|
2768
2823
|
if (!params.body && isMultiTenant && !resolvedWorkspaceName) {
|
|
2769
2824
|
const workspaces = await services.workspacesApi.listWorkspaces();
|
|
@@ -2781,6 +2836,7 @@ function registerCreateEmbed(server, client) {
|
|
|
2781
2836
|
if (isMultiTenant) {
|
|
2782
2837
|
const body2 = params.body ? { ...params.body } : {
|
|
2783
2838
|
dashboardId: params.dashboardId,
|
|
2839
|
+
dataAppName: params.dataAppName,
|
|
2784
2840
|
workspaceName: resolvedWorkspaceName,
|
|
2785
2841
|
clientId: params.clientId,
|
|
2786
2842
|
name: params.name,
|
|
@@ -2792,10 +2848,13 @@ function registerCreateEmbed(server, client) {
|
|
|
2792
2848
|
};
|
|
2793
2849
|
const callerAccess = body2.accessSettings && typeof body2.accessSettings === "object" ? body2.accessSettings : {};
|
|
2794
2850
|
body2.accessSettings = { ...DEFAULT_ACCESS_SETTINGS2, ...callerAccess };
|
|
2851
|
+
if (body2.templateDashboardId) body2.templateDashboardId = await dashboardSlug(body2.templateDashboardId, body2.workspaceName);
|
|
2852
|
+
const { dataAppName, ...payload } = body2;
|
|
2795
2853
|
const result2 = await client.request({
|
|
2796
2854
|
method: "POST",
|
|
2797
2855
|
path: "/api/v2/data-app/dashboard-embeds",
|
|
2798
|
-
|
|
2856
|
+
query: typeof dataAppName === "string" ? { dataAppName } : void 0,
|
|
2857
|
+
body: payload,
|
|
2799
2858
|
auth: "api",
|
|
2800
2859
|
invalidateCache: ["list_embeds"]
|
|
2801
2860
|
});
|
|
@@ -2816,6 +2875,7 @@ Next: use \`generate_guest_token\` with the clientId and \`generate_embed_code\`
|
|
|
2816
2875
|
}
|
|
2817
2876
|
const body = params.body ? { ...params.body } : {
|
|
2818
2877
|
dashboardId: params.dashboardId,
|
|
2878
|
+
dataAppName: params.dataAppName,
|
|
2819
2879
|
workspaceName: params.workspaceName,
|
|
2820
2880
|
embedType: params.embedType || "dashboard",
|
|
2821
2881
|
metricId: params.metricId,
|
|
@@ -2836,6 +2896,7 @@ Next: use \`generate_guest_token\` with the clientId and \`generate_embed_code\`
|
|
|
2836
2896
|
if (!datamartName || typeof datamartName !== "string") {
|
|
2837
2897
|
return toolError("create_embed requires `accessSettings.datamartName`.");
|
|
2838
2898
|
}
|
|
2899
|
+
body.dashboardId = await dashboardSlug(body.dashboardId, body.workspaceName);
|
|
2839
2900
|
const result = await services.embedService.createEmbedStrict(body);
|
|
2840
2901
|
if (result.error) {
|
|
2841
2902
|
return toolError(`Embed creation failed: ${stringify(result.error)}`);
|
|
@@ -2925,12 +2986,13 @@ function registerGetEmbedDetails(server, client) {
|
|
|
2925
2986
|
"get_embed_details",
|
|
2926
2987
|
"Get full embed configuration including access settings and theme.",
|
|
2927
2988
|
{
|
|
2928
|
-
embedId: z16.string().describe("ID of the embed")
|
|
2989
|
+
embedId: z16.string().describe("ID of the embed"),
|
|
2990
|
+
dataAppName: z16.string().trim().min(1).optional().describe("Data app when this embed ID belongs to several apps")
|
|
2929
2991
|
},
|
|
2930
2992
|
READ_ONLY,
|
|
2931
2993
|
async (params) => {
|
|
2932
2994
|
try {
|
|
2933
|
-
const embed = await services.embedService.resolveEmbedStrict(params.embedId);
|
|
2995
|
+
const embed = await services.embedService.resolveEmbedStrict(params.embedId, params.dataAppName);
|
|
2934
2996
|
return toolSuccess(JSON.stringify({ embedId: params.embedId, ...embed }));
|
|
2935
2997
|
} catch (err) {
|
|
2936
2998
|
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
@@ -2980,6 +3042,7 @@ function registerUpdateEmbed(server, client) {
|
|
|
2980
3042
|
"Update embed access settings, permissions, theme, or name.",
|
|
2981
3043
|
{
|
|
2982
3044
|
embedId: z17.string().describe("ID of the embed to update"),
|
|
3045
|
+
dataAppName: z17.string().trim().min(1).optional().describe("Data app when this embed ID belongs to several apps"),
|
|
2983
3046
|
body: z17.record(z17.unknown()).optional().describe(
|
|
2984
3047
|
"Raw API body for PUT /api/v2/data-app/embeds. If provided, it is sent directly (passthrough mode)."
|
|
2985
3048
|
),
|
|
@@ -2997,6 +3060,7 @@ function registerUpdateEmbed(server, client) {
|
|
|
2997
3060
|
if (params.body) {
|
|
2998
3061
|
const rawBody = { ...params.body };
|
|
2999
3062
|
if (!rawBody.embedId) rawBody.embedId = params.embedId;
|
|
3063
|
+
if (params.dataAppName) rawBody.dataAppName = params.dataAppName;
|
|
3000
3064
|
const result2 = await services.embedsApi.updateEmbed(rawBody);
|
|
3001
3065
|
if (result2.error) {
|
|
3002
3066
|
return toolError(`Embed update failed: ${stringify(result2.error)}`);
|
|
@@ -3011,7 +3075,8 @@ function registerUpdateEmbed(server, client) {
|
|
|
3011
3075
|
if (params.theme) patch.theme = params.theme;
|
|
3012
3076
|
const result = await services.embedService.updateEmbed(
|
|
3013
3077
|
params.embedId,
|
|
3014
|
-
patch
|
|
3078
|
+
patch,
|
|
3079
|
+
params.dataAppName
|
|
3015
3080
|
);
|
|
3016
3081
|
if (result.error) {
|
|
3017
3082
|
return toolError(`Embed update failed: ${stringify(result.error)}`);
|
|
@@ -3373,6 +3438,7 @@ function registerCustomizeTheme(server, client) {
|
|
|
3373
3438
|
`Theme an embed: presets (${Object.keys(THEME_PRESETS).join("/")}), colors, fonts, cards, chart appearance, responsive layout, or access presets (${Object.keys(ACCESS_PRESETS).join("/")}).`,
|
|
3374
3439
|
{
|
|
3375
3440
|
embedId: z18.string().describe("ID of the embed to theme"),
|
|
3441
|
+
dataAppName: z18.string().trim().min(1).optional().describe("Data app when this embed ID belongs to several apps"),
|
|
3376
3442
|
body: z18.record(z18.unknown()).optional().describe(
|
|
3377
3443
|
"Raw API body for PUT /api/v2/data-app/embeds. If provided, sent directly and all convenience fields are ignored."
|
|
3378
3444
|
),
|
|
@@ -3485,11 +3551,12 @@ function registerCustomizeTheme(server, client) {
|
|
|
3485
3551
|
},
|
|
3486
3552
|
WRITE_IDEMPOTENT,
|
|
3487
3553
|
async (params) => {
|
|
3488
|
-
const { embedId, body, themePreset, accessPreset, ...themeParams } = params;
|
|
3554
|
+
const { embedId, dataAppName, body, themePreset, accessPreset, ...themeParams } = params;
|
|
3489
3555
|
if (body) {
|
|
3490
3556
|
try {
|
|
3491
3557
|
const rawBody = { ...body };
|
|
3492
3558
|
if (!rawBody.embedId) rawBody.embedId = embedId;
|
|
3559
|
+
if (dataAppName) rawBody.dataAppName = dataAppName;
|
|
3493
3560
|
const result = await services.embedsApi.updateEmbed(rawBody);
|
|
3494
3561
|
if (result.error) {
|
|
3495
3562
|
return toolError(`Theme update failed: ${stringify(result.error)}`);
|
|
@@ -3522,7 +3589,7 @@ function registerCustomizeTheme(server, client) {
|
|
|
3522
3589
|
return toolError("No theme properties provided. Specify at least one property, or use a themePreset.");
|
|
3523
3590
|
}
|
|
3524
3591
|
try {
|
|
3525
|
-
const result = await services.embedService.updateEmbed(embedId, patch);
|
|
3592
|
+
const result = await services.embedService.updateEmbed(embedId, patch, dataAppName);
|
|
3526
3593
|
if (result.error) {
|
|
3527
3594
|
return toolError(`Theme update failed: ${stringify(result.error)}`);
|
|
3528
3595
|
}
|
|
@@ -3567,6 +3634,7 @@ function registerGetDemoLink(server, client) {
|
|
|
3567
3634
|
clientId: z19.string().describe("End-customer identifier to scope the preview to."),
|
|
3568
3635
|
dashboardName: z19.string().optional().describe("Dashboard name (non-technical). Preferred \u2014 pass with optional `workspaceName`."),
|
|
3569
3636
|
workspaceName: z19.string().optional().describe("Workspace name. Only needed if `dashboardName` is ambiguous."),
|
|
3637
|
+
dataAppName: z19.string().trim().min(1).optional().describe("Data app name when dashboard embeds span several apps"),
|
|
3570
3638
|
embedId: z19.string().optional().describe("Technical alternative to `dashboardName`."),
|
|
3571
3639
|
expiryHours: z19.number().min(1).max(MAX_EXPIRY_HOURS).optional().describe(`Token TTL in hours. Defaults to ${DEFAULT_EXPIRY_HOURS}. Max ${MAX_EXPIRY_HOURS} (30 days).`)
|
|
3572
3640
|
},
|
|
@@ -3577,12 +3645,13 @@ function registerGetDemoLink(server, client) {
|
|
|
3577
3645
|
embedId: params.embedId,
|
|
3578
3646
|
dashboardName: params.dashboardName,
|
|
3579
3647
|
workspaceName: params.workspaceName,
|
|
3648
|
+
dataAppName: params.dataAppName,
|
|
3580
3649
|
required: true
|
|
3581
3650
|
});
|
|
3582
3651
|
if (!embed.ok) return toolError(embed.error);
|
|
3583
3652
|
const embedId = embed.value.embedId;
|
|
3584
3653
|
const dashboardName = embed.value.resolvedFrom?.dashboardName || null;
|
|
3585
|
-
const ctx = await services.widgetService.resolveEmbedContext(embedId);
|
|
3654
|
+
const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
|
|
3586
3655
|
if (!ctx.dataAppName) {
|
|
3587
3656
|
return toolError(
|
|
3588
3657
|
`Could not resolve the data app for embed "${embedId}". The demo link needs a data app to mint a guest token against. Run \`list_data_apps\` to confirm one exists, or pass \`embedId\` for an embed with metadata.dataAppName populated.`
|
|
@@ -4818,9 +4887,11 @@ function registerListDashboards(server, client) {
|
|
|
4818
4887
|
count: result.length,
|
|
4819
4888
|
scope: useDataAppScope ? "data_app" : "global",
|
|
4820
4889
|
dashboards: result.map((d) => {
|
|
4821
|
-
const { name, dashboardId, workspaceName, metricCount } = services.discoveryService.normalizeDashboard(d);
|
|
4890
|
+
const { name, dashboardId, externalDashboardId, workspaceName, metricCount } = services.discoveryService.normalizeDashboard(d);
|
|
4822
4891
|
return {
|
|
4823
4892
|
id: dashboardId || null,
|
|
4893
|
+
dashboardId,
|
|
4894
|
+
externalDashboardId,
|
|
4824
4895
|
name,
|
|
4825
4896
|
workspace: workspaceName || null,
|
|
4826
4897
|
metricCount: metricCount ?? null
|
|
@@ -4926,6 +4997,7 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
4926
4997
|
"Generate multiple workspace dashboard tile drafts without persisting them.",
|
|
4927
4998
|
{
|
|
4928
4999
|
workspaceName: workspaceNameSchema,
|
|
5000
|
+
clientId: z24.string().trim().min(1).describe("Tenant client identifier in this data app; use list_tenants to choose it"),
|
|
4929
5001
|
dataAppName: z24.string().optional().describe("Data app in the workspace; auto-resolved when unique"),
|
|
4930
5002
|
integrationId: z24.string().optional().describe("Datasource UUID override"),
|
|
4931
5003
|
integrationName: z24.string().optional().describe("Datasource name override"),
|
|
@@ -4955,10 +5027,10 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
4955
5027
|
method: "POST",
|
|
4956
5028
|
path: "/api/v2/guest-token/create",
|
|
4957
5029
|
auth: "service",
|
|
4958
|
-
body: { clientId:
|
|
5030
|
+
body: { clientId: params.clientId, dataAppName: resolution.name }
|
|
4959
5031
|
});
|
|
4960
5032
|
const datasource = await services.widgetService.resolveDatasource(guest.token, {
|
|
4961
|
-
clientId:
|
|
5033
|
+
clientId: params.clientId,
|
|
4962
5034
|
integrationId: params.integrationId,
|
|
4963
5035
|
integrationName: params.integrationName
|
|
4964
5036
|
});
|
|
@@ -4979,11 +5051,6 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
4979
5051
|
`Tile "${item.input.id}" generation failed: ${stringify(item.result.error)}`
|
|
4980
5052
|
);
|
|
4981
5053
|
}
|
|
4982
|
-
if (typeof item.result.chartType !== "string" || !item.result.chartType) {
|
|
4983
|
-
return toolError(
|
|
4984
|
-
`Tile "${item.input.id}" returned no chartType. Refine its userInputs and retry.`
|
|
4985
|
-
);
|
|
4986
|
-
}
|
|
4987
5054
|
}
|
|
4988
5055
|
return toolSuccess(
|
|
4989
5056
|
stringify({
|
|
@@ -4995,7 +5062,7 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
4995
5062
|
position: input.position,
|
|
4996
5063
|
title: input.title,
|
|
4997
5064
|
chart: {
|
|
4998
|
-
type: result.chartType,
|
|
5065
|
+
type: result.chartType || "table",
|
|
4999
5066
|
query: result.query,
|
|
5000
5067
|
companyIntegrationId: datasource.value.integrationId
|
|
5001
5068
|
}
|
|
@@ -5020,12 +5087,13 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
5020
5087
|
// src/tools/dashboard/get-dashboard.ts
|
|
5021
5088
|
import { z as z25 } from "zod";
|
|
5022
5089
|
function registerGetDashboard(server, client) {
|
|
5023
|
-
server.tool(
|
|
5090
|
+
const tool = server.tool(
|
|
5024
5091
|
"get_dashboard",
|
|
5025
5092
|
"Retrieve a workspace dashboard projected back into the versioned tile-list contract.",
|
|
5026
5093
|
{
|
|
5027
|
-
workspaceName: workspaceNameSchema,
|
|
5028
|
-
|
|
5094
|
+
workspaceName: workspaceNameSchema.optional(),
|
|
5095
|
+
workspaceId: z25.string().min(1).optional().describe("Workspace UUID from a dashboard link wid parameter"),
|
|
5096
|
+
dashboardId: z25.string().min(1).describe("Dashboard UUID or externalDashboardId slug returned by list_dashboards or create_dashboard")
|
|
5029
5097
|
},
|
|
5030
5098
|
READ_ONLY,
|
|
5031
5099
|
async (params) => {
|
|
@@ -5035,10 +5103,33 @@ function registerGetDashboard(server, client) {
|
|
|
5035
5103
|
path: "/api/v2/workspace/dashboard",
|
|
5036
5104
|
auth: "service",
|
|
5037
5105
|
query: {
|
|
5038
|
-
workspaceName: params.workspaceName,
|
|
5106
|
+
...params.workspaceName ? { workspaceName: params.workspaceName } : {},
|
|
5107
|
+
...params.workspaceId ? { workspaceId: params.workspaceId } : {},
|
|
5039
5108
|
dashboardId: params.dashboardId
|
|
5040
5109
|
}
|
|
5041
5110
|
});
|
|
5111
|
+
if (hasChartApps(server) && client.isHosted()) {
|
|
5112
|
+
const tiles = Array.isArray(result.tiles) ? result.tiles : [];
|
|
5113
|
+
const charts = tiles.filter((t) => !t.unavailable && t.chart?.metricRef);
|
|
5114
|
+
const dashboardId = String(result.dashboardId || params.dashboardId);
|
|
5115
|
+
const picker = {
|
|
5116
|
+
kind: "dashboard",
|
|
5117
|
+
title: result.title || "Dashboard",
|
|
5118
|
+
rows: [],
|
|
5119
|
+
widgets: charts.map((t) => ({
|
|
5120
|
+
name: t.title || "Widget",
|
|
5121
|
+
chartType: t.chart.type || null,
|
|
5122
|
+
reference: { dashboardId, widgetId: t.chart.metricRef }
|
|
5123
|
+
})),
|
|
5124
|
+
dashboardId: result.dashboardId
|
|
5125
|
+
};
|
|
5126
|
+
try {
|
|
5127
|
+
return chartResult(stringify(result), picker);
|
|
5128
|
+
} catch (error) {
|
|
5129
|
+
if (!(error instanceof Error) || error.message !== "Chart page exceeds payload budget") throw error;
|
|
5130
|
+
return chartResult(`Dashboard ready. ${picker.widgets.length} saved charts. The tile shows a compact picker because the full dashboard document exceeds the App payload budget. ` + chartIndex(dashboardId, picker.widgets.map((w) => ({ widgetId: w.reference.widgetId, name: w.name, chartType: w.chartType }))), picker);
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5042
5133
|
return toolSuccess(stringify(result));
|
|
5043
5134
|
} catch (err) {
|
|
5044
5135
|
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
@@ -5046,6 +5137,7 @@ function registerGetDashboard(server, client) {
|
|
|
5046
5137
|
}
|
|
5047
5138
|
}
|
|
5048
5139
|
);
|
|
5140
|
+
tagChartTool(server, tool);
|
|
5049
5141
|
}
|
|
5050
5142
|
|
|
5051
5143
|
// src/tools/dashboard/dashboard-snapshot.ts
|
|
@@ -5315,14 +5407,14 @@ function registerAskQuestion(server, client) {
|
|
|
5315
5407
|
async (params) => {
|
|
5316
5408
|
try {
|
|
5317
5409
|
let datamartName = params.datamartName;
|
|
5318
|
-
|
|
5410
|
+
{
|
|
5319
5411
|
const datamarts = await services.datamartsApi.listDatamarts();
|
|
5320
5412
|
if (!Array.isArray(datamarts) || datamarts.length === 0) {
|
|
5321
5413
|
return toolError(
|
|
5322
|
-
"No datamarts
|
|
5414
|
+
"No datamarts are available in this workspace. A datamart is **required** before you can query data.\n\nTell the user they need to create a datamart in the **Databrain UI** first:\n1. Go to the Databrain platform and connect a datasource\n2. Create a datamart by selecting tables from the datasource\n3. Come back here and use `ask_question` with the `datamartName`\n\nYou can use `list_datasources` and `discover_datasource_schema` to explore what's available."
|
|
5323
5415
|
);
|
|
5324
5416
|
}
|
|
5325
|
-
if (datamarts.length === 1) {
|
|
5417
|
+
if (!datamartName && datamarts.length === 1) {
|
|
5326
5418
|
const resolvedName = datamarts[0].name || datamarts[0].datamartName;
|
|
5327
5419
|
if (!resolvedName) {
|
|
5328
5420
|
return toolError(
|
|
@@ -5330,7 +5422,7 @@ function registerAskQuestion(server, client) {
|
|
|
5330
5422
|
);
|
|
5331
5423
|
}
|
|
5332
5424
|
datamartName = resolvedName;
|
|
5333
|
-
} else {
|
|
5425
|
+
} else if (!datamartName) {
|
|
5334
5426
|
const list = datamarts.map(formatDatamart).join("\n");
|
|
5335
5427
|
return toolSuccess(
|
|
5336
5428
|
`Found ${datamarts.length} datamarts. Please specify which one to query:
|
|
@@ -5985,19 +6077,17 @@ function registerEvalSemanticModel(server, client) {
|
|
|
5985
6077
|
const api = createServiceBundle(client).semanticModelApi;
|
|
5986
6078
|
server.tool(
|
|
5987
6079
|
"eval_semantic_model",
|
|
5988
|
-
"Evaluate
|
|
5989
|
-
{ datamartId, source: source.optional(), questions: z30.array(z30.
|
|
5990
|
-
|
|
5991
|
-
question: z30.string().min(1),
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
expected_empty: z30.boolean().optional()
|
|
5995
|
-
}).strict()])).max(100).optional() },
|
|
6080
|
+
"Evaluate closed world semantic assertions against proposed or stored YAML. Each question must name an id and the model entities it exercises. Does not execute warehouse SQL or write the model.",
|
|
6081
|
+
{ datamartId, source: source.optional(), questions: z30.array(z30.object({
|
|
6082
|
+
id: z30.string().trim().min(1),
|
|
6083
|
+
question: z30.string().trim().min(1),
|
|
6084
|
+
exercises: z30.array(z30.string().trim().min(1)).describe("Exact model entity names asserted by this question; an empty list is reported as unscoreable.")
|
|
6085
|
+
}).strict()).max(100).default([]) },
|
|
5996
6086
|
READ_ONLY,
|
|
5997
6087
|
async ({ datamartId: datamartId2, source: source2, questions }) => {
|
|
5998
|
-
const shaped = questions?.map((entry) => typeof entry === "string" ? { question: entry } : entry);
|
|
5999
6088
|
try {
|
|
6000
|
-
|
|
6089
|
+
const yaml = source2 ?? (await api.pull(datamartId2, "combined")).source;
|
|
6090
|
+
return toolSuccess(stringify(await api.evaluate(datamartId2, { source: yaml, questions })));
|
|
6001
6091
|
} catch (error) {
|
|
6002
6092
|
return failure(error);
|
|
6003
6093
|
}
|
|
@@ -6292,7 +6382,7 @@ function registerStartSemanticLayerGeneration(server, client) {
|
|
|
6292
6382
|
const services = createServiceBundle(client);
|
|
6293
6383
|
server.tool(
|
|
6294
6384
|
"start_semantic_layer_generation",
|
|
6295
|
-
"Start backend semantic-layer auto-generation for a datamart. Returns an async job id to poll.",
|
|
6385
|
+
"Start backend semantic-layer auto-generation for a datamart through a hosted OAuth sign-in. Static service-token authentication is not supported by this backend route. Returns an async job id to poll.",
|
|
6296
6386
|
{
|
|
6297
6387
|
datamartName: z32.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
6298
6388
|
workspaceName: z32.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
@@ -6336,7 +6426,7 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
6336
6426
|
const services = createServiceBundle(client);
|
|
6337
6427
|
server.tool(
|
|
6338
6428
|
"get_semantic_layer_generation_status",
|
|
6339
|
-
"Get semantic-layer auto-generation job status for a datamart.",
|
|
6429
|
+
"Get semantic-layer auto-generation job status for a datamart through a hosted OAuth sign-in. Static service-token authentication is not supported by this backend route.",
|
|
6340
6430
|
{
|
|
6341
6431
|
datamartName: z32.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
6342
6432
|
workspaceName: z32.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
@@ -6388,9 +6478,12 @@ function registerListWidgets(server, client) {
|
|
|
6388
6478
|
const services = createServiceBundle(client);
|
|
6389
6479
|
const tool = server.tool(
|
|
6390
6480
|
"list_widgets",
|
|
6391
|
-
"List widgets
|
|
6481
|
+
"List saved widgets. Hosted sign-ins read an authorized workspace dashboard without an embed; guest/API flows use clientId and an embed. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Returns each widget's externalMetricId (uuid, used for update/remove), metricId (slug), name, published state, and creator identifier.",
|
|
6392
6482
|
{
|
|
6393
|
-
clientId: z33.string().describe("Client/tenant identifier for this dashboard"),
|
|
6483
|
+
clientId: z33.string().optional().describe("Client/tenant identifier for this dashboard"),
|
|
6484
|
+
dataAppName: z33.string().trim().min(1).optional().describe("Data app for a tenant/embed read. Omit to resolve it from the embed; hosted workspace reads need none."),
|
|
6485
|
+
workspaceId: z33.string().min(1).optional().describe("Workspace UUID from the wid query parameter in a dashboard link."),
|
|
6486
|
+
dashboardId: z33.string().min(1).optional().describe("Dashboard UUID or slug returned by list_dashboards. Hosted sign-ins only."),
|
|
6394
6487
|
dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6395
6488
|
workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6396
6489
|
embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
|
|
@@ -6398,17 +6491,62 @@ function registerListWidgets(server, client) {
|
|
|
6398
6491
|
READ_ONLY,
|
|
6399
6492
|
async (params) => {
|
|
6400
6493
|
try {
|
|
6494
|
+
if (params.dashboardId && (params.clientId !== void 0 || params.embedId)) {
|
|
6495
|
+
return toolError("dashboardId is for hosted workspace reads. For a tenant, use clientId with dashboardName or embedId.");
|
|
6496
|
+
}
|
|
6497
|
+
if (client.isHosted() && params.clientId === void 0 && !params.embedId) {
|
|
6498
|
+
const result = await client.request({
|
|
6499
|
+
method: "POST",
|
|
6500
|
+
path: "/api/v2/workspace/dashboard/widgets",
|
|
6501
|
+
auth: "service",
|
|
6502
|
+
body: {
|
|
6503
|
+
...params.dashboardId ? { dashboardId: params.dashboardId } : {},
|
|
6504
|
+
...params.dashboardName ? { dashboardName: params.dashboardName } : {},
|
|
6505
|
+
workspaceName: params.workspaceName,
|
|
6506
|
+
workspaceId: params.workspaceId
|
|
6507
|
+
}
|
|
6508
|
+
});
|
|
6509
|
+
const widgets2 = result.widgets;
|
|
6510
|
+
if (hasChartApps(server)) {
|
|
6511
|
+
const references = widgets2.slice(0, 50).flatMap((widget) => {
|
|
6512
|
+
const widgetId = widgetReferenceId(widget);
|
|
6513
|
+
return widgetId ? [{
|
|
6514
|
+
name: widget.name || "Untitled widget",
|
|
6515
|
+
chartType: typeof widget.chartType === "string" ? widget.chartType : null,
|
|
6516
|
+
reference: { dashboardId: result.dashboardId, widgetId }
|
|
6517
|
+
}] : [];
|
|
6518
|
+
});
|
|
6519
|
+
return chartResult(`${widgets2.length} saved widgets available. ` + chartIndex(
|
|
6520
|
+
result.dashboardId,
|
|
6521
|
+
references.map((r) => ({ widgetId: r.reference.widgetId, name: r.name, chartType: r.chartType }))
|
|
6522
|
+
), {
|
|
6523
|
+
kind: "dashboard",
|
|
6524
|
+
title: params.dashboardName || "Dashboard",
|
|
6525
|
+
rows: [],
|
|
6526
|
+
widgets: references,
|
|
6527
|
+
dashboardId: result.dashboardId,
|
|
6528
|
+
externalDashboardId: result.externalDashboardId,
|
|
6529
|
+
description: widgets2.length > 50 ? "Showing the first 50 widgets." : ""
|
|
6530
|
+
});
|
|
6531
|
+
}
|
|
6532
|
+
return toolSuccess(JSON.stringify(result));
|
|
6533
|
+
}
|
|
6534
|
+
if (!params.clientId) return toolError("clientId is required for an embed/tenant dashboard. Hosted sign-ins may omit it for a workspace dashboard.");
|
|
6401
6535
|
const embed = await services.widgetService.resolveEmbedId({
|
|
6402
6536
|
embedId: params.embedId,
|
|
6403
6537
|
dashboardName: params.dashboardName,
|
|
6404
6538
|
workspaceName: params.workspaceName,
|
|
6539
|
+
dataAppName: params.dataAppName,
|
|
6405
6540
|
required: true
|
|
6406
6541
|
});
|
|
6407
6542
|
if (!embed.ok) return toolError(embed.error);
|
|
6408
6543
|
const embedId = embed.value.embedId;
|
|
6544
|
+
const app = client.isHosted() ? await services.widgetService.resolveDataAppName(params.dataAppName, embedId) : { ok: true, value: params.dataAppName };
|
|
6545
|
+
if (!app.ok) return toolError(app.error);
|
|
6409
6546
|
const widgets = await services.widgetsApi.listWidgets(
|
|
6410
6547
|
embedId,
|
|
6411
|
-
params.clientId
|
|
6548
|
+
params.clientId,
|
|
6549
|
+
app.value
|
|
6412
6550
|
);
|
|
6413
6551
|
if (hasChartApps(server)) {
|
|
6414
6552
|
const visible = (Array.isArray(widgets) ? widgets : []).slice(0, 50);
|
|
@@ -6416,7 +6554,12 @@ function registerListWidgets(server, client) {
|
|
|
6416
6554
|
const widgetId = widgetReferenceId(w);
|
|
6417
6555
|
return widgetId ? [{
|
|
6418
6556
|
name: typeof w.name === "string" ? w.name.slice(0, 500) : "Untitled widget",
|
|
6419
|
-
reference: {
|
|
6557
|
+
reference: {
|
|
6558
|
+
widgetId,
|
|
6559
|
+
embedId,
|
|
6560
|
+
clientId: params.clientId,
|
|
6561
|
+
...app.value ? { dataAppName: app.value } : {}
|
|
6562
|
+
}
|
|
6420
6563
|
}] : [];
|
|
6421
6564
|
});
|
|
6422
6565
|
return chartResult(`${references.length} widgets available in the first dashboard page.`, {
|
|
@@ -6488,11 +6631,12 @@ function registerAddWidget(server, client) {
|
|
|
6488
6631
|
embedId: params.embedId,
|
|
6489
6632
|
dashboardName: params.dashboardName,
|
|
6490
6633
|
workspaceName: params.workspaceName,
|
|
6634
|
+
dataAppName: params.dataAppName,
|
|
6491
6635
|
required: true
|
|
6492
6636
|
});
|
|
6493
6637
|
if (!embed.ok) return toolError(embed.error);
|
|
6494
6638
|
const embedId = embed.value.embedId;
|
|
6495
|
-
const ctx = await services.widgetService.resolveEmbedContext(embedId);
|
|
6639
|
+
const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
|
|
6496
6640
|
if (!ctx.externalDashboardId) {
|
|
6497
6641
|
return toolError(
|
|
6498
6642
|
`Dashboard "${params.dashboardName || embedId}" has no externalDashboardId \u2014 cannot attach a widget. Verify via \`get_embed_details\`.`
|
|
@@ -6652,6 +6796,7 @@ function registerUpdateWidget(server, client) {
|
|
|
6652
6796
|
embedId: params.embedId,
|
|
6653
6797
|
dashboardName: params.dashboardName,
|
|
6654
6798
|
workspaceName: params.workspaceName,
|
|
6799
|
+
dataAppName: params.dataAppName,
|
|
6655
6800
|
required: true
|
|
6656
6801
|
});
|
|
6657
6802
|
if (!embed.ok) return toolError(embed.error);
|
|
@@ -6724,6 +6869,7 @@ function registerRemoveWidget(server, client) {
|
|
|
6724
6869
|
embedId: params.embedId,
|
|
6725
6870
|
dashboardName: params.dashboardName,
|
|
6726
6871
|
workspaceName: params.workspaceName,
|
|
6872
|
+
dataAppName: params.dataAppName,
|
|
6727
6873
|
required: true
|
|
6728
6874
|
});
|
|
6729
6875
|
if (!embed.ok) return toolError(embed.error);
|
|
@@ -6794,6 +6940,7 @@ function registerGenerateWidget(server, client) {
|
|
|
6794
6940
|
embedId: params.embedId,
|
|
6795
6941
|
dashboardName: params.dashboardName,
|
|
6796
6942
|
workspaceName: params.workspaceName,
|
|
6943
|
+
dataAppName: params.dataAppName,
|
|
6797
6944
|
required: true
|
|
6798
6945
|
});
|
|
6799
6946
|
if (!embed.ok) return toolError(embed.error);
|
|
@@ -6918,11 +7065,12 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
6918
7065
|
embedId: params.embedId,
|
|
6919
7066
|
dashboardName: params.dashboardName,
|
|
6920
7067
|
workspaceName: params.workspaceName,
|
|
7068
|
+
dataAppName: params.dataAppName,
|
|
6921
7069
|
required: true
|
|
6922
7070
|
});
|
|
6923
7071
|
if (!embed.ok) return toolError(embed.error);
|
|
6924
7072
|
const embedId = embed.value.embedId;
|
|
6925
|
-
const ctx = await services.widgetService.resolveEmbedContext(embedId);
|
|
7073
|
+
const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
|
|
6926
7074
|
if (!ctx.externalDashboardId) {
|
|
6927
7075
|
return toolError(
|
|
6928
7076
|
`Dashboard "${params.dashboardName || embedId}" has no externalDashboardId. Verify with get_embed_details before creating metrics.`
|
|
@@ -7171,11 +7319,12 @@ function registerValidateMetricSpec(server, client) {
|
|
|
7171
7319
|
embedId: params.embedId,
|
|
7172
7320
|
dashboardName: params.dashboardName,
|
|
7173
7321
|
workspaceName: params.workspaceName,
|
|
7322
|
+
dataAppName: params.dataAppName,
|
|
7174
7323
|
required: true
|
|
7175
7324
|
});
|
|
7176
7325
|
if (!embed.ok) return toolError(embed.error);
|
|
7177
7326
|
const embedId = embed.value.embedId;
|
|
7178
|
-
const ctx = await services.widgetService.resolveEmbedContext(embedId);
|
|
7327
|
+
const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
|
|
7179
7328
|
if (!ctx.externalDashboardId) {
|
|
7180
7329
|
return toolError(
|
|
7181
7330
|
`Dashboard "${params.dashboardName || embedId}" has no externalDashboardId. Verify with get_embed_details before creating metrics.`
|
|
@@ -7260,6 +7409,7 @@ function registerListScheduledReports(server, client) {
|
|
|
7260
7409
|
clientId: z41.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
|
|
7261
7410
|
dashboardName: z41.string().optional().describe("Dashboard name to inspect"),
|
|
7262
7411
|
workspaceName: z41.string().optional().describe("Workspace name if dashboardName is ambiguous"),
|
|
7412
|
+
dataAppName: z41.string().trim().min(1).optional().describe("Data app name when dashboard embeds span several apps"),
|
|
7263
7413
|
embedId: z41.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
7264
7414
|
userIdentifier: z41.string().optional().describe("Optional creator/user filter"),
|
|
7265
7415
|
pageNumber: z41.number().int().positive().optional().describe("Page number when pagination is enabled"),
|
|
@@ -7272,12 +7422,16 @@ function registerListScheduledReports(server, client) {
|
|
|
7272
7422
|
embedId: params.embedId,
|
|
7273
7423
|
dashboardName: params.dashboardName,
|
|
7274
7424
|
workspaceName: params.workspaceName,
|
|
7425
|
+
dataAppName: params.dataAppName,
|
|
7275
7426
|
required: true
|
|
7276
7427
|
});
|
|
7277
7428
|
if (!embed.ok) return toolError(embed.error);
|
|
7278
7429
|
const embedId = embed.value.embedId;
|
|
7430
|
+
const app = client.isHosted() ? await services.widgetService.resolveDataAppName(params.dataAppName, embedId) : { ok: true, value: params.dataAppName };
|
|
7431
|
+
if (!app.ok) return toolError(app.error);
|
|
7279
7432
|
const reports = await services.scheduledReportsApi.listByEmbed({
|
|
7280
7433
|
embedId,
|
|
7434
|
+
dataAppName: app.value,
|
|
7281
7435
|
clientId: params.clientId,
|
|
7282
7436
|
userIdentifier: params.userIdentifier,
|
|
7283
7437
|
pageNumber: params.pageNumber,
|
|
@@ -7746,6 +7900,7 @@ var TOOL_TITLES = {
|
|
|
7746
7900
|
apply_semantic_change: "Apply semantic model change",
|
|
7747
7901
|
eval_semantic_model: "Evaluate semantic model",
|
|
7748
7902
|
list_widgets: "List widgets",
|
|
7903
|
+
render_metric_tile: "Render a saved metric tile",
|
|
7749
7904
|
validate_metric_spec: "Validate metric spec",
|
|
7750
7905
|
create_or_update_metric_from_query: "Create or update metric from SQL",
|
|
7751
7906
|
create_workspace_metric: "Create workspace metric",
|
|
@@ -9737,7 +9892,7 @@ The v2 source is one YAML document. Use the configured service credential and a
|
|
|
9737
9892
|
|
|
9738
9893
|
1. \`search_semantic_model\` finds entities by question. \`get_semantic_entity\` reads one entity by kind and name. Column names use table.column.
|
|
9739
9894
|
2. \`propose_semantic_change\` takes complete source YAML, a YAML patch or one structured change. It returns candidate source, validation errors, a plan and the stored expected hash without writing.
|
|
9740
|
-
3. \`eval_semantic_model\`
|
|
9895
|
+
3. \`eval_semantic_model\` checks closed-world assertions against the proposed source; optional questions use id, question and exercises (exact entity names). It does not execute SQL.
|
|
9741
9896
|
4. \`apply_semantic_change\` defaults to a dry run. Live apply requires the exact reviewed source, \`expected_content_hash\`, \`dry_run: false\` and user supplied \`confirm: "APPLY_TO_PRODUCTION"\`. Never supply confirmation yourself. Apply changes the live model for all consumers. On a conflict, prepare a fresh proposal and review it again.
|
|
9742
9897
|
|
|
9743
9898
|
YAML patches merge mappings and named lists by name. Other lists replace. Empty lists clear; null removes a field. Structured changes use operation upsert or remove, kind, name, and value for upsert. Column changes also require table. Patch output normalizes formatting and comments, so review the returned source before apply.
|
|
@@ -10818,7 +10973,7 @@ var populateSemanticLayerPrompt = `You are helping the user populate the semanti
|
|
|
10818
10973
|
If \`list_datamarts\` reports \`semanticLayerVersion: "v2"\`, or the selected datamart is already known to use v2, use this workflow instead of the legacy steps below:
|
|
10819
10974
|
1. Inspect relevant entities with \`search_semantic_model\` and \`get_semantic_entity\`, using the datamart ID.
|
|
10820
10975
|
2. Call \`propose_semantic_change\` with a YAML patch or structured change. Show its errors and plan. Correct errors before continuing.
|
|
10821
|
-
3. Call \`eval_semantic_model\` with the returned source. Show the verdicts and let the user review the candidate.
|
|
10976
|
+
3. Call \`eval_semantic_model\` with the returned source. For scored assertions, supply questions with id, question and exercises (exact model entity names); this does not run SQL. Show the verdicts and let the user review the candidate.
|
|
10822
10977
|
4. Stop for the user's explicit \`APPLY_TO_PRODUCTION\` confirmation. Never supply it yourself.
|
|
10823
10978
|
5. Call \`apply_semantic_change\` with the exact reviewed source, its \`expected_content_hash\`, \`dry_run: false\` and the user's confirmation. A conflict requires a new proposal and review.
|
|
10824
10979
|
6. Call \`get_semantic_entity\` and inspect the changed entity. Do not call \`update_semantic_layer\` for a v2 model.
|
|
@@ -11193,6 +11348,7 @@ export {
|
|
|
11193
11348
|
CUSTOM_BUNDLE_CHUNK_CHARS,
|
|
11194
11349
|
rowsSchema,
|
|
11195
11350
|
referenceSchema,
|
|
11351
|
+
dateRangeSchema,
|
|
11196
11352
|
viewSchema,
|
|
11197
11353
|
pageSchema,
|
|
11198
11354
|
bounded,
|
|
@@ -11211,4 +11367,4 @@ export {
|
|
|
11211
11367
|
registerAllResources,
|
|
11212
11368
|
registerAllPrompts
|
|
11213
11369
|
};
|
|
11214
|
-
//# sourceMappingURL=chunk-
|
|
11370
|
+
//# sourceMappingURL=chunk-OLOC2MTA.js.map
|