@databrainhq/mcp-server 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -550,14 +550,20 @@ var EmbedsApi = class {
550
550
  invalidateCache: ["list_embeds"]
551
551
  });
552
552
  }
553
- async listEmbeds(dataAppName) {
554
- const query = dataAppName && dataAppName.trim().length > 0 ? { dataAppName } : void 0;
553
+ async listEmbeds(dataAppName, clientId) {
554
+ const query = {};
555
+ if (dataAppName && dataAppName.trim().length > 0) {
556
+ query.dataAppName = dataAppName.trim();
557
+ }
558
+ if (clientId && clientId.trim().length > 0) {
559
+ query.clientId = clientId.trim();
560
+ }
555
561
  return this.client.request({
556
562
  method: "GET",
557
563
  path: "/api/v2/data-app/embeds",
558
564
  auth: "api",
559
- query,
560
- cacheKey: "list_embeds",
565
+ query: Object.keys(query).length > 0 ? query : void 0,
566
+ cacheKey: `list_embeds:${query.dataAppName || "*"}:${query.clientId || "*"}`,
561
567
  cacheTtlMs: 5 * 60 * 1e3
562
568
  });
563
569
  }
@@ -646,7 +652,7 @@ var QueryApi = class {
646
652
  async startSemanticLayerGeneration(datamartId) {
647
653
  return this.client.request({
648
654
  method: "POST",
649
- path: `/api/schema/datamart/${encodeURIComponent(datamartId)}/semantic-layer/auto-generate`,
655
+ path: `/schema/datamart/${encodeURIComponent(datamartId)}/semantic-layer/auto-generate`,
650
656
  auth: "service",
651
657
  invalidateCache: ["semantic_layer_"]
652
658
  });
@@ -654,7 +660,7 @@ var QueryApi = class {
654
660
  async getSemanticLayerGenerationStatus(datamartId) {
655
661
  return this.client.request({
656
662
  method: "GET",
657
- path: `/api/schema/datamart/${encodeURIComponent(datamartId)}/semantic-layer/generation-status`,
663
+ path: `/schema/datamart/${encodeURIComponent(datamartId)}/semantic-layer/generation-status`,
658
664
  auth: "service"
659
665
  });
660
666
  }
@@ -708,6 +714,36 @@ var ScheduledReportsApi = class {
708
714
  }
709
715
  };
710
716
 
717
+ // src/api/tenants-api.ts
718
+ var TenantsApi = class {
719
+ constructor(client) {
720
+ this.client = client;
721
+ }
722
+ async listTenants(params) {
723
+ const input = {
724
+ workspaceId: params.workspaceId
725
+ };
726
+ if (params.limit !== void 0) input.limit = String(params.limit);
727
+ if (params.searchValue && params.searchValue.trim().length > 0) {
728
+ input.searchValue = params.searchValue.trim();
729
+ }
730
+ if (params.datasourceId && params.datasourceId.trim().length > 0) {
731
+ input.datasourceId = params.datasourceId.trim();
732
+ }
733
+ if (params.datamartId && params.datamartId.trim().length > 0) {
734
+ input.datamartId = params.datamartId.trim();
735
+ }
736
+ return this.client.request({
737
+ method: "POST",
738
+ path: "/hasuraActions/getClientData",
739
+ auth: "service",
740
+ body: { input: { input } },
741
+ cacheKey: `list_tenants:${JSON.stringify(input)}`,
742
+ cacheTtlMs: 5 * 60 * 1e3
743
+ });
744
+ }
745
+ };
746
+
711
747
  // src/api/widgets-api.ts
712
748
  var WidgetsApi = class {
713
749
  constructor(client) {
@@ -899,8 +935,8 @@ var EmbedService = class {
899
935
  this.embedsApi = embedsApi;
900
936
  this.datamartsApi = datamartsApi;
901
937
  }
902
- async listEmbeds(dataAppName) {
903
- return this.embedsApi.listEmbeds(dataAppName);
938
+ async listEmbeds(dataAppName, clientId) {
939
+ return this.embedsApi.listEmbeds(dataAppName, clientId);
904
940
  }
905
941
  async createEmbedStrict(body) {
906
942
  const accessSettings = asRecord2(body.accessSettings);
@@ -1213,6 +1249,66 @@ var WidgetService = class {
1213
1249
  error: `Multiple data apps exist (${normalized.map((a) => `"${a.name}"`).join(", ")}). Pass \`dataAppName\` explicitly, or pass \`embedId\` to auto-resolve.`
1214
1250
  };
1215
1251
  }
1252
+ async ensureTenantDashboardScope(params) {
1253
+ const clientId = params.clientId.trim();
1254
+ if (!clientId) {
1255
+ return {
1256
+ ok: false,
1257
+ error: "`clientId` cannot be empty. Pick a tenant/client first."
1258
+ };
1259
+ }
1260
+ const ctx = await this.resolveEmbedContext(params.embedId);
1261
+ if (ctx.externalDashboardCreatedBy === clientId) {
1262
+ return {
1263
+ ok: true,
1264
+ value: {
1265
+ embedId: params.embedId,
1266
+ clientId,
1267
+ dataAppName: ctx.dataAppName
1268
+ }
1269
+ };
1270
+ }
1271
+ let tenantEmbeds;
1272
+ try {
1273
+ tenantEmbeds = await this.embedsApi.listEmbeds(params.dataAppName, clientId);
1274
+ } catch (err) {
1275
+ return {
1276
+ ok: false,
1277
+ error: `Could not verify tenant dashboard ownership for client "${clientId}": ${err.message}`
1278
+ };
1279
+ }
1280
+ const target = params.embedId.trim();
1281
+ const matches = tenantEmbeds.map((embed) => ({
1282
+ raw: embed,
1283
+ normalized: this.discoveryService.normalizeEmbed(embed)
1284
+ })).filter(({ raw, normalized }) => {
1285
+ const ids = [
1286
+ raw.embedId,
1287
+ raw.id,
1288
+ raw._id,
1289
+ raw.dashboardId,
1290
+ normalized.embedId,
1291
+ normalized.internalId,
1292
+ normalized.dashboardId
1293
+ ].filter(isNonEmptyString3);
1294
+ return ids.includes(target);
1295
+ });
1296
+ if (matches.length === 0) {
1297
+ const dashboardLabel = params.dashboardName || params.embedId;
1298
+ return {
1299
+ ok: false,
1300
+ error: `Refusing to touch dashboard "${dashboardLabel}" for client "${clientId}". The tenant-scoped embed lookup did not return this dashboard, which means the resolved target is likely the production/data-app dashboard rather than this client's dashboard copy. Create or select a multi-tenant dashboard embed for this client first (\`create_embed\` with \`clientId\`), then retry.`
1301
+ };
1302
+ }
1303
+ return {
1304
+ ok: true,
1305
+ value: {
1306
+ embedId: params.embedId,
1307
+ clientId,
1308
+ dataAppName: matches[0].normalized.dataAppName
1309
+ }
1310
+ };
1311
+ }
1216
1312
  /**
1217
1313
  * Resolve a datasource for a widget. Priority:
1218
1314
  * 1. Explicit integrationId + integrationName (fast path).
@@ -1397,7 +1493,8 @@ Do not guess between the two \u2014 wait for the user to choose.`
1397
1493
  dataAppId,
1398
1494
  dataAppName,
1399
1495
  externalDashboardId,
1400
- dashboardId
1496
+ dashboardId,
1497
+ externalDashboardCreatedBy: (externalDashboard && isNonEmptyString3(externalDashboard.createdBy) ? externalDashboard.createdBy : null) || (isNonEmptyString3(exact.createdBy) ? exact.createdBy : null) || (metadata && isNonEmptyString3(metadata.createdBy) ? metadata.createdBy : null) || (isNonEmptyString3(exact.clientId) ? exact.clientId : null)
1401
1498
  };
1402
1499
  }
1403
1500
  async mintGuestToken(clientId, dataAppName, allowedEmbeds) {
@@ -1482,6 +1579,7 @@ function createServiceBundle(client) {
1482
1579
  const guestTokenApi = new GuestTokenApi(client);
1483
1580
  const queryApi = new QueryApi(client);
1484
1581
  const scheduledReportsApi = new ScheduledReportsApi(client);
1582
+ const tenantsApi = new TenantsApi(client);
1485
1583
  const widgetsApi = new WidgetsApi(client);
1486
1584
  const workspacesApi = new WorkspacesApi(client);
1487
1585
  const discoveryService = new DiscoveryService();
@@ -1495,6 +1593,7 @@ function createServiceBundle(client) {
1495
1593
  workspacesApi,
1496
1594
  queryApi,
1497
1595
  scheduledReportsApi,
1596
+ tenantsApi,
1498
1597
  widgetsApi,
1499
1598
  embedService: new EmbedService(embedsApi, datamartsApi),
1500
1599
  discoveryService,
@@ -1572,16 +1671,153 @@ function registerListDataApps(server, client) {
1572
1671
  );
1573
1672
  }
1574
1673
 
1575
- // src/tools/data-app/create-data-app.ts
1674
+ // src/tools/data-app/list-tenants.ts
1576
1675
  import { z as z3 } from "zod";
1676
+ var isNonEmptyString4 = (value) => typeof value === "string" && value.trim().length > 0;
1677
+ function workspaceIdFrom(workspace) {
1678
+ return isNonEmptyString4(workspace.id) && workspace.id || isNonEmptyString4(workspace._id) && workspace._id || isNonEmptyString4(workspace.workspaceId) && workspace.workspaceId || null;
1679
+ }
1680
+ function tenantErrorMessage(result) {
1681
+ const message = result.message;
1682
+ if (isNonEmptyString4(message)) return message;
1683
+ const error = result.error;
1684
+ if (isNonEmptyString4(error)) return error;
1685
+ if (error !== null && error !== void 0) {
1686
+ try {
1687
+ return JSON.stringify(error);
1688
+ } catch {
1689
+ return String(error);
1690
+ }
1691
+ }
1692
+ return null;
1693
+ }
1694
+ function normalizeTenantList(result) {
1695
+ if (Array.isArray(result)) {
1696
+ return { tenants: result };
1697
+ }
1698
+ const errorMessage = tenantErrorMessage(result);
1699
+ if (errorMessage) {
1700
+ return { tenants: [], errorMessage };
1701
+ }
1702
+ return {
1703
+ tenants: Array.isArray(result.data) ? result.data : [],
1704
+ clientColumnType: isNonEmptyString4(result.clientColumnType) ? result.clientColumnType : void 0
1705
+ };
1706
+ }
1707
+ function registerListTenants(server, client) {
1708
+ const services = createServiceBundle(client);
1709
+ server.tool(
1710
+ "list_tenants",
1711
+ "List available tenants/clients from the workspace tenancy database. Use this to pick a valid clientId before creating guest tokens or editing customer dashboards.",
1712
+ {
1713
+ workspaceName: z3.string().optional().describe("Workspace name. Preferred for non-technical callers; the tool resolves it to a workspaceId."),
1714
+ workspaceId: z3.string().optional().describe("Technical workspace UUID. Provide this instead of workspaceName if already known."),
1715
+ searchValue: z3.string().optional().describe("Optional case-insensitive search string for tenant/client labels."),
1716
+ limit: z3.number().int().positive().max(500).optional().describe("Maximum number of tenants to return. Backend may apply connector-specific limits."),
1717
+ datasourceId: z3.string().optional().describe("Optional datasource UUID override when the workspace has multiple datasource contexts."),
1718
+ datamartId: z3.string().optional().describe("Optional datamart UUID override when tenant lookup should use a specific datamart.")
1719
+ },
1720
+ READ_ONLY,
1721
+ async (params) => {
1722
+ try {
1723
+ let workspaceId = params.workspaceId?.trim() || "";
1724
+ let workspaceName = params.workspaceName?.trim() || "";
1725
+ if (!workspaceId && !workspaceName) {
1726
+ return toolError(
1727
+ "Provide `workspaceName` or `workspaceId` so the tenancy lookup can resolve the client table."
1728
+ );
1729
+ }
1730
+ if (!workspaceId) {
1731
+ const workspaces = await services.workspacesApi.listWorkspaces();
1732
+ const matches = workspaces.filter(
1733
+ (workspace) => isNonEmptyString4(workspace.name) && workspace.name.trim().toLowerCase() === workspaceName.toLowerCase()
1734
+ );
1735
+ if (matches.length === 0) {
1736
+ const available = workspaces.map((workspace) => workspace.name).filter(isNonEmptyString4).map((name) => `"${name}"`).join(", ");
1737
+ return toolError(
1738
+ `No workspace named "${workspaceName}". ` + (available ? `Available workspaces: ${available}.` : "No workspaces were returned by list_workspaces.")
1739
+ );
1740
+ }
1741
+ if (matches.length > 1) {
1742
+ return toolError(
1743
+ `Workspace name "${workspaceName}" is ambiguous. Retry with \`workspaceId\`.`
1744
+ );
1745
+ }
1746
+ workspaceId = workspaceIdFrom(matches[0]) || "";
1747
+ if (!workspaceId) {
1748
+ return toolError(
1749
+ `Workspace "${workspaceName}" did not include an id in the backend response. Retry with \`workspaceId\`.`
1750
+ );
1751
+ }
1752
+ workspaceName = matches[0].name || workspaceName;
1753
+ }
1754
+ if (!workspaceName) {
1755
+ try {
1756
+ const workspaces = await services.workspacesApi.listWorkspaces();
1757
+ const match = workspaces.find(
1758
+ (workspace) => workspaceIdFrom(workspace) === workspaceId
1759
+ );
1760
+ if (match?.name) workspaceName = match.name;
1761
+ } catch {
1762
+ }
1763
+ }
1764
+ const result = await services.tenantsApi.listTenants({
1765
+ workspaceId,
1766
+ limit: params.limit,
1767
+ searchValue: params.searchValue,
1768
+ datasourceId: params.datasourceId,
1769
+ datamartId: params.datamartId
1770
+ });
1771
+ const { tenants, clientColumnType, errorMessage } = normalizeTenantList(result);
1772
+ if (errorMessage) {
1773
+ return toolError(`Failed to list tenants: ${errorMessage}`);
1774
+ }
1775
+ if (tenants.length === 0) {
1776
+ return toolSuccess(
1777
+ `No tenants found for workspace "${workspaceName || workspaceId}".`
1778
+ );
1779
+ }
1780
+ return toolSuccess(
1781
+ JSON.stringify(
1782
+ {
1783
+ count: tenants.length,
1784
+ source: "tenancy_database",
1785
+ workspace: {
1786
+ id: workspaceId,
1787
+ name: workspaceName || null
1788
+ },
1789
+ clientColumnType: clientColumnType || "STRING",
1790
+ tenants: tenants.map((tenant) => {
1791
+ const clientId = isNonEmptyString4(tenant.value) ? tenant.value : null;
1792
+ const label = isNonEmptyString4(tenant.label) ? tenant.label : clientId;
1793
+ return {
1794
+ clientId,
1795
+ label
1796
+ };
1797
+ })
1798
+ },
1799
+ null,
1800
+ 2
1801
+ )
1802
+ );
1803
+ } catch (err) {
1804
+ if (err instanceof DatabrainApiError) return err.toToolResponse();
1805
+ return toolError(`Unexpected error: ${err.message}`);
1806
+ }
1807
+ }
1808
+ );
1809
+ }
1810
+
1811
+ // src/tools/data-app/create-data-app.ts
1812
+ import { z as z4 } from "zod";
1577
1813
  function registerCreateDataApp(server, client) {
1578
1814
  server.tool(
1579
1815
  "create_data_app",
1580
1816
  "Create a new data app.",
1581
1817
  {
1582
- name: z3.string().describe("Data app name"),
1583
- workspaceName: z3.string().optional().describe("Optional workspace name if required by backend"),
1584
- body: z3.record(z3.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/")
1818
+ name: z4.string().describe("Data app name"),
1819
+ workspaceName: z4.string().optional().describe("Optional workspace name if required by backend"),
1820
+ body: z4.record(z4.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/")
1585
1821
  },
1586
1822
  WRITE_CREATE,
1587
1823
  async (params) => {
@@ -1615,7 +1851,7 @@ function registerCreateDataApp(server, client) {
1615
1851
  }
1616
1852
 
1617
1853
  // src/tools/data-app/list-api-tokens.ts
1618
- import { z as z4 } from "zod";
1854
+ import { z as z5 } from "zod";
1619
1855
 
1620
1856
  // src/utils/resolve-resource.ts
1621
1857
  function resolveResource(options) {
@@ -1684,7 +1920,7 @@ function registerListApiTokens(server, client) {
1684
1920
  "list_api_tokens",
1685
1921
  "List API tokens for a data app. Omit dataAppName to auto-resolve.",
1686
1922
  {
1687
- dataAppName: z4.string().optional().describe("Data app name. Omit to list available data apps.")
1923
+ dataAppName: z5.string().optional().describe("Data app name. Omit to list available data apps.")
1688
1924
  },
1689
1925
  READ_ONLY,
1690
1926
  async (params) => {
@@ -1729,16 +1965,16 @@ function registerListApiTokens(server, client) {
1729
1965
  }
1730
1966
 
1731
1967
  // src/tools/data-app/create-api-token.ts
1732
- import { z as z5 } from "zod";
1968
+ import { z as z6 } from "zod";
1733
1969
  function registerCreateApiToken(server, client) {
1734
1970
  const services = createServiceBundle(client);
1735
1971
  server.tool(
1736
1972
  "create_api_token",
1737
1973
  "Create an API token for a data app and activate it for this session. Omit dataAppName to auto-resolve.",
1738
1974
  {
1739
- dataAppName: z5.string().optional().describe("Data app name. Omit to list available data apps."),
1740
- name: z5.string().optional().describe("Optional token name"),
1741
- body: z5.record(z5.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/api-tokens")
1975
+ dataAppName: z6.string().optional().describe("Data app name. Omit to list available data apps."),
1976
+ name: z6.string().optional().describe("Optional token name"),
1977
+ body: z6.record(z6.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/api-tokens")
1742
1978
  },
1743
1979
  WRITE_CREATE,
1744
1980
  async (params) => {
@@ -1790,16 +2026,16 @@ Save this in backend secrets immediately. This value may not be returned again.`
1790
2026
  }
1791
2027
 
1792
2028
  // src/tools/data-app/rotate-api-token.ts
1793
- import { z as z6 } from "zod";
2029
+ import { z as z7 } from "zod";
1794
2030
  function registerRotateApiToken(server, client) {
1795
2031
  const services = createServiceBundle(client);
1796
2032
  server.tool(
1797
2033
  "rotate_api_token",
1798
2034
  "Rotate (invalidate and replace) an API token. Previous token becomes stale.",
1799
2035
  {
1800
- dataAppName: z6.string().optional().describe("Data app name. Omit to list available data apps."),
1801
- tokenId: z6.string().optional().describe("Optional token ID to rotate"),
1802
- body: z6.record(z6.unknown()).optional().describe("Raw API body passthrough for rotate endpoint")
2036
+ dataAppName: z7.string().optional().describe("Data app name. Omit to list available data apps."),
2037
+ tokenId: z7.string().optional().describe("Optional token ID to rotate"),
2038
+ body: z7.record(z7.unknown()).optional().describe("Raw API body passthrough for rotate endpoint")
1803
2039
  },
1804
2040
  DESTRUCTIVE,
1805
2041
  async (params) => {
@@ -1918,14 +2154,14 @@ function registerListDatasources(server, client) {
1918
2154
  }
1919
2155
 
1920
2156
  // src/tools/infrastructure/discover-datasource-schema.ts
1921
- import { z as z7 } from "zod";
2157
+ import { z as z8 } from "zod";
1922
2158
  function registerDiscoverDatasourceSchema(server, client) {
1923
2159
  const services = createServiceBundle(client);
1924
2160
  server.tool(
1925
2161
  "discover_datasource_schema",
1926
2162
  "Inspect tables and columns in a datasource. Omit datasourceName to list available datasources.",
1927
2163
  {
1928
- datasourceName: z7.string().optional().describe(
2164
+ datasourceName: z8.string().optional().describe(
1929
2165
  "Name of the datasource to inspect. Omit to list available datasources."
1930
2166
  )
1931
2167
  },
@@ -2008,13 +2244,13 @@ Fix the connection or add a new database in the **Databrain UI**.`
2008
2244
  }
2009
2245
 
2010
2246
  // src/tools/infrastructure/list-datamarts.ts
2011
- import { z as z8 } from "zod";
2247
+ import { z as z9 } from "zod";
2012
2248
  function registerListDatamarts(server, client) {
2013
2249
  server.tool(
2014
2250
  "list_datamarts",
2015
2251
  "List datamarts with table counts and semantic layer scores.",
2016
2252
  {
2017
- dataAppName: z8.string().optional().describe("Filter by data app name")
2253
+ dataAppName: z9.string().optional().describe("Filter by data app name")
2018
2254
  },
2019
2255
  READ_ONLY,
2020
2256
  async (params) => {
@@ -2103,7 +2339,7 @@ function registerListWorkspaces(server, client) {
2103
2339
  }
2104
2340
 
2105
2341
  // src/tools/infrastructure/create-datamart.ts
2106
- import { z as z9 } from "zod";
2342
+ import { z as z10 } from "zod";
2107
2343
 
2108
2344
  // src/tools/widgets/production-guards.ts
2109
2345
  var APPLY_TO_PRODUCTION_CONFIRMATION = "APPLY_TO_PRODUCTION";
@@ -2121,29 +2357,29 @@ function genericProductionSafetyNote() {
2121
2357
  }
2122
2358
 
2123
2359
  // src/tools/infrastructure/create-datamart.ts
2124
- var columnSchema = z9.object({
2125
- name: z9.string(),
2126
- alias: z9.string().optional(),
2127
- label: z9.string().optional(),
2128
- isHide: z9.boolean().optional(),
2129
- isCustomColumn: z9.boolean().optional(),
2130
- sql: z9.string().optional()
2360
+ var columnSchema = z10.object({
2361
+ name: z10.string(),
2362
+ alias: z10.string().optional(),
2363
+ label: z10.string().optional(),
2364
+ isHide: z10.boolean().optional(),
2365
+ isCustomColumn: z10.boolean().optional(),
2366
+ sql: z10.string().optional()
2131
2367
  });
2132
- var tableSchema = z9.object({
2133
- name: z9.string(),
2134
- schemaName: z9.string().optional(),
2135
- clientColumn: z9.string().optional(),
2136
- label: z9.string().optional(),
2137
- columns: z9.array(columnSchema)
2368
+ var tableSchema = z10.object({
2369
+ name: z10.string(),
2370
+ schemaName: z10.string().optional(),
2371
+ clientColumn: z10.string().optional(),
2372
+ label: z10.string().optional(),
2373
+ columns: z10.array(columnSchema)
2138
2374
  });
2139
- var relationshipSchema = z9.object({
2140
- parentTableName: z9.string(),
2141
- parentColumnName: z9.string(),
2142
- childTableName: z9.string(),
2143
- childColumnName: z9.string(),
2144
- cardinality: z9.enum(["OneToOne", "OneToMany", "ManyToOne", "ManyToMany"]),
2145
- join: z9.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN"]),
2146
- relationshipName: z9.string()
2375
+ var relationshipSchema = z10.object({
2376
+ parentTableName: z10.string(),
2377
+ parentColumnName: z10.string(),
2378
+ childTableName: z10.string(),
2379
+ childColumnName: z10.string(),
2380
+ cardinality: z10.enum(["OneToOne", "OneToMany", "ManyToOne", "ManyToMany"]),
2381
+ join: z10.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN"]),
2382
+ relationshipName: z10.string()
2147
2383
  });
2148
2384
  function datamartSuccessMessage(name, result) {
2149
2385
  return `Datamart "${name}" created successfully (ID: ${result.id || result._id || "UNKNOWN"}).
@@ -2164,21 +2400,21 @@ function registerCreateDatamart(server, client) {
2164
2400
  "create_datamart",
2165
2401
  "Create a production datamart from selected tables and relationships. Defaults to dry-run; apply requires explicit confirmation.",
2166
2402
  {
2167
- mode: z9.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run returns the plan only; apply creates the datamart"),
2168
- confirm: z9.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
2169
- name: z9.string().describe("Unique datamart name"),
2170
- datasourceName: z9.string().optional().describe("Name of the datasource to use. Omit to list available datasources."),
2171
- tableList: z9.array(tableSchema).optional().describe("Array of tables with their columns"),
2172
- relationships: z9.array(relationshipSchema).optional().describe("Join definitions between tables"),
2173
- tenancySettings: z9.object({
2174
- tenancyLevel: z9.enum(["TABLE", "DATABASE"]).optional(),
2175
- clientColumnType: z9.string().optional(),
2176
- schemaName: z9.string().optional(),
2177
- tableName: z9.string().optional(),
2178
- tableClientNameColumn: z9.string().optional(),
2179
- tablePrimaryKeyColumn: z9.string().optional()
2403
+ mode: z10.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run returns the plan only; apply creates the datamart"),
2404
+ confirm: z10.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
2405
+ name: z10.string().describe("Unique datamart name"),
2406
+ datasourceName: z10.string().optional().describe("Name of the datasource to use. Omit to list available datasources."),
2407
+ tableList: z10.array(tableSchema).optional().describe("Array of tables with their columns"),
2408
+ relationships: z10.array(relationshipSchema).optional().describe("Join definitions between tables"),
2409
+ tenancySettings: z10.object({
2410
+ tenancyLevel: z10.enum(["TABLE", "DATABASE"]).optional(),
2411
+ clientColumnType: z10.string().optional(),
2412
+ schemaName: z10.string().optional(),
2413
+ tableName: z10.string().optional(),
2414
+ tableClientNameColumn: z10.string().optional(),
2415
+ tablePrimaryKeyColumn: z10.string().optional()
2180
2416
  }).optional().describe("Multi-tenant configuration"),
2181
- body: z9.record(z9.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/datamarts")
2417
+ body: z10.record(z10.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/datamarts")
2182
2418
  },
2183
2419
  WRITE_CREATE,
2184
2420
  async (params) => {
@@ -2273,14 +2509,14 @@ function registerCreateDatamart(server, client) {
2273
2509
  }
2274
2510
 
2275
2511
  // src/tools/infrastructure/sync-datasource.ts
2276
- import { z as z10 } from "zod";
2512
+ import { z as z11 } from "zod";
2277
2513
  function registerSyncDatasource(server, client) {
2278
2514
  const datasourcesApi = new DatasourcesApi(client);
2279
2515
  server.tool(
2280
2516
  "sync_datasource",
2281
2517
  "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.",
2282
2518
  {
2283
- datasourceName: z10.string().optional().describe("Name of the datasource to sync. Omit to list available datasources.")
2519
+ datasourceName: z11.string().optional().describe("Name of the datasource to sync. Omit to list available datasources.")
2284
2520
  },
2285
2521
  WRITE_IDEMPOTENT,
2286
2522
  async (params) => {
@@ -2329,7 +2565,7 @@ The schema metadata will be refreshed. You can now use \`discover_datasource_sch
2329
2565
  }
2330
2566
 
2331
2567
  // src/tools/embed/create-embed.ts
2332
- import { z as z11 } from "zod";
2568
+ import { z as z12 } from "zod";
2333
2569
  var DEFAULT_ACCESS_SETTINGS2 = {
2334
2570
  isAllowMetricCreation: false,
2335
2571
  isAllowMetricDeletion: false,
@@ -2342,18 +2578,18 @@ var DEFAULT_ACCESS_SETTINGS2 = {
2342
2578
  isAllowCreateDashboardView: false,
2343
2579
  metricCreationMode: "DRAG_DROP"
2344
2580
  };
2345
- var accessSettingsSchema = z11.object({
2346
- datamartName: z11.string().describe("Name of the datamart (required)"),
2347
- isAllowMetricCreation: z11.boolean().optional().describe("Allow end-users to create metrics"),
2348
- metricCreationMode: z11.enum(["DRAG_DROP", "CHAT"]).optional().describe("Metric creation mode"),
2349
- isAllowMetricDeletion: z11.boolean().optional().describe("Allow metric deletion"),
2350
- isAllowMetricUpdate: z11.boolean().optional().describe("Allow metric editing"),
2351
- isAllowMetricLayoutChange: z11.boolean().optional().describe("Allow layout changes"),
2352
- isAllowManageMetrics: z11.boolean().optional().describe("Show manage metrics UI"),
2353
- isAllowUnderlyingData: z11.boolean().optional().describe("Allow viewing underlying data"),
2354
- isAllowAiPilot: z11.boolean().optional().describe("Enable AI pilot features"),
2355
- isAllowEmailReports: z11.boolean().optional().describe("Allow email report scheduling"),
2356
- isAllowCreateDashboardView: z11.boolean().optional().describe("Allow creating dashboard views")
2581
+ var accessSettingsSchema = z12.object({
2582
+ datamartName: z12.string().describe("Name of the datamart (required)"),
2583
+ isAllowMetricCreation: z12.boolean().optional().describe("Allow end-users to create metrics"),
2584
+ metricCreationMode: z12.enum(["DRAG_DROP", "CHAT"]).optional().describe("Metric creation mode"),
2585
+ isAllowMetricDeletion: z12.boolean().optional().describe("Allow metric deletion"),
2586
+ isAllowMetricUpdate: z12.boolean().optional().describe("Allow metric editing"),
2587
+ isAllowMetricLayoutChange: z12.boolean().optional().describe("Allow layout changes"),
2588
+ isAllowManageMetrics: z12.boolean().optional().describe("Show manage metrics UI"),
2589
+ isAllowUnderlyingData: z12.boolean().optional().describe("Allow viewing underlying data"),
2590
+ isAllowAiPilot: z12.boolean().optional().describe("Enable AI pilot features"),
2591
+ isAllowEmailReports: z12.boolean().optional().describe("Allow email report scheduling"),
2592
+ isAllowCreateDashboardView: z12.boolean().optional().describe("Allow creating dashboard views")
2357
2593
  }).describe("Embed access settings. Only datamartName is required \u2014 all flags default to false.");
2358
2594
  function registerCreateEmbed(server, client) {
2359
2595
  const services = createServiceBundle(client);
@@ -2361,23 +2597,23 @@ function registerCreateEmbed(server, client) {
2361
2597
  "create_embed",
2362
2598
  "Create an embed. Provide dashboardId, workspaceName, accessSettings.datamartName. Add clientId for multi-tenant per-client dashboards. All isAllow* flags default to false.",
2363
2599
  {
2364
- body: z11.record(z11.unknown()).optional().describe(
2600
+ body: z12.record(z12.unknown()).optional().describe(
2365
2601
  "Raw API body passthrough. If provided, all other fields are ignored."
2366
2602
  ),
2367
- dashboardId: z11.string().optional().describe("Dashboard identifier for embed creation"),
2368
- workspaceName: z11.string().optional().describe("Workspace name. Omit to auto-resolve if only one exists."),
2369
- embedType: z11.enum(["dashboard", "metric"]).optional().describe("Embed type"),
2370
- metricId: z11.string().optional().describe("Required for metric embed type"),
2371
- name: z11.string().optional().describe("Human-readable embed config name"),
2603
+ dashboardId: z12.string().optional().describe("Dashboard identifier for embed creation"),
2604
+ workspaceName: z12.string().optional().describe("Workspace name. Omit to auto-resolve if only one exists."),
2605
+ embedType: z12.enum(["dashboard", "metric"]).optional().describe("Embed type"),
2606
+ metricId: z12.string().optional().describe("Required for metric embed type"),
2607
+ name: z12.string().optional().describe("Human-readable embed config name"),
2372
2608
  accessSettings: accessSettingsSchema.optional(),
2373
2609
  // Multi-tenant (dashboard embed) params
2374
- clientId: z11.string().optional().describe(
2610
+ clientId: z12.string().optional().describe(
2375
2611
  "Client identifier for multi-tenant isolation. When provided, creates a dashboard embed (each client gets their own dashboard copy)."
2376
2612
  ),
2377
- templateDashboardId: z11.string().optional().describe("Template dashboard ID to clone metrics from"),
2378
- isImportMetrics: z11.boolean().optional().describe("Import metrics from template dashboard"),
2379
- metricImportMode: z11.enum(["COPY", "LINK"]).optional().describe("COPY duplicates metrics, LINK shares them"),
2380
- isRenameDashboard: z11.boolean().optional().describe("Rename the dashboard for this client")
2613
+ templateDashboardId: z12.string().optional().describe("Template dashboard ID to clone metrics from"),
2614
+ isImportMetrics: z12.boolean().optional().describe("Import metrics from template dashboard"),
2615
+ metricImportMode: z12.enum(["COPY", "LINK"]).optional().describe("COPY duplicates metrics, LINK shares them"),
2616
+ isRenameDashboard: z12.boolean().optional().describe("Rename the dashboard for this client")
2381
2617
  },
2382
2618
  WRITE_CREATE,
2383
2619
  async (params) => {
@@ -2479,14 +2715,14 @@ Next: run \`get_embed_details\` to verify the embed configuration.`
2479
2715
  }
2480
2716
 
2481
2717
  // src/tools/embed/list-embeds.ts
2482
- import { z as z12 } from "zod";
2718
+ import { z as z13 } from "zod";
2483
2719
  function registerListEmbeds(server, client) {
2484
2720
  const services = createServiceBundle(client);
2485
2721
  server.tool(
2486
2722
  "list_embeds",
2487
- "List embed configurations. Requires an API token (use create_api_token first). Response includes `distinctClientIds` \u2014 a deduped list of end-customer client IDs that have multi-tenant dashboard embeds, useful for discovering which customers exist when only the workspace operator persona knows them.",
2723
+ "List embed configurations. Requires an API token (use create_api_token first). Use `list_tenants` for authoritative client discovery from the tenancy database.",
2488
2724
  {
2489
- dataAppName: z12.string().optional().describe("Filter by data app name")
2725
+ dataAppName: z13.string().optional().describe("Filter by data app name")
2490
2726
  },
2491
2727
  READ_ONLY,
2492
2728
  async (params) => {
@@ -2499,7 +2735,15 @@ function registerListEmbeds(server, client) {
2499
2735
  }
2500
2736
  const embeds = result.map((e) => {
2501
2737
  const n = services.discoveryService.normalizeEmbed(e);
2502
- const clientId = typeof e.clientId === "string" && e.clientId.trim().length > 0 ? e.clientId : null;
2738
+ const externalDashboard = e.externalDashboard && typeof e.externalDashboard === "object" ? e.externalDashboard : {};
2739
+ let clientId = null;
2740
+ if (typeof e.clientId === "string" && e.clientId.trim().length > 0) {
2741
+ clientId = e.clientId;
2742
+ } else if (typeof e.createdBy === "string" && e.createdBy.trim().length > 0) {
2743
+ clientId = e.createdBy;
2744
+ } else if (typeof externalDashboard.createdBy === "string" && externalDashboard.createdBy.trim().length > 0) {
2745
+ clientId = externalDashboard.createdBy;
2746
+ }
2503
2747
  return {
2504
2748
  embedId: n.embedId || null,
2505
2749
  internalId: !n.embedId && n.internalId ? n.internalId : void 0,
@@ -2529,14 +2773,14 @@ function registerListEmbeds(server, client) {
2529
2773
  }
2530
2774
 
2531
2775
  // src/tools/embed/get-embed-details.ts
2532
- import { z as z13 } from "zod";
2776
+ import { z as z14 } from "zod";
2533
2777
  function registerGetEmbedDetails(server, client) {
2534
2778
  const services = createServiceBundle(client);
2535
2779
  server.tool(
2536
2780
  "get_embed_details",
2537
2781
  "Get full embed configuration including access settings and theme.",
2538
2782
  {
2539
- embedId: z13.string().describe("ID of the embed")
2783
+ embedId: z14.string().describe("ID of the embed")
2540
2784
  },
2541
2785
  READ_ONLY,
2542
2786
  async (params) => {
@@ -2552,7 +2796,7 @@ function registerGetEmbedDetails(server, client) {
2552
2796
  }
2553
2797
 
2554
2798
  // src/tools/embed/update-embed.ts
2555
- import { z as z14 } from "zod";
2799
+ import { z as z15 } from "zod";
2556
2800
 
2557
2801
  // src/tools/embed/helpers.ts
2558
2802
  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.";
@@ -2590,15 +2834,15 @@ function registerUpdateEmbed(server, client) {
2590
2834
  "update_embed",
2591
2835
  "Update embed access settings, permissions, theme, or name.",
2592
2836
  {
2593
- embedId: z14.string().describe("ID of the embed to update"),
2594
- body: z14.record(z14.unknown()).optional().describe(
2837
+ embedId: z15.string().describe("ID of the embed to update"),
2838
+ body: z15.record(z15.unknown()).optional().describe(
2595
2839
  "Raw API body for PUT /api/v2/data-app/embeds. If provided, it is sent directly (passthrough mode)."
2596
2840
  ),
2597
- name: z14.string().optional().describe("New name for the embed"),
2598
- accessSettings: z14.record(z14.unknown()).optional().describe(
2841
+ name: z15.string().optional().describe("New name for the embed"),
2842
+ accessSettings: z15.record(z15.unknown()).optional().describe(
2599
2843
  "Partial update of access settings. See `get_embed_details` for current settings."
2600
2844
  ),
2601
- theme: z14.record(z14.unknown()).optional().describe(
2845
+ theme: z15.record(z15.unknown()).optional().describe(
2602
2846
  "Theme configuration object. See databrain://theme-reference for the full schema."
2603
2847
  )
2604
2848
  },
@@ -2644,7 +2888,7 @@ function registerUpdateEmbed(server, client) {
2644
2888
  }
2645
2889
 
2646
2890
  // src/tools/embed/customize-theme.ts
2647
- import { z as z15 } from "zod";
2891
+ import { z as z16 } from "zod";
2648
2892
  var THEME_PRESETS = {
2649
2893
  light: {
2650
2894
  general: {
@@ -2778,7 +3022,7 @@ var ACCESS_PRESETS = {
2778
3022
  metricCreationMode: "CHAT"
2779
3023
  }
2780
3024
  };
2781
- var fontFamilyEnum = z15.enum([
3025
+ var fontFamilyEnum = z16.enum([
2782
3026
  "Inter",
2783
3027
  "Roboto",
2784
3028
  "Open Sans",
@@ -2790,17 +3034,17 @@ var fontFamilyEnum = z15.enum([
2790
3034
  "Nunito",
2791
3035
  "system-ui"
2792
3036
  ]).describe("Font family");
2793
- var tooltipFontSchema = z15.object({
2794
- fontFamily: z15.string().optional(),
2795
- fontSize: z15.number().optional(),
2796
- fontWeight: z15.number().optional(),
2797
- fontColor: z15.string().optional()
3037
+ var tooltipFontSchema = z16.object({
3038
+ fontFamily: z16.string().optional(),
3039
+ fontSize: z16.number().optional(),
3040
+ fontWeight: z16.number().optional(),
3041
+ fontColor: z16.string().optional()
2798
3042
  }).optional();
2799
- var fontConfigInputSchema = z15.object({
2800
- fontSize: z15.number().optional(),
2801
- fontFamily: z15.string().optional(),
2802
- fontWeight: z15.number().optional(),
2803
- color: z15.string().optional()
3043
+ var fontConfigInputSchema = z16.object({
3044
+ fontSize: z16.number().optional(),
3045
+ fontFamily: z16.string().optional(),
3046
+ fontWeight: z16.number().optional(),
3047
+ color: z16.string().optional()
2804
3048
  }).optional();
2805
3049
  function mapToFrontendFontConfig(input) {
2806
3050
  if (!input) return void 0;
@@ -2811,23 +3055,23 @@ function mapToFrontendFontConfig(input) {
2811
3055
  ...input.color && { color: input.color }
2812
3056
  };
2813
3057
  }
2814
- var axisSchema = z15.object({
2815
- hideAxisLines: z15.boolean().optional().describe("Hide axis lines"),
2816
- hideSplitLines: z15.boolean().optional().describe("Hide grid/split lines"),
2817
- hideAxisLabels: z15.boolean().optional().describe("Hide axis labels"),
2818
- hideAxisTicks: z15.boolean().optional().describe("Hide axis tick marks"),
2819
- axisName: z15.string().optional().describe("Axis title text"),
2820
- axisNameOffset: z15.number().optional().describe("Axis title offset from axis"),
2821
- axisLabelMargin: z15.number().optional().describe("Margin between axis labels and chart area in pixels"),
2822
- fontSize: z15.number().optional().describe("Axis label font size"),
2823
- fontFamily: z15.string().optional().describe("Axis label font family"),
2824
- fontWeight: z15.number().optional().describe("Axis label font weight"),
2825
- color: z15.string().optional().describe("Axis label color (hex)"),
2826
- axisColor: z15.string().optional().describe("Axis line color (hex)"),
2827
- axisNameFontConfig: z15.object({
2828
- fontFamily: z15.string().optional(),
2829
- fontSize: z15.number().optional(),
2830
- fontWeight: z15.number().optional()
3058
+ var axisSchema = z16.object({
3059
+ hideAxisLines: z16.boolean().optional().describe("Hide axis lines"),
3060
+ hideSplitLines: z16.boolean().optional().describe("Hide grid/split lines"),
3061
+ hideAxisLabels: z16.boolean().optional().describe("Hide axis labels"),
3062
+ hideAxisTicks: z16.boolean().optional().describe("Hide axis tick marks"),
3063
+ axisName: z16.string().optional().describe("Axis title text"),
3064
+ axisNameOffset: z16.number().optional().describe("Axis title offset from axis"),
3065
+ axisLabelMargin: z16.number().optional().describe("Margin between axis labels and chart area in pixels"),
3066
+ fontSize: z16.number().optional().describe("Axis label font size"),
3067
+ fontFamily: z16.string().optional().describe("Axis label font family"),
3068
+ fontWeight: z16.number().optional().describe("Axis label font weight"),
3069
+ color: z16.string().optional().describe("Axis label color (hex)"),
3070
+ axisColor: z16.string().optional().describe("Axis line color (hex)"),
3071
+ axisNameFontConfig: z16.object({
3072
+ fontFamily: z16.string().optional(),
3073
+ fontSize: z16.number().optional(),
3074
+ fontWeight: z16.number().optional()
2831
3075
  }).optional().describe("Axis title font configuration")
2832
3076
  }).optional();
2833
3077
  function buildAdminTheme(p, presetBase) {
@@ -2983,94 +3227,94 @@ function registerCustomizeTheme(server, client) {
2983
3227
  "customize_embed_theme",
2984
3228
  `Theme an embed: presets (${Object.keys(THEME_PRESETS).join("/")}), colors, fonts, cards, chart appearance, responsive layout, or access presets (${Object.keys(ACCESS_PRESETS).join("/")}).`,
2985
3229
  {
2986
- embedId: z15.string().describe("ID of the embed to theme"),
2987
- body: z15.record(z15.unknown()).optional().describe(
3230
+ embedId: z16.string().describe("ID of the embed to theme"),
3231
+ body: z16.record(z16.unknown()).optional().describe(
2988
3232
  "Raw API body for PUT /api/v2/data-app/embeds. If provided, sent directly and all convenience fields are ignored."
2989
3233
  ),
2990
3234
  // --- Presets ---
2991
- themePreset: z15.enum(["light", "dark", "corporate", "minimal"]).optional().describe("Apply a theme preset as the base. Granular params below override preset values."),
2992
- accessPreset: z15.enum(["view-only", "power-user", "export-only", "ai-enabled"]).optional().describe("Apply an access settings preset"),
3235
+ themePreset: z16.enum(["light", "dark", "corporate", "minimal"]).optional().describe("Apply a theme preset as the base. Granular params below override preset values."),
3236
+ accessPreset: z16.enum(["view-only", "power-user", "export-only", "ai-enabled"]).optional().describe("Apply an access settings preset"),
2993
3237
  // --- General ---
2994
3238
  fontFamily: fontFamilyEnum.optional().describe("Global font family"),
2995
- datePickerFormat: z15.string().optional().describe("Date picker display format"),
3239
+ datePickerFormat: z16.string().optional().describe("Date picker display format"),
2996
3240
  // --- Colors ---
2997
- primaryColor: z15.string().optional().describe("Primary accent color (hex)"),
2998
- backgroundColor: z15.string().optional().describe("Overall background color (hex)"),
2999
- textColor: z15.string().optional().describe("Default text color (hex)"),
3000
- ctaColor: z15.string().optional().describe("Call-to-action button color (hex)"),
3001
- ctaTextColor: z15.string().optional().describe("CTA button text color (hex)"),
3241
+ primaryColor: z16.string().optional().describe("Primary accent color (hex)"),
3242
+ backgroundColor: z16.string().optional().describe("Overall background color (hex)"),
3243
+ textColor: z16.string().optional().describe("Default text color (hex)"),
3244
+ ctaColor: z16.string().optional().describe("Call-to-action button color (hex)"),
3245
+ ctaTextColor: z16.string().optional().describe("CTA button text color (hex)"),
3002
3246
  // --- Dashboard ---
3003
- dashboardBackgroundColor: z15.string().optional().describe("Dashboard container background (hex)"),
3004
- selectBoxSize: z15.enum(["small", "medium", "large"]).optional().describe("Filter select box size"),
3005
- selectBoxVariant: z15.enum(["floating", "outlined", "standard"]).optional().describe("Filter select box style"),
3006
- selectBoxBorderRadius: z15.string().optional().describe('Select box border radius (e.g. "20px")'),
3007
- selectBoxTextColor: z15.string().optional().describe("Select box text color (hex)"),
3008
- metricCardColor: z15.string().optional().describe("Metric card background color (hex)"),
3247
+ dashboardBackgroundColor: z16.string().optional().describe("Dashboard container background (hex)"),
3248
+ selectBoxSize: z16.enum(["small", "medium", "large"]).optional().describe("Filter select box size"),
3249
+ selectBoxVariant: z16.enum(["floating", "outlined", "standard"]).optional().describe("Filter select box style"),
3250
+ selectBoxBorderRadius: z16.string().optional().describe('Select box border radius (e.g. "20px")'),
3251
+ selectBoxTextColor: z16.string().optional().describe("Select box text color (hex)"),
3252
+ metricCardColor: z16.string().optional().describe("Metric card background color (hex)"),
3009
3253
  // --- Card Title ---
3010
- cardTitleFontSize: z15.string().optional().describe('Card title font size (e.g. "16px")'),
3011
- cardTitleFontWeight: z15.string().optional().describe('Card title font weight (e.g. "600")'),
3012
- cardTitleColor: z15.string().optional().describe("Card title text color (hex)"),
3254
+ cardTitleFontSize: z16.string().optional().describe('Card title font size (e.g. "16px")'),
3255
+ cardTitleFontWeight: z16.string().optional().describe('Card title font weight (e.g. "600")'),
3256
+ cardTitleColor: z16.string().optional().describe("Card title text color (hex)"),
3013
3257
  // --- Card Description ---
3014
- cardDescriptionFontSize: z15.string().optional().describe("Card description font size"),
3015
- cardDescriptionFontWeight: z15.string().optional().describe("Card description font weight"),
3016
- cardDescriptionColor: z15.string().optional().describe("Card description text color (hex)"),
3258
+ cardDescriptionFontSize: z16.string().optional().describe("Card description font size"),
3259
+ cardDescriptionFontWeight: z16.string().optional().describe("Card description font weight"),
3260
+ cardDescriptionColor: z16.string().optional().describe("Card description text color (hex)"),
3017
3261
  // --- Card Customization ---
3018
- cardPadding: z15.string().optional().describe("Card internal padding"),
3019
- cardBorderRadius: z15.string().optional().describe("Card border radius"),
3020
- cardShadow: z15.string().optional().describe("Card box-shadow CSS value"),
3021
- disableShadowOnHover: z15.boolean().optional().describe("Disable shadow on card hover"),
3022
- disableCardStroke: z15.boolean().optional().describe("Remove card border stroke"),
3023
- cardStrokeColor: z15.string().optional().describe("Card border stroke color (hex)"),
3262
+ cardPadding: z16.string().optional().describe("Card internal padding"),
3263
+ cardBorderRadius: z16.string().optional().describe("Card border radius"),
3264
+ cardShadow: z16.string().optional().describe("Card box-shadow CSS value"),
3265
+ disableShadowOnHover: z16.boolean().optional().describe("Disable shadow on card hover"),
3266
+ disableCardStroke: z16.boolean().optional().describe("Remove card border stroke"),
3267
+ cardStrokeColor: z16.string().optional().describe("Card border stroke color (hex)"),
3024
3268
  // --- Chart Colors (admin theme level) ---
3025
- chartFontFamily: z15.string().optional().describe("Font family for chart text"),
3026
- chartFontColor: z15.string().optional().describe("Font color for chart text (hex)"),
3027
- chartColors: z15.array(z15.string()).optional().describe("Array of chart series colors (hex)"),
3028
- chartPaletteName: z15.string().optional().describe("Name for a custom palette"),
3029
- chartPaletteColors: z15.array(z15.string()).optional().describe("9 colors for a custom palette"),
3269
+ chartFontFamily: z16.string().optional().describe("Font family for chart text"),
3270
+ chartFontColor: z16.string().optional().describe("Font color for chart text (hex)"),
3271
+ chartColors: z16.array(z16.string()).optional().describe("Array of chart series colors (hex)"),
3272
+ chartPaletteName: z16.string().optional().describe("Name for a custom palette"),
3273
+ chartPaletteColors: z16.array(z16.string()).optional().describe("9 colors for a custom palette"),
3030
3274
  // --- Chart Tooltip (admin theme level) ---
3031
3275
  tooltipHeader: tooltipFontSchema.describe("Tooltip header font config"),
3032
3276
  tooltipLabel: tooltipFontSchema.describe("Tooltip label font config"),
3033
3277
  tooltipValue: tooltipFontSchema.describe("Tooltip value font config"),
3034
3278
  // --- Component Theme ---
3035
- buttonPrimaryColor: z15.string().optional().describe("Primary button background (hex)"),
3036
- buttonPrimaryTextColor: z15.string().optional().describe("Primary button text (hex)"),
3037
- buttonSecondaryColor: z15.string().optional().describe("Secondary button background (hex)"),
3038
- buttonSecondaryTextColor: z15.string().optional().describe("Secondary button text (hex)"),
3039
- checkboxCheckedColor: z15.string().optional().describe("Checked checkbox color (hex)"),
3040
- checkboxUncheckedColor: z15.string().optional().describe("Unchecked checkbox color (hex)"),
3041
- switchEnabledColor: z15.string().optional().describe("Enabled switch color (hex)"),
3042
- switchDisabledColor: z15.string().optional().describe("Disabled switch color (hex)"),
3043
- breadcrumbFontFamily: z15.string().optional().describe("Drill breadcrumb font family"),
3044
- breadcrumbFontColor: z15.string().optional().describe("Drill breadcrumb font color (hex)"),
3045
- breadcrumbActiveColor: z15.string().optional().describe("Active breadcrumb color (hex)"),
3046
- filterBadgeColor: z15.string().optional().describe("Multi-select filter badge color (hex)"),
3047
- filterBadgeTextColor: z15.string().optional().describe("Filter badge text color (hex)"),
3048
- datePickerColor: z15.string().optional().describe("Date picker accent color (hex)"),
3279
+ buttonPrimaryColor: z16.string().optional().describe("Primary button background (hex)"),
3280
+ buttonPrimaryTextColor: z16.string().optional().describe("Primary button text (hex)"),
3281
+ buttonSecondaryColor: z16.string().optional().describe("Secondary button background (hex)"),
3282
+ buttonSecondaryTextColor: z16.string().optional().describe("Secondary button text (hex)"),
3283
+ checkboxCheckedColor: z16.string().optional().describe("Checked checkbox color (hex)"),
3284
+ checkboxUncheckedColor: z16.string().optional().describe("Unchecked checkbox color (hex)"),
3285
+ switchEnabledColor: z16.string().optional().describe("Enabled switch color (hex)"),
3286
+ switchDisabledColor: z16.string().optional().describe("Disabled switch color (hex)"),
3287
+ breadcrumbFontFamily: z16.string().optional().describe("Drill breadcrumb font family"),
3288
+ breadcrumbFontColor: z16.string().optional().describe("Drill breadcrumb font color (hex)"),
3289
+ breadcrumbActiveColor: z16.string().optional().describe("Active breadcrumb color (hex)"),
3290
+ filterBadgeColor: z16.string().optional().describe("Multi-select filter badge color (hex)"),
3291
+ filterBadgeTextColor: z16.string().optional().describe("Filter badge text color (hex)"),
3292
+ datePickerColor: z16.string().optional().describe("Date picker accent color (hex)"),
3049
3293
  // --- Responsive Breakpoints ---
3050
- breakpointXl: z15.number().optional().describe("XL breakpoint width in px"),
3051
- breakpointLg: z15.number().optional().describe("LG breakpoint width in px"),
3052
- breakpointMd: z15.number().optional().describe("MD breakpoint width in px"),
3053
- breakpointSm: z15.number().optional().describe("SM breakpoint width in px"),
3054
- breakpointXs: z15.number().optional().describe("XS breakpoint width in px"),
3294
+ breakpointXl: z16.number().optional().describe("XL breakpoint width in px"),
3295
+ breakpointLg: z16.number().optional().describe("LG breakpoint width in px"),
3296
+ breakpointMd: z16.number().optional().describe("MD breakpoint width in px"),
3297
+ breakpointSm: z16.number().optional().describe("SM breakpoint width in px"),
3298
+ breakpointXs: z16.number().optional().describe("XS breakpoint width in px"),
3055
3299
  // --- Metric Layout Columns ---
3056
- layoutColsXl: z15.number().optional().describe("Grid columns at XL breakpoint"),
3057
- layoutColsLg: z15.number().optional().describe("Grid columns at LG breakpoint"),
3058
- layoutColsMd: z15.number().optional().describe("Grid columns at MD breakpoint"),
3059
- layoutColsSm: z15.number().optional().describe("Grid columns at SM breakpoint"),
3060
- layoutColsXs: z15.number().optional().describe("Grid columns at XS breakpoint"),
3061
- layoutColsXxs: z15.number().optional().describe("Grid columns at XXS breakpoint"),
3300
+ layoutColsXl: z16.number().optional().describe("Grid columns at XL breakpoint"),
3301
+ layoutColsLg: z16.number().optional().describe("Grid columns at LG breakpoint"),
3302
+ layoutColsMd: z16.number().optional().describe("Grid columns at MD breakpoint"),
3303
+ layoutColsSm: z16.number().optional().describe("Grid columns at SM breakpoint"),
3304
+ layoutColsXs: z16.number().optional().describe("Grid columns at XS breakpoint"),
3305
+ layoutColsXxs: z16.number().optional().describe("Grid columns at XXS breakpoint"),
3062
3306
  // --- Chart Appearance (detailed) ---
3063
3307
  tooltipHeaderStyle: fontConfigInputSchema.describe("Tooltip header: fontSize, fontFamily, fontWeight, color"),
3064
3308
  tooltipLabelStyle: fontConfigInputSchema.describe("Tooltip label: fontSize, fontFamily, fontWeight, color"),
3065
3309
  tooltipValueStyle: fontConfigInputSchema.describe("Tooltip value: fontSize, fontFamily, fontWeight, color"),
3066
- labelPosition: z15.enum(["hidden", "top", "left", "right", "bottom", "inside"]).optional().describe("Data label position on charts"),
3067
- radialLabelPosition: z15.enum(["outside", "inside"]).optional().describe("Label position for radial/pie charts"),
3068
- marginTop: z15.number().optional().describe("Chart top margin in pixels"),
3069
- marginLeft: z15.number().optional().describe("Chart left margin in pixels"),
3070
- marginRight: z15.number().optional().describe("Chart right margin in pixels"),
3071
- marginBottom: z15.number().optional().describe("Chart bottom margin in pixels"),
3072
- legendShow: z15.boolean().optional().describe("Show or hide chart legend"),
3073
- legendPosition: z15.enum([
3310
+ labelPosition: z16.enum(["hidden", "top", "left", "right", "bottom", "inside"]).optional().describe("Data label position on charts"),
3311
+ radialLabelPosition: z16.enum(["outside", "inside"]).optional().describe("Label position for radial/pie charts"),
3312
+ marginTop: z16.number().optional().describe("Chart top margin in pixels"),
3313
+ marginLeft: z16.number().optional().describe("Chart left margin in pixels"),
3314
+ marginRight: z16.number().optional().describe("Chart right margin in pixels"),
3315
+ marginBottom: z16.number().optional().describe("Chart bottom margin in pixels"),
3316
+ legendShow: z16.boolean().optional().describe("Show or hide chart legend"),
3317
+ legendPosition: z16.enum([
3074
3318
  "top-left",
3075
3319
  "top-center",
3076
3320
  "top-right",
@@ -3080,17 +3324,17 @@ function registerCustomizeTheme(server, client) {
3080
3324
  "bottom-center",
3081
3325
  "bottom-right"
3082
3326
  ]).optional().describe("Legend position"),
3083
- legendLayout: z15.enum(["horizontal", "vertical"]).optional().describe("Legend layout direction"),
3084
- legendShape: z15.enum(["circle", "rect", "roundRect", "triangle", "diamond", "arrow", "none"]).optional().describe("Legend icon shape"),
3085
- legendFontSize: z15.number().optional().describe("Legend font size"),
3086
- legendFontWeight: z15.number().optional().describe("Legend font weight"),
3087
- legendFontFamily: z15.string().optional().describe("Legend font family"),
3088
- legendColor: z15.string().optional().describe("Legend text color (hex)"),
3089
- legendTruncateLength: z15.number().optional().describe("Max characters before truncating legend"),
3090
- disableLegendScrolling: z15.boolean().optional().describe("Disable scrolling in legend"),
3091
- enableVariableLegendPosition: z15.boolean().optional().describe("Allow user to drag legend"),
3092
- legendTop: z15.number().optional().describe("Custom legend top offset"),
3093
- legendLeft: z15.number().optional().describe("Custom legend left offset"),
3327
+ legendLayout: z16.enum(["horizontal", "vertical"]).optional().describe("Legend layout direction"),
3328
+ legendShape: z16.enum(["circle", "rect", "roundRect", "triangle", "diamond", "arrow", "none"]).optional().describe("Legend icon shape"),
3329
+ legendFontSize: z16.number().optional().describe("Legend font size"),
3330
+ legendFontWeight: z16.number().optional().describe("Legend font weight"),
3331
+ legendFontFamily: z16.string().optional().describe("Legend font family"),
3332
+ legendColor: z16.string().optional().describe("Legend text color (hex)"),
3333
+ legendTruncateLength: z16.number().optional().describe("Max characters before truncating legend"),
3334
+ disableLegendScrolling: z16.boolean().optional().describe("Disable scrolling in legend"),
3335
+ enableVariableLegendPosition: z16.boolean().optional().describe("Allow user to drag legend"),
3336
+ legendTop: z16.number().optional().describe("Custom legend top offset"),
3337
+ legendLeft: z16.number().optional().describe("Custom legend left offset"),
3094
3338
  verticalAxis: axisSchema.describe("Vertical (Y) axis configuration"),
3095
3339
  horizontalAxis: axisSchema.describe("Horizontal (X) axis configuration")
3096
3340
  },
@@ -3165,7 +3409,7 @@ Use \`get_embed_details\` to view the full current theme configuration.` + GUEST
3165
3409
  }
3166
3410
 
3167
3411
  // src/tools/embed/get-demo-link.ts
3168
- import { z as z16 } from "zod";
3412
+ import { z as z17 } from "zod";
3169
3413
  var HOUR_MS = 60 * 60 * 1e3;
3170
3414
  var DEFAULT_EXPIRY_HOURS = 24;
3171
3415
  var MAX_EXPIRY_HOURS = 24 * 30;
@@ -3175,11 +3419,11 @@ function registerGetDemoLink(server, client) {
3175
3419
  "get_demo_link",
3176
3420
  `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.`,
3177
3421
  {
3178
- clientId: z16.string().describe("End-customer identifier to scope the preview to."),
3179
- dashboardName: z16.string().optional().describe("Dashboard name (non-technical). Preferred \u2014 pass with optional `workspaceName`."),
3180
- workspaceName: z16.string().optional().describe("Workspace name. Only needed if `dashboardName` is ambiguous."),
3181
- embedId: z16.string().optional().describe("Technical alternative to `dashboardName`."),
3182
- expiryHours: z16.number().min(1).max(MAX_EXPIRY_HOURS).optional().describe(`Token TTL in hours. Defaults to ${DEFAULT_EXPIRY_HOURS}. Max ${MAX_EXPIRY_HOURS} (30 days).`)
3422
+ clientId: z17.string().describe("End-customer identifier to scope the preview to."),
3423
+ dashboardName: z17.string().optional().describe("Dashboard name (non-technical). Preferred \u2014 pass with optional `workspaceName`."),
3424
+ workspaceName: z17.string().optional().describe("Workspace name. Only needed if `dashboardName` is ambiguous."),
3425
+ embedId: z17.string().optional().describe("Technical alternative to `dashboardName`."),
3426
+ 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).`)
3183
3427
  },
3184
3428
  WRITE_CREATE,
3185
3429
  async (params) => {
@@ -3235,70 +3479,70 @@ function registerGetDemoLink(server, client) {
3235
3479
  }
3236
3480
 
3237
3481
  // src/tools/frontend/generate-guest-token.ts
3238
- import { z as z17 } from "zod";
3482
+ import { z as z18 } from "zod";
3239
3483
  function registerGenerateGuestToken(server, client) {
3240
3484
  const services = createServiceBundle(client);
3241
3485
  server.tool(
3242
3486
  "generate_guest_token",
3243
3487
  "Generate a guest token for frontend embedding. Requires clientId. Omit dataAppName to auto-resolve.",
3244
3488
  {
3245
- clientId: z17.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
3246
- dataAppName: z17.string().optional().describe("Name of the data app. Omit to list available data apps."),
3247
- body: z17.record(z17.unknown()).optional().describe(
3489
+ clientId: z18.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
3490
+ dataAppName: z18.string().optional().describe("Name of the data app. Omit to list available data apps."),
3491
+ body: z18.record(z18.unknown()).optional().describe(
3248
3492
  "Raw API body for POST /api/v2/guest-token/create. If provided, sent directly and convenience fields are ignored."
3249
3493
  ),
3250
- params: z17.object({
3251
- allowedEmbeds: z17.array(z17.string()).optional().describe("Restrict token to specific embed IDs"),
3252
- rlsSettings: z17.array(z17.object({
3253
- metricId: z17.string().describe("Metric ID"),
3254
- values: z17.record(z17.unknown()).describe("RLS values by column name")
3494
+ params: z18.object({
3495
+ allowedEmbeds: z18.array(z18.string()).optional().describe("Restrict token to specific embed IDs"),
3496
+ rlsSettings: z18.array(z18.object({
3497
+ metricId: z18.string().describe("Metric ID"),
3498
+ values: z18.record(z18.unknown()).describe("RLS values by column name")
3255
3499
  })).optional().describe("Legacy alias used in docs/examples; forwarded as params.rlsSettings"),
3256
- appFilters: z17.array(z17.object({
3257
- metricId: z17.string().describe("Metric ID"),
3258
- values: z17.record(z17.unknown()).describe("Filter values by column name")
3500
+ appFilters: z18.array(z18.object({
3501
+ metricId: z18.string().describe("Metric ID"),
3502
+ values: z18.record(z18.unknown()).describe("Filter values by column name")
3259
3503
  })).optional().describe("Metric-level app filters"),
3260
- dashboardAppFilters: z17.array(z17.object({
3261
- dashboardId: z17.string().describe("Dashboard ID"),
3262
- values: z17.record(z17.unknown()).describe("Filter values by dashboard filter name"),
3263
- isShowOnUrl: z17.boolean().optional().describe("Whether filters are shown in URL params")
3504
+ dashboardAppFilters: z18.array(z18.object({
3505
+ dashboardId: z18.string().describe("Dashboard ID"),
3506
+ values: z18.record(z18.unknown()).describe("Filter values by dashboard filter name"),
3507
+ isShowOnUrl: z18.boolean().optional().describe("Whether filters are shown in URL params")
3264
3508
  })).optional().describe("Dashboard-level app filters"),
3265
- hideDashboardFilters: z17.array(z17.string()).optional().describe("Dashboard filter names to hide"),
3266
- userIdentifier: z17.string().optional().describe("End-user identifier for private metrics support"),
3267
- timezone: z17.string().optional().describe('IANA timezone, e.g. "America/New_York"')
3509
+ hideDashboardFilters: z18.array(z18.string()).optional().describe("Dashboard filter names to hide"),
3510
+ userIdentifier: z18.string().optional().describe("End-user identifier for private metrics support"),
3511
+ timezone: z18.string().optional().describe('IANA timezone, e.g. "America/New_York"')
3268
3512
  }).optional().describe("Advanced token params"),
3269
3513
  // Legacy convenience fields (auto-mapped into params for backward compatibility)
3270
- timezone: z17.string().optional().describe("Legacy: moved under params.timezone"),
3271
- allowedEmbeds: z17.array(z17.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
3272
- rlsSettings: z17.array(z17.object({
3273
- metricId: z17.string().describe("Metric ID"),
3274
- values: z17.record(z17.unknown()).describe("RLS values by column name")
3514
+ timezone: z18.string().optional().describe("Legacy: moved under params.timezone"),
3515
+ allowedEmbeds: z18.array(z18.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
3516
+ rlsSettings: z18.array(z18.object({
3517
+ metricId: z18.string().describe("Metric ID"),
3518
+ values: z18.record(z18.unknown()).describe("RLS values by column name")
3275
3519
  })).optional().describe("Legacy: moved under params.rlsSettings"),
3276
- appFilters: z17.array(z17.object({
3277
- metricId: z17.string().describe("Metric ID"),
3278
- values: z17.record(z17.unknown()).describe("Filter values by column name")
3520
+ appFilters: z18.array(z18.object({
3521
+ metricId: z18.string().describe("Metric ID"),
3522
+ values: z18.record(z18.unknown()).describe("Filter values by column name")
3279
3523
  })).optional().describe("Legacy: moved under params.appFilters"),
3280
- dashboardAppFilters: z17.array(z17.object({
3281
- dashboardId: z17.string().describe("Dashboard ID to apply filters to"),
3282
- values: z17.record(z17.unknown()).describe("Filter values for this dashboard"),
3283
- isShowOnUrl: z17.boolean().optional().describe("Whether filters are shown in URL params")
3524
+ dashboardAppFilters: z18.array(z18.object({
3525
+ dashboardId: z18.string().describe("Dashboard ID to apply filters to"),
3526
+ values: z18.record(z18.unknown()).describe("Filter values for this dashboard"),
3527
+ isShowOnUrl: z18.boolean().optional().describe("Whether filters are shown in URL params")
3284
3528
  })).optional().describe("Legacy: moved under params.dashboardAppFilters"),
3285
- permissions: z17.object({
3286
- isEnableArchiveMetrics: z17.boolean().optional(),
3287
- isEnableManageMetrics: z17.boolean().optional(),
3288
- isEnableCreateDashboardView: z17.boolean().optional(),
3289
- isEnableMetricUpdation: z17.boolean().optional(),
3290
- isEnableCustomizeLayout: z17.boolean().optional(),
3291
- isEnableUnderlyingData: z17.boolean().optional(),
3292
- isEnableDownloadMetrics: z17.boolean().optional(),
3293
- isShowSideBar: z17.boolean().optional(),
3294
- isShowDashboardName: z17.boolean().optional(),
3295
- isDisableMetricCreation: z17.boolean().optional()
3529
+ permissions: z18.object({
3530
+ isEnableArchiveMetrics: z18.boolean().optional(),
3531
+ isEnableManageMetrics: z18.boolean().optional(),
3532
+ isEnableCreateDashboardView: z18.boolean().optional(),
3533
+ isEnableMetricUpdation: z18.boolean().optional(),
3534
+ isEnableCustomizeLayout: z18.boolean().optional(),
3535
+ isEnableUnderlyingData: z18.boolean().optional(),
3536
+ isEnableDownloadMetrics: z18.boolean().optional(),
3537
+ isShowSideBar: z18.boolean().optional(),
3538
+ isShowDashboardName: z18.boolean().optional(),
3539
+ isDisableMetricCreation: z18.boolean().optional()
3296
3540
  }).optional().describe("Runtime permission overrides (merge with and override embed-level settings)"),
3297
- theme: z17.record(z17.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
3298
- hideDashboardFilters: z17.array(z17.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
3299
- userIdentifier: z17.string().optional().describe("Legacy: moved under params.userIdentifier"),
3300
- expiryTime: z17.number().optional().describe("Token TTL in milliseconds"),
3301
- datasourceName: z17.string().optional().describe("Datasource name for multi-datasource routing")
3541
+ theme: z18.record(z18.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
3542
+ hideDashboardFilters: z18.array(z18.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
3543
+ userIdentifier: z18.string().optional().describe("Legacy: moved under params.userIdentifier"),
3544
+ expiryTime: z18.number().optional().describe("Token TTL in milliseconds"),
3545
+ datasourceName: z18.string().optional().describe("Datasource name for multi-datasource routing")
3302
3546
  },
3303
3547
  WRITE_CREATE,
3304
3548
  async (params) => {
@@ -3351,7 +3595,7 @@ Next step: Use \`generate_embed_code\` to get copy-paste-ready frontend code.`
3351
3595
  }
3352
3596
 
3353
3597
  // src/tools/frontend/generate-embed-code.ts
3354
- import { z as z18 } from "zod";
3598
+ import { z as z19 } from "zod";
3355
3599
 
3356
3600
  // src/templates/react.ts
3357
3601
  function kebabCase(str) {
@@ -4347,22 +4591,22 @@ function registerGenerateEmbedCode(server, _client) {
4347
4591
  "generate_embed_code",
4348
4592
  "Generate framework-specific embed code (React, Next.js, Vue, Angular, Svelte, SolidJS, vanilla HTML).",
4349
4593
  {
4350
- embedId: z18.string().describe("The embed ID to generate code for"),
4351
- framework: z18.enum(FRAMEWORKS).describe("Frontend framework"),
4352
- embedType: z18.enum(["dashboard", "metric"]).optional().default("dashboard"),
4353
- options: z18.object({
4354
- enableDownloadCsv: z18.boolean().optional(),
4355
- enableEmailCsv: z18.boolean().optional(),
4356
- enableDownloadAllMetrics: z18.boolean().optional(),
4357
- enableDownloadAllPdf: z18.boolean().optional(),
4358
- disableFullscreen: z18.boolean().optional(),
4359
- isHideTablePreview: z18.boolean().optional(),
4360
- isHideChartSettings: z18.boolean().optional(),
4361
- isStickyDashboardFilters: z18.boolean().optional(),
4362
- enableMultiMetricFilters: z18.boolean().optional(),
4363
- themeName: z18.string().optional(),
4364
- customTheme: z18.record(z18.unknown()).optional(),
4365
- optionsIcon: z18.enum(["kebab-menu-vertical", "download"]).optional()
4594
+ embedId: z19.string().describe("The embed ID to generate code for"),
4595
+ framework: z19.enum(FRAMEWORKS).describe("Frontend framework"),
4596
+ embedType: z19.enum(["dashboard", "metric"]).optional().default("dashboard"),
4597
+ options: z19.object({
4598
+ enableDownloadCsv: z19.boolean().optional(),
4599
+ enableEmailCsv: z19.boolean().optional(),
4600
+ enableDownloadAllMetrics: z19.boolean().optional(),
4601
+ enableDownloadAllPdf: z19.boolean().optional(),
4602
+ disableFullscreen: z19.boolean().optional(),
4603
+ isHideTablePreview: z19.boolean().optional(),
4604
+ isHideChartSettings: z19.boolean().optional(),
4605
+ isStickyDashboardFilters: z19.boolean().optional(),
4606
+ enableMultiMetricFilters: z19.boolean().optional(),
4607
+ themeName: z19.string().optional(),
4608
+ customTheme: z19.record(z19.unknown()).optional(),
4609
+ optionsIcon: z19.enum(["kebab-menu-vertical", "download"]).optional()
4366
4610
  }).optional()
4367
4611
  },
4368
4612
  READ_ONLY,
@@ -4402,17 +4646,17 @@ ${generated.instructions}`;
4402
4646
  }
4403
4647
 
4404
4648
  // src/tools/analytics/list-dashboards.ts
4405
- import { z as z19 } from "zod";
4649
+ import { z as z20 } from "zod";
4406
4650
  function registerListDashboards(server, client) {
4407
4651
  const services = createServiceBundle(client);
4408
4652
  server.tool(
4409
4653
  "list_dashboards",
4410
4654
  "List dashboards globally or for a specific data app.",
4411
4655
  {
4412
- scope: z19.enum(["auto", "global", "data_app"]).optional().describe(
4656
+ scope: z20.enum(["auto", "global", "data_app"]).optional().describe(
4413
4657
  "Dashboard listing scope. auto: use data_app when dataAppName + API token are available, otherwise global."
4414
4658
  ),
4415
- dataAppName: z19.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
4659
+ dataAppName: z20.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
4416
4660
  },
4417
4661
  READ_ONLY,
4418
4662
  async (params) => {
@@ -4447,7 +4691,7 @@ function registerListDashboards(server, client) {
4447
4691
  }
4448
4692
 
4449
4693
  // src/tools/query/ask-question.ts
4450
- import { z as z20 } from "zod";
4694
+ import { z as z21 } from "zod";
4451
4695
  var MAX_DISPLAY_ROWS = 50;
4452
4696
  function formatDataTable(data, columns) {
4453
4697
  if (!Array.isArray(data) || data.length === 0) return "_No data returned._";
@@ -4477,16 +4721,16 @@ function registerAskQuestion(server, client) {
4477
4721
  "ask_question",
4478
4722
  "Ask a natural language question \u2014 converts to SQL, returns data + chart suggestion. Requires datamartName with a populated semantic layer.",
4479
4723
  {
4480
- question: z20.string().describe(
4724
+ question: z21.string().describe(
4481
4725
  'The natural language question to ask (e.g. "What was total revenue last month?")'
4482
4726
  ),
4483
- datamartName: z20.string().optional().describe(
4727
+ datamartName: z21.string().optional().describe(
4484
4728
  "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."
4485
4729
  ),
4486
- sessionId: z20.string().optional().describe(
4730
+ sessionId: z21.string().optional().describe(
4487
4731
  "Session ID from a previous ask_question response, for follow-up questions."
4488
4732
  ),
4489
- timezone: z20.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
4733
+ timezone: z21.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
4490
4734
  },
4491
4735
  WRITE_CREATE,
4492
4736
  async (params) => {
@@ -4662,14 +4906,14 @@ To fix this:
4662
4906
  }
4663
4907
 
4664
4908
  // src/tools/query/get-semantic-layer.ts
4665
- import { z as z21 } from "zod";
4909
+ import { z as z22 } from "zod";
4666
4910
  function registerGetSemanticLayer(server, client) {
4667
4911
  const services = createServiceBundle(client);
4668
4912
  server.tool(
4669
4913
  "get_semantic_layer",
4670
4914
  "Get semantic layer metadata for a datamart: table/column descriptions, synonyms, completion score. Omit datamartName to auto-resolve.",
4671
4915
  {
4672
- datamartName: z21.string().optional().describe(
4916
+ datamartName: z22.string().optional().describe(
4673
4917
  "Name of the datamart to inspect. Omit to list available datamarts."
4674
4918
  )
4675
4919
  },
@@ -4779,15 +5023,15 @@ ${result.feedback}`);
4779
5023
  }
4780
5024
 
4781
5025
  // src/tools/query/update-semantic-layer.ts
4782
- import { z as z22 } from "zod";
4783
- var columnSchema2 = z22.object({
4784
- name: z22.string().describe("Column name (must exist in the datamart)"),
4785
- description: z22.string().optional().describe("Business-friendly description of what this column represents"),
4786
- synonyms: z22.array(z22.string()).optional().describe(
5026
+ import { z as z23 } from "zod";
5027
+ var columnSchema2 = z23.object({
5028
+ name: z23.string().describe("Column name (must exist in the datamart)"),
5029
+ description: z23.string().optional().describe("Business-friendly description of what this column represents"),
5030
+ synonyms: z23.array(z23.string()).optional().describe(
4787
5031
  'Alternative names users might use for this column (e.g. ["revenue", "sales amount"])'
4788
5032
  ),
4789
- miscellaneousInfo: z22.string().optional().describe("Additional context such as valid values, units, or caveats"),
4790
- columnType: z22.enum([
5033
+ miscellaneousInfo: z23.string().optional().describe("Additional context such as valid values, units, or caveats"),
5034
+ columnType: z23.enum([
4791
5035
  "String",
4792
5036
  "Long String",
4793
5037
  "String (Custom)",
@@ -4800,16 +5044,16 @@ var columnSchema2 = z22.object({
4800
5044
  "JSON"
4801
5045
  ]).nullable().optional().describe('Semantic column type. Use "Identifier" for IDs/foreign keys, "Number" for numeric values, "String" for text. Omit for date and boolean columns.')
4802
5046
  });
4803
- var tableSchema2 = z22.object({
4804
- name: z22.string().describe("Table name (must exist in the datamart)"),
4805
- description: z22.string().optional().describe(
5047
+ var tableSchema2 = z23.object({
5048
+ name: z23.string().describe("Table name (must exist in the datamart)"),
5049
+ description: z23.string().optional().describe(
4806
5050
  "Business-friendly description of what this table represents"
4807
5051
  ),
4808
- synonyms: z22.array(z22.string()).optional().describe("Alternative names for this table"),
4809
- miscellaneousInfo: z22.string().optional().describe(
5052
+ synonyms: z23.array(z23.string()).optional().describe("Alternative names for this table"),
5053
+ miscellaneousInfo: z23.string().optional().describe(
4810
5054
  "Additional context such as how the table relates to other tables"
4811
5055
  ),
4812
- columns: z22.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
5056
+ columns: z23.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
4813
5057
  });
4814
5058
  function columnKey(tableName, columnName) {
4815
5059
  return `${tableName.toLowerCase()}\0${columnName.toLowerCase()}`;
@@ -4876,13 +5120,13 @@ function registerUpdateSemanticLayer(server, client) {
4876
5120
  "update_semantic_layer",
4877
5121
  "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.",
4878
5122
  {
4879
- datamartName: z22.string().optional().describe(
5123
+ datamartName: z23.string().optional().describe(
4880
5124
  "Name of the datamart to update. Omit to list available datamarts."
4881
5125
  ),
4882
- tables: z22.array(tableSchema2).optional().describe(
5126
+ tables: z23.array(tableSchema2).optional().describe(
4883
5127
  "Table and column definitions to update. Only include tables/columns you want to change."
4884
5128
  ),
4885
- feedback: z22.string().optional().describe(
5129
+ feedback: z23.string().optional().describe(
4886
5130
  'General feedback or context about the datamart (e.g. "This datamart contains e-commerce data for the US market").'
4887
5131
  )
4888
5132
  },
@@ -4944,7 +5188,7 @@ You can now:
4944
5188
  }
4945
5189
 
4946
5190
  // src/tools/query/semantic-generation.ts
4947
- import { z as z23 } from "zod";
5191
+ import { z as z24 } from "zod";
4948
5192
  function nonEmptyString(value) {
4949
5193
  return typeof value === "string" && value.trim().length > 0 ? value : null;
4950
5194
  }
@@ -5099,9 +5343,9 @@ function registerStartSemanticLayerGeneration(server, client) {
5099
5343
  "start_semantic_layer_generation",
5100
5344
  "Start backend semantic-layer auto-generation for a datamart. Returns an async job id to poll.",
5101
5345
  {
5102
- datamartName: z23.string().optional().describe("Datamart name. Omit to list available datamarts"),
5103
- workspaceName: z23.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
5104
- datamartId: z23.string().optional().describe("Datamart UUID. Use only when already known.")
5346
+ datamartName: z24.string().optional().describe("Datamart name. Omit to list available datamarts"),
5347
+ workspaceName: z24.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
5348
+ datamartId: z24.string().optional().describe("Datamart UUID. Use only when already known.")
5105
5349
  },
5106
5350
  WRITE_CREATE,
5107
5351
  async (params) => {
@@ -5143,9 +5387,9 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
5143
5387
  "get_semantic_layer_generation_status",
5144
5388
  "Get semantic-layer auto-generation job status for a datamart.",
5145
5389
  {
5146
- datamartName: z23.string().optional().describe("Datamart name. Omit to list available datamarts"),
5147
- workspaceName: z23.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
5148
- datamartId: z23.string().optional().describe("Datamart UUID. Use only when already known.")
5390
+ datamartName: z24.string().optional().describe("Datamart name. Omit to list available datamarts"),
5391
+ workspaceName: z24.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
5392
+ datamartId: z24.string().optional().describe("Datamart UUID. Use only when already known.")
5149
5393
  },
5150
5394
  READ_ONLY,
5151
5395
  async (params) => {
@@ -5182,17 +5426,17 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
5182
5426
  }
5183
5427
 
5184
5428
  // src/tools/widgets/list-widgets.ts
5185
- import { z as z24 } from "zod";
5429
+ import { z as z25 } from "zod";
5186
5430
  function registerListWidgets(server, client) {
5187
5431
  const services = createServiceBundle(client);
5188
5432
  server.tool(
5189
5433
  "list_widgets",
5190
5434
  "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.",
5191
5435
  {
5192
- clientId: z24.string().describe("Client/tenant identifier for this dashboard"),
5193
- dashboardName: z24.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5194
- workspaceName: z24.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5195
- embedId: z24.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
5436
+ clientId: z25.string().describe("Client/tenant identifier for this dashboard"),
5437
+ dashboardName: z25.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5438
+ workspaceName: z25.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5439
+ embedId: z25.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
5196
5440
  },
5197
5441
  READ_ONLY,
5198
5442
  async (params) => {
@@ -5238,28 +5482,28 @@ function registerListWidgets(server, client) {
5238
5482
  }
5239
5483
 
5240
5484
  // src/tools/widgets/add-widget.ts
5241
- import { z as z25 } from "zod";
5485
+ import { z as z26 } from "zod";
5242
5486
  function registerAddWidget(server, client) {
5243
5487
  const services = createServiceBundle(client);
5244
5488
  server.tool(
5245
5489
  "add_widget",
5246
5490
  "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.",
5247
5491
  {
5248
- clientId: z25.string().describe("Client/tenant identifier (widget is created in their scope)"),
5249
- dashboardName: z25.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5250
- workspaceName: z25.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5251
- embedId: z25.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5252
- dataAppName: z25.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
5253
- templateMetricId: z25.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
5254
- name: z25.string().optional().describe("Widget name (overrides template)"),
5255
- metricId: z25.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
5256
- query: z25.string().optional().describe("SQL query body (overrides template)"),
5257
- chartOptions: z25.record(z25.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
5258
- description: z25.string().optional(),
5259
- companyIntegrationId: z25.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
5260
- integrationName: z25.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.'),
5261
- isPublished: z25.boolean().optional().describe("Publish immediately (default true)"),
5262
- body: z25.record(z25.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params.")
5492
+ clientId: z26.string().describe("Client/tenant identifier (widget is created in their scope)"),
5493
+ dashboardName: z26.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5494
+ workspaceName: z26.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5495
+ embedId: z26.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5496
+ dataAppName: z26.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
5497
+ templateMetricId: z26.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
5498
+ name: z26.string().optional().describe("Widget name (overrides template)"),
5499
+ metricId: z26.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
5500
+ query: z26.string().optional().describe("SQL query body (overrides template)"),
5501
+ chartOptions: z26.record(z26.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
5502
+ description: z26.string().optional(),
5503
+ companyIntegrationId: z26.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
5504
+ integrationName: z26.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.'),
5505
+ isPublished: z26.boolean().optional().describe("Publish immediately (default true)"),
5506
+ body: z26.record(z26.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params.")
5263
5507
  },
5264
5508
  WRITE_CREATE,
5265
5509
  async (params) => {
@@ -5283,6 +5527,13 @@ function registerAddWidget(server, client) {
5283
5527
  embedId
5284
5528
  );
5285
5529
  if (!dataAppName.ok) return toolError(dataAppName.error);
5530
+ const tenantScope = await services.widgetService.ensureTenantDashboardScope({
5531
+ embedId,
5532
+ clientId: params.clientId,
5533
+ dataAppName: dataAppName.value,
5534
+ dashboardName: embed.value.resolvedFrom?.dashboardName || params.dashboardName || null
5535
+ });
5536
+ if (!tenantScope.ok) return toolError(tenantScope.error);
5286
5537
  const guestToken = await services.widgetService.mintGuestToken(
5287
5538
  params.clientId,
5288
5539
  dataAppName.value,
@@ -5369,20 +5620,20 @@ Use \`list_widgets\` to verify, or \`update_widget\`/\`remove_widget\` to adjust
5369
5620
  }
5370
5621
 
5371
5622
  // src/tools/widgets/update-widget.ts
5372
- import { z as z26 } from "zod";
5623
+ import { z as z27 } from "zod";
5373
5624
  function registerUpdateWidget(server, client) {
5374
5625
  const services = createServiceBundle(client);
5375
5626
  server.tool(
5376
5627
  "update_widget",
5377
5628
  "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.",
5378
5629
  {
5379
- externalMetricId: z26.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
5380
- clientId: z26.string().describe("Client/tenant identifier"),
5381
- dashboardName: z26.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5382
- workspaceName: z26.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5383
- embedId: z26.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5384
- dataAppName: z26.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
5385
- set: z26.record(z26.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
5630
+ externalMetricId: z27.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
5631
+ clientId: z27.string().describe("Client/tenant identifier"),
5632
+ dashboardName: z27.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5633
+ workspaceName: z27.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5634
+ embedId: z27.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5635
+ dataAppName: z27.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
5636
+ set: z27.record(z27.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
5386
5637
  },
5387
5638
  WRITE_IDEMPOTENT,
5388
5639
  async (params) => {
@@ -5393,19 +5644,27 @@ function registerUpdateWidget(server, client) {
5393
5644
  const embed = await services.widgetService.resolveEmbedId({
5394
5645
  embedId: params.embedId,
5395
5646
  dashboardName: params.dashboardName,
5396
- workspaceName: params.workspaceName
5647
+ workspaceName: params.workspaceName,
5648
+ required: true
5397
5649
  });
5398
5650
  if (!embed.ok) return toolError(embed.error);
5399
5651
  const embedId = embed.value.embedId;
5400
5652
  const dataAppName = await services.widgetService.resolveDataAppName(
5401
5653
  params.dataAppName,
5402
- embedId || void 0
5654
+ embedId
5403
5655
  );
5404
5656
  if (!dataAppName.ok) return toolError(dataAppName.error);
5657
+ const tenantScope = await services.widgetService.ensureTenantDashboardScope({
5658
+ embedId,
5659
+ clientId: params.clientId,
5660
+ dataAppName: dataAppName.value,
5661
+ dashboardName: embed.value.resolvedFrom?.dashboardName || params.dashboardName || null
5662
+ });
5663
+ if (!tenantScope.ok) return toolError(tenantScope.error);
5405
5664
  const guestToken = await services.widgetService.mintGuestToken(
5406
5665
  params.clientId,
5407
5666
  dataAppName.value,
5408
- embedId ? [embedId] : void 0
5667
+ [embedId]
5409
5668
  );
5410
5669
  const result = await services.widgetsApi.updateMetric(guestToken, {
5411
5670
  externalMetricId: params.externalMetricId,
@@ -5437,19 +5696,19 @@ Use \`list_widgets\` to verify.`
5437
5696
  }
5438
5697
 
5439
5698
  // src/tools/widgets/remove-widget.ts
5440
- import { z as z27 } from "zod";
5699
+ import { z as z28 } from "zod";
5441
5700
  function registerRemoveWidget(server, client) {
5442
5701
  const services = createServiceBundle(client);
5443
5702
  server.tool(
5444
5703
  "remove_widget",
5445
5704
  "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.",
5446
5705
  {
5447
- externalMetricId: z27.string().describe("Internal uuid of the widget (externalMetrics.id)"),
5448
- clientId: z27.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
5449
- dashboardName: z27.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5450
- workspaceName: z27.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5451
- embedId: z27.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5452
- dataAppName: z27.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
5706
+ externalMetricId: z28.string().describe("Internal uuid of the widget (externalMetrics.id)"),
5707
+ clientId: z28.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
5708
+ dashboardName: z28.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5709
+ workspaceName: z28.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5710
+ embedId: z28.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5711
+ dataAppName: z28.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
5453
5712
  },
5454
5713
  DESTRUCTIVE,
5455
5714
  async (params) => {
@@ -5457,19 +5716,27 @@ function registerRemoveWidget(server, client) {
5457
5716
  const embed = await services.widgetService.resolveEmbedId({
5458
5717
  embedId: params.embedId,
5459
5718
  dashboardName: params.dashboardName,
5460
- workspaceName: params.workspaceName
5719
+ workspaceName: params.workspaceName,
5720
+ required: true
5461
5721
  });
5462
5722
  if (!embed.ok) return toolError(embed.error);
5463
5723
  const embedId = embed.value.embedId;
5464
5724
  const dataAppName = await services.widgetService.resolveDataAppName(
5465
5725
  params.dataAppName,
5466
- embedId || void 0
5726
+ embedId
5467
5727
  );
5468
5728
  if (!dataAppName.ok) return toolError(dataAppName.error);
5729
+ const tenantScope = await services.widgetService.ensureTenantDashboardScope({
5730
+ embedId,
5731
+ clientId: params.clientId,
5732
+ dataAppName: dataAppName.value,
5733
+ dashboardName: embed.value.resolvedFrom?.dashboardName || params.dashboardName || null
5734
+ });
5735
+ if (!tenantScope.ok) return toolError(tenantScope.error);
5469
5736
  const guestToken = await services.widgetService.mintGuestToken(
5470
5737
  params.clientId,
5471
5738
  dataAppName.value,
5472
- embedId ? [embedId] : void 0
5739
+ [embedId]
5473
5740
  );
5474
5741
  const result = await services.widgetsApi.markArchived(guestToken, {
5475
5742
  id: params.externalMetricId,
@@ -5497,21 +5764,21 @@ Note: this is a per-client soft delete. The widget remains available for other c
5497
5764
  }
5498
5765
 
5499
5766
  // src/tools/widgets/generate-widget.ts
5500
- import { z as z28 } from "zod";
5767
+ import { z as z29 } from "zod";
5501
5768
  function registerGenerateWidget(server, client) {
5502
5769
  const services = createServiceBundle(client);
5503
5770
  server.tool(
5504
5771
  "generate_widget",
5505
5772
  "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.",
5506
5773
  {
5507
- clientId: z28.string().describe("Client/tenant identifier"),
5508
- userInputs: z28.record(z28.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
5509
- dashboardName: z28.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5510
- workspaceName: z28.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5511
- embedId: z28.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5512
- dataAppName: z28.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
5513
- integrationId: z28.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."),
5514
- integrationName: z28.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.")
5774
+ clientId: z29.string().describe("Client/tenant identifier"),
5775
+ userInputs: z29.record(z29.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
5776
+ dashboardName: z29.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5777
+ workspaceName: z29.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5778
+ embedId: z29.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5779
+ dataAppName: z29.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
5780
+ integrationId: z29.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."),
5781
+ integrationName: z29.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.")
5515
5782
  },
5516
5783
  READ_ONLY,
5517
5784
  async (params) => {
@@ -5529,6 +5796,13 @@ function registerGenerateWidget(server, client) {
5529
5796
  embedId
5530
5797
  );
5531
5798
  if (!dataAppName.ok) return toolError(dataAppName.error);
5799
+ const tenantScope = await services.widgetService.ensureTenantDashboardScope({
5800
+ embedId,
5801
+ clientId: params.clientId,
5802
+ dataAppName: dataAppName.value,
5803
+ dashboardName: embed.value.resolvedFrom?.dashboardName || params.dashboardName || null
5804
+ });
5805
+ if (!tenantScope.ok) return toolError(tenantScope.error);
5532
5806
  const guestToken = await services.widgetService.mintGuestToken(
5533
5807
  params.clientId,
5534
5808
  dataAppName.value,
@@ -5577,9 +5851,9 @@ function registerGenerateWidget(server, client) {
5577
5851
  }
5578
5852
 
5579
5853
  // src/tools/widgets/create-or-update-metric-from-query.ts
5580
- import { z as z29 } from "zod";
5581
- var modeSchema = z29.enum(["dry_run", "apply"]).default("dry_run");
5582
- var conflictStrategySchema = z29.enum(["fail", "update", "create_new"]).default("fail");
5854
+ import { z as z30 } from "zod";
5855
+ var modeSchema = z30.enum(["dry_run", "apply"]).default("dry_run");
5856
+ var conflictStrategySchema = z30.enum(["fail", "update", "create_new"]).default("fail");
5583
5857
  function makePlanText(plan) {
5584
5858
  return JSON.stringify(plan, null, 2);
5585
5859
  }
@@ -5596,22 +5870,22 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
5596
5870
  "Create or update a production metric/widget from validated SQL. Defaults to dry-run; apply requires explicit confirmation.",
5597
5871
  {
5598
5872
  mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
5599
- confirm: z29.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
5873
+ confirm: z30.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
5600
5874
  conflictStrategy: conflictStrategySchema.describe("What to do if metricId already exists on the target dashboard"),
5601
- clientId: z29.string().describe("Client/tenant identifier for the dashboard widget scope"),
5602
- workspaceName: z29.string().describe("Target workspace name"),
5603
- dashboardName: z29.string().optional().describe("Dashboard name to attach to"),
5604
- embedId: z29.string().optional().describe("Technical embed id alternative to dashboardName"),
5605
- dataAppName: z29.string().optional().describe("Optional data app override"),
5606
- name: z29.string().describe("Metric/widget display name"),
5607
- metricId: z29.string().describe("Stable metric slug to create or update"),
5608
- query: z29.string().describe("SQL query already validated against the warehouse"),
5609
- description: z29.string().optional().describe("Metric description"),
5610
- chartOptions: z29.record(z29.unknown()).optional().describe("Chart configuration JSON"),
5611
- companyIntegrationId: z29.string().optional().describe("Datasource UUID override"),
5612
- integrationName: z29.string().optional().describe("Datasource display name override"),
5613
- isPublished: z29.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
5614
- requestedBy: z29.string().optional().describe("Human operator/request context to store on the metric when supported")
5875
+ clientId: z30.string().describe("Client/tenant identifier for the dashboard widget scope"),
5876
+ workspaceName: z30.string().describe("Target workspace name"),
5877
+ dashboardName: z30.string().optional().describe("Dashboard name to attach to"),
5878
+ embedId: z30.string().optional().describe("Technical embed id alternative to dashboardName"),
5879
+ dataAppName: z30.string().optional().describe("Optional data app override"),
5880
+ name: z30.string().describe("Metric/widget display name"),
5881
+ metricId: z30.string().describe("Stable metric slug to create or update"),
5882
+ query: z30.string().describe("SQL query already validated against the warehouse"),
5883
+ description: z30.string().optional().describe("Metric description"),
5884
+ chartOptions: z30.record(z30.unknown()).optional().describe("Chart configuration JSON"),
5885
+ companyIntegrationId: z30.string().optional().describe("Datasource UUID override"),
5886
+ integrationName: z30.string().optional().describe("Datasource display name override"),
5887
+ isPublished: z30.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
5888
+ requestedBy: z30.string().optional().describe("Human operator/request context to store on the metric when supported")
5615
5889
  },
5616
5890
  WRITE_IDEMPOTENT,
5617
5891
  async (params) => {
@@ -5640,6 +5914,13 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
5640
5914
  embedId
5641
5915
  );
5642
5916
  if (!dataAppName.ok) return toolError(dataAppName.error);
5917
+ const tenantScope = await services.widgetService.ensureTenantDashboardScope({
5918
+ embedId,
5919
+ clientId: params.clientId,
5920
+ dataAppName: dataAppName.value,
5921
+ dashboardName: embed.value.resolvedFrom?.dashboardName || params.dashboardName || null
5922
+ });
5923
+ if (!tenantScope.ok) return toolError(tenantScope.error);
5643
5924
  const guestToken = await services.widgetService.mintGuestToken(
5644
5925
  params.clientId,
5645
5926
  dataAppName.value,
@@ -5702,6 +5983,7 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
5702
5983
  dataAppName: dataAppName.value,
5703
5984
  externalDashboardId: ctx.externalDashboardId,
5704
5985
  clientId: params.clientId,
5986
+ tenantScoped: true,
5705
5987
  datasource: datasource.value
5706
5988
  },
5707
5989
  conflict: existing ? {
@@ -5763,25 +6045,25 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
5763
6045
  }
5764
6046
 
5765
6047
  // src/tools/widgets/validate-metric-spec.ts
5766
- import { z as z30 } from "zod";
6048
+ import { z as z31 } from "zod";
5767
6049
  function registerValidateMetricSpec(server, client) {
5768
6050
  const services = createServiceBundle(client);
5769
6051
  server.tool(
5770
6052
  "validate_metric_spec",
5771
6053
  "Validate a production metric spec from SQL without saving it. Resolves dashboard, data app, datasource, conflicts, and required fields.",
5772
6054
  {
5773
- clientId: z30.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
5774
- workspaceName: z30.string().describe("Target production-backed workspace name"),
5775
- dashboardName: z30.string().optional().describe("Dashboard name to attach to"),
5776
- embedId: z30.string().optional().describe("Technical embed id alternative to dashboardName"),
5777
- dataAppName: z30.string().optional().describe("Optional data app override"),
5778
- name: z30.string().describe("Metric/widget display name"),
5779
- metricId: z30.string().describe("Stable metric slug to create or update"),
5780
- query: z30.string().describe("SQL query already validated against the warehouse"),
5781
- description: z30.string().optional().describe("Metric description"),
5782
- chartOptions: z30.record(z30.unknown()).optional().describe("Chart configuration JSON"),
5783
- companyIntegrationId: z30.string().optional().describe("Datasource UUID override"),
5784
- integrationName: z30.string().optional().describe("Datasource display name override")
6055
+ clientId: z31.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
6056
+ workspaceName: z31.string().describe("Target production-backed workspace name"),
6057
+ dashboardName: z31.string().optional().describe("Dashboard name to attach to"),
6058
+ embedId: z31.string().optional().describe("Technical embed id alternative to dashboardName"),
6059
+ dataAppName: z31.string().optional().describe("Optional data app override"),
6060
+ name: z31.string().describe("Metric/widget display name"),
6061
+ metricId: z31.string().describe("Stable metric slug to create or update"),
6062
+ query: z31.string().describe("SQL query already validated against the warehouse"),
6063
+ description: z31.string().optional().describe("Metric description"),
6064
+ chartOptions: z31.record(z31.unknown()).optional().describe("Chart configuration JSON"),
6065
+ companyIntegrationId: z31.string().optional().describe("Datasource UUID override"),
6066
+ integrationName: z31.string().optional().describe("Datasource display name override")
5785
6067
  },
5786
6068
  READ_ONLY,
5787
6069
  async (params) => {
@@ -5808,6 +6090,13 @@ function registerValidateMetricSpec(server, client) {
5808
6090
  embedId
5809
6091
  );
5810
6092
  if (!dataAppName.ok) return toolError(dataAppName.error);
6093
+ const tenantScope = await services.widgetService.ensureTenantDashboardScope({
6094
+ embedId,
6095
+ clientId: params.clientId,
6096
+ dataAppName: dataAppName.value,
6097
+ dashboardName: embed.value.resolvedFrom?.dashboardName || params.dashboardName || null
6098
+ });
6099
+ if (!tenantScope.ok) return toolError(tenantScope.error);
5811
6100
  const guestToken = await services.widgetService.mintGuestToken(
5812
6101
  params.clientId,
5813
6102
  dataAppName.value,
@@ -5833,6 +6122,7 @@ function registerValidateMetricSpec(server, client) {
5833
6122
  embedId,
5834
6123
  dataAppName: dataAppName.value,
5835
6124
  externalDashboardId: ctx.externalDashboardId,
6125
+ tenantScoped: true,
5836
6126
  datasource
5837
6127
  },
5838
6128
  conflict: existing ? {
@@ -5864,20 +6154,20 @@ function registerValidateMetricSpec(server, client) {
5864
6154
  }
5865
6155
 
5866
6156
  // src/tools/reports/list-scheduled-reports.ts
5867
- import { z as z31 } from "zod";
6157
+ import { z as z32 } from "zod";
5868
6158
  function registerListScheduledReports(server, client) {
5869
6159
  const services = createServiceBundle(client);
5870
6160
  server.tool(
5871
6161
  "list_scheduled_reports",
5872
6162
  "List configured scheduled reports for a specific client/dashboard embed. Shows schedule metadata, not delivery success history.",
5873
6163
  {
5874
- clientId: z31.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
5875
- dashboardName: z31.string().optional().describe("Dashboard name to inspect"),
5876
- workspaceName: z31.string().optional().describe("Workspace name if dashboardName is ambiguous"),
5877
- embedId: z31.string().optional().describe("Technical embed id alternative to dashboardName"),
5878
- userIdentifier: z31.string().optional().describe("Optional creator/user filter"),
5879
- pageNumber: z31.number().int().positive().optional().describe("Page number when pagination is enabled"),
5880
- isPagination: z31.boolean().optional().describe("Enable backend pagination")
6164
+ clientId: z32.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
6165
+ dashboardName: z32.string().optional().describe("Dashboard name to inspect"),
6166
+ workspaceName: z32.string().optional().describe("Workspace name if dashboardName is ambiguous"),
6167
+ embedId: z32.string().optional().describe("Technical embed id alternative to dashboardName"),
6168
+ userIdentifier: z32.string().optional().describe("Optional creator/user filter"),
6169
+ pageNumber: z32.number().int().positive().optional().describe("Page number when pagination is enabled"),
6170
+ isPagination: z32.boolean().optional().describe("Enable backend pagination")
5881
6171
  },
5882
6172
  READ_ONLY,
5883
6173
  async (params) => {
@@ -5988,6 +6278,7 @@ function registerAllTools(server, client) {
5988
6278
  registerSyncDatasource(server, client);
5989
6279
  registerCreateDatamart(server, client);
5990
6280
  registerListDataApps(server, client);
6281
+ registerListTenants(server, client);
5991
6282
  registerCreateDataApp(server, client);
5992
6283
  registerListApiTokens(server, client);
5993
6284
  registerCreateApiToken(server, client);
@@ -9277,7 +9568,7 @@ function createServer(config) {
9277
9568
  },
9278
9569
  {
9279
9570
  instructions: operatorBlurb + `Databrain uses two token types \u2014 never confuse them:
9280
- \u2022 SERVICE TOKEN (DATABRAIN_SERVICE_TOKEN): org-level, pre-configured in MCP server config. Used for: list_datasources, discover_datasource_schema, sync_datasource, list_datamarts, create_datamart, list_workspaces, list_data_apps, create_data_app, list_api_tokens, create_api_token, rotate_api_token, list_dashboards (workspace scope), ask_question, get_semantic_layer, update_semantic_layer, start_semantic_layer_generation, get_semantic_layer_generation_status. ${serviceTokenStatus}
9571
+ \u2022 SERVICE TOKEN (DATABRAIN_SERVICE_TOKEN): org-level, pre-configured in MCP server config. Used for: list_datasources, discover_datasource_schema, sync_datasource, list_datamarts, create_datamart, list_workspaces, list_data_apps, list_tenants, create_data_app, list_api_tokens, create_api_token, rotate_api_token, list_dashboards (workspace scope), ask_question, get_semantic_layer, update_semantic_layer, start_semantic_layer_generation, get_semantic_layer_generation_status. ${serviceTokenStatus}
9281
9572
  \u2022 API TOKEN (per data app): scoped to one data app, used for embedding operations (list_embeds, get_embed_details, create_embed, update_embed, generate_guest_token), data-app scoped dashboard listing, widget CRUD/metric creation through guest-token APIs, and list_scheduled_reports. ${apiTokenStatus}
9282
9573
 
9283
9574
  4 core use cases \u2014 match the user's scenario to the right prompt:
@@ -9286,7 +9577,7 @@ function createServer(config) {
9286
9577
  3. Explore & query data \u2192 prompt: query-data (list_datamarts \u2192 get_semantic_layer \u2192 if empty, auto-generate descriptions with update_semantic_layer or use populate-semantic-layer prompt \u2192 ask_question)
9287
9578
  4. SQL-to-metric migration \u2192 prompt: sql-to-metric-migration (validate_metric_spec \u2192 create_or_update_metric_from_query dry_run \u2192 apply only with explicit production confirmation)
9288
9579
 
9289
- Production safety: OAuth may not be available yet. In service-token mode, writes are actor=system. For create_datamart and create_or_update_metric_from_query, dry_run is the default. Apply requires confirm="APPLY_TO_PRODUCTION" after the user reviews the plan. Do not apply from a vague request.
9580
+ Production safety: OAuth may not be available yet. In service-token mode, writes are actor=system. For create_datamart and create_or_update_metric_from_query, dry_run is the default. Apply requires confirm="APPLY_TO_PRODUCTION" after the user reviews the plan. Widget validation and mutation tools must verify the resolved embed is tenant-scoped for the requested clientId; if not, they must refuse to touch it as a likely production/data-app dashboard. Do not apply from a vague request.
9290
9581
 
9291
9582
  Infrastructure creation is intentionally narrow in MCP: datasource and workspace creation still happen outside this workflow; sync_datasource and dry-run/apply create_datamart are available. The semantic layer is populated and maintained via MCP tools (get_semantic_layer, update_semantic_layer, start_semantic_layer_generation, get_semantic_layer_generation_status). When the semantic layer is empty, use these tools \u2014 do NOT send the user to the UI for semantic metadata.
9292
9583