@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.
@@ -13,7 +13,7 @@ import {
13
13
  toolContext,
14
14
  toolError,
15
15
  toolSuccess
16
- } from "./chunk-RHK62ZJ6.js";
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.object({ embedId: id, clientId: id, widgetId: id }).strict();
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: CHART_URI, visibility }, [RESOURCE_URI_META_KEY]: CHART_URI };
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
- body,
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: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generate`,
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: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generation-status`,
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: "GET",
807
+ method: "POST",
791
808
  path: this.path(id2, "retrieve"),
792
809
  auth: "service",
793
- query: { q, k: String(k) }
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/v2/dashboard/markArchived",
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.externalDashboardId) && dashboard.externalDashboardId || isNonEmptyString(dashboard._id) && dashboard._id || isNonEmptyString(dashboard.id) && dashboard.id || isNonEmptyString(dashboard.uid) && dashboard.uid || null,
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
- embeds = await this.embedsApi.listEmbeds();
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}). Ask the user which embed name they want to customize.`
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
- const target = datasources.find(matchPredicate);
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 (invalidate and replace) an API token. Previous token becomes stale.",
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}-api-token-rotated`
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
- ` : "") + "Treat previous tokens as stale and update backend secrets."
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
- dashboardId: z14.string().optional().describe("Dashboard identifier for embed creation"),
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 ID to clone metrics from"),
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(["COPY", "LINK"]).optional().describe("COPY duplicates metrics, LINK shares them"),
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
- body: body2,
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: "None", dataAppName: resolution.name }
5030
+ body: { clientId: params.clientId, dataAppName: resolution.name }
4959
5031
  });
4960
5032
  const datasource = await services.widgetService.resolveDatasource(guest.token, {
4961
- clientId: "None",
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
- dashboardId: z25.string().min(1).describe("Dashboard identifier returned by create_dashboard")
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
- if (!datamartName) {
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 found. 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."
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 saved queries and optional questions against a proposed or stored semantic model. Returns the pass rate and each verdict with its reason. Runs warehouse queries without changing the model.",
5989
- { datamartId, source: source.optional(), questions: z30.array(z30.union([z30.string().trim().min(1), z30.object({
5990
- name: z30.string().optional(),
5991
- question: z30.string().min(1),
5992
- sql: z30.string().optional(),
5993
- expected_columns: z30.array(z30.string()).optional(),
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
- return toolSuccess(stringify(await api.evaluate(datamartId2, { source: source2, questions: shaped })));
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 on a customer dashboard. 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.",
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: { widgetId, embedId, clientId: params.clientId }
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\` evaluates the proposed source.
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-ES2LUP4U.js.map
11370
+ //# sourceMappingURL=chunk-OLOC2MTA.js.map