@databrainhq/mcp-server 0.3.0-rc.10 → 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.
@@ -3,22 +3,31 @@ import {
3
3
  CustomChartsApi,
4
4
  DatabrainApiError,
5
5
  apiProblems,
6
- buildProject,
7
- bundleSizeBytes,
8
- contentHash,
9
6
  createCustomChartsApi,
7
+ editWithBryn,
10
8
  logger,
11
- optionSchema,
12
9
  planUpload,
13
- readDist,
14
10
  scaffoldProject,
15
- slotSchema,
16
11
  stringify,
12
+ studioDraftSchema,
17
13
  toolContext,
18
14
  toolError,
19
- toolSuccess,
15
+ toolSuccess
16
+ } from "./chunk-347U6GXR.js";
17
+ import {
18
+ MAX_BUNDLE_CSS_BYTES,
19
+ MAX_BUNDLE_JS_BYTES,
20
+ MAX_ICON_BYTES,
21
+ MAX_MANIFEST_BYTES,
22
+ buildProject,
23
+ buildSource,
24
+ bundleSizeBytes,
25
+ contentHash,
26
+ optionSchema,
27
+ readDist,
28
+ slotSchema,
20
29
  validateBundle
21
- } from "./chunk-OCBTNAAO.js";
30
+ } from "./chunk-Q5EYDKSA.js";
22
31
 
23
32
  // src/apps/contract.ts
24
33
  import { z } from "zod";
@@ -28,10 +37,15 @@ var APP_MIME = "text/html;profile=mcp-app";
28
37
  var RESOURCE_URI_META_KEY = "ui/resourceUri";
29
38
  var PAGE_SIZE = 50;
30
39
  var MAX_RESULT_CHARS = 124e3;
40
+ var MAX_CUSTOM_BUNDLE_CHARS = 6 * (MAX_BUNDLE_JS_BYTES + MAX_BUNDLE_CSS_BYTES + MAX_ICON_BYTES) + MAX_MANIFEST_BYTES + 128;
41
+ var CUSTOM_BUNDLE_CHUNK_CHARS = 32e3;
31
42
  var id = z.string().trim().min(1).max(256);
32
43
  var cell = z.union([z.string().max(2e3), z.number().finite(), z.boolean(), z.null()]);
33
44
  var rowsSchema = z.array(z.record(cell)).max(PAGE_SIZE);
34
- 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
+ ]);
35
49
  var point = z.object({
36
50
  key: id,
37
51
  label: z.string().max(2e3),
@@ -52,22 +66,33 @@ var viewSchema = z.object({
52
66
  drillId: id.optional()
53
67
  }).strict();
54
68
  var pageSchema = z.object({
55
- kind: z.enum(["chart", "kpi"]),
69
+ kind: z.enum(["chart", "kpi", "table"]),
56
70
  title: z.string().max(500),
71
+ description: z.string().max(2e3).optional(),
72
+ rowsTotal: z.number().int().nonnegative().optional(),
57
73
  chartSpec: z.object({ type: z.string().max(80) }).strict(),
58
74
  rows: rowsSchema,
59
75
  columns: z.array(z.string().max(128)).max(20),
60
76
  svg: z.string().max(1e5).optional(),
77
+ customChart: z.object({
78
+ key: z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/),
79
+ bindings: z.record(z.array(z.string().max(128)).max(20)),
80
+ options: z.record(z.union([z.string().max(2e3), z.number().finite(), z.boolean()]))
81
+ }).strict().optional(),
82
+ fallback: z.string().max(1e3).optional(),
61
83
  width: z.number().int().min(240).max(1200),
62
84
  height: z.number().int().min(120).max(800),
63
85
  points: z.array(point).max(PAGE_SIZE),
64
86
  value: cell.optional(),
65
87
  nextCursor: z.string().min(1).max(2048).optional()
66
88
  }).strict().superRefine((page, ctx) => {
67
- if (page.kind === "chart" && !page.svg) ctx.addIssue({ code: "custom", message: "SVG required" });
89
+ if (page.kind === "chart" && !page.svg && !page.customChart) ctx.addIssue({ code: "custom", message: "SVG required" });
68
90
  if (page.kind === "kpi" && (page.svg || page.points.length || page.value === void 0)) {
69
91
  ctx.addIssue({ code: "custom", message: "KPI requires a value and no SVG/points" });
70
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
+ }
71
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)) {
72
97
  ctx.addIssue({ code: "custom", message: "Invalid point geometry" });
73
98
  }
@@ -88,8 +113,8 @@ function firstRows(value) {
88
113
  }
89
114
 
90
115
  // src/apps/feature.ts
91
- function appMeta(visibility) {
92
- 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 };
93
118
  }
94
119
  var enabled = /* @__PURE__ */ new WeakSet();
