@databrainhq/mcp-server 0.2.14 → 0.2.16

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