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