95
120
  function enableChartApps(server) {
@@ -105,9 +130,196 @@ function chartResult(summary, payload) {
105
130
  return bounded({
106
131
  content: [{ type: "text", text: summary }],
107
132
  structuredContent: payload,
108
- _meta: appMeta(["model", "app"])
133
+ _meta: appMeta(["model", "app"], payload.kind === "kpi" ? KPI_URI : CHART_URI)
109
134
  });
110
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
+ }
141
+
142
+ // src/tools/custom-chart/author.ts
143
+ import { z as z3 } from "zod";
144
+
145
+ // src/tools/annotations.ts
146
+ var READ_ONLY = {
147
+ readOnlyHint: true,
148
+ destructiveHint: false,
149
+ idempotentHint: true,
150
+ openWorldHint: false
151
+ };
152
+ var WRITE_IDEMPOTENT = {
153
+ readOnlyHint: false,
154
+ destructiveHint: false,
155
+ idempotentHint: true,
156
+ openWorldHint: false
157
+ };
158
+ var WRITE_CREATE = {
159
+ readOnlyHint: false,
160
+ destructiveHint: false,
161
+ idempotentHint: false,
162
+ openWorldHint: false
163
+ };
164
+ var DESTRUCTIVE = {
165
+ readOnlyHint: false,
166
+ destructiveHint: true,
167
+ idempotentHint: false,
168
+ openWorldHint: false
169
+ };
170
+
171
+ // src/tools/custom-chart/shared.ts
172
+ import { z as z2 } from "zod";
173
+ var slotInputSchema = slotSchema;
174
+ var optionInputSchema = optionSchema;
175
+ var manifestInputSchema = z2.record(z2.unknown()).describe("manifest.json as an object (schemaVersion 2). Validated with the same rules as the server.");
176
+ var bundleInputShape = {
177
+ manifest: manifestInputSchema.optional(),
178
+ chartTs: z2.string().max(64e3).optional().describe("Hosted source: export render(ctx) in TypeScript or JavaScript. Compiled in memory without filesystem or imports. Pass instead of bundleJs/projectDir."),
179
+ bundleJs: z2.string().optional().describe("dist/index.js from `databrain custom-chart build` (the shim plus the chart module)."),
180
+ bundleCss: z2.string().optional().describe("The raw chart.css, kept for provenance."),
181
+ iconSvg: z2.string().optional().describe("icon.svg, at most 16 KiB, no script, no foreignObject, no external href."),
182
+ projectDir: z2.string().optional().describe("Local only: a chart project folder. The tool reads it instead of manifest/bundleJs/iconSvg.")
183
+ };
184
+ function formatProblems(problems) {
185
+ if (problems.length === 0) return "No problems.";
186
+ return problems.map((p) => `${p.path || "/"} ${p.code} ${p.message}`).join("\n");
187
+ }
188
+ function projectDirRefusal() {
189
+ if (process.env.DATABRAIN_HOSTED_PUBLIC_URL) {
190
+ return "projectDir is not available on the hosted server. Pass chartTs, manifest, bundleCss and iconSvg to compile in memory, or pass an already built bundleJs.";
191
+ }
192
+ return null;
193
+ }
194
+ async function resolveBundleInput(params, options) {
195
+ if (params.chartTs !== void 0) {
196
+ if (params.projectDir || params.bundleJs || !params.manifest || !params.iconSvg) {
197
+ return { problems: [], error: "chartTs needs manifest and iconSvg, and cannot be combined with bundleJs or projectDir.", source: "arguments" };
198
+ }
199
+ const result = await buildSource({ manifest: params.manifest, chartTs: params.chartTs, bundleCss: params.bundleCss, iconSvg: params.iconSvg });
200
+ return { bundle: result.outputs, problems: result.problems, source: "arguments" };
201
+ }
202
+ if (params.projectDir) {
203
+ const refusal = projectDirRefusal();
204
+ if (refusal) return { problems: [], error: refusal, source: "projectDir" };
205
+ if (params.manifest || params.bundleJs || params.iconSvg) {
206
+ return { problems: [], error: "Pass either projectDir or manifest/bundleJs/iconSvg, not both.", source: "projectDir" };
207
+ }
208
+ if (options.build) {
209
+ const result = await buildProject(params.projectDir, { write: false });
210
+ if (!result.ok || !result.outputs) return { problems: result.problems, source: "projectDir" };
211
+ const { manifest, bundleJs, bundleCss, iconSvg } = result.outputs;
212
+ return { bundle: { manifest, bundleJs, bundleCss, iconSvg }, problems: [], source: "projectDir" };
213
+ }
214
+ const dist = readDist(params.projectDir);
215
+ if (!dist.bundle) return { problems: dist.problems, source: "projectDir" };
216
+ return { bundle: dist.bundle, problems: [], source: "projectDir" };
217
+ }
218
+ if (!params.manifest) {
219
+ return { problems: [], error: "manifest is required (or pass projectDir when running locally).", source: "arguments" };
220
+ }
221
+ return {
222
+ bundle: {
223
+ manifest: params.manifest,
224
+ bundleJs: params.bundleJs,
225
+ bundleCss: params.bundleCss,
226
+ iconSvg: params.iconSvg
227
+ },
228
+ problems: [],
229
+ source: "arguments"
230
+ };
231
+ }
232
+ async function remoteValidate(client, bundle) {
233
+ try {
234
+ const result = await createCustomChartsApi(client).validate(bundle);
235
+ return { status: "ok", result };
236
+ } catch (err) {
237
+ if (err instanceof DatabrainApiError) {
238
+ if (err.code === "MISSING_SERVICE_TOKEN") {
239
+ return { status: "skipped", reason: "No service token is configured, so only the local validator ran." };
240
+ }
241
+ return { status: "failed", reason: err.message, problems: apiProblems(err) };
242
+ }
243
+ return { status: "failed", reason: err.message, problems: [] };
244
+ }
245
+ }
246
+ function isCompleteBundle(bundle) {
247
+ return typeof bundle.bundleJs === "string" && typeof bundle.iconSvg === "string";
248
+ }
249
+
250
+ // src/tools/custom-chart/author.ts
251
+ function registerCustomChartAuthor(server, client) {
252
+ server.tool(
253
+ "custom_chart_edit",
254
+ "Iterate a custom chart draft with Chart studio Bryn in a granted workspace. Never saves a version or publishes. Feature-detects the backend route. If absent, returns the draft for the calling agent to edit and validate with chartTs, without a local build.",
255
+ {
256
+ key: z3.string().regex(/^[a-z][a-z0-9_-]{0,63}$/),
257
+ workspaceId: z3.string().uuid(),
258
+ message: z3.string().trim().min(1).max(8e3),
259
+ draft: studioDraftSchema,
260
+ sessionId: z3.string().uuid().optional()
261
+ },
262
+ WRITE_CREATE,
263
+ async (input) => {
264
+ try {
265
+ if (input.draft.manifest.id !== input.key) return toolError("The draft manifest id must match key.");
266
+ const result = await editWithBryn(client, input);
267
+ return toolSuccess(JSON.stringify({
268
+ ...result,
269
+ nextStep: result.available ? "Check the returned draft. Validate and preview before saving. Publishing still requires the author to confirm." : "Studio Bryn is not installed. Edit draft.code as plain JavaScript defining render(ctx), then use custom_chart_validate / custom_chart_preview / custom_chart_upload with chartTs=draft.code, manifest, bundleCss=draft.css and iconSvg. Nothing was saved."
270
+ }));
271
+ } catch (error) {
272
+ return error instanceof DatabrainApiError ? error.toToolResponse() : toolError("Bryn could not edit the draft. No version was saved.");
273
+ }
274
+ }
275
+ );
276
+ const preview = server.tool(
277
+ "custom_chart_preview",
278
+ "Validate and preview a draft custom chart as an MCP App tile using bounded sample query rows. No version is saved. Pass chartTs to compile on the server without local files; bindings map manifest slot ids to row column names. Hosts without MCP Apps get the rows and validation result.",
279
+ {
280
+ ...bundleInputShape,
281
+ rows: rowsSchema,
282
+ bindings: z3.record(z3.array(z3.string().max(128)).max(20)),
283
+ options: z3.record(z3.union([z3.string().max(2e3), z3.number().finite(), z3.boolean()])).default({})
284
+ },
285
+ READ_ONLY,
286
+ async (input) => {
287
+ try {
288
+ const resolved = await resolveBundleInput(input, { build: true });
289
+ if (resolved.error) return toolError(resolved.error);
290
+ if (!resolved.bundle || !isCompleteBundle(resolved.bundle)) return toolError(formatProblems(resolved.problems));
291
+ const { validateBundle: validateBundle2 } = await import("./bundle-TDQXWIPM.js");
292
+ const problems = validateBundle2(resolved.bundle);
293
+ if (problems.length) return toolError(formatProblems(problems));
294
+ const manifest = resolved.bundle.manifest;
295
+ const columns = Object.keys(input.rows[0] ?? {});
296
+ const missing = manifest.slots.filter((slot) => slot.required && !(input.bindings[slot.id] ?? []).some((column) => columns.includes(column)));
297
+ if (input.rows.length && missing.length) return toolError(`Bind required slots ${missing.map((slot) => slot.id).join(", ")} to sample columns: ${columns.join(", ") || "(none)"}.`);
298
+ const customChart = { key: manifest.id, bindings: input.bindings, options: input.options };
299
+ const payload = {
300
+ kind: "chart",
301
+ title: manifest.name,
302
+ chartSpec: { type: `custom:${manifest.id}` },
303
+ rows: input.rows,
304
+ columns: Object.keys(input.rows[0] ?? {}),
305
+ width: 600,
306
+ height: 320,
307
+ points: [],
308
+ customChart,
309
+ fallback: "Draft validated. An MCP Apps host can preview it. These are sample rows, not a saved metric."
310
+ };
311
+ if (!hasChartApps(server)) return toolSuccess(JSON.stringify({ ...payload, ok: true }));
312
+ const result = chartResult("Custom chart draft validated. Preview uses sample rows. Nothing was saved or published.", payload);
313
+ result._meta = { ...result._meta, "databrain/custom-chart-bundle": resolved.bundle };
314
+ if (JSON.stringify(result).length > MAX_RESULT_CHARS) return toolError("Draft preview exceeds the MCP host result budget. Reduce the source, CSS or sample rows before previewing.");
315
+ return result;
316
+ } catch (error) {
317
+ return error instanceof DatabrainApiError ? error.toToolResponse() : toolError(error instanceof Error && error.message === "Chart page exceeds payload budget" ? "Draft preview exceeds the MCP host result budget. Reduce the source, CSS or sample rows before previewing." : "Custom chart preview could not be prepared. Check the manifest, source and bindings.");
318
+ }
319
+ }
320
+ );
321
+ tagChartTool(server, preview);
322
+ }
111
323
 
112
324
  // src/tools/tool-metadata.ts
113
325
  var DEFAULT_TOOL_META = {
@@ -182,6 +394,7 @@ var TOOL_METADATA = {
182
394
  preview_chart: { category: "internal_analytics", workflowIntent: "query_data", effect: "view" },
183
395
  save_chart_as_dashboard: { category: "internal_analytics", workflowIntent: "author_dashboard", effect: "commit" },
184
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" },
185
398
  render_chart: { category: "embedding", workflowIntent: "chart_app", effect: "commit" },
186
399
  refresh_widget: { category: "embedding", workflowIntent: "chart_app", effect: "commit" },
187
400
  drill_down: { category: "embedding", workflowIntent: "chart_app", effect: "commit" },
@@ -204,6 +417,10 @@ var TOOL_METADATA = {
204
417
  list_scheduled_reports: { category: "reports", workflowIntent: "reports", effect: "read" },
205
418
  // Custom chart types (DAT-925)
206
419
  // Returns template files; nothing is written or persisted.
420
+ custom_chart_edit: { category: "custom_charts", workflowIntent: "custom_chart", effect: "commit" },
421
+ custom_chart_preview: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
422
+ load_custom_chart_bundle: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
423
+ load_custom_chart_runtime: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
207
424
  custom_chart_scaffold: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
208
425
  // Local rules plus POST /validate, which never persists.
209
426
  custom_chart_validate: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
@@ -214,7 +431,7 @@ var TOOL_METADATA = {
214
431
  };
215
432
 
216
433
  // src/tools/data-app/list-data-apps.ts
217
- import { z as z2 } from "zod";
434
+ import { z as z4 } from "zod";
218
435
 
219
436
  // src/api/dashboards-api.ts
220
437
  var DashboardsApi = class {
@@ -399,10 +616,12 @@ var EmbedsApi = class {
399
616
  this.client = client;
400
617
  }
401
618
  async createEmbed(body) {
619
+ const { dataAppName, ...payload } = body;
402
620
  return this.client.request({
403
621
  method: "POST",
404
622
  path: "/api/v2/data-app/embeds",
405
- body,
623
+ query: typeof dataAppName === "string" ? { dataAppName } : void 0,
624
+ body: payload,
406
625
  auth: "api",
407
626
  invalidateCache: ["list_embeds"]
408
627
  });
@@ -424,12 +643,12 @@ var EmbedsApi = class {
424
643
  cacheTtlMs: 5 * 60 * 1e3
425
644
  });
426
645
  }
427
- async getByEmbedId(embedId) {
646
+ async getByEmbedId(embedId, dataAppName) {
428
647
  return this.client.request({
429
648
  method: "GET",
430
649
  path: "/api/v2/data-app/embeds",
431
650
  auth: "api",
432
- query: { embedId }
651
+ query: { embedId, ...dataAppName ? { dataAppName } : {} }
433
652
  });
434
653
  }
435
654
  async updateEmbed(body) {
@@ -509,7 +728,7 @@ var QueryApi = class {
509
728
  async startSemanticLayerGeneration(datamartId2) {
510
729
  return this.client.request({
511
730
  method: "POST",
512
- path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generate`,
731
+ path: `/schema/datamart/${encodeURIComponent(datamartId2)}/semantic-layer/auto-generate`,
513
732
  auth: "service",
514
733
  invalidateCache: ["semantic_layer_"]
515
734
  });
@@ -517,7 +736,7 @@ var QueryApi = class {
517
736
  async getSemanticLayerGenerationStatus(datamartId2) {
518
737
  return this.client.request({
519
738
  method: "GET",
520
- path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generation-status`,
739
+ path: `/schema/datamart/${encodeURIComponent(datamartId2)}/semantic-layer/generation-status`,
521
740
  auth: "service"
522
741
  });
523
742
  }
@@ -556,6 +775,7 @@ var ScheduledReportsApi = class {
556
775
  embedId: params.embedId,
557
776
  clientId: params.clientId
558
777
  };
778
+ if (params.dataAppName) query.dataAppName = params.dataAppName;
559
779
  if (params.userIdentifier) query.userIdentifier = params.userIdentifier;
560
780
  if (params.pageNumber !== void 0) query.pageNumber = String(params.pageNumber);
561
781
  if (params.isPagination !== void 0) {
@@ -579,15 +799,15 @@ var SemanticModelApi = class {
579
799
  path(id2, verb) {
580
800
  return `/api/v2/semantic/${encodeURIComponent(id2)}/${verb}`;
581
801
  }
582
- pull(id2) {
583
- 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 } } : {} });
584
804
  }
585
805
  retrieve(id2, q, k) {
586
806
  return this.client.request({
587
- method: "GET",
807
+ method: "POST",
588
808
  path: this.path(id2, "retrieve"),
589
809
  auth: "service",
590
- query: { q, k: String(k) }
810
+ body: { q, k }
591
811
  });
592
812
  }
593
813
  validate(id2, source2) {
@@ -663,12 +883,12 @@ var WidgetsApi = class {
663
883
  constructor(client) {
664
884
  this.client = client;
665
885
  }
666
- async listWidgets(embedId, clientId) {
886
+ async listWidgets(embedId, clientId, dataAppName) {
667
887
  return this.client.request({
668
888
  method: "GET",
669
889
  path: "/api/v2/data-app/metrics",
670
890
  auth: "api",
671
- query: { embedId, clientId }
891
+ query: { embedId, clientId, ...dataAppName ? { dataAppName } : {} }
672
892
  });
673
893
  }
674
894
  async createMetric(guestToken, body) {
@@ -677,7 +897,7 @@ var WidgetsApi = class {
677
897
  path: "/api/v2/dashboard/createMetric",
678
898
  auth: "none",
679
899
  token: guestToken,
680
- body
900
+ body: { ...body, isLive: body.isLive ?? false }
681
901
  });
682
902
  }
683
903
  async updateMetric(guestToken, body) {
@@ -692,7 +912,7 @@ var WidgetsApi = class {
692
912
  async markArchived(guestToken, body) {
693
913
  return this.client.request({
694
914
  method: "POST",
695
- path: "/api/v2/dashboard/markArchived",
915
+ path: "/api/v3/metric/markArchived",
696
916
  auth: "none",
697
917
  token: guestToken,
698
918
  body
@@ -758,7 +978,7 @@ var DiscoveryService = class {
758
978
  normalizeDashboard(dashboard) {
759
979
  return {
760
980
  name: isNonEmptyString(dashboard.name) && dashboard.name || isNonEmptyString(dashboard.dashboardName) && dashboard.dashboardName || "Unnamed Dashboard",
761
- 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,
762
982
  externalDashboardId: isNonEmptyString(dashboard.externalDashboardId) && dashboard.externalDashboardId || null,
763
983
  workspaceName: isNonEmptyString(dashboard.workspaceName) && dashboard.workspaceName || isNonEmptyString(dashboard.workspace) && dashboard.workspace || isNonEmptyString(dashboard.workspaceId) && dashboard.workspaceId || null,
764
984
  metricCount: typeof dashboard.metricCount === "number" ? dashboard.metricCount : null
@@ -874,8 +1094,8 @@ var EmbedService = class {
874
1094
  };
875
1095
  return this.embedsApi.createEmbed(body);
876
1096
  }
877
- async resolveEmbedStrict(embedId) {
878
- const response = await this.embedsApi.getByEmbedId(embedId);
1097
+ async resolveEmbedStrict(embedId, dataAppName) {
1098
+ const response = await this.embedsApi.getByEmbedId(embedId, dataAppName);
879
1099
  const records = Array.isArray(response) ? response.filter((r) => !!r && typeof r === "object") : [response];
880
1100
  const exact = records.filter((r) => r.embedId === embedId);
881
1101
  if (exact.length === 1) return exact[0];
@@ -890,8 +1110,8 @@ var EmbedService = class {
890
1110
  Received records (sample): ${sample || "none"}`
891
1111
  );
892
1112
  }
893
- async buildUpdateBase(embedId) {
894
- const embed = await this.resolveEmbedStrict(embedId);
1113
+ async buildUpdateBase(embedId, dataAppName) {
1114
+ const embed = await this.resolveEmbedStrict(embedId, dataAppName);
895
1115
  const existingAccess = extractAccessSettings(embed);
896
1116
  if (existingAccess) {
897
1117
  return { embedId, accessSettings: existingAccess };
@@ -939,8 +1159,8 @@ Run \`update_embed\` with explicit \`accessSettings.datamartName\`, or recreate
939
1159
  }
940
1160
  };
941
1161
  }
942
- async updateEmbed(embedId, updatePatch) {
943
- const body = await this.buildUpdateBase(embedId);
1162
+ async updateEmbed(embedId, updatePatch, dataAppName) {
1163
+ const body = await this.buildUpdateBase(embedId, dataAppName);
944
1164
  const baseAccessSettings = asRecord2(body.accessSettings);
945
1165
  const patchAccessSettings = asRecord2(updatePatch.accessSettings);
946
1166
  for (const [key, value] of Object.entries(updatePatch)) {
@@ -955,7 +1175,7 @@ Run \`update_embed\` with explicit \`accessSettings.datamartName\`, or recreate
955
1175
  } else if (patchAccessSettings) {
956
1176
  body.accessSettings = patchAccessSettings;
957
1177
  }
958
- return this.embedsApi.updateEmbed(body);
1178
+ return this.embedsApi.updateEmbed({ ...body, ...dataAppName ? { dataAppName } : {} });
959
1179
  }
960
1180
  };
961
1181
 
@@ -1023,7 +1243,7 @@ var WidgetService = class {
1023
1243
  * where the user only knows the dashboard + workspace, not the embedId or
1024
1244
  * data app name. Returns a business-user-friendly error on ambiguity.
1025
1245
  */
1026
- async resolveEmbedFromDashboard(dashboardName, workspaceName) {
1246
+ async resolveEmbedFromDashboard(dashboardName, workspaceName, dataAppName) {
1027
1247
  const wsProvided = workspaceName && workspaceName.trim().length > 0 ? workspaceName.trim() : void 0;
1028
1248
  let dashboards;
1029
1249
  try {
@@ -1065,8 +1285,23 @@ var WidgetService = class {
1065
1285
  };
1066
1286
  }
1067
1287
  let embeds;
1288
+ let dataAppSelectionAvailable = false;
1068
1289
  try {
1069
- 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
+ }
1070
1305
  } catch (err) {
1071
1306
  return {
1072
1307
  ok: false,
@@ -1092,10 +1327,11 @@ var WidgetService = class {
1092
1327
  };
1093
1328
  }
1094
1329
  if (embedMatches.length > 1) {
1095
- 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))];
1096
1332
  return {
1097
1333
  ok: false,
1098
- 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.")
1099
1335
  };
1100
1336
  }
1101
1337
  return {
@@ -1121,7 +1357,8 @@ var WidgetService = class {
1121
1357
  if (params.dashboardName && params.dashboardName.trim().length > 0) {
1122
1358
  const res = await this.resolveEmbedFromDashboard(
1123
1359
  params.dashboardName,
1124
- params.workspaceName
1360
+ params.workspaceName,
1361
+ params.dataAppName
1125
1362
  );
1126
1363
  if (!res.ok) return res;
1127
1364
  return {
@@ -1171,7 +1408,7 @@ var WidgetService = class {
1171
1408
  error: "`clientId` cannot be empty. Pick a tenant/client first."
1172
1409
  };
1173
1410
  }
1174
- const ctx = await this.resolveEmbedContext(params.embedId);
1411
+ const ctx = await this.resolveEmbedContext(params.embedId, params.dataAppName);
1175
1412
  if (ctx.externalDashboardCreatedBy === clientId) {
1176
1413
  return {
1177
1414
  ok: true,
@@ -1246,7 +1483,7 @@ var WidgetService = class {
1246
1483
  }
1247
1484
  let ctx;
1248
1485
  try {
1249
- ctx = await this.resolveEmbedContext(params.embedId);
1486
+ ctx = await this.resolveEmbedContext(params.embedId, params.dataAppName);
1250
1487
  } catch (err) {
1251
1488
  return { ok: false, error: err.message };
1252
1489
  }
@@ -1354,7 +1591,17 @@ Do not guess between the two \u2014 wait for the user to choose.`
1354
1591
  }
1355
1592
  return false;
1356
1593
  };
1357
- 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
+ }
1358
1605
  if (!target) {
1359
1606
  const available = datasources.filter((ds) => typeof ds.name === "string" && ds.name.trim().length > 0).map((ds) => `"${ds.name}"`).join(", ");
1360
1607
  const inactiveMatch = allDatasources.find(
@@ -1424,8 +1671,8 @@ Do not guess between the two \u2014 wait for the user to choose.`
1424
1671
  }
1425
1672
  return { kind: "no_metric_fields", widgetsTried: toProbe.length, fetchErrors };
1426
1673
  }
1427
- async resolveEmbedContext(embedId) {
1428
- const response = await this.embedsApi.getByEmbedId(embedId);
1674
+ async resolveEmbedContext(embedId, dataAppNameHint) {
1675
+ const response = await this.embedsApi.getByEmbedId(embedId, dataAppNameHint);
1429
1676
  const records = Array.isArray(response) ? response : response ? [response] : [];
1430
1677
  const exact = records.find((r) => r && r.embedId === embedId) || records[0];
1431
1678
  if (!exact) {
@@ -1577,32 +1824,6 @@ function createServiceBundle(client) {
1577
1824
  };
1578
1825
  }
1579
1826
 
1580
- // src/tools/annotations.ts
1581
- var READ_ONLY = {
1582
- readOnlyHint: true,
1583
- destructiveHint: false,
1584
- idempotentHint: true,
1585
- openWorldHint: false
1586
- };
1587
- var WRITE_IDEMPOTENT = {
1588
- readOnlyHint: false,
1589
- destructiveHint: false,
1590
- idempotentHint: true,
1591
- openWorldHint: false
1592
- };
1593
- var WRITE_CREATE = {
1594
- readOnlyHint: false,
1595
- destructiveHint: false,
1596
- idempotentHint: false,
1597
- openWorldHint: false
1598
- };
1599
- var DESTRUCTIVE = {
1600
- readOnlyHint: false,
1601
- destructiveHint: true,
1602
- idempotentHint: false,
1603
- openWorldHint: false
1604
- };
1605
-
1606
1827
  // src/tools/data-app/list-data-apps.ts
1607
1828
  function registerListDataApps(server, client) {
1608
1829
  const services = createServiceBundle(client);
@@ -1610,7 +1831,7 @@ function registerListDataApps(server, client) {
1610
1831
  "list_data_apps",
1611
1832
  "List data apps in your organization. Start here for embed setup.",
1612
1833
  {
1613
- workspaceName: z2.string().optional().describe("Filter by workspace name")
1834
+ workspaceName: z4.string().optional().describe("Filter by workspace name")
1614
1835
  },
1615
1836
  READ_ONLY,
1616
1837
  async (params) => {
@@ -1639,7 +1860,7 @@ function registerListDataApps(server, client) {
1639
1860
  }
1640
1861
 
1641
1862
  // src/tools/data-app/list-tenants.ts
1642
- import { z as z3 } from "zod";
1863
+ import { z as z5 } from "zod";
1643
1864
  var isNonEmptyString4 = (value) => typeof value === "string" && value.trim().length > 0;
1644
1865
  function workspaceIdFrom(workspace) {
1645
1866
  return isNonEmptyString4(workspace.id) && workspace.id || isNonEmptyString4(workspace._id) && workspace._id || isNonEmptyString4(workspace.workspaceId) && workspace.workspaceId || null;
@@ -1677,12 +1898,12 @@ function registerListTenants(server, client) {
1677
1898
  "list_tenants",
1678
1899
  "List available tenants/clients from the workspace tenancy database. Use this to pick a valid clientId before creating guest tokens or editing customer dashboards.",
1679
1900
  {
1680
- workspaceName: z3.string().optional().describe("Workspace name. Preferred for non-technical callers; the tool resolves it to a workspaceId."),
1681
- workspaceId: z3.string().optional().describe("Technical workspace UUID. Provide this instead of workspaceName if already known."),
1682
- searchValue: z3.string().optional().describe("Optional case-insensitive search string for tenant/client labels."),
1683
- limit: z3.number().int().positive().max(500).optional().describe("Maximum number of tenants to return. Backend may apply connector-specific limits."),
1684
- datasourceId: z3.string().optional().describe("Optional datasource UUID override when the workspace has multiple datasource contexts."),
1685
- datamartId: z3.string().optional().describe("Optional datamart UUID override when tenant lookup should use a specific datamart.")
1901
+ workspaceName: z5.string().optional().describe("Workspace name. Preferred for non-technical callers; the tool resolves it to a workspaceId."),
1902
+ workspaceId: z5.string().optional().describe("Technical workspace UUID. Provide this instead of workspaceName if already known."),
1903
+ searchValue: z5.string().optional().describe("Optional case-insensitive search string for tenant/client labels."),
1904
+ limit: z5.number().int().positive().max(500).optional().describe("Maximum number of tenants to return. Backend may apply connector-specific limits."),
1905
+ datasourceId: z5.string().optional().describe("Optional datasource UUID override when the workspace has multiple datasource contexts."),
1906
+ datamartId: z5.string().optional().describe("Optional datamart UUID override when tenant lookup should use a specific datamart.")
1686
1907
  },
1687
1908
  READ_ONLY,
1688
1909
  async (params) => {
@@ -1776,15 +1997,15 @@ function registerListTenants(server, client) {
1776
1997
  }
1777
1998
 
1778
1999
  // src/tools/data-app/create-data-app.ts
1779
- import { z as z4 } from "zod";
2000
+ import { z as z6 } from "zod";
1780
2001
  function registerCreateDataApp(server, client) {
1781
2002
  server.tool(
1782
2003
  "create_data_app",
1783
2004
  "Create a new data app.",
1784
2005
  {
1785
- name: z4.string().describe("Data app name"),
1786
- workspaceName: z4.string().optional().describe("Optional workspace name if required by backend"),
1787
- body: z4.record(z4.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/")
2006
+ name: z6.string().describe("Data app name"),
2007
+ workspaceName: z6.string().optional().describe("Optional workspace name if required by backend"),
2008
+ body: z6.record(z6.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/")
1788
2009
  },
1789
2010
  WRITE_CREATE,
1790
2011
  async (params) => {
@@ -1818,7 +2039,7 @@ function registerCreateDataApp(server, client) {
1818
2039
  }
1819
2040
 
1820
2041
  // src/tools/data-app/list-api-tokens.ts
1821
- import { z as z5 } from "zod";
2042
+ import { z as z7 } from "zod";
1822
2043
 
1823
2044
  // src/utils/resolve-resource.ts
1824
2045
  function resolveResource(options) {
@@ -1887,7 +2108,7 @@ function registerListApiTokens(server, client) {
1887
2108
  "list_api_tokens",
1888
2109
  "List API tokens for a data app. Omit dataAppName to auto-resolve.",
1889
2110
  {
1890
- dataAppName: z5.string().optional().describe("Data app name. Omit to list available data apps.")
2111
+ dataAppName: z7.string().optional().describe("Data app name. Omit to list available data apps.")
1891
2112
  },
1892
2113
  READ_ONLY,
1893
2114
  async (params) => {
@@ -1932,16 +2153,16 @@ function registerListApiTokens(server, client) {
1932
2153
  }
1933
2154
 
1934
2155
  // src/tools/data-app/create-api-token.ts
1935
- import { z as z6 } from "zod";
2156
+ import { z as z8 } from "zod";
1936
2157
  function registerCreateApiToken(server, client) {
1937
2158
  const services = createServiceBundle(client);
1938
2159
  server.tool(
1939
2160
  "create_api_token",
1940
2161
  "Create an API token for a data app and activate it for this session. Omit dataAppName to auto-resolve.",
1941
2162
  {
1942
- dataAppName: z6.string().optional().describe("Data app name. Omit to list available data apps."),
1943
- name: z6.string().optional().describe("Optional token name"),
1944
- body: z6.record(z6.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/api-tokens")
2163
+ dataAppName: z8.string().optional().describe("Data app name. Omit to list available data apps."),
2164
+ name: z8.string().optional().describe("Optional token name"),
2165
+ body: z8.record(z8.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/api-tokens")
1945
2166
  },
1946
2167
  WRITE_CREATE,
1947
2168
  async (params) => {
@@ -1994,16 +2215,17 @@ Save this in backend secrets immediately. This value may not be returned again.`
1994
2215
  }
1995
2216
 
1996
2217
  // src/tools/data-app/rotate-api-token.ts
1997
- import { z as z7 } from "zod";
2218
+ import { randomUUID } from "crypto";
2219
+ import { z as z9 } from "zod";
1998
2220
  function registerRotateApiToken(server, client) {
1999
2221
  const services = createServiceBundle(client);
2000
2222
  server.tool(
2001
2223
  "rotate_api_token",
2002
- "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.",
2003
2225
  {
2004
- dataAppName: z7.string().optional().describe("Data app name. Omit to list available data apps."),
2005
- tokenId: z7.string().optional().describe("Optional token ID to rotate"),
2006
- body: z7.record(z7.unknown()).optional().describe("Raw API body passthrough for rotate endpoint")
2226
+ dataAppName: z9.string().optional().describe("Data app name. Omit to list available data apps."),
2227
+ tokenId: z9.string().optional().describe("Optional token ID to rotate"),
2228
+ body: z9.record(z9.unknown()).optional().describe("Raw API body passthrough for rotate endpoint")
2007
2229
  },
2008
2230
  DESTRUCTIVE,
2009
2231
  async (params) => {
@@ -2039,13 +2261,14 @@ function registerRotateApiToken(server, client) {
2039
2261
  ` : "") + "Update backend secrets immediately."
2040
2262
  );
2041
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.");
2042
2265
  const createRes = await client.request({
2043
2266
  method: "POST",
2044
2267
  path: "/api/v2/data-app/api-tokens",
2045
2268
  auth: "service",
2046
2269
  body: {
2047
2270
  dataAppName,
2048
- name: `${dataAppName}-api-token-rotated`
2271
+ name: `${dataAppName}-rotated-${randomUUID()}`
2049
2272
  }
2050
2273
  });
2051
2274
  if (createRes.error) {
@@ -2058,7 +2281,7 @@ function registerRotateApiToken(server, client) {
2058
2281
 
2059
2282
  ` + (key ? `**New Token:** \`${key}\`
2060
2283
 
2061
- ` : "") + "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."
2062
2285
  );
2063
2286
  } catch (err) {
2064
2287
  if (err instanceof DatabrainApiError) return err.toToolResponse();
@@ -2122,14 +2345,14 @@ function registerListDatasources(server, client) {
2122
2345
  }
2123
2346
 
2124
2347
  // src/tools/infrastructure/discover-datasource-schema.ts
2125
- import { z as z8 } from "zod";
2348
+ import { z as z10 } from "zod";
2126
2349
  function registerDiscoverDatasourceSchema(server, client) {
2127
2350
  const services = createServiceBundle(client);
2128
2351
  server.tool(
2129
2352
  "discover_datasource_schema",
2130
2353
  "Inspect tables and columns in a datasource. Omit datasourceName to list available datasources.",
2131
2354
  {
2132
- datasourceName: z8.string().optional().describe(
2355
+ datasourceName: z10.string().optional().describe(
2133
2356
  "Name of the datasource to inspect. Omit to list available datasources."
2134
2357
  )
2135
2358
  },
@@ -2212,13 +2435,13 @@ Fix the connection or add a new database in the **Databrain UI**.`
2212
2435
  }
2213
2436
 
2214
2437
  // src/tools/infrastructure/list-datamarts.ts
2215
- import { z as z9 } from "zod";
2438
+ import { z as z11 } from "zod";
2216
2439
  function registerListDatamarts(server, client) {
2217
2440
  server.tool(
2218
2441
  "list_datamarts",
2219
2442
  "List datamarts with table counts and semantic layer scores.",
2220
2443
  {
2221
- dataAppName: z9.string().optional().describe("Filter by data app name")
2444
+ dataAppName: z11.string().optional().describe("Filter by data app name")
2222
2445
  },
2223
2446
  READ_ONLY,
2224
2447
  async (params) => {
@@ -2307,7 +2530,7 @@ function registerListWorkspaces(server, client) {
2307
2530
  }
2308
2531
 
2309
2532
  // src/tools/infrastructure/create-datamart.ts
2310
- import { z as z10 } from "zod";
2533
+ import { z as z12 } from "zod";
2311
2534
 
2312
2535
  // src/tools/widgets/production-guards.ts
2313
2536
  var APPLY_TO_PRODUCTION_CONFIRMATION = "APPLY_TO_PRODUCTION";
@@ -2325,29 +2548,29 @@ function genericProductionSafetyNote() {
2325
2548
  }
2326
2549
 
2327
2550
  // src/tools/infrastructure/create-datamart.ts
2328
- var columnSchema = z10.object({
2329
- name: z10.string(),
2330
- alias: z10.string().optional(),
2331
- label: z10.string().optional(),
2332
- isHide: z10.boolean().optional(),
2333
- isCustomColumn: z10.boolean().optional(),
2334
- sql: z10.string().optional()
2551
+ var columnSchema = z12.object({
2552
+ name: z12.string(),
2553
+ alias: z12.string().optional(),
2554
+ label: z12.string().optional(),
2555
+ isHide: z12.boolean().optional(),
2556
+ isCustomColumn: z12.boolean().optional(),
2557
+ sql: z12.string().optional()
2335
2558
  });
2336
- var tableSchema = z10.object({
2337
- name: z10.string(),
2338
- schemaName: z10.string().optional(),
2339
- clientColumn: z10.string().optional(),
2340
- label: z10.string().optional(),
2341
- columns: z10.array(columnSchema)
2559
+ var tableSchema = z12.object({
2560
+ name: z12.string(),
2561
+ schemaName: z12.string().optional(),
2562
+ clientColumn: z12.string().optional(),
2563
+ label: z12.string().optional(),
2564
+ columns: z12.array(columnSchema)
2342
2565
  });
2343
- var relationshipSchema = z10.object({
2344
- parentTableName: z10.string(),
2345
- parentColumnName: z10.string(),
2346
- childTableName: z10.string(),
2347
- childColumnName: z10.string(),
2348
- cardinality: z10.enum(["OneToOne", "OneToMany", "ManyToOne", "ManyToMany"]),
2349
- join: z10.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN"]),
2350
- relationshipName: z10.string()
2566
+ var relationshipSchema = z12.object({
2567
+ parentTableName: z12.string(),
2568
+ parentColumnName: z12.string(),
2569
+ childTableName: z12.string(),
2570
+ childColumnName: z12.string(),
2571
+ cardinality: z12.enum(["OneToOne", "OneToMany", "ManyToOne", "ManyToMany"]),
2572
+ join: z12.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN"]),
2573
+ relationshipName: z12.string()
2351
2574
  });
2352
2575
  function datamartSuccessMessage(name, result) {
2353
2576
  return `Datamart "${name}" created successfully (ID: ${result.id || result._id || "UNKNOWN"}).
@@ -2368,21 +2591,21 @@ function registerCreateDatamart(server, client) {
2368
2591
  "create_datamart",
2369
2592
  "Create a production datamart from selected tables and relationships. Defaults to dry-run; apply requires explicit confirmation.",
2370
2593
  {
2371
- mode: z10.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run returns the plan only; apply creates the datamart"),
2372
- confirm: z10.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
2373
- name: z10.string().describe("Unique datamart name"),
2374
- datasourceName: z10.string().optional().describe("Name of the datasource to use. Omit to list available datasources."),
2375
- tableList: z10.array(tableSchema).optional().describe("Array of tables with their columns"),
2376
- relationships: z10.array(relationshipSchema).optional().describe("Join definitions between tables"),
2377
- tenancySettings: z10.object({
2378
- tenancyLevel: z10.enum(["TABLE", "DATABASE"]).optional(),
2379
- clientColumnType: z10.string().optional(),
2380
- schemaName: z10.string().optional(),
2381
- tableName: z10.string().optional(),
2382
- tableClientNameColumn: z10.string().optional(),
2383
- tablePrimaryKeyColumn: z10.string().optional()
2594
+ mode: z12.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run returns the plan only; apply creates the datamart"),
2595
+ confirm: z12.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
2596
+ name: z12.string().describe("Unique datamart name"),
2597
+ datasourceName: z12.string().optional().describe("Name of the datasource to use. Omit to list available datasources."),
2598
+ tableList: z12.array(tableSchema).optional().describe("Array of tables with their columns"),
2599
+ relationships: z12.array(relationshipSchema).optional().describe("Join definitions between tables"),
2600
+ tenancySettings: z12.object({
2601
+ tenancyLevel: z12.enum(["TABLE", "DATABASE"]).optional(),
2602
+ clientColumnType: z12.string().optional(),
2603
+ schemaName: z12.string().optional(),
2604
+ tableName: z12.string().optional(),
2605
+ tableClientNameColumn: z12.string().optional(),
2606
+ tablePrimaryKeyColumn: z12.string().optional()
2384
2607
  }).optional().describe("Multi-tenant configuration"),
2385
- body: z10.record(z10.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/datamarts")
2608
+ body: z12.record(z12.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/datamarts")
2386
2609
  },
2387
2610
  WRITE_CREATE,
2388
2611
  async (params) => {
@@ -2477,14 +2700,14 @@ function registerCreateDatamart(server, client) {
2477
2700
  }
2478
2701
 
2479
2702
  // src/tools/infrastructure/sync-datasource.ts
2480
- import { z as z11 } from "zod";
2703
+ import { z as z13 } from "zod";
2481
2704
  function registerSyncDatasource(server, client) {
2482
2705
  const datasourcesApi = new DatasourcesApi(client);
2483
2706
  server.tool(
2484
2707
  "sync_datasource",
2485
2708
  "Trigger a datasource schema sync to refresh table and column metadata. If datasourceName is omitted and there are multiple datasources, returns the list so the user can choose.",
2486
2709
  {
2487
- datasourceName: z11.string().optional().describe("Name of the datasource to sync. Omit to list available datasources.")
2710
+ datasourceName: z13.string().optional().describe("Name of the datasource to sync. Omit to list available datasources.")
2488
2711
  },
2489
2712
  WRITE_IDEMPOTENT,
2490
2713
  async (params) => {
@@ -2533,7 +2756,7 @@ The schema metadata will be refreshed. You can now use \`discover_datasource_sch
2533
2756
  }
2534
2757
 
2535
2758
  // src/tools/embed/create-embed.ts
2536
- import { z as z12 } from "zod";
2759
+ import { z as z14 } from "zod";
2537
2760
  var DEFAULT_ACCESS_SETTINGS2 = {
2538
2761
  isAllowMetricCreation: false,
2539
2762
  isAllowMetricDeletion: false,
@@ -2546,18 +2769,18 @@ var DEFAULT_ACCESS_SETTINGS2 = {
2546
2769
  isAllowCreateDashboardView: false,
2547
2770
  metricCreationMode: "DRAG_DROP"
2548
2771
  };
2549
- var accessSettingsSchema = z12.object({
2550
- datamartName: z12.string().describe("Name of the datamart (required)"),
2551
- isAllowMetricCreation: z12.boolean().optional().describe("Allow end-users to create metrics"),
2552
- metricCreationMode: z12.enum(["DRAG_DROP", "CHAT"]).optional().describe("Metric creation mode"),
2553
- isAllowMetricDeletion: z12.boolean().optional().describe("Allow metric deletion"),
2554
- isAllowMetricUpdate: z12.boolean().optional().describe("Allow metric editing"),
2555
- isAllowMetricLayoutChange: z12.boolean().optional().describe("Allow layout changes"),
2556
- isAllowManageMetrics: z12.boolean().optional().describe("Show manage metrics UI"),
2557
- isAllowUnderlyingData: z12.boolean().optional().describe("Allow viewing underlying data"),
2558
- isAllowAiPilot: z12.boolean().optional().describe("Enable AI pilot features"),
2559
- isAllowEmailReports: z12.boolean().optional().describe("Allow email report scheduling"),
2560
- isAllowCreateDashboardView: z12.boolean().optional().describe("Allow creating dashboard views")
2772
+ var accessSettingsSchema = z14.object({
2773
+ datamartName: z14.string().describe("Name of the datamart (required)"),
2774
+ isAllowMetricCreation: z14.boolean().optional().describe("Allow end-users to create metrics"),
2775
+ metricCreationMode: z14.enum(["DRAG_DROP", "CHAT"]).optional().describe("Metric creation mode"),
2776
+ isAllowMetricDeletion: z14.boolean().optional().describe("Allow metric deletion"),
2777
+ isAllowMetricUpdate: z14.boolean().optional().describe("Allow metric editing"),
2778
+ isAllowMetricLayoutChange: z14.boolean().optional().describe("Allow layout changes"),
2779
+ isAllowManageMetrics: z14.boolean().optional().describe("Show manage metrics UI"),
2780
+ isAllowUnderlyingData: z14.boolean().optional().describe("Allow viewing underlying data"),
2781
+ isAllowAiPilot: z14.boolean().optional().describe("Enable AI pilot features"),
2782
+ isAllowEmailReports: z14.boolean().optional().describe("Allow email report scheduling"),
2783
+ isAllowCreateDashboardView: z14.boolean().optional().describe("Allow creating dashboard views")
2561
2784
  }).describe("Embed access settings. Only datamartName is required \u2014 all flags default to false.");
2562
2785
  function registerCreateEmbed(server, client) {
2563
2786
  const services = createServiceBundle(client);
@@ -2565,28 +2788,37 @@ function registerCreateEmbed(server, client) {
2565
2788
  "create_embed",
2566
2789
  "Create an embed. Provide dashboardId, workspaceName, accessSettings.datamartName. Add clientId for multi-tenant per-client dashboards. All isAllow* flags default to false.",
2567
2790
  {
2568
- body: z12.record(z12.unknown()).optional().describe(
2791
+ body: z14.record(z14.unknown()).optional().describe(
2569
2792
  "Raw API body passthrough. If provided, all other fields are ignored."
2570
2793
  ),
2571
- dashboardId: z12.string().optional().describe("Dashboard identifier for embed creation"),
2572
- workspaceName: z12.string().optional().describe("Workspace name. Omit to auto-resolve if only one exists."),
2573
- embedType: z12.enum(["dashboard", "metric"]).optional().describe("Embed type"),
2574
- metricId: z12.string().optional().describe("Required for metric embed type"),
2575
- name: z12.string().optional().describe("Human-readable embed config name"),
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"),
2796
+ workspaceName: z14.string().optional().describe("Workspace name. Omit to auto-resolve if only one exists."),
2797
+ embedType: z14.enum(["dashboard", "metric"]).optional().describe("Embed type"),
2798
+ metricId: z14.string().optional().describe("Required for metric embed type"),
2799
+ name: z14.string().optional().describe("Human-readable embed config name"),
2576
2800
  accessSettings: accessSettingsSchema.optional(),
2577
2801
  // Multi-tenant (dashboard embed) params
2578
- clientId: z12.string().optional().describe(
2802
+ clientId: z14.string().optional().describe(
2579
2803
  "Client identifier for multi-tenant isolation. When provided, creates a dashboard embed (each client gets their own dashboard copy)."
2580
2804
  ),
2581
- templateDashboardId: z12.string().optional().describe("Template dashboard ID to clone metrics from"),
2582
- isImportMetrics: z12.boolean().optional().describe("Import metrics from template dashboard"),
2583
- metricImportMode: z12.enum(["COPY", "LINK"]).optional().describe("COPY duplicates metrics, LINK shares them"),
2584
- isRenameDashboard: z12.boolean().optional().describe("Rename the dashboard for this client")
2805
+ templateDashboardId: z14.string().optional().describe("Template dashboard UUID or externalDashboardId slug to clone metrics from"),
2806
+ isImportMetrics: z14.boolean().optional().describe("Import metrics from template dashboard"),
2807
+ metricImportMode: z14.enum(["GALLERY", "METRIC"]).optional().describe("GALLERY imports dashboard gallery metrics; METRIC imports selected metrics"),
2808
+ isRenameDashboard: z14.boolean().optional().describe("Rename the dashboard for this client")
2585
2809
  },
2586
2810
  WRITE_CREATE,
2587
2811
  async (params) => {
2588
2812
  try {
2589
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
+ };
2590
2822
  let resolvedWorkspaceName = params.workspaceName;
2591
2823
  if (!params.body && isMultiTenant && !resolvedWorkspaceName) {
2592
2824
  const workspaces = await services.workspacesApi.listWorkspaces();
@@ -2604,6 +2836,7 @@ function registerCreateEmbed(server, client) {
2604
2836
  if (isMultiTenant) {
2605
2837
  const body2 = params.body ? { ...params.body } : {
2606
2838
  dashboardId: params.dashboardId,
2839
+ dataAppName: params.dataAppName,
2607
2840
  workspaceName: resolvedWorkspaceName,
2608
2841
  clientId: params.clientId,
2609
2842
  name: params.name,
@@ -2615,10 +2848,13 @@ function registerCreateEmbed(server, client) {
2615
2848
  };
2616
2849
  const callerAccess = body2.accessSettings && typeof body2.accessSettings === "object" ? body2.accessSettings : {};
2617
2850
  body2.accessSettings = { ...DEFAULT_ACCESS_SETTINGS2, ...callerAccess };
2851
+ if (body2.templateDashboardId) body2.templateDashboardId = await dashboardSlug(body2.templateDashboardId, body2.workspaceName);
2852
+ const { dataAppName, ...payload } = body2;
2618
2853
  const result2 = await client.request({
2619
2854
  method: "POST",
2620
2855
  path: "/api/v2/data-app/dashboard-embeds",
2621
- body: body2,
2856
+ query: typeof dataAppName === "string" ? { dataAppName } : void 0,
2857
+ body: payload,
2622
2858
  auth: "api",
2623
2859
  invalidateCache: ["list_embeds"]
2624
2860
  });
@@ -2639,6 +2875,7 @@ Next: use \`generate_guest_token\` with the clientId and \`generate_embed_code\`
2639
2875
  }
2640
2876
  const body = params.body ? { ...params.body } : {
2641
2877
  dashboardId: params.dashboardId,
2878
+ dataAppName: params.dataAppName,
2642
2879
  workspaceName: params.workspaceName,
2643
2880
  embedType: params.embedType || "dashboard",
2644
2881
  metricId: params.metricId,
@@ -2659,6 +2896,7 @@ Next: use \`generate_guest_token\` with the clientId and \`generate_embed_code\`
2659
2896
  if (!datamartName || typeof datamartName !== "string") {
2660
2897
  return toolError("create_embed requires `accessSettings.datamartName`.");
2661
2898
  }
2899
+ body.dashboardId = await dashboardSlug(body.dashboardId, body.workspaceName);
2662
2900
  const result = await services.embedService.createEmbedStrict(body);
2663
2901
  if (result.error) {
2664
2902
  return toolError(`Embed creation failed: ${stringify(result.error)}`);
@@ -2683,14 +2921,14 @@ Next: run \`get_embed_details\` to verify the embed configuration.`
2683
2921
  }
2684
2922
 
2685
2923
  // src/tools/embed/list-embeds.ts
2686
- import { z as z13 } from "zod";
2924
+ import { z as z15 } from "zod";
2687
2925
  function registerListEmbeds(server, client) {
2688
2926
  const services = createServiceBundle(client);
2689
2927
  server.tool(
2690
2928
  "list_embeds",
2691
2929
  "List embed configurations. Requires an API token (use create_api_token first). Use `list_tenants` for authoritative client discovery from the tenancy database.",
2692
2930
  {
2693
- dataAppName: z13.string().optional().describe("Filter by data app name")
2931
+ dataAppName: z15.string().optional().describe("Filter by data app name")
2694
2932
  },
2695
2933
  READ_ONLY,
2696
2934
  async (params) => {
@@ -2741,19 +2979,20 @@ function registerListEmbeds(server, client) {
2741
2979
  }
2742
2980
 
2743
2981
  // src/tools/embed/get-embed-details.ts
2744
- import { z as z14 } from "zod";
2982
+ import { z as z16 } from "zod";
2745
2983
  function registerGetEmbedDetails(server, client) {
2746
2984
  const services = createServiceBundle(client);
2747
2985
  server.tool(
2748
2986
  "get_embed_details",
2749
2987
  "Get full embed configuration including access settings and theme.",
2750
2988
  {
2751
- embedId: z14.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")
2752
2991
  },
2753
2992
  READ_ONLY,
2754
2993
  async (params) => {
2755
2994
  try {
2756
- const embed = await services.embedService.resolveEmbedStrict(params.embedId);
2995
+ const embed = await services.embedService.resolveEmbedStrict(params.embedId, params.dataAppName);
2757
2996
  return toolSuccess(JSON.stringify({ embedId: params.embedId, ...embed }));
2758
2997
  } catch (err) {
2759
2998
  if (err instanceof DatabrainApiError) return err.toToolResponse();
@@ -2764,7 +3003,7 @@ function registerGetEmbedDetails(server, client) {
2764
3003
  }
2765
3004
 
2766
3005
  // src/tools/embed/update-embed.ts
2767
- import { z as z15 } from "zod";
3006
+ import { z as z17 } from "zod";
2768
3007
 
2769
3008
  // src/tools/embed/helpers.ts
2770
3009
  var GUEST_TOKEN_REMINDER = "\n\n\u26A0\uFE0F **Guest tokens must be regenerated** for these changes to take effect. Existing guest tokens still carry the old settings. Call `generate_guest_token` to create a new token with the updated configuration.";
@@ -2802,15 +3041,16 @@ function registerUpdateEmbed(server, client) {
2802
3041
  "update_embed",
2803
3042
  "Update embed access settings, permissions, theme, or name.",
2804
3043
  {
2805
- embedId: z15.string().describe("ID of the embed to update"),
2806
- body: z15.record(z15.unknown()).optional().describe(
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"),
3046
+ body: z17.record(z17.unknown()).optional().describe(
2807
3047
  "Raw API body for PUT /api/v2/data-app/embeds. If provided, it is sent directly (passthrough mode)."
2808
3048
  ),
2809
- name: z15.string().optional().describe("New name for the embed"),
2810
- accessSettings: z15.record(z15.unknown()).optional().describe(
3049
+ name: z17.string().optional().describe("New name for the embed"),
3050
+ accessSettings: z17.record(z17.unknown()).optional().describe(
2811
3051
  "Partial update of access settings. See `get_embed_details` for current settings."
2812
3052
  ),
2813
- theme: z15.record(z15.unknown()).optional().describe(
3053
+ theme: z17.record(z17.unknown()).optional().describe(
2814
3054
  "Theme configuration object. See databrain://theme-reference for the full schema."
2815
3055
  )
2816
3056
  },
@@ -2820,6 +3060,7 @@ function registerUpdateEmbed(server, client) {
2820
3060
  if (params.body) {
2821
3061
  const rawBody = { ...params.body };
2822
3062
  if (!rawBody.embedId) rawBody.embedId = params.embedId;
3063
+ if (params.dataAppName) rawBody.dataAppName = params.dataAppName;
2823
3064
  const result2 = await services.embedsApi.updateEmbed(rawBody);
2824
3065
  if (result2.error) {
2825
3066
  return toolError(`Embed update failed: ${stringify(result2.error)}`);
@@ -2834,7 +3075,8 @@ function registerUpdateEmbed(server, client) {
2834
3075
  if (params.theme) patch.theme = params.theme;
2835
3076
  const result = await services.embedService.updateEmbed(
2836
3077
  params.embedId,
2837
- patch
3078
+ patch,
3079
+ params.dataAppName
2838
3080
  );
2839
3081
  if (result.error) {
2840
3082
  return toolError(`Embed update failed: ${stringify(result.error)}`);
@@ -2856,7 +3098,7 @@ function registerUpdateEmbed(server, client) {
2856
3098
  }
2857
3099
 
2858
3100
  // src/tools/embed/customize-theme.ts
2859
- import { z as z16 } from "zod";
3101
+ import { z as z18 } from "zod";
2860
3102
  var THEME_PRESETS = {
2861
3103
  light: {
2862
3104
  general: {
@@ -2990,7 +3232,7 @@ var ACCESS_PRESETS = {
2990
3232
  metricCreationMode: "CHAT"
2991
3233
  }
2992
3234
  };
2993
- var fontFamilyEnum = z16.enum([
3235
+ var fontFamilyEnum = z18.enum([
2994
3236
  "Inter",
2995
3237
  "Roboto",
2996
3238
  "Open Sans",
@@ -3002,17 +3244,17 @@ var fontFamilyEnum = z16.enum([
3002
3244
  "Nunito",
3003
3245
  "system-ui"
3004
3246
  ]).describe("Font family");
3005
- var tooltipFontSchema = z16.object({
3006
- fontFamily: z16.string().optional(),
3007
- fontSize: z16.number().optional(),
3008
- fontWeight: z16.number().optional(),
3009
- fontColor: z16.string().optional()
3247
+ var tooltipFontSchema = z18.object({
3248
+ fontFamily: z18.string().optional(),
3249
+ fontSize: z18.number().optional(),
3250
+ fontWeight: z18.number().optional(),
3251
+ fontColor: z18.string().optional()
3010
3252
  }).optional();
3011
- var fontConfigInputSchema = z16.object({
3012
- fontSize: z16.number().optional(),
3013
- fontFamily: z16.string().optional(),
3014
- fontWeight: z16.number().optional(),
3015
- color: z16.string().optional()
3253
+ var fontConfigInputSchema = z18.object({
3254
+ fontSize: z18.number().optional(),
3255
+ fontFamily: z18.string().optional(),
3256
+ fontWeight: z18.number().optional(),
3257
+ color: z18.string().optional()
3016
3258
  }).optional();
3017
3259
  function mapToFrontendFontConfig(input) {
3018
3260
  if (!input) return void 0;
@@ -3023,23 +3265,23 @@ function mapToFrontendFontConfig(input) {
3023
3265
  ...input.color && { color: input.color }
3024
3266
  };
3025
3267
  }
3026
- var axisSchema = z16.object({
3027
- hideAxisLines: z16.boolean().optional().describe("Hide axis lines"),
3028
- hideSplitLines: z16.boolean().optional().describe("Hide grid/split lines"),
3029
- hideAxisLabels: z16.boolean().optional().describe("Hide axis labels"),
3030
- hideAxisTicks: z16.boolean().optional().describe("Hide axis tick marks"),
3031
- axisName: z16.string().optional().describe("Axis title text"),
3032
- axisNameOffset: z16.number().optional().describe("Axis title offset from axis"),
3033
- axisLabelMargin: z16.number().optional().describe("Margin between axis labels and chart area in pixels"),
3034
- fontSize: z16.number().optional().describe("Axis label font size"),
3035
- fontFamily: z16.string().optional().describe("Axis label font family"),
3036
- fontWeight: z16.number().optional().describe("Axis label font weight"),
3037
- color: z16.string().optional().describe("Axis label color (hex)"),
3038
- axisColor: z16.string().optional().describe("Axis line color (hex)"),
3039
- axisNameFontConfig: z16.object({
3040
- fontFamily: z16.string().optional(),
3041
- fontSize: z16.number().optional(),
3042
- fontWeight: z16.number().optional()
3268
+ var axisSchema = z18.object({
3269
+ hideAxisLines: z18.boolean().optional().describe("Hide axis lines"),
3270
+ hideSplitLines: z18.boolean().optional().describe("Hide grid/split lines"),
3271
+ hideAxisLabels: z18.boolean().optional().describe("Hide axis labels"),
3272
+ hideAxisTicks: z18.boolean().optional().describe("Hide axis tick marks"),
3273
+ axisName: z18.string().optional().describe("Axis title text"),
3274
+ axisNameOffset: z18.number().optional().describe("Axis title offset from axis"),
3275
+ axisLabelMargin: z18.number().optional().describe("Margin between axis labels and chart area in pixels"),
3276
+ fontSize: z18.number().optional().describe("Axis label font size"),
3277
+ fontFamily: z18.string().optional().describe("Axis label font family"),
3278
+ fontWeight: z18.number().optional().describe("Axis label font weight"),
3279
+ color: z18.string().optional().describe("Axis label color (hex)"),
3280
+ axisColor: z18.string().optional().describe("Axis line color (hex)"),
3281
+ axisNameFontConfig: z18.object({
3282
+ fontFamily: z18.string().optional(),
3283
+ fontSize: z18.number().optional(),
3284
+ fontWeight: z18.number().optional()
3043
3285
  }).optional().describe("Axis title font configuration")
3044
3286
  }).optional();
3045
3287
  function buildAdminTheme(p, presetBase) {
@@ -3195,94 +3437,95 @@ function registerCustomizeTheme(server, client) {
3195
3437
  "customize_embed_theme",
3196
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("/")}).`,
3197
3439
  {
3198
- embedId: z16.string().describe("ID of the embed to theme"),
3199
- body: z16.record(z16.unknown()).optional().describe(
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"),
3442
+ body: z18.record(z18.unknown()).optional().describe(
3200
3443
  "Raw API body for PUT /api/v2/data-app/embeds. If provided, sent directly and all convenience fields are ignored."
3201
3444
  ),
3202
3445
  // --- Presets ---
3203
- themePreset: z16.enum(["light", "dark", "corporate", "minimal"]).optional().describe("Apply a theme preset as the base. Granular params below override preset values."),
3204
- accessPreset: z16.enum(["view-only", "power-user", "export-only", "ai-enabled"]).optional().describe("Apply an access settings preset"),
3446
+ themePreset: z18.enum(["light", "dark", "corporate", "minimal"]).optional().describe("Apply a theme preset as the base. Granular params below override preset values."),
3447
+ accessPreset: z18.enum(["view-only", "power-user", "export-only", "ai-enabled"]).optional().describe("Apply an access settings preset"),
3205
3448
  // --- General ---
3206
3449
  fontFamily: fontFamilyEnum.optional().describe("Global font family"),
3207
- datePickerFormat: z16.string().optional().describe("Date picker display format"),
3450
+ datePickerFormat: z18.string().optional().describe("Date picker display format"),
3208
3451
  // --- Colors ---
3209
- primaryColor: z16.string().optional().describe("Primary accent color (hex)"),
3210
- backgroundColor: z16.string().optional().describe("Overall background color (hex)"),
3211
- textColor: z16.string().optional().describe("Default text color (hex)"),
3212
- ctaColor: z16.string().optional().describe("Call-to-action button color (hex)"),
3213
- ctaTextColor: z16.string().optional().describe("CTA button text color (hex)"),
3452
+ primaryColor: z18.string().optional().describe("Primary accent color (hex)"),
3453
+ backgroundColor: z18.string().optional().describe("Overall background color (hex)"),
3454
+ textColor: z18.string().optional().describe("Default text color (hex)"),
3455
+ ctaColor: z18.string().optional().describe("Call-to-action button color (hex)"),
3456
+ ctaTextColor: z18.string().optional().describe("CTA button text color (hex)"),
3214
3457
  // --- Dashboard ---
3215
- dashboardBackgroundColor: z16.string().optional().describe("Dashboard container background (hex)"),
3216
- selectBoxSize: z16.enum(["small", "medium", "large"]).optional().describe("Filter select box size"),
3217
- selectBoxVariant: z16.enum(["floating", "outlined", "standard"]).optional().describe("Filter select box style"),
3218
- selectBoxBorderRadius: z16.string().optional().describe('Select box border radius (e.g. "20px")'),
3219
- selectBoxTextColor: z16.string().optional().describe("Select box text color (hex)"),
3220
- metricCardColor: z16.string().optional().describe("Metric card background color (hex)"),
3458
+ dashboardBackgroundColor: z18.string().optional().describe("Dashboard container background (hex)"),
3459
+ selectBoxSize: z18.enum(["small", "medium", "large"]).optional().describe("Filter select box size"),
3460
+ selectBoxVariant: z18.enum(["floating", "outlined", "standard"]).optional().describe("Filter select box style"),
3461
+ selectBoxBorderRadius: z18.string().optional().describe('Select box border radius (e.g. "20px")'),
3462
+ selectBoxTextColor: z18.string().optional().describe("Select box text color (hex)"),
3463
+ metricCardColor: z18.string().optional().describe("Metric card background color (hex)"),
3221
3464
  // --- Card Title ---
3222
- cardTitleFontSize: z16.string().optional().describe('Card title font size (e.g. "16px")'),
3223
- cardTitleFontWeight: z16.string().optional().describe('Card title font weight (e.g. "600")'),
3224
- cardTitleColor: z16.string().optional().describe("Card title text color (hex)"),
3465
+ cardTitleFontSize: z18.string().optional().describe('Card title font size (e.g. "16px")'),
3466
+ cardTitleFontWeight: z18.string().optional().describe('Card title font weight (e.g. "600")'),
3467
+ cardTitleColor: z18.string().optional().describe("Card title text color (hex)"),
3225
3468
  // --- Card Description ---
3226
- cardDescriptionFontSize: z16.string().optional().describe("Card description font size"),
3227
- cardDescriptionFontWeight: z16.string().optional().describe("Card description font weight"),
3228
- cardDescriptionColor: z16.string().optional().describe("Card description text color (hex)"),
3469
+ cardDescriptionFontSize: z18.string().optional().describe("Card description font size"),
3470
+ cardDescriptionFontWeight: z18.string().optional().describe("Card description font weight"),
3471
+ cardDescriptionColor: z18.string().optional().describe("Card description text color (hex)"),
3229
3472
  // --- Card Customization ---
3230
- cardPadding: z16.string().optional().describe("Card internal padding"),
3231
- cardBorderRadius: z16.string().optional().describe("Card border radius"),
3232
- cardShadow: z16.string().optional().describe("Card box-shadow CSS value"),
3233
- disableShadowOnHover: z16.boolean().optional().describe("Disable shadow on card hover"),
3234
- disableCardStroke: z16.boolean().optional().describe("Remove card border stroke"),
3235
- cardStrokeColor: z16.string().optional().describe("Card border stroke color (hex)"),
3473
+ cardPadding: z18.string().optional().describe("Card internal padding"),
3474
+ cardBorderRadius: z18.string().optional().describe("Card border radius"),
3475
+ cardShadow: z18.string().optional().describe("Card box-shadow CSS value"),
3476
+ disableShadowOnHover: z18.boolean().optional().describe("Disable shadow on card hover"),
3477
+ disableCardStroke: z18.boolean().optional().describe("Remove card border stroke"),
3478
+ cardStrokeColor: z18.string().optional().describe("Card border stroke color (hex)"),
3236
3479
  // --- Chart Colors (admin theme level) ---
3237
- chartFontFamily: z16.string().optional().describe("Font family for chart text"),
3238
- chartFontColor: z16.string().optional().describe("Font color for chart text (hex)"),
3239
- chartColors: z16.array(z16.string()).optional().describe("Array of chart series colors (hex)"),
3240
- chartPaletteName: z16.string().optional().describe("Name for a custom palette"),
3241
- chartPaletteColors: z16.array(z16.string()).optional().describe("9 colors for a custom palette"),
3480
+ chartFontFamily: z18.string().optional().describe("Font family for chart text"),
3481
+ chartFontColor: z18.string().optional().describe("Font color for chart text (hex)"),
3482
+ chartColors: z18.array(z18.string()).optional().describe("Array of chart series colors (hex)"),
3483
+ chartPaletteName: z18.string().optional().describe("Name for a custom palette"),
3484
+ chartPaletteColors: z18.array(z18.string()).optional().describe("9 colors for a custom palette"),
3242
3485
  // --- Chart Tooltip (admin theme level) ---
3243
3486
  tooltipHeader: tooltipFontSchema.describe("Tooltip header font config"),
3244
3487
  tooltipLabel: tooltipFontSchema.describe("Tooltip label font config"),
3245
3488
  tooltipValue: tooltipFontSchema.describe("Tooltip value font config"),
3246
3489
  // --- Component Theme ---
3247
- buttonPrimaryColor: z16.string().optional().describe("Primary button background (hex)"),
3248
- buttonPrimaryTextColor: z16.string().optional().describe("Primary button text (hex)"),
3249
- buttonSecondaryColor: z16.string().optional().describe("Secondary button background (hex)"),
3250
- buttonSecondaryTextColor: z16.string().optional().describe("Secondary button text (hex)"),
3251
- checkboxCheckedColor: z16.string().optional().describe("Checked checkbox color (hex)"),
3252
- checkboxUncheckedColor: z16.string().optional().describe("Unchecked checkbox color (hex)"),
3253
- switchEnabledColor: z16.string().optional().describe("Enabled switch color (hex)"),
3254
- switchDisabledColor: z16.string().optional().describe("Disabled switch color (hex)"),
3255
- breadcrumbFontFamily: z16.string().optional().describe("Drill breadcrumb font family"),
3256
- breadcrumbFontColor: z16.string().optional().describe("Drill breadcrumb font color (hex)"),
3257
- breadcrumbActiveColor: z16.string().optional().describe("Active breadcrumb color (hex)"),
3258
- filterBadgeColor: z16.string().optional().describe("Multi-select filter badge color (hex)"),
3259
- filterBadgeTextColor: z16.string().optional().describe("Filter badge text color (hex)"),
3260
- datePickerColor: z16.string().optional().describe("Date picker accent color (hex)"),
3490
+ buttonPrimaryColor: z18.string().optional().describe("Primary button background (hex)"),
3491
+ buttonPrimaryTextColor: z18.string().optional().describe("Primary button text (hex)"),
3492
+ buttonSecondaryColor: z18.string().optional().describe("Secondary button background (hex)"),
3493
+ buttonSecondaryTextColor: z18.string().optional().describe("Secondary button text (hex)"),
3494
+ checkboxCheckedColor: z18.string().optional().describe("Checked checkbox color (hex)"),
3495
+ checkboxUncheckedColor: z18.string().optional().describe("Unchecked checkbox color (hex)"),
3496
+ switchEnabledColor: z18.string().optional().describe("Enabled switch color (hex)"),
3497
+ switchDisabledColor: z18.string().optional().describe("Disabled switch color (hex)"),
3498
+ breadcrumbFontFamily: z18.string().optional().describe("Drill breadcrumb font family"),
3499
+ breadcrumbFontColor: z18.string().optional().describe("Drill breadcrumb font color (hex)"),
3500
+ breadcrumbActiveColor: z18.string().optional().describe("Active breadcrumb color (hex)"),
3501
+ filterBadgeColor: z18.string().optional().describe("Multi-select filter badge color (hex)"),
3502
+ filterBadgeTextColor: z18.string().optional().describe("Filter badge text color (hex)"),
3503
+ datePickerColor: z18.string().optional().describe("Date picker accent color (hex)"),
3261
3504
  // --- Responsive Breakpoints ---
3262
- breakpointXl: z16.number().optional().describe("XL breakpoint width in px"),
3263
- breakpointLg: z16.number().optional().describe("LG breakpoint width in px"),
3264
- breakpointMd: z16.number().optional().describe("MD breakpoint width in px"),
3265
- breakpointSm: z16.number().optional().describe("SM breakpoint width in px"),
3266
- breakpointXs: z16.number().optional().describe("XS breakpoint width in px"),
3505
+ breakpointXl: z18.number().optional().describe("XL breakpoint width in px"),
3506
+ breakpointLg: z18.number().optional().describe("LG breakpoint width in px"),
3507
+ breakpointMd: z18.number().optional().describe("MD breakpoint width in px"),
3508
+ breakpointSm: z18.number().optional().describe("SM breakpoint width in px"),
3509
+ breakpointXs: z18.number().optional().describe("XS breakpoint width in px"),
3267
3510
  // --- Metric Layout Columns ---
3268
- layoutColsXl: z16.number().optional().describe("Grid columns at XL breakpoint"),
3269
- layoutColsLg: z16.number().optional().describe("Grid columns at LG breakpoint"),
3270
- layoutColsMd: z16.number().optional().describe("Grid columns at MD breakpoint"),
3271
- layoutColsSm: z16.number().optional().describe("Grid columns at SM breakpoint"),
3272
- layoutColsXs: z16.number().optional().describe("Grid columns at XS breakpoint"),
3273
- layoutColsXxs: z16.number().optional().describe("Grid columns at XXS breakpoint"),
3511
+ layoutColsXl: z18.number().optional().describe("Grid columns at XL breakpoint"),
3512
+ layoutColsLg: z18.number().optional().describe("Grid columns at LG breakpoint"),
3513
+ layoutColsMd: z18.number().optional().describe("Grid columns at MD breakpoint"),
3514
+ layoutColsSm: z18.number().optional().describe("Grid columns at SM breakpoint"),
3515
+ layoutColsXs: z18.number().optional().describe("Grid columns at XS breakpoint"),
3516
+ layoutColsXxs: z18.number().optional().describe("Grid columns at XXS breakpoint"),
3274
3517
  // --- Chart Appearance (detailed) ---
3275
3518
  tooltipHeaderStyle: fontConfigInputSchema.describe("Tooltip header: fontSize, fontFamily, fontWeight, color"),
3276
3519
  tooltipLabelStyle: fontConfigInputSchema.describe("Tooltip label: fontSize, fontFamily, fontWeight, color"),
3277
3520
  tooltipValueStyle: fontConfigInputSchema.describe("Tooltip value: fontSize, fontFamily, fontWeight, color"),
3278
- labelPosition: z16.enum(["hidden", "top", "left", "right", "bottom", "inside"]).optional().describe("Data label position on charts"),
3279
- radialLabelPosition: z16.enum(["outside", "inside"]).optional().describe("Label position for radial/pie charts"),
3280
- marginTop: z16.number().optional().describe("Chart top margin in pixels"),
3281
- marginLeft: z16.number().optional().describe("Chart left margin in pixels"),
3282
- marginRight: z16.number().optional().describe("Chart right margin in pixels"),
3283
- marginBottom: z16.number().optional().describe("Chart bottom margin in pixels"),
3284
- legendShow: z16.boolean().optional().describe("Show or hide chart legend"),
3285
- legendPosition: z16.enum([
3521
+ labelPosition: z18.enum(["hidden", "top", "left", "right", "bottom", "inside"]).optional().describe("Data label position on charts"),
3522
+ radialLabelPosition: z18.enum(["outside", "inside"]).optional().describe("Label position for radial/pie charts"),
3523
+ marginTop: z18.number().optional().describe("Chart top margin in pixels"),
3524
+ marginLeft: z18.number().optional().describe("Chart left margin in pixels"),
3525
+ marginRight: z18.number().optional().describe("Chart right margin in pixels"),
3526
+ marginBottom: z18.number().optional().describe("Chart bottom margin in pixels"),
3527
+ legendShow: z18.boolean().optional().describe("Show or hide chart legend"),
3528
+ legendPosition: z18.enum([
3286
3529
  "top-left",
3287
3530
  "top-center",
3288
3531
  "top-right",
@@ -3292,27 +3535,28 @@ function registerCustomizeTheme(server, client) {
3292
3535
  "bottom-center",
3293
3536
  "bottom-right"
3294
3537
  ]).optional().describe("Legend position"),
3295
- legendLayout: z16.enum(["horizontal", "vertical"]).optional().describe("Legend layout direction"),
3296
- legendShape: z16.enum(["circle", "rect", "roundRect", "triangle", "diamond", "arrow", "none"]).optional().describe("Legend icon shape"),
3297
- legendFontSize: z16.number().optional().describe("Legend font size"),
3298
- legendFontWeight: z16.number().optional().describe("Legend font weight"),
3299
- legendFontFamily: z16.string().optional().describe("Legend font family"),
3300
- legendColor: z16.string().optional().describe("Legend text color (hex)"),
3301
- legendTruncateLength: z16.number().optional().describe("Max characters before truncating legend"),
3302
- disableLegendScrolling: z16.boolean().optional().describe("Disable scrolling in legend"),
3303
- enableVariableLegendPosition: z16.boolean().optional().describe("Allow user to drag legend"),
3304
- legendTop: z16.number().optional().describe("Custom legend top offset"),
3305
- legendLeft: z16.number().optional().describe("Custom legend left offset"),
3538
+ legendLayout: z18.enum(["horizontal", "vertical"]).optional().describe("Legend layout direction"),
3539
+ legendShape: z18.enum(["circle", "rect", "roundRect", "triangle", "diamond", "arrow", "none"]).optional().describe("Legend icon shape"),
3540
+ legendFontSize: z18.number().optional().describe("Legend font size"),
3541
+ legendFontWeight: z18.number().optional().describe("Legend font weight"),
3542
+ legendFontFamily: z18.string().optional().describe("Legend font family"),
3543
+ legendColor: z18.string().optional().describe("Legend text color (hex)"),
3544
+ legendTruncateLength: z18.number().optional().describe("Max characters before truncating legend"),
3545
+ disableLegendScrolling: z18.boolean().optional().describe("Disable scrolling in legend"),
3546
+ enableVariableLegendPosition: z18.boolean().optional().describe("Allow user to drag legend"),
3547
+ legendTop: z18.number().optional().describe("Custom legend top offset"),
3548
+ legendLeft: z18.number().optional().describe("Custom legend left offset"),
3306
3549
  verticalAxis: axisSchema.describe("Vertical (Y) axis configuration"),
3307
3550
  horizontalAxis: axisSchema.describe("Horizontal (X) axis configuration")
3308
3551
  },
3309
3552
  WRITE_IDEMPOTENT,
3310
3553
  async (params) => {
3311
- const { embedId, body, themePreset, accessPreset, ...themeParams } = params;
3554
+ const { embedId, dataAppName, body, themePreset, accessPreset, ...themeParams } = params;
3312
3555
  if (body) {
3313
3556
  try {
3314
3557
  const rawBody = { ...body };
3315
3558
  if (!rawBody.embedId) rawBody.embedId = embedId;
3559
+ if (dataAppName) rawBody.dataAppName = dataAppName;
3316
3560
  const result = await services.embedsApi.updateEmbed(rawBody);
3317
3561
  if (result.error) {
3318
3562
  return toolError(`Theme update failed: ${stringify(result.error)}`);
@@ -3345,7 +3589,7 @@ function registerCustomizeTheme(server, client) {
3345
3589
  return toolError("No theme properties provided. Specify at least one property, or use a themePreset.");
3346
3590
  }
3347
3591
  try {
3348
- const result = await services.embedService.updateEmbed(embedId, patch);
3592
+ const result = await services.embedService.updateEmbed(embedId, patch, dataAppName);
3349
3593
  if (result.error) {
3350
3594
  return toolError(`Theme update failed: ${stringify(result.error)}`);
3351
3595
  }
@@ -3377,7 +3621,7 @@ Use \`get_embed_details\` to view the full current theme configuration.` + GUEST
3377
3621
  }
3378
3622
 
3379
3623
  // src/tools/embed/get-demo-link.ts
3380
- import { z as z17 } from "zod";
3624
+ import { z as z19 } from "zod";
3381
3625
  var HOUR_MS = 60 * 60 * 1e3;
3382
3626
  var DEFAULT_EXPIRY_HOURS = 24;
3383
3627
  var MAX_EXPIRY_HOURS = 24 * 30;
@@ -3387,11 +3631,12 @@ function registerGetDemoLink(server, client) {
3387
3631
  "get_demo_link",
3388
3632
  `Get a shareable demo URL for a dashboard embed scoped to a specific clientId \u2014 same link the UI's "Share Demo Link" generates. Loads the dashboard as that end-customer sees it. Use to verify ops-built widgets without a test app.`,
3389
3633
  {
3390
- clientId: z17.string().describe("End-customer identifier to scope the preview to."),
3391
- dashboardName: z17.string().optional().describe("Dashboard name (non-technical). Preferred \u2014 pass with optional `workspaceName`."),
3392
- workspaceName: z17.string().optional().describe("Workspace name. Only needed if `dashboardName` is ambiguous."),
3393
- embedId: z17.string().optional().describe("Technical alternative to `dashboardName`."),
3394
- expiryHours: z17.number().min(1).max(MAX_EXPIRY_HOURS).optional().describe(`Token TTL in hours. Defaults to ${DEFAULT_EXPIRY_HOURS}. Max ${MAX_EXPIRY_HOURS} (30 days).`)
3634
+ clientId: z19.string().describe("End-customer identifier to scope the preview to."),
3635
+ dashboardName: z19.string().optional().describe("Dashboard name (non-technical). Preferred \u2014 pass with optional `workspaceName`."),
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"),
3638
+ embedId: z19.string().optional().describe("Technical alternative to `dashboardName`."),
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).`)
3395
3640
  },
3396
3641
  WRITE_CREATE,
3397
3642
  async (params) => {
@@ -3400,12 +3645,13 @@ function registerGetDemoLink(server, client) {
3400
3645
  embedId: params.embedId,
3401
3646
  dashboardName: params.dashboardName,
3402
3647
  workspaceName: params.workspaceName,
3648
+ dataAppName: params.dataAppName,
3403
3649
  required: true
3404
3650
  });
3405
3651
  if (!embed.ok) return toolError(embed.error);
3406
3652
  const embedId = embed.value.embedId;
3407
3653
  const dashboardName = embed.value.resolvedFrom?.dashboardName || null;
3408
- const ctx = await services.widgetService.resolveEmbedContext(embedId);
3654
+ const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
3409
3655
  if (!ctx.dataAppName) {
3410
3656
  return toolError(
3411
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.`
@@ -3447,70 +3693,70 @@ function registerGetDemoLink(server, client) {
3447
3693
  }
3448
3694
 
3449
3695
  // src/tools/frontend/generate-guest-token.ts
3450
- import { z as z18 } from "zod";
3696
+ import { z as z20 } from "zod";
3451
3697
  function registerGenerateGuestToken(server, client) {
3452
3698
  const services = createServiceBundle(client);
3453
3699
  server.tool(
3454
3700
  "generate_guest_token",
3455
3701
  "Generate a guest token for frontend embedding. Requires clientId. Omit dataAppName to auto-resolve.",
3456
3702
  {
3457
- clientId: z18.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
3458
- dataAppName: z18.string().optional().describe("Name of the data app. Omit to list available data apps."),
3459
- body: z18.record(z18.unknown()).optional().describe(
3703
+ clientId: z20.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
3704
+ dataAppName: z20.string().optional().describe("Name of the data app. Omit to list available data apps."),
3705
+ body: z20.record(z20.unknown()).optional().describe(
3460
3706
  "Raw API body for POST /api/v2/guest-token/create. If provided, sent directly and convenience fields are ignored."
3461
3707
  ),
3462
- params: z18.object({
3463
- allowedEmbeds: z18.array(z18.string()).optional().describe("Restrict token to specific embed IDs"),
3464
- rlsSettings: z18.array(z18.object({
3465
- metricId: z18.string().describe("Metric ID"),
3466
- values: z18.record(z18.unknown()).describe("RLS values by column name")
3708
+ params: z20.object({
3709
+ allowedEmbeds: z20.array(z20.string()).optional().describe("Restrict token to specific embed IDs"),
3710
+ rlsSettings: z20.array(z20.object({
3711
+ metricId: z20.string().describe("Metric ID"),
3712
+ values: z20.record(z20.unknown()).describe("RLS values by column name")
3467
3713
  })).optional().describe("Legacy alias used in docs/examples; forwarded as params.rlsSettings"),
3468
- appFilters: z18.array(z18.object({
3469
- metricId: z18.string().describe("Metric ID"),
3470
- values: z18.record(z18.unknown()).describe("Filter values by column name")
3714
+ appFilters: z20.array(z20.object({
3715
+ metricId: z20.string().describe("Metric ID"),
3716
+ values: z20.record(z20.unknown()).describe("Filter values by column name")
3471
3717
  })).optional().describe("Metric-level app filters"),
3472
- dashboardAppFilters: z18.array(z18.object({
3473
- dashboardId: z18.string().describe("Dashboard ID"),
3474
- values: z18.record(z18.unknown()).describe("Filter values by dashboard filter name"),
3475
- isShowOnUrl: z18.boolean().optional().describe("Whether filters are shown in URL params")
3718
+ dashboardAppFilters: z20.array(z20.object({
3719
+ dashboardId: z20.string().describe("Dashboard ID"),
3720
+ values: z20.record(z20.unknown()).describe("Filter values by dashboard filter name"),
3721
+ isShowOnUrl: z20.boolean().optional().describe("Whether filters are shown in URL params")
3476
3722
  })).optional().describe("Dashboard-level app filters"),
3477
- hideDashboardFilters: z18.array(z18.string()).optional().describe("Dashboard filter names to hide"),
3478
- userIdentifier: z18.string().optional().describe("End-user identifier for private metrics support"),
3479
- timezone: z18.string().optional().describe('IANA timezone, e.g. "America/New_York"')
3723
+ hideDashboardFilters: z20.array(z20.string()).optional().describe("Dashboard filter names to hide"),
3724
+ userIdentifier: z20.string().optional().describe("End-user identifier for private metrics support"),
3725
+ timezone: z20.string().optional().describe('IANA timezone, e.g. "America/New_York"')
3480
3726
  }).optional().describe("Advanced token params"),
3481
3727
  // Legacy convenience fields (auto-mapped into params for backward compatibility)
3482
- timezone: z18.string().optional().describe("Legacy: moved under params.timezone"),
3483
- allowedEmbeds: z18.array(z18.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
3484
- rlsSettings: z18.array(z18.object({
3485
- metricId: z18.string().describe("Metric ID"),
3486
- values: z18.record(z18.unknown()).describe("RLS values by column name")
3728
+ timezone: z20.string().optional().describe("Legacy: moved under params.timezone"),
3729
+ allowedEmbeds: z20.array(z20.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
3730
+ rlsSettings: z20.array(z20.object({
3731
+ metricId: z20.string().describe("Metric ID"),
3732
+ values: z20.record(z20.unknown()).describe("RLS values by column name")
3487
3733
  })).optional().describe("Legacy: moved under params.rlsSettings"),
3488
- appFilters: z18.array(z18.object({
3489
- metricId: z18.string().describe("Metric ID"),
3490
- values: z18.record(z18.unknown()).describe("Filter values by column name")
3734
+ appFilters: z20.array(z20.object({
3735
+ metricId: z20.string().describe("Metric ID"),
3736
+ values: z20.record(z20.unknown()).describe("Filter values by column name")
3491
3737
  })).optional().describe("Legacy: moved under params.appFilters"),
3492
- dashboardAppFilters: z18.array(z18.object({
3493
- dashboardId: z18.string().describe("Dashboard ID to apply filters to"),
3494
- values: z18.record(z18.unknown()).describe("Filter values for this dashboard"),
3495
- isShowOnUrl: z18.boolean().optional().describe("Whether filters are shown in URL params")
3738
+ dashboardAppFilters: z20.array(z20.object({
3739
+ dashboardId: z20.string().describe("Dashboard ID to apply filters to"),
3740
+ values: z20.record(z20.unknown()).describe("Filter values for this dashboard"),
3741
+ isShowOnUrl: z20.boolean().optional().describe("Whether filters are shown in URL params")
3496
3742
  })).optional().describe("Legacy: moved under params.dashboardAppFilters"),
3497
- permissions: z18.object({
3498
- isEnableArchiveMetrics: z18.boolean().optional(),
3499
- isEnableManageMetrics: z18.boolean().optional(),
3500
- isEnableCreateDashboardView: z18.boolean().optional(),
3501
- isEnableMetricUpdation: z18.boolean().optional(),
3502
- isEnableCustomizeLayout: z18.boolean().optional(),
3503
- isEnableUnderlyingData: z18.boolean().optional(),
3504
- isEnableDownloadMetrics: z18.boolean().optional(),
3505
- isShowSideBar: z18.boolean().optional(),
3506
- isShowDashboardName: z18.boolean().optional(),
3507
- isDisableMetricCreation: z18.boolean().optional()
3743
+ permissions: z20.object({
3744
+ isEnableArchiveMetrics: z20.boolean().optional(),
3745
+ isEnableManageMetrics: z20.boolean().optional(),
3746
+ isEnableCreateDashboardView: z20.boolean().optional(),
3747
+ isEnableMetricUpdation: z20.boolean().optional(),
3748
+ isEnableCustomizeLayout: z20.boolean().optional(),
3749
+ isEnableUnderlyingData: z20.boolean().optional(),
3750
+ isEnableDownloadMetrics: z20.boolean().optional(),
3751
+ isShowSideBar: z20.boolean().optional(),
3752
+ isShowDashboardName: z20.boolean().optional(),
3753
+ isDisableMetricCreation: z20.boolean().optional()
3508
3754
  }).optional().describe("Runtime permission overrides (merge with and override embed-level settings)"),
3509
- theme: z18.record(z18.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
3510
- hideDashboardFilters: z18.array(z18.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
3511
- userIdentifier: z18.string().optional().describe("Legacy: moved under params.userIdentifier"),
3512
- expiryTime: z18.number().optional().describe("Token TTL in milliseconds"),
3513
- datasourceName: z18.string().optional().describe("Datasource name for multi-datasource routing")
3755
+ theme: z20.record(z20.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
3756
+ hideDashboardFilters: z20.array(z20.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
3757
+ userIdentifier: z20.string().optional().describe("Legacy: moved under params.userIdentifier"),
3758
+ expiryTime: z20.number().optional().describe("Token TTL in milliseconds"),
3759
+ datasourceName: z20.string().optional().describe("Datasource name for multi-datasource routing")
3514
3760
  },
3515
3761
  WRITE_CREATE,
3516
3762
  async (params) => {
@@ -3563,7 +3809,7 @@ Next step: Use \`generate_embed_code\` to get copy-paste-ready frontend code.`
3563
3809
  }
3564
3810
 
3565
3811
  // src/tools/frontend/generate-embed-code.ts
3566
- import { z as z19 } from "zod";
3812
+ import { z as z21 } from "zod";
3567
3813
 
3568
3814
  // src/templates/react.ts
3569
3815
  function kebabCase(str) {
@@ -4559,22 +4805,22 @@ function registerGenerateEmbedCode(server, _client) {
4559
4805
  "generate_embed_code",
4560
4806
  "Generate framework-specific embed code (React, Next.js, Vue, Angular, Svelte, SolidJS, vanilla HTML).",
4561
4807
  {
4562
- embedId: z19.string().describe("The embed ID to generate code for"),
4563
- framework: z19.enum(FRAMEWORKS).describe("Frontend framework"),
4564
- embedType: z19.enum(["dashboard", "metric"]).optional().default("dashboard"),
4565
- options: z19.object({
4566
- enableDownloadCsv: z19.boolean().optional(),
4567
- enableEmailCsv: z19.boolean().optional(),
4568
- enableDownloadAllMetrics: z19.boolean().optional(),
4569
- enableDownloadAllPdf: z19.boolean().optional(),
4570
- disableFullscreen: z19.boolean().optional(),
4571
- isHideTablePreview: z19.boolean().optional(),
4572
- isHideChartSettings: z19.boolean().optional(),
4573
- isStickyDashboardFilters: z19.boolean().optional(),
4574
- enableMultiMetricFilters: z19.boolean().optional(),
4575
- themeName: z19.string().optional(),
4576
- customTheme: z19.record(z19.unknown()).optional(),
4577
- optionsIcon: z19.enum(["kebab-menu-vertical", "download"]).optional()
4808
+ embedId: z21.string().describe("The embed ID to generate code for"),
4809
+ framework: z21.enum(FRAMEWORKS).describe("Frontend framework"),
4810
+ embedType: z21.enum(["dashboard", "metric"]).optional().default("dashboard"),
4811
+ options: z21.object({
4812
+ enableDownloadCsv: z21.boolean().optional(),
4813
+ enableEmailCsv: z21.boolean().optional(),
4814
+ enableDownloadAllMetrics: z21.boolean().optional(),
4815
+ enableDownloadAllPdf: z21.boolean().optional(),
4816
+ disableFullscreen: z21.boolean().optional(),
4817
+ isHideTablePreview: z21.boolean().optional(),
4818
+ isHideChartSettings: z21.boolean().optional(),
4819
+ isStickyDashboardFilters: z21.boolean().optional(),
4820
+ enableMultiMetricFilters: z21.boolean().optional(),
4821
+ themeName: z21.string().optional(),
4822
+ customTheme: z21.record(z21.unknown()).optional(),
4823
+ optionsIcon: z21.enum(["kebab-menu-vertical", "download"]).optional()
4578
4824
  }).optional()
4579
4825
  },
4580
4826
  READ_ONLY,
@@ -4614,17 +4860,17 @@ ${generated.instructions}`;
4614
4860
  }
4615
4861
 
4616
4862
  // src/tools/analytics/list-dashboards.ts
4617
- import { z as z20 } from "zod";
4863
+ import { z as z22 } from "zod";
4618
4864
  function registerListDashboards(server, client) {
4619
4865
  const services = createServiceBundle(client);
4620
4866
  server.tool(
4621
4867
  "list_dashboards",
4622
4868
  "List dashboards globally or for a specific data app.",
4623
4869
  {
4624
- scope: z20.enum(["auto", "global", "data_app"]).optional().describe(
4870
+ scope: z22.enum(["auto", "global", "data_app"]).optional().describe(
4625
4871
  "Dashboard listing scope. auto: use data_app when dataAppName + API token are available, otherwise global."
4626
4872
  ),
4627
- dataAppName: z20.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
4873
+ dataAppName: z22.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
4628
4874
  },
4629
4875
  READ_ONLY,
4630
4876
  async (params) => {
@@ -4641,9 +4887,11 @@ function registerListDashboards(server, client) {
4641
4887
  count: result.length,
4642
4888
  scope: useDataAppScope ? "data_app" : "global",
4643
4889
  dashboards: result.map((d) => {
4644
- const { name, dashboardId, workspaceName, metricCount } = services.discoveryService.normalizeDashboard(d);
4890
+ const { name, dashboardId, externalDashboardId, workspaceName, metricCount } = services.discoveryService.normalizeDashboard(d);
4645
4891
  return {
4646
4892
  id: dashboardId || null,
4893
+ dashboardId,
4894
+ externalDashboardId,
4647
4895
  name,
4648
4896
  workspace: workspaceName || null,
4649
4897
  metricCount: metricCount ?? null
@@ -4659,36 +4907,36 @@ function registerListDashboards(server, client) {
4659
4907
  }
4660
4908
 
4661
4909
  // src/tools/dashboard/schema.ts
4662
- import { z as z21 } from "zod";
4663
- var dashboardPositionSchema = z21.object({
4664
- x: z21.number().int().min(0).max(23),
4665
- y: z21.number().int().min(0),
4666
- w: z21.number().int().min(1).max(24),
4667
- h: z21.number().int().min(1).max(40)
4910
+ import { z as z23 } from "zod";
4911
+ var dashboardPositionSchema = z23.object({
4912
+ x: z23.number().int().min(0).max(23),
4913
+ y: z23.number().int().min(0),
4914
+ w: z23.number().int().min(1).max(24),
4915
+ h: z23.number().int().min(1).max(40)
4668
4916
  }).strict();
4669
- var dashboardChartSchema = z21.object({
4670
- type: z21.string(),
4671
- metricRef: z21.string().optional(),
4672
- query: z21.string().optional(),
4673
- companyIntegrationId: z21.string().optional(),
4674
- chartOptions: z21.record(z21.unknown()).optional()
4917
+ var dashboardChartSchema = z23.object({
4918
+ type: z23.string(),
4919
+ metricRef: z23.string().optional(),
4920
+ query: z23.string().optional(),
4921
+ companyIntegrationId: z23.string().optional(),
4922
+ chartOptions: z23.record(z23.unknown()).optional()
4675
4923
  }).passthrough();
4676
- var dashboardTileSchema = z21.object({
4677
- id: z21.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
4678
- kind: z21.literal("databrain-chart"),
4924
+ var dashboardTileSchema = z23.object({
4925
+ id: z23.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
4926
+ kind: z23.literal("databrain-chart"),
4679
4927
  position: dashboardPositionSchema,
4680
- title: z21.string().min(1).max(200),
4928
+ title: z23.string().min(1).max(200),
4681
4929
  chart: dashboardChartSchema.optional()
4682
4930
  }).strict();
4683
4931
  var dashboardDocumentShape = {
4684
- schemaVersion: z21.literal(1),
4685
- title: z21.string().min(1).max(200),
4686
- description: z21.string().max(2e3).optional(),
4687
- layout: z21.object({ columns: z21.union([z21.literal(12), z21.literal(24)]).default(24) }).strict().optional(),
4688
- tiles: z21.array(dashboardTileSchema).min(1).max(50)
4932
+ schemaVersion: z23.literal(1),
4933
+ title: z23.string().min(1).max(200),
4934
+ description: z23.string().max(2e3).optional(),
4935
+ layout: z23.object({ columns: z23.union([z23.literal(12), z23.literal(24)]).default(24) }).strict().optional(),
4936
+ tiles: z23.array(dashboardTileSchema).min(1).max(50)
4689
4937
  };
4690
- var workspaceNameSchema = z21.string().min(1).describe("Target workspace name");
4691
- var idempotencyKeySchema = z21.string().regex(
4938
+ var workspaceNameSchema = z23.string().min(1).describe("Target workspace name");
4939
+ var idempotencyKeySchema = z23.string().regex(
4692
4940
  /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
4693
4941
  "idempotencyKey must be a UUID v4"
4694
4942
  ).describe("UUID v4 unique to this workspace dashboard create request");
@@ -4741,7 +4989,7 @@ ${stringify(validation)}`
4741
4989
  }
4742
4990
 
4743
4991
  // src/tools/dashboard/generate-dashboard-tiles.ts
4744
- import { z as z22 } from "zod";
4992
+ import { z as z24 } from "zod";
4745
4993
  function registerGenerateDashboardTiles(server, client) {
4746
4994
  const services = createServiceBundle(client);
4747
4995
  server.tool(
@@ -4749,15 +4997,16 @@ function registerGenerateDashboardTiles(server, client) {
4749
4997
  "Generate multiple workspace dashboard tile drafts without persisting them.",
4750
4998
  {
4751
4999
  workspaceName: workspaceNameSchema,
4752
- dataAppName: z22.string().optional().describe("Data app in the workspace; auto-resolved when unique"),
4753
- integrationId: z22.string().optional().describe("Datasource UUID override"),
4754
- integrationName: z22.string().optional().describe("Datasource name override"),
4755
- tiles: z22.array(
4756
- z22.object({
4757
- id: z22.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
4758
- title: z22.string().min(1).max(200),
5000
+ clientId: z24.string().trim().min(1).describe("Tenant client identifier in this data app; use list_tenants to choose it"),
5001
+ dataAppName: z24.string().optional().describe("Data app in the workspace; auto-resolved when unique"),
5002
+ integrationId: z24.string().optional().describe("Datasource UUID override"),
5003
+ integrationName: z24.string().optional().describe("Datasource name override"),
5004
+ tiles: z24.array(
5005
+ z24.object({
5006
+ id: z24.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
5007
+ title: z24.string().min(1).max(200),
4759
5008
  position: dashboardPositionSchema,
4760
- userInputs: z22.record(z22.unknown())
5009
+ userInputs: z24.record(z24.unknown())
4761
5010
  }).strict()
4762
5011
  ).min(1).max(50)
4763
5012
  },
@@ -4778,10 +5027,10 @@ function registerGenerateDashboardTiles(server, client) {
4778
5027
  method: "POST",
4779
5028
  path: "/api/v2/guest-token/create",
4780
5029
  auth: "service",
4781
- body: { clientId: "None", dataAppName: resolution.name }
5030
+ body: { clientId: params.clientId, dataAppName: resolution.name }
4782
5031
  });
4783
5032
  const datasource = await services.widgetService.resolveDatasource(guest.token, {
4784
- clientId: "None",
5033
+ clientId: params.clientId,
4785
5034
  integrationId: params.integrationId,
4786
5035
  integrationName: params.integrationName
4787
5036
  });
@@ -4802,11 +5051,6 @@ function registerGenerateDashboardTiles(server, client) {
4802
5051
  `Tile "${item.input.id}" generation failed: ${stringify(item.result.error)}`
4803
5052
  );
4804
5053
  }
4805
- if (typeof item.result.chartType !== "string" || !item.result.chartType) {
4806
- return toolError(
4807
- `Tile "${item.input.id}" returned no chartType. Refine its userInputs and retry.`
4808
- );
4809
- }
4810
5054
  }
4811
5055
  return toolSuccess(
4812
5056
  stringify({
@@ -4818,7 +5062,7 @@ function registerGenerateDashboardTiles(server, client) {
4818
5062
  position: input.position,
4819
5063
  title: input.title,
4820
5064
  chart: {
4821
- type: result.chartType,
5065
+ type: result.chartType || "table",
4822
5066
  query: result.query,
4823
5067
  companyIntegrationId: datasource.value.integrationId
4824
5068
  }
@@ -4841,14 +5085,15 @@ function registerGenerateDashboardTiles(server, client) {
4841
5085
  }
4842
5086
 
4843
5087
  // src/tools/dashboard/get-dashboard.ts
4844
- import { z as z23 } from "zod";
5088
+ import { z as z25 } from "zod";
4845
5089
  function registerGetDashboard(server, client) {
4846
- server.tool(
5090
+ const tool = server.tool(
4847
5091
  "get_dashboard",
4848
5092
  "Retrieve a workspace dashboard projected back into the versioned tile-list contract.",
4849
5093
  {
4850
- workspaceName: workspaceNameSchema,
4851
- dashboardId: z23.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")
4852
5097
  },
4853
5098
  READ_ONLY,
4854
5099
  async (params) => {
@@ -4858,10 +5103,33 @@ function registerGetDashboard(server, client) {
4858
5103
  path: "/api/v2/workspace/dashboard",
4859
5104
  auth: "service",
4860
5105
  query: {
4861
- workspaceName: params.workspaceName,
5106
+ ...params.workspaceName ? { workspaceName: params.workspaceName } : {},
5107
+ ...params.workspaceId ? { workspaceId: params.workspaceId } : {},
4862
5108
  dashboardId: params.dashboardId
4863
5109
  }
4864
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
+ }
4865
5133
  return toolSuccess(stringify(result));
4866
5134
  } catch (err) {
4867
5135
  if (err instanceof DatabrainApiError) return err.toToolResponse();
@@ -4869,10 +5137,11 @@ function registerGetDashboard(server, client) {
4869
5137
  }
4870
5138
  }
4871
5139
  );
5140
+ tagChartTool(server, tool);
4872
5141
  }
4873
5142
 
4874
5143
  // src/tools/dashboard/dashboard-snapshot.ts
4875
- import { z as z24 } from "zod";
5144
+ import { z as z26 } from "zod";
4876
5145
  var SNAPSHOT_REQUEST_MS = 45e3;
4877
5146
  function registerDashboardSnapshot(server, client) {
4878
5147
  server.tool(
@@ -4880,10 +5149,10 @@ function registerDashboardSnapshot(server, client) {
4880
5149
  "Get a PNG picture of a dashboard, or of one chart on it (metricId), as the signed-in user sees it in Databrain. Light or dark, 480 to 1600 px wide. Needs a Databrain sign-in (OAuth), not a service token.",
4881
5150
  {
4882
5151
  workspaceName: workspaceNameSchema.optional(),
4883
- dashboardId: z24.string().min(1).max(200).optional().describe("Dashboard id from list_dashboards. Needed unless metricId is given."),
4884
- metricId: z24.string().min(1).max(200).optional().describe("One chart (metric) id: draw only that chart, from a dashboard it is on."),
4885
- theme: z24.enum(["light", "dark"]).default("light").describe("Picture colours"),
4886
- width: z24.number().int().min(480).max(1600).default(1200).describe("Picture width in pixels")
5152
+ dashboardId: z26.string().min(1).max(200).optional().describe("Dashboard id from list_dashboards. Needed unless metricId is given."),
5153
+ metricId: z26.string().min(1).max(200).optional().describe("One chart (metric) id: draw only that chart, from a dashboard it is on."),
5154
+ theme: z26.enum(["light", "dark"]).default("light").describe("Picture colours"),
5155
+ width: z26.number().int().min(480).max(1600).default(1200).describe("Picture width in pixels")
4887
5156
  },
4888
5157
  READ_ONLY,
4889
5158
  async (params) => {
@@ -4928,18 +5197,18 @@ function registerDashboardSnapshot(server, client) {
4928
5197
 
4929
5198
  // src/apps/preview.ts
4930
5199
  import { createHash } from "crypto";
4931
- import { z as z25 } from "zod";
4932
- var draftSchema = z25.object({
4933
- question: z25.string().min(1).max(2e3),
4934
- sql: z25.string().min(1).max(2e4),
4935
- datamartName: z25.string().min(1).max(256),
4936
- dimensions: z25.array(z25.string().max(128)).max(20).default([]),
4937
- measures: z25.array(z25.string().max(128)).max(20).default([]),
4938
- chartType: z25.string().min(1).max(80),
4939
- title: z25.string().min(1).max(200),
4940
- sessionId: z25.string().max(256).optional(),
5200
+ import { z as z27 } from "zod";
5201
+ var draftSchema = z27.object({
5202
+ question: z27.string().min(1).max(2e3),
5203
+ sql: z27.string().min(1).max(2e4),
5204
+ datamartName: z27.string().min(1).max(256),
5205
+ dimensions: z27.array(z27.string().max(128)).max(20).default([]),
5206
+ measures: z27.array(z27.string().max(128)).max(20).default([]),
5207
+ chartType: z27.string().min(1).max(80),
5208
+ title: z27.string().min(1).max(200),
5209
+ sessionId: z27.string().max(256).optional(),
4941
5210
  /** Workspaces built on this datamart. One means Save needs no choice. */
4942
- workspaces: z25.array(z25.string().max(256)).max(50).default([])
5211
+ workspaces: z27.array(z27.string().max(256)).max(50).default([])
4943
5212
  }).strict();
4944
5213
  var PREVIEW_PATH = "/api/v2/data-app/chart-preview";
4945
5214
  var METRIC_PATH = "/api/mcp/metrics/create";
@@ -5066,7 +5335,7 @@ async function saveDraftAsDashboard(client, draft, input) {
5066
5335
  }
5067
5336
 
5068
5337
  // src/tools/query/ask-question.ts
5069
- import { z as z26 } from "zod";
5338
+ import { z as z28 } from "zod";
5070
5339
 
5071
5340
  // src/tools/query/answer-format.ts
5072
5341
  function formatConfidence(value) {
@@ -5123,29 +5392,29 @@ function registerAskQuestion(server, client) {
5123
5392
  "ask_question",
5124
5393
  "Ask a natural language question \u2014 converts to SQL, returns data + chart suggestion. Requires datamartName with a populated semantic layer.",
5125
5394
  {
5126
- question: z26.string().describe(
5395
+ question: z28.string().describe(
5127
5396
  'The natural language question to ask (e.g. "What was total revenue last month?")'
5128
5397
  ),
5129
- datamartName: z26.string().optional().describe(
5398
+ datamartName: z28.string().optional().describe(
5130
5399
  "Name of the datamart to query. Use `list_datamarts` to find available names. REQUIRED unless exactly one datamart exists (auto-resolved). Do NOT pass a workspace name here."
5131
5400
  ),
5132
- sessionId: z26.string().optional().describe(
5401
+ sessionId: z28.string().optional().describe(
5133
5402
  "Session ID from a previous ask_question response, for follow-up questions."
5134
5403
  ),
5135
- timezone: z26.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
5404
+ timezone: z28.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
5136
5405
  },
5137
5406
  WRITE_CREATE,
5138
5407
  async (params) => {
5139
5408
  try {
5140
5409
  let datamartName = params.datamartName;
5141
- if (!datamartName) {
5410
+ {
5142
5411
  const datamarts = await services.datamartsApi.listDatamarts();
5143
5412
  if (!Array.isArray(datamarts) || datamarts.length === 0) {
5144
5413
  return toolError(
5145
- "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."
5146
5415
  );
5147
5416
  }
5148
- if (datamarts.length === 1) {
5417
+ if (!datamartName && datamarts.length === 1) {
5149
5418
  const resolvedName = datamarts[0].name || datamarts[0].datamartName;
5150
5419
  if (!resolvedName) {
5151
5420
  return toolError(
@@ -5153,7 +5422,7 @@ function registerAskQuestion(server, client) {
5153
5422
  );
5154
5423
  }
5155
5424
  datamartName = resolvedName;
5156
- } else {
5425
+ } else if (!datamartName) {
5157
5426
  const list = datamarts.map(formatDatamart).join("\n");
5158
5427
  return toolSuccess(
5159
5428
  `Found ${datamarts.length} datamarts. Please specify which one to query:
@@ -5332,14 +5601,14 @@ To fix this:
5332
5601
  }
5333
5602
 
5334
5603
  // src/tools/query/get-semantic-layer.ts
5335
- import { z as z27 } from "zod";
5604
+ import { z as z29 } from "zod";
5336
5605
  function registerGetSemanticLayer(server, client) {
5337
5606
  const services = createServiceBundle(client);
5338
5607
  server.tool(
5339
5608
  "get_semantic_layer",
5340
5609
  "Get semantic layer metadata for a datamart: table/column descriptions, synonyms, completion score. Omit datamartName to auto-resolve.",
5341
5610
  {
5342
- datamartName: z27.string().optional().describe(
5611
+ datamartName: z29.string().optional().describe(
5343
5612
  "Name of the datamart to inspect. Omit to list available datamarts."
5344
5613
  )
5345
5614
  },
@@ -5449,7 +5718,7 @@ ${result.feedback}`);
5449
5718
  }
5450
5719
 
5451
5720
  // src/tools/query/semantic-model.ts
5452
- import { z as z28 } from "zod";
5721
+ import { z as z30 } from "zod";
5453
5722
 
5454
5723
  // src/services/semantic-source.ts
5455
5724
  import { parseDocument, stringify as stringify2 } from "yaml";
@@ -5649,21 +5918,21 @@ function retrieveSemanticSource(source2, question, k) {
5649
5918
  }
5650
5919
 
5651
5920
  // src/tools/query/semantic-model.ts
5652
- var datamartId = z28.string().trim().min(1).max(256).describe("Datamart ID from list_datamarts.");
5653
- var source = z28.string().min(1).max(2e6).describe("Complete proposed semantic model as YAML.");
5654
- var entityKind = z28.enum(["table", "column", "measure", "metric", "join", "entity", "glossary", "saved_query", "instruction", "access_rule"]);
5921
+ var datamartId = z30.string().trim().min(1).max(256).describe("Datamart ID from list_datamarts.");
5922
+ var source = z30.string().min(1).max(2e6).describe("Complete proposed semantic model as YAML.");
5923
+ var entityKind = z30.enum(["table", "column", "measure", "metric", "join", "entity", "glossary", "saved_query", "instruction", "access_rule"]);
5655
5924
  var changeInputs = {
5656
5925
  source: source.optional(),
5657
- yaml_patch: z28.string().min(1).max(2e6).optional().describe("YAML mapping merged into the pulled model. Named lists merge by name. Other lists replace. Null removes a field. An empty list clears it."),
5658
- change: z28.object({
5659
- operation: z28.enum(["upsert", "remove"]),
5926
+ yaml_patch: z30.string().min(1).max(2e6).optional().describe("YAML mapping merged into the pulled model. Named lists merge by name. Other lists replace. Null removes a field. An empty list clears it."),
5927
+ change: z30.object({
5928
+ operation: z30.enum(["upsert", "remove"]),
5660
5929
  kind: entityKind,
5661
- name: z28.string().trim().min(1),
5662
- table: z28.string().trim().min(1).optional().describe("Required for columns. Use the table name and an unqualified column name."),
5663
- value: z28.record(z28.unknown()).optional().describe("Fields to merge for upsert. Omit for remove.")
5930
+ name: z30.string().trim().min(1),
5931
+ table: z30.string().trim().min(1).optional().describe("Required for columns. Use the table name and an unqualified column name."),
5932
+ value: z30.record(z30.unknown()).optional().describe("Fields to merge for upsert. Omit for remove.")
5664
5933
  }).strict().optional().describe("One named entity change. Supply exactly one of source, yaml_patch or change.")
5665
5934
  };
5666
- var expectedHash = z28.string().trim().min(1).nullable().optional().describe("Stored content_hash from the reviewed proposal. Null explicitly means the first apply. Required when dry_run is false.");
5935
+ var expectedHash = z30.string().trim().min(1).nullable().optional().describe("Stored content_hash from the reviewed proposal. Null explicitly means the first apply. Required when dry_run is false.");
5667
5936
  function errorCode(error) {
5668
5937
  const nested = isObject(error.details) && isObject(error.details.error) ? error.details.error : void 0;
5669
5938
  return typeof nested?.code === "string" ? nested.code : error.code;
@@ -5722,7 +5991,7 @@ function registerSearchSemanticModel(server, client) {
5722
5991
  server.tool(
5723
5992
  "search_semantic_model",
5724
5993
  "Find semantic model entities relevant to a question. Ranks names, aliases, synonyms and descriptions, then includes related entities. Uses the stored model and never writes.",
5725
- { datamartId, question: z28.string().trim().min(1).max(1e4), k: z28.number().int().min(1).max(100).default(10) },
5994
+ { datamartId, question: z30.string().trim().min(1).max(1e4), k: z30.number().int().min(1).max(100).default(10) },
5726
5995
  READ_ONLY,
5727
5996
  async ({ datamartId: datamartId2, question, k }) => {
5728
5997
  try {
@@ -5745,7 +6014,7 @@ function registerGetSemanticEntity(server, client) {
5745
6014
  server.tool(
5746
6015
  "get_semantic_entity",
5747
6016
  "Read one entity by kind and exact name from the stored semantic model. For a column, use table.column. Returns the stored content hash for change review.",
5748
- { datamartId, kind: entityKind, name: z28.string().trim().min(1) },
6017
+ { datamartId, kind: entityKind, name: z30.string().trim().min(1) },
5749
6018
  READ_ONLY,
5750
6019
  async ({ datamartId: datamartId2, kind, name }) => {
5751
6020
  try {
@@ -5783,9 +6052,9 @@ function registerApplySemanticChange(server, client) {
5783
6052
  {
5784
6053
  datamartId,
5785
6054
  ...changeInputs,
5786
- dry_run: z28.boolean().default(true),
6055
+ dry_run: z30.boolean().default(true),
5787
6056
  expected_content_hash: expectedHash,
5788
- confirm: z28.string().optional().describe("User supplied APPLY_TO_PRODUCTION after reviewing the plan. Never invent confirmation.")
6057
+ confirm: z30.string().optional().describe("User supplied APPLY_TO_PRODUCTION after reviewing the plan. Never invent confirmation.")
5789
6058
  },
5790
6059
  DESTRUCTIVE,
5791
6060
  async (params) => {
@@ -5808,19 +6077,17 @@ function registerEvalSemanticModel(server, client) {
5808
6077
  const api = createServiceBundle(client).semanticModelApi;
5809
6078
  server.tool(
5810
6079
  "eval_semantic_model",
5811
- "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.",
5812
- { datamartId, source: source.optional(), questions: z28.array(z28.union([z28.string().trim().min(1), z28.object({
5813
- name: z28.string().optional(),
5814
- question: z28.string().min(1),
5815
- sql: z28.string().optional(),
5816
- expected_columns: z28.array(z28.string()).optional(),
5817
- expected_empty: z28.boolean().optional()
5818
- }).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([]) },
5819
6086
  READ_ONLY,
5820
6087
  async ({ datamartId: datamartId2, source: source2, questions }) => {
5821
- const shaped = questions?.map((entry) => typeof entry === "string" ? { question: entry } : entry);
5822
6088
  try {
5823
- 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 })));
5824
6091
  } catch (error) {
5825
6092
  return failure(error);
5826
6093
  }
@@ -5829,15 +6096,15 @@ function registerEvalSemanticModel(server, client) {
5829
6096
  }
5830
6097
 
5831
6098
  // src/tools/query/update-semantic-layer.ts
5832
- import { z as z29 } from "zod";
5833
- var columnSchema2 = z29.object({
5834
- name: z29.string().describe("Column name (must exist in the datamart)"),
5835
- description: z29.string().optional().describe("Business-friendly description of what this column represents"),
5836
- synonyms: z29.array(z29.string()).optional().describe(
6099
+ import { z as z31 } from "zod";
6100
+ var columnSchema2 = z31.object({
6101
+ name: z31.string().describe("Column name (must exist in the datamart)"),
6102
+ description: z31.string().optional().describe("Business-friendly description of what this column represents"),
6103
+ synonyms: z31.array(z31.string()).optional().describe(
5837
6104
  'Alternative names users might use for this column (e.g. ["revenue", "sales amount"])'
5838
6105
  ),
5839
- miscellaneousInfo: z29.string().optional().describe("Additional context such as valid values, units, or caveats"),
5840
- columnType: z29.enum([
6106
+ miscellaneousInfo: z31.string().optional().describe("Additional context such as valid values, units, or caveats"),
6107
+ columnType: z31.enum([
5841
6108
  "String",
5842
6109
  "Long String",
5843
6110
  "String (Custom)",
@@ -5852,21 +6119,21 @@ var columnSchema2 = z29.object({
5852
6119
  // The public PUT accepts these three as well (docs: Update Semantic Layer).
5853
6120
  // They were missing here, and zod strips unknown keys, so a caller who set
5854
6121
  // `isIdentifier: true` got a success message and a row with `false`.
5855
- columnTypeConfig: z29.union([z29.record(z29.string(), z29.string()), z29.string(), z29.object({ lowerLimit: z29.number(), upperLimit: z29.number() }), z29.null()]).optional().describe("Shape depends on columnType: value-to-description object for String / String (Custom) / ENUM / Mapper, { lowerLimit, upperLimit } for Range, a string for Expression and JSON. Omit for Identifier, Number and Long String."),
5856
- isIdentifier: z29.boolean().optional().describe("Mark the column as an identifier (primary or foreign key)."),
5857
- isNotIndexed: z29.boolean().optional().describe("Exclude the column from AI indexing.")
6122
+ columnTypeConfig: z31.union([z31.record(z31.string(), z31.string()), z31.string(), z31.object({ lowerLimit: z31.number(), upperLimit: z31.number() }), z31.null()]).optional().describe("Shape depends on columnType: value-to-description object for String / String (Custom) / ENUM / Mapper, { lowerLimit, upperLimit } for Range, a string for Expression and JSON. Omit for Identifier, Number and Long String."),
6123
+ isIdentifier: z31.boolean().optional().describe("Mark the column as an identifier (primary or foreign key)."),
6124
+ isNotIndexed: z31.boolean().optional().describe("Exclude the column from AI indexing.")
5858
6125
  });
5859
- var tableSchema2 = z29.object({
5860
- name: z29.string().describe("Table name (must exist in the datamart)"),
5861
- schemaName: z29.string().optional().describe("Schema the table lives in, when the datamart has the same table name in more than one schema."),
5862
- description: z29.string().optional().describe(
6126
+ var tableSchema2 = z31.object({
6127
+ name: z31.string().describe("Table name (must exist in the datamart)"),
6128
+ schemaName: z31.string().optional().describe("Schema the table lives in, when the datamart has the same table name in more than one schema."),
6129
+ description: z31.string().optional().describe(
5863
6130
  "Business-friendly description of what this table represents"
5864
6131
  ),
5865
- synonyms: z29.array(z29.string()).optional().describe("Alternative names for this table"),
5866
- miscellaneousInfo: z29.string().optional().describe(
6132
+ synonyms: z31.array(z31.string()).optional().describe("Alternative names for this table"),
6133
+ miscellaneousInfo: z31.string().optional().describe(
5867
6134
  "Additional context such as how the table relates to other tables"
5868
6135
  ),
5869
- columns: z29.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
6136
+ columns: z31.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
5870
6137
  });
5871
6138
  function columnKey(tableName, columnName) {
5872
6139
  return `${tableName.toLowerCase()}\0${columnName.toLowerCase()}`;
@@ -5943,13 +6210,13 @@ function registerUpdateSemanticLayer(server, client) {
5943
6210
  "update_semantic_layer",
5944
6211
  "Populate or update the semantic layer for a datamart. Add table/column descriptions, synonyms, context. Required before ask_question works. Auto-generate descriptions from column names when empty.",
5945
6212
  {
5946
- datamartName: z29.string().optional().describe(
6213
+ datamartName: z31.string().optional().describe(
5947
6214
  "Name of the datamart to update. Omit to list available datamarts."
5948
6215
  ),
5949
- tables: z29.array(tableSchema2).optional().describe(
6216
+ tables: z31.array(tableSchema2).optional().describe(
5950
6217
  "Table and column definitions to update. Only include tables/columns you want to change."
5951
6218
  ),
5952
- feedback: z29.string().optional().describe(
6219
+ feedback: z31.string().optional().describe(
5953
6220
  'General feedback or context about the datamart (e.g. "This datamart contains e-commerce data for the US market").'
5954
6221
  )
5955
6222
  },
@@ -6011,7 +6278,7 @@ You can now:
6011
6278
  }
6012
6279
 
6013
6280
  // src/tools/query/semantic-generation.ts
6014
- import { z as z30 } from "zod";
6281
+ import { z as z32 } from "zod";
6015
6282
  function nonEmptyString(value) {
6016
6283
  return typeof value === "string" && value.trim().length > 0 ? value : null;
6017
6284
  }
@@ -6115,11 +6382,11 @@ function registerStartSemanticLayerGeneration(server, client) {
6115
6382
  const services = createServiceBundle(client);
6116
6383
  server.tool(
6117
6384
  "start_semantic_layer_generation",
6118
- "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.",
6119
6386
  {
6120
- datamartName: z30.string().optional().describe("Datamart name. Omit to list available datamarts"),
6121
- workspaceName: z30.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
6122
- datamartId: z30.string().optional().describe("Datamart UUID. Use only when already known.")
6387
+ datamartName: z32.string().optional().describe("Datamart name. Omit to list available datamarts"),
6388
+ workspaceName: z32.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
6389
+ datamartId: z32.string().optional().describe("Datamart UUID. Use only when already known.")
6123
6390
  },
6124
6391
  WRITE_CREATE,
6125
6392
  async (params) => {
@@ -6159,11 +6426,11 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
6159
6426
  const services = createServiceBundle(client);
6160
6427
  server.tool(
6161
6428
  "get_semantic_layer_generation_status",
6162
- "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.",
6163
6430
  {
6164
- datamartName: z30.string().optional().describe("Datamart name. Omit to list available datamarts"),
6165
- workspaceName: z30.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
6166
- datamartId: z30.string().optional().describe("Datamart UUID. Use only when already known.")
6431
+ datamartName: z32.string().optional().describe("Datamart name. Omit to list available datamarts"),
6432
+ workspaceName: z32.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
6433
+ datamartId: z32.string().optional().describe("Datamart UUID. Use only when already known.")
6167
6434
  },
6168
6435
  READ_ONLY,
6169
6436
  async (params) => {
@@ -6200,7 +6467,7 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
6200
6467
  }
6201
6468
 
6202
6469
  // src/tools/widgets/list-widgets.ts
6203
- import { z as z31 } from "zod";
6470
+ import { z as z33 } from "zod";
6204
6471
  function widgetReferenceId(widget) {
6205
6472
  for (const candidate2 of [widget.externalMetricId, widget.id, widget._id, widget.metricId]) {
6206
6473
  if (typeof candidate2 === "string" && candidate2.trim().length > 0) return candidate2.trim();
@@ -6211,27 +6478,75 @@ function registerListWidgets(server, client) {
6211
6478
  const services = createServiceBundle(client);
6212
6479
  const tool = server.tool(
6213
6480
  "list_widgets",
6214
- "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.",
6215
6482
  {
6216
- clientId: z31.string().describe("Client/tenant identifier for this dashboard"),
6217
- dashboardName: z31.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6218
- workspaceName: z31.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6219
- embedId: z31.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
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."),
6487
+ dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6488
+ workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6489
+ embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
6220
6490
  },
6221
6491
  READ_ONLY,
6222
6492
  async (params) => {
6223
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.");
6224
6535
  const embed = await services.widgetService.resolveEmbedId({
6225
6536
  embedId: params.embedId,
6226
6537
  dashboardName: params.dashboardName,
6227
6538
  workspaceName: params.workspaceName,
6539
+ dataAppName: params.dataAppName,
6228
6540
  required: true
6229
6541
  });
6230
6542
  if (!embed.ok) return toolError(embed.error);
6231
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);
6232
6546
  const widgets = await services.widgetsApi.listWidgets(
6233
6547
  embedId,
6234
- params.clientId
6548
+ params.clientId,
6549
+ app.value
6235
6550
  );
6236
6551
  if (hasChartApps(server)) {
6237
6552
  const visible = (Array.isArray(widgets) ? widgets : []).slice(0, 50);
@@ -6239,7 +6554,12 @@ function registerListWidgets(server, client) {
6239
6554
  const widgetId = widgetReferenceId(w);
6240
6555
  return widgetId ? [{
6241
6556
  name: typeof w.name === "string" ? w.name.slice(0, 500) : "Untitled widget",
6242
- reference: { widgetId, embedId, clientId: params.clientId }
6557
+ reference: {
6558
+ widgetId,
6559
+ embedId,
6560
+ clientId: params.clientId,
6561
+ ...app.value ? { dataAppName: app.value } : {}
6562
+ }
6243
6563
  }] : [];
6244
6564
  });
6245
6565
  return chartResult(`${references.length} widgets available in the first dashboard page.`, {
@@ -6280,29 +6600,29 @@ function registerListWidgets(server, client) {
6280
6600
  }
6281
6601
 
6282
6602
  // src/tools/widgets/add-widget.ts
6283
- import { z as z32 } from "zod";
6603
+ import { z as z34 } from "zod";
6284
6604
  function registerAddWidget(server, client) {
6285
6605
  const services = createServiceBundle(client);
6286
6606
  server.tool(
6287
6607
  "add_widget",
6288
6608
  "Add a widget to a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Cloning via `templateMetricId` is the recommended path \u2014 query, chart config, and datasource are all inherited. Data app and datasource auto-resolve; never ask the user for them.",
6289
6609
  {
6290
- clientId: z32.string().describe("Client/tenant identifier (widget is created in their scope)"),
6291
- dashboardName: z32.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6292
- workspaceName: z32.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6293
- embedId: z32.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6294
- dataAppName: z32.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6295
- templateMetricId: z32.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
6296
- name: z32.string().optional().describe("Widget name (overrides template)"),
6297
- metricId: z32.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
6298
- query: z32.string().optional().describe("SQL query body (overrides template)"),
6299
- chartOptions: z32.record(z32.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
6300
- description: z32.string().optional(),
6301
- companyIntegrationId: z32.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
6302
- integrationName: z32.string().optional().describe('Datasource display name (e.g. "Snowflake"). Use this when the user picks a datasource by name \u2014 the id is looked up automatically.'),
6303
- isPublished: z32.boolean().optional().describe("Publish immediately (default true)"),
6304
- body: z32.record(z32.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params."),
6305
- widgets: z32.array(z32.record(z32.unknown())).min(1).max(50).optional().describe("Batch of raw createMetric bodies. Dashboard attachment is added to each body.")
6610
+ clientId: z34.string().describe("Client/tenant identifier (widget is created in their scope)"),
6611
+ dashboardName: z34.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6612
+ workspaceName: z34.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6613
+ embedId: z34.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6614
+ dataAppName: z34.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6615
+ templateMetricId: z34.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
6616
+ name: z34.string().optional().describe("Widget name (overrides template)"),
6617
+ metricId: z34.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
6618
+ query: z34.string().optional().describe("SQL query body (overrides template)"),
6619
+ chartOptions: z34.record(z34.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
6620
+ description: z34.string().optional(),
6621
+ companyIntegrationId: z34.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
6622
+ integrationName: z34.string().optional().describe('Datasource display name (e.g. "Snowflake"). Use this when the user picks a datasource by name \u2014 the id is looked up automatically.'),
6623
+ isPublished: z34.boolean().optional().describe("Publish immediately (default true)"),
6624
+ body: z34.record(z34.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params."),
6625
+ widgets: z34.array(z34.record(z34.unknown())).min(1).max(50).optional().describe("Batch of raw createMetric bodies. Dashboard attachment is added to each body.")
6306
6626
  },
6307
6627
  WRITE_CREATE,
6308
6628
  async (params) => {
@@ -6311,11 +6631,12 @@ function registerAddWidget(server, client) {
6311
6631
  embedId: params.embedId,
6312
6632
  dashboardName: params.dashboardName,
6313
6633
  workspaceName: params.workspaceName,
6634
+ dataAppName: params.dataAppName,
6314
6635
  required: true
6315
6636
  });
6316
6637
  if (!embed.ok) return toolError(embed.error);
6317
6638
  const embedId = embed.value.embedId;
6318
- const ctx = await services.widgetService.resolveEmbedContext(embedId);
6639
+ const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
6319
6640
  if (!ctx.externalDashboardId) {
6320
6641
  return toolError(
6321
6642
  `Dashboard "${params.dashboardName || embedId}" has no externalDashboardId \u2014 cannot attach a widget. Verify via \`get_embed_details\`.`
@@ -6450,20 +6771,20 @@ Use \`list_widgets\` to verify, or \`update_widget\`/\`remove_widget\` to adjust
6450
6771
  }
6451
6772
 
6452
6773
  // src/tools/widgets/update-widget.ts
6453
- import { z as z33 } from "zod";
6774
+ import { z as z35 } from "zod";
6454
6775
  function registerUpdateWidget(server, client) {
6455
6776
  const services = createServiceBundle(client);
6456
6777
  server.tool(
6457
6778
  "update_widget",
6458
6779
  "Update a widget on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId` + `set`. Data app auto-resolves from the dashboard.",
6459
6780
  {
6460
- externalMetricId: z33.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
6461
- clientId: z33.string().describe("Client/tenant identifier"),
6462
- dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6463
- workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6464
- embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6465
- dataAppName: z33.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
6466
- set: z33.record(z33.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
6781
+ externalMetricId: z35.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
6782
+ clientId: z35.string().describe("Client/tenant identifier"),
6783
+ dashboardName: z35.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6784
+ workspaceName: z35.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6785
+ embedId: z35.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6786
+ dataAppName: z35.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
6787
+ set: z35.record(z35.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
6467
6788
  },
6468
6789
  WRITE_IDEMPOTENT,
6469
6790
  async (params) => {
@@ -6475,6 +6796,7 @@ function registerUpdateWidget(server, client) {
6475
6796
  embedId: params.embedId,
6476
6797
  dashboardName: params.dashboardName,
6477
6798
  workspaceName: params.workspaceName,
6799
+ dataAppName: params.dataAppName,
6478
6800
  required: true
6479
6801
  });
6480
6802
  if (!embed.ok) return toolError(embed.error);
@@ -6526,19 +6848,19 @@ Use \`list_widgets\` to verify.`
6526
6848
  }
6527
6849
 
6528
6850
  // src/tools/widgets/remove-widget.ts
6529
- import { z as z34 } from "zod";
6851
+ import { z as z36 } from "zod";
6530
6852
  function registerRemoveWidget(server, client) {
6531
6853
  const services = createServiceBundle(client);
6532
6854
  server.tool(
6533
6855
  "remove_widget",
6534
6856
  "Remove a widget from a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId`. Per-client soft delete \u2014 the widget stays on the template dashboard and on other customers' dashboards.",
6535
6857
  {
6536
- externalMetricId: z34.string().describe("Internal uuid of the widget (externalMetrics.id)"),
6537
- clientId: z34.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
6538
- dashboardName: z34.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6539
- workspaceName: z34.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6540
- embedId: z34.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6541
- dataAppName: z34.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
6858
+ externalMetricId: z36.string().describe("Internal uuid of the widget (externalMetrics.id)"),
6859
+ clientId: z36.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
6860
+ dashboardName: z36.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6861
+ workspaceName: z36.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6862
+ embedId: z36.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6863
+ dataAppName: z36.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
6542
6864
  },
6543
6865
  DESTRUCTIVE,
6544
6866
  async (params) => {
@@ -6547,6 +6869,7 @@ function registerRemoveWidget(server, client) {
6547
6869
  embedId: params.embedId,
6548
6870
  dashboardName: params.dashboardName,
6549
6871
  workspaceName: params.workspaceName,
6872
+ dataAppName: params.dataAppName,
6550
6873
  required: true
6551
6874
  });
6552
6875
  if (!embed.ok) return toolError(embed.error);
@@ -6594,21 +6917,21 @@ Note: this is a per-client soft delete. The widget remains available for other c
6594
6917
  }
6595
6918
 
6596
6919
  // src/tools/widgets/generate-widget.ts
6597
- import { z as z35 } from "zod";
6920
+ import { z as z37 } from "zod";
6598
6921
  function registerGenerateWidget(server, client) {
6599
6922
  const services = createServiceBundle(client);
6600
6923
  const tool = server.tool(
6601
6924
  "generate_widget",
6602
6925
  "AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft to pass to `add_widget`. Non-technical flow: `clientId` + `dashboardName` (+ `workspaceName` if ambiguous); data app and datasource auto-resolve. Never ask the user for data app, datasource, or integration.",
6603
6926
  {
6604
- clientId: z35.string().describe("Client/tenant identifier"),
6605
- userInputs: z35.record(z35.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
6606
- dashboardName: z35.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6607
- workspaceName: z35.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6608
- embedId: z35.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6609
- dataAppName: z35.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6610
- integrationId: z35.string().optional().describe("Technical override \u2014 the datasource UUID. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets."),
6611
- integrationName: z35.string().optional().describe("Technical override \u2014 datasource display name. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets.")
6927
+ clientId: z37.string().describe("Client/tenant identifier"),
6928
+ userInputs: z37.record(z37.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
6929
+ dashboardName: z37.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6930
+ workspaceName: z37.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6931
+ embedId: z37.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6932
+ dataAppName: z37.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6933
+ integrationId: z37.string().optional().describe("Technical override \u2014 the datasource UUID. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets."),
6934
+ integrationName: z37.string().optional().describe("Technical override \u2014 datasource display name. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets.")
6612
6935
  },
6613
6936
  READ_ONLY,
6614
6937
  async (params) => {
@@ -6617,6 +6940,7 @@ function registerGenerateWidget(server, client) {
6617
6940
  embedId: params.embedId,
6618
6941
  dashboardName: params.dashboardName,
6619
6942
  workspaceName: params.workspaceName,
6943
+ dataAppName: params.dataAppName,
6620
6944
  required: true
6621
6945
  });
6622
6946
  if (!embed.ok) return toolError(embed.error);
@@ -6693,9 +7017,9 @@ function registerGenerateWidget(server, client) {
6693
7017
  }
6694
7018
 
6695
7019
  // src/tools/widgets/create-or-update-metric-from-query.ts
6696
- import { z as z36 } from "zod";
6697
- var modeSchema = z36.enum(["dry_run", "apply"]).default("dry_run");
6698
- var conflictStrategySchema = z36.enum(["fail", "update", "create_new"]).default("fail");
7020
+ import { z as z38 } from "zod";
7021
+ var modeSchema = z38.enum(["dry_run", "apply"]).default("dry_run");
7022
+ var conflictStrategySchema = z38.enum(["fail", "update", "create_new"]).default("fail");
6699
7023
  function makePlanText(plan) {
6700
7024
  return JSON.stringify(plan, null, 2);
6701
7025
  }
@@ -6712,22 +7036,22 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
6712
7036
  "[EMBEDDING] Create or update a production metric/widget on a customer dashboard from validated SQL. Requires clientId (multi-tenant embed). Defaults to dry-run; apply requires explicit confirmation.",
6713
7037
  {
6714
7038
  mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
6715
- confirm: z36.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
7039
+ confirm: z38.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6716
7040
  conflictStrategy: conflictStrategySchema.describe("What to do if metricId already exists on the target dashboard"),
6717
- clientId: z36.string().describe("Client/tenant identifier for the dashboard widget scope"),
6718
- workspaceName: z36.string().describe("Target workspace name"),
6719
- dashboardName: z36.string().optional().describe("Dashboard name to attach to"),
6720
- embedId: z36.string().optional().describe("Technical embed id alternative to dashboardName"),
6721
- dataAppName: z36.string().optional().describe("Optional data app override"),
6722
- name: z36.string().describe("Metric/widget display name"),
6723
- metricId: z36.string().describe("Stable metric slug to create or update"),
6724
- query: z36.string().describe("SQL query already validated against the warehouse"),
6725
- description: z36.string().optional().describe("Metric description"),
6726
- chartOptions: z36.record(z36.unknown()).optional().describe("Chart configuration JSON"),
6727
- companyIntegrationId: z36.string().optional().describe("Datasource UUID override"),
6728
- integrationName: z36.string().optional().describe("Datasource display name override"),
6729
- isPublished: z36.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
6730
- requestedBy: z36.string().optional().describe("Human operator/request context to store on the metric when supported")
7041
+ clientId: z38.string().describe("Client/tenant identifier for the dashboard widget scope"),
7042
+ workspaceName: z38.string().describe("Target workspace name"),
7043
+ dashboardName: z38.string().optional().describe("Dashboard name to attach to"),
7044
+ embedId: z38.string().optional().describe("Technical embed id alternative to dashboardName"),
7045
+ dataAppName: z38.string().optional().describe("Optional data app override"),
7046
+ name: z38.string().describe("Metric/widget display name"),
7047
+ metricId: z38.string().describe("Stable metric slug to create or update"),
7048
+ query: z38.string().describe("SQL query already validated against the warehouse"),
7049
+ description: z38.string().optional().describe("Metric description"),
7050
+ chartOptions: z38.record(z38.unknown()).optional().describe("Chart configuration JSON"),
7051
+ companyIntegrationId: z38.string().optional().describe("Datasource UUID override"),
7052
+ integrationName: z38.string().optional().describe("Datasource display name override"),
7053
+ isPublished: z38.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
7054
+ requestedBy: z38.string().optional().describe("Human operator/request context to store on the metric when supported")
6731
7055
  },
6732
7056
  WRITE_IDEMPOTENT,
6733
7057
  async (params) => {
@@ -6741,11 +7065,12 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
6741
7065
  embedId: params.embedId,
6742
7066
  dashboardName: params.dashboardName,
6743
7067
  workspaceName: params.workspaceName,
7068
+ dataAppName: params.dataAppName,
6744
7069
  required: true
6745
7070
  });
6746
7071
  if (!embed.ok) return toolError(embed.error);
6747
7072
  const embedId = embed.value.embedId;
6748
- const ctx = await services.widgetService.resolveEmbedContext(embedId);
7073
+ const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
6749
7074
  if (!ctx.externalDashboardId) {
6750
7075
  return toolError(
6751
7076
  `Dashboard "${params.dashboardName || embedId}" has no externalDashboardId. Verify with get_embed_details before creating metrics.`
@@ -6887,8 +7212,8 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
6887
7212
  }
6888
7213
 
6889
7214
  // src/tools/widgets/create-workspace-metric.ts
6890
- import { z as z37 } from "zod";
6891
- var modeSchema2 = z37.enum(["dry_run", "apply"]).default("dry_run");
7215
+ import { z as z39 } from "zod";
7216
+ var modeSchema2 = z39.enum(["dry_run", "apply"]).default("dry_run");
6892
7217
  function registerCreateWorkspaceMetric(server, client) {
6893
7218
  server.tool(
6894
7219
  "create_workspace_metric",
@@ -6897,18 +7222,18 @@ function registerCreateWorkspaceMetric(server, client) {
6897
7222
  mode: modeSchema2.describe(
6898
7223
  "dry_run validates the SQL only (no write); apply creates or updates the metric after confirmation"
6899
7224
  ),
6900
- confirm: z37.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6901
- workspaceName: z37.string().describe("Target workspace name"),
6902
- name: z37.string().describe("Metric display name"),
6903
- query: z37.string().describe(
7225
+ confirm: z39.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
7226
+ workspaceName: z39.string().describe("Target workspace name"),
7227
+ name: z39.string().describe("Metric display name"),
7228
+ query: z39.string().describe(
6904
7229
  "SQL query to validate and store. In a TABLE-tenancy workspace never compare the client column to a fixed client value; use `client_id_variable` so each client sees only its own rows (the backend refuses it with TENANT_KEY_LITERAL)."
6905
7230
  ),
6906
- metricId: z37.string().optional().describe(
7231
+ metricId: z39.string().optional().describe(
6907
7232
  "Stable metric slug. Pass an existing slug to UPDATE that metric in place. Omit to create \u2014 the slug is derived deterministically from the name (no random/uniqueness suffix), so re-running with the same name upserts the same metric rather than creating a duplicate."
6908
7233
  ),
6909
- description: z37.string().optional().describe("Metric description"),
6910
- datasourceId: z37.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
6911
- isPublished: z37.boolean().optional().describe(
7234
+ description: z39.string().optional().describe("Metric description"),
7235
+ datasourceId: z39.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
7236
+ isPublished: z39.boolean().optional().describe(
6912
7237
  "Publish after apply. Defaults to false. Applies to creates; on updates the existing publish state is preserved."
6913
7238
  )
6914
7239
  },
@@ -6964,25 +7289,25 @@ function registerCreateWorkspaceMetric(server, client) {
6964
7289
  }
6965
7290
 
6966
7291
  // src/tools/widgets/validate-metric-spec.ts
6967
- import { z as z38 } from "zod";
7292
+ import { z as z40 } from "zod";
6968
7293
  function registerValidateMetricSpec(server, client) {
6969
7294
  const services = createServiceBundle(client);
6970
7295
  server.tool(
6971
7296
  "validate_metric_spec",
6972
7297
  "Validate a production metric spec from SQL without saving it. Resolves dashboard, data app, datasource, conflicts, and required fields.",
6973
7298
  {
6974
- clientId: z38.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
6975
- workspaceName: z38.string().describe("Target production-backed workspace name"),
6976
- dashboardName: z38.string().optional().describe("Dashboard name to attach to"),
6977
- embedId: z38.string().optional().describe("Technical embed id alternative to dashboardName"),
6978
- dataAppName: z38.string().optional().describe("Optional data app override"),
6979
- name: z38.string().describe("Metric/widget display name"),
6980
- metricId: z38.string().describe("Stable metric slug to create or update"),
6981
- query: z38.string().describe("SQL query already validated against the warehouse"),
6982
- description: z38.string().optional().describe("Metric description"),
6983
- chartOptions: z38.record(z38.unknown()).optional().describe("Chart configuration JSON"),
6984
- companyIntegrationId: z38.string().optional().describe("Datasource UUID override"),
6985
- integrationName: z38.string().optional().describe("Datasource display name override")
7299
+ clientId: z40.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
7300
+ workspaceName: z40.string().describe("Target production-backed workspace name"),
7301
+ dashboardName: z40.string().optional().describe("Dashboard name to attach to"),
7302
+ embedId: z40.string().optional().describe("Technical embed id alternative to dashboardName"),
7303
+ dataAppName: z40.string().optional().describe("Optional data app override"),
7304
+ name: z40.string().describe("Metric/widget display name"),
7305
+ metricId: z40.string().describe("Stable metric slug to create or update"),
7306
+ query: z40.string().describe("SQL query already validated against the warehouse"),
7307
+ description: z40.string().optional().describe("Metric description"),
7308
+ chartOptions: z40.record(z40.unknown()).optional().describe("Chart configuration JSON"),
7309
+ companyIntegrationId: z40.string().optional().describe("Datasource UUID override"),
7310
+ integrationName: z40.string().optional().describe("Datasource display name override")
6986
7311
  },
6987
7312
  READ_ONLY,
6988
7313
  async (params) => {
@@ -6994,11 +7319,12 @@ function registerValidateMetricSpec(server, client) {
6994
7319
  embedId: params.embedId,
6995
7320
  dashboardName: params.dashboardName,
6996
7321
  workspaceName: params.workspaceName,
7322
+ dataAppName: params.dataAppName,
6997
7323
  required: true
6998
7324
  });
6999
7325
  if (!embed.ok) return toolError(embed.error);
7000
7326
  const embedId = embed.value.embedId;
7001
- const ctx = await services.widgetService.resolveEmbedContext(embedId);
7327
+ const ctx = await services.widgetService.resolveEmbedContext(embedId, params.dataAppName);
7002
7328
  if (!ctx.externalDashboardId) {
7003
7329
  return toolError(
7004
7330
  `Dashboard "${params.dashboardName || embedId}" has no externalDashboardId. Verify with get_embed_details before creating metrics.`
@@ -7073,20 +7399,21 @@ function registerValidateMetricSpec(server, client) {
7073
7399
  }
7074
7400
 
7075
7401
  // src/tools/reports/list-scheduled-reports.ts
7076
- import { z as z39 } from "zod";
7402
+ import { z as z41 } from "zod";
7077
7403
  function registerListScheduledReports(server, client) {
7078
7404
  const services = createServiceBundle(client);
7079
7405
  server.tool(
7080
7406
  "list_scheduled_reports",
7081
7407
  "List configured scheduled reports for a specific client/dashboard embed. Shows schedule metadata, not delivery success history.",
7082
7408
  {
7083
- clientId: z39.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
7084
- dashboardName: z39.string().optional().describe("Dashboard name to inspect"),
7085
- workspaceName: z39.string().optional().describe("Workspace name if dashboardName is ambiguous"),
7086
- embedId: z39.string().optional().describe("Technical embed id alternative to dashboardName"),
7087
- userIdentifier: z39.string().optional().describe("Optional creator/user filter"),
7088
- pageNumber: z39.number().int().positive().optional().describe("Page number when pagination is enabled"),
7089
- isPagination: z39.boolean().optional().describe("Enable backend pagination")
7409
+ clientId: z41.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
7410
+ dashboardName: z41.string().optional().describe("Dashboard name to inspect"),
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"),
7413
+ embedId: z41.string().optional().describe("Technical embed id alternative to dashboardName"),
7414
+ userIdentifier: z41.string().optional().describe("Optional creator/user filter"),
7415
+ pageNumber: z41.number().int().positive().optional().describe("Page number when pagination is enabled"),
7416
+ isPagination: z41.boolean().optional().describe("Enable backend pagination")
7090
7417
  },
7091
7418
  READ_ONLY,
7092
7419
  async (params) => {
@@ -7095,12 +7422,16 @@ function registerListScheduledReports(server, client) {
7095
7422
  embedId: params.embedId,
7096
7423
  dashboardName: params.dashboardName,
7097
7424
  workspaceName: params.workspaceName,
7425
+ dataAppName: params.dataAppName,
7098
7426
  required: true
7099
7427
  });
7100
7428
  if (!embed.ok) return toolError(embed.error);
7101
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);
7102
7432
  const reports = await services.scheduledReportsApi.listByEmbed({
7103
7433
  embedId,
7434
+ dataAppName: app.value,
7104
7435
  clientId: params.clientId,
7105
7436
  userIdentifier: params.userIdentifier,
7106
7437
  pageNumber: params.pageNumber,
@@ -7196,90 +7527,18 @@ function registerWhoami(server, client) {
7196
7527
  }
7197
7528
 
7198
7529
  // src/tools/custom-chart/scaffold.ts
7199
- import { z as z41 } from "zod";
7200
-
7201
- // src/tools/custom-chart/shared.ts
7202
- import { z as z40 } from "zod";
7203
- var slotInputSchema = slotSchema;
7204
- var optionInputSchema = optionSchema;
7205
- var manifestInputSchema = z40.record(z40.unknown()).describe("manifest.json as an object (schemaVersion 2). Validated with the same rules as the server.");
7206
- var bundleInputShape = {
7207
- manifest: manifestInputSchema.optional(),
7208
- bundleJs: z40.string().optional().describe("dist/index.js from `databrain custom-chart build` (the shim plus the chart module)."),
7209
- bundleCss: z40.string().optional().describe("The raw chart.css, kept for provenance."),
7210
- iconSvg: z40.string().optional().describe("icon.svg, at most 16 KiB, no script, no foreignObject, no external href."),
7211
- projectDir: z40.string().optional().describe("Local only: a chart project folder. The tool reads it instead of manifest/bundleJs/iconSvg.")
7212
- };
7213
- function formatProblems(problems) {
7214
- if (problems.length === 0) return "No problems.";
7215
- return problems.map((p) => `${p.path || "/"} ${p.code} ${p.message}`).join("\n");
7216
- }
7217
- function projectDirRefusal() {
7218
- if (process.env.DATABRAIN_HOSTED_PUBLIC_URL) {
7219
- return "projectDir is not available on the hosted server. Pass manifest, bundleJs, bundleCss and iconSvg instead.";
7220
- }
7221
- return null;
7222
- }
7223
- async function resolveBundleInput(params, options) {
7224
- if (params.projectDir) {
7225
- const refusal = projectDirRefusal();
7226
- if (refusal) return { problems: [], error: refusal, source: "projectDir" };
7227
- if (params.manifest || params.bundleJs || params.iconSvg) {
7228
- return { problems: [], error: "Pass either projectDir or manifest/bundleJs/iconSvg, not both.", source: "projectDir" };
7229
- }
7230
- if (options.build) {
7231
- const result = await buildProject(params.projectDir, { write: false });
7232
- if (!result.ok || !result.outputs) return { problems: result.problems, source: "projectDir" };
7233
- const { manifest, bundleJs, bundleCss, iconSvg } = result.outputs;
7234
- return { bundle: { manifest, bundleJs, bundleCss, iconSvg }, problems: [], source: "projectDir" };
7235
- }
7236
- const dist = readDist(params.projectDir);
7237
- if (!dist.bundle) return { problems: dist.problems, source: "projectDir" };
7238
- return { bundle: dist.bundle, problems: [], source: "projectDir" };
7239
- }
7240
- if (!params.manifest) {
7241
- return { problems: [], error: "manifest is required (or pass projectDir when running locally).", source: "arguments" };
7242
- }
7243
- return {
7244
- bundle: {
7245
- manifest: params.manifest,
7246
- bundleJs: params.bundleJs,
7247
- bundleCss: params.bundleCss,
7248
- iconSvg: params.iconSvg
7249
- },
7250
- problems: [],
7251
- source: "arguments"
7252
- };
7253
- }
7254
- async function remoteValidate(client, bundle) {
7255
- try {
7256
- const result = await createCustomChartsApi(client).validate(bundle);
7257
- return { status: "ok", result };
7258
- } catch (err) {
7259
- if (err instanceof DatabrainApiError) {
7260
- if (err.code === "MISSING_SERVICE_TOKEN") {
7261
- return { status: "skipped", reason: "No service token is configured, so only the local validator ran." };
7262
- }
7263
- return { status: "failed", reason: err.message, problems: apiProblems(err) };
7264
- }
7265
- return { status: "failed", reason: err.message, problems: [] };
7266
- }
7267
- }
7268
- function isCompleteBundle(bundle) {
7269
- return typeof bundle.bundleJs === "string" && typeof bundle.iconSvg === "string";
7270
- }
7271
-
7272
- // src/tools/custom-chart/scaffold.ts
7530
+ import { transformSync } from "esbuild";
7531
+ import { z as z42 } from "zod";
7273
7532
  function registerCustomChartScaffold(server, _client) {
7274
7533
  server.tool(
7275
7534
  "custom_chart_scaffold",
7276
- "Return the files of a new custom chart project (manifest.json, chart.ts, chart.css, icon.svg, sample data, guide). Nothing is written or persisted; write the files yourself.",
7535
+ "Return the files of a new custom chart project (manifest.json, chart.ts, chart.css, icon.svg, sample data, guide). Nothing is persisted. For a hosted AI app, edit the returned draft and pass chartTs to validate, preview and upload without local files.",
7277
7536
  {
7278
- key: z41.string().describe("Chart key, becomes chart type custom:<key>. Lowercase letters, digits, _ and -, 64 max."),
7279
- name: z41.string().describe("Display name shown in the chart picker, 1 to 256 characters."),
7280
- description: z41.string().optional().describe("Optional description, up to 500 characters."),
7281
- slots: z41.array(slotInputSchema).optional().describe("Data slots. Defaults to category (dimension), value (measure), target (optional measure)."),
7282
- options: z41.array(optionInputSchema).optional().describe("Chart options. Defaults to the template options (labels, subtitle, radius, colour, sort).")
7537
+ key: z42.string().describe("Chart key, becomes chart type custom:<key>. Lowercase letters, digits, _ and -, 64 max."),
7538
+ name: z42.string().describe("Display name shown in the chart picker, 1 to 256 characters."),
7539
+ description: z42.string().optional().describe("Optional description, up to 500 characters."),
7540
+ slots: z42.array(slotInputSchema).optional().describe("Data slots. Defaults to category (dimension), value (measure), target (optional measure)."),
7541
+ options: z42.array(optionInputSchema).optional().describe("Chart options. Defaults to the template options (labels, subtitle, radius, colour, sort).")
7283
7542
  },
7284
7543
  READ_ONLY,
7285
7544
  async (params) => {
@@ -7302,7 +7561,19 @@ ${formatProblems(result.problems)}`);
7302
7561
  key: params.key,
7303
7562
  chartType: `custom:${params.key}`,
7304
7563
  files: result.files,
7305
- nextStep: result.nextStep
7564
+ draft: {
7565
+ code: transformSync(result.files["chart.ts"], { loader: "ts", format: "esm" }).code.replace(/export\s*\{[^}]*\};?\s*$/, ""),
7566
+ css: result.files["chart.css"],
7567
+ manifest: JSON.parse(result.files["manifest.json"]),
7568
+ sampleData: (() => {
7569
+ const sample = JSON.parse(result.files["sample-data.json"]);
7570
+ return { columns: sample.columns.map(({ id: id2, label, type, slotId }) => ({ id: id2, label, type, ...slotId ? { slotId } : {} })), rows: sample.rows };
7571
+ })(),
7572
+ name: params.name,
7573
+ description: params.description ?? "",
7574
+ iconSvg: result.files["icon.svg"]
7575
+ },
7576
+ nextStep: "Hosted: pass draft to custom_chart_edit with a description and workspaceId. Then validate/preview/upload using chartTs=draft.code, manifest, bundleCss=draft.css and iconSvg. Upload saves a version; publishing requires explicit confirmation. Local project: " + result.nextStep
7306
7577
  },
7307
7578
  null,
7308
7579
  2
@@ -7371,18 +7642,18 @@ function registerCustomChartValidate(server, client) {
7371
7642
  }
7372
7643
 
7373
7644
  // src/tools/custom-chart/upload.ts
7374
- import { z as z42 } from "zod";
7645
+ import { z as z43 } from "zod";
7375
7646
  function registerCustomChartUpload(server, client) {
7376
7647
  const api = createCustomChartsApi(client);
7377
7648
  server.tool(
7378
7649
  "custom_chart_upload",
7379
7650
  "Upload a built custom chart bundle to the company. Creates the chart (private, version 1) or a new preview version. Defaults to dry_run; apply requires explicit confirmation and can publish in the same call.",
7380
7651
  {
7381
- mode: z42.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run validates and returns the plan; apply uploads."),
7382
- confirm: z42.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
7383
- publish: z42.boolean().default(false).describe("After a successful apply, publish the uploaded version so every builder can use it."),
7384
- name: z42.string().optional().describe("Display name override. Defaults to manifest.name."),
7385
- description: z42.string().optional().describe("Description override. Defaults to manifest.description."),
7652
+ mode: z43.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run validates and returns the plan; apply uploads."),
7653
+ confirm: z43.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
7654
+ publish: z43.boolean().default(false).describe("After a successful apply, publish the uploaded version so every builder can use it."),
7655
+ name: z43.string().optional().describe("Display name override. Defaults to manifest.name."),
7656
+ description: z43.string().optional().describe("Description override. Defaults to manifest.description."),
7386
7657
  ...bundleInputShape
7387
7658
  },
7388
7659
  WRITE_CREATE,
@@ -7523,17 +7794,17 @@ function registerListCustomCharts(server, client) {
7523
7794
  }
7524
7795
 
7525
7796
  // src/tools/custom-chart/publish.ts
7526
- import { z as z43 } from "zod";
7797
+ import { z as z44 } from "zod";
7527
7798
  function registerPublishCustomChart(server, client) {
7528
7799
  const api = createCustomChartsApi(client);
7529
7800
  server.tool(
7530
7801
  "publish_custom_chart",
7531
7802
  "Publish a custom chart version so every builder in the company can use chart type custom:<key>. Defaults to the preview version. Defaults to dry_run; apply requires explicit confirmation.",
7532
7803
  {
7533
- key: z43.string().describe("The chart key (manifest.id), for example revenue-bars."),
7534
- versionId: z43.string().optional().describe("A specific version id to publish. Defaults to the preview version."),
7535
- mode: z43.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run shows what would be published; apply publishes."),
7536
- confirm: z43.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`)
7804
+ key: z44.string().describe("The chart key (manifest.id), for example revenue-bars."),
7805
+ versionId: z44.string().optional().describe("A specific version id to publish. Defaults to the preview version."),
7806
+ mode: z44.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run shows what would be published; apply publishes."),
7807
+ confirm: z44.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`)
7537
7808
  },
7538
7809
  WRITE_IDEMPOTENT,
7539
7810
  async (params) => {
@@ -7629,6 +7900,7 @@ var TOOL_TITLES = {
7629
7900
  apply_semantic_change: "Apply semantic model change",
7630
7901
  eval_semantic_model: "Evaluate semantic model",
7631
7902
  list_widgets: "List widgets",
7903
+ render_metric_tile: "Render a saved metric tile",
7632
7904
  validate_metric_spec: "Validate metric spec",
7633
7905
  create_or_update_metric_from_query: "Create or update metric from SQL",
7634
7906
  create_workspace_metric: "Create workspace metric",
@@ -7728,6 +8000,7 @@ function registerAllTools(server, client) {
7728
8000
  registerGenerateWidget(server, client);
7729
8001
  registerListScheduledReports(server, client);
7730
8002
  registerCustomChartScaffold(server, client);
8003
+ registerCustomChartAuthor(server, client);
7731
8004
  registerCustomChartValidate(server, client);
7732
8005
  registerCustomChartUpload(server, client);
7733
8006
  registerListCustomCharts(server, client);
@@ -9619,7 +9892,7 @@ The v2 source is one YAML document. Use the configured service credential and a
9619
9892
 
9620
9893
  1. \`search_semantic_model\` finds entities by question. \`get_semantic_entity\` reads one entity by kind and name. Column names use table.column.
9621
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.
9622
- 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.
9623
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.
9624
9897
 
9625
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.
@@ -10700,7 +10973,7 @@ var populateSemanticLayerPrompt = `You are helping the user populate the semanti
10700
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:
10701
10974
  1. Inspect relevant entities with \`search_semantic_model\` and \`get_semantic_entity\`, using the datamart ID.
10702
10975
  2. Call \`propose_semantic_change\` with a YAML patch or structured change. Show its errors and plan. Correct errors before continuing.
10703
- 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.
10704
10977
  4. Stop for the user's explicit \`APPLY_TO_PRODUCTION\` confirmation. Never supply it yourself.
10705
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.
10706
10979
  6. Call \`get_semantic_entity\` and inspect the changed entity. Do not call \`update_semantic_layer\` for a v2 model.
@@ -11066,20 +11339,24 @@ function registerAllPrompts(server) {
11066
11339
  }
11067
11340
 
11068
11341
  export {
11069
- createServiceBundle,
11070
11342
  READ_ONLY,
11071
11343
  WRITE_CREATE,
11072
- APPLY_TO_PRODUCTION_CONFIRMATION,
11073
11344
  CHART_URI,
11074
11345
  KPI_URI,
11075
11346
  APP_MIME,
11347
+ MAX_CUSTOM_BUNDLE_CHARS,
11348
+ CUSTOM_BUNDLE_CHUNK_CHARS,
11076
11349
  rowsSchema,
11077
11350
  referenceSchema,
11351
+ dateRangeSchema,
11078
11352
  viewSchema,
11079
11353
  pageSchema,
11354
+ bounded,
11080
11355
  appMeta,
11081
11356
  enableChartApps,
11082
11357
  chartResult,
11358
+ createServiceBundle,
11359
+ APPLY_TO_PRODUCTION_CONFIRMATION,
11083
11360
  draftSchema,
11084
11361
  PREVIEW_PATH,
11085
11362
  RENDER_TYPES,
@@ -11090,4 +11367,4 @@ export {
11090
11367
  registerAllResources,
11091
11368
  registerAllPrompts
11092
11369
  };
11093
- //# sourceMappingURL=chunk-ERTAKB2H.js.map
11370
+ //# sourceMappingURL=chunk-OLOC2MTA.js.map