@databrainhq/mcp-server 0.3.0-rc.3 → 0.3.0-rc.5
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 +56 -1
- package/dist/apps/server.d.ts +6 -6
- package/dist/apps/server.js +3 -2
- package/dist/apps/stdio.js +4 -3
- package/dist/apps/stdio.js.map +1 -1
- package/dist/{chunk-CX3CS62P.js → chunk-3GF7WSKK.js} +4 -2
- package/dist/chunk-3GF7WSKK.js.map +1 -0
- package/dist/{chunk-4LIULTLT.js → chunk-4DYPD273.js} +204 -67
- package/dist/chunk-4DYPD273.js.map +1 -0
- package/dist/{chunk-DZOYK5BB.js → chunk-5IQ7MVUH.js} +1044 -1029
- package/dist/chunk-5IQ7MVUH.js.map +1 -0
- package/dist/chunk-PRE4CGOV.js +1903 -0
- package/dist/chunk-PRE4CGOV.js.map +1 -0
- package/dist/{chunk-OHTV24NG.js → chunk-V64ZQINV.js} +2 -2
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +495 -0
- package/dist/cli.js.map +1 -0
- package/dist/custom-chart/runtime-shim.js +146 -0
- package/dist/custom-chart/template/AGENTS.md +374 -0
- package/dist/custom-chart/template/README.md +374 -0
- package/dist/custom-chart/template/chart.css +28 -0
- package/dist/custom-chart/template/chart.ts +192 -0
- package/dist/custom-chart/template/custom-chart.d.ts +78 -0
- package/dist/custom-chart/template/icon.svg +1 -0
- package/dist/custom-chart/template/manifest.json +23 -0
- package/dist/custom-chart/template/package.json +14 -0
- package/dist/custom-chart/template/sample-data.json +18 -0
- package/dist/custom-chart/template/tsconfig.json +12 -0
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/{main-VPCN2764.js → main-Q6XT4O46.js} +34 -8
- package/dist/main-Q6XT4O46.js.map +1 -0
- package/dist/{test-only-B4EVUNFU.js → test-only-TJ3DQUYW.js} +2 -2
- package/package.json +6 -3
- package/dist/chunk-4LIULTLT.js.map +0 -1
- package/dist/chunk-CX3CS62P.js.map +0 -1
- package/dist/chunk-DZOYK5BB.js.map +0 -1
- package/dist/main-VPCN2764.js.map +0 -1
- /package/dist/{chunk-OHTV24NG.js.map → chunk-V64ZQINV.js.map} +0 -0
- /package/dist/{test-only-B4EVUNFU.js.map → test-only-TJ3DQUYW.js.map} +0 -0
|
@@ -1,620 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// package.json
|
|
25
|
-
var package_default = {
|
|
26
|
-
name: "@databrainhq/mcp-server",
|
|
27
|
-
version: "0.3.0-rc.3",
|
|
28
|
-
description: "MCP server for Databrain embedded analytics - connect databases, create datamarts, configure embeds, and generate frontend code",
|
|
29
|
-
type: "module",
|
|
30
|
-
bin: {
|
|
31
|
-
"mcp-server": "dist/index.js",
|
|
32
|
-
"databrain-mcp-server": "dist/index.js",
|
|
33
|
-
"databrain-mcp-chart-apps": "dist/apps/stdio.js"
|
|
34
|
-
},
|
|
35
|
-
main: "dist/index.js",
|
|
36
|
-
types: "dist/index.d.ts",
|
|
37
|
-
files: [
|
|
38
|
-
"dist",
|
|
39
|
-
"skills",
|
|
40
|
-
".claude-plugin",
|
|
41
|
-
".mcp.json"
|
|
42
|
-
],
|
|
43
|
-
scripts: {
|
|
44
|
-
build: "tsup",
|
|
45
|
-
dev: "tsx src/index.ts",
|
|
46
|
-
test: "vitest run",
|
|
47
|
-
"test:watch": "vitest",
|
|
48
|
-
"test:live": "vitest run tests/integration/prompt-flows.test.ts",
|
|
49
|
-
lint: "tsc --noEmit",
|
|
50
|
-
prepublishOnly: "npm run build"
|
|
51
|
-
},
|
|
52
|
-
keywords: [
|
|
53
|
-
"mcp",
|
|
54
|
-
"databrain",
|
|
55
|
-
"embedded-analytics",
|
|
56
|
-
"ai",
|
|
57
|
-
"model-context-protocol"
|
|
58
|
-
],
|
|
59
|
-
license: "MIT",
|
|
60
|
-
dependencies: {
|
|
61
|
-
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
62
|
-
"@posthog/mcp": "^0.10.7",
|
|
63
|
-
"posthog-node": "^5.21.2",
|
|
64
|
-
zod: "^3.24.0"
|
|
65
|
-
},
|
|
66
|
-
devDependencies: {
|
|
67
|
-
"@types/node": "^25.3.5",
|
|
68
|
-
tsup: "^8.4.0",
|
|
69
|
-
tsx: "^4.19.0",
|
|
70
|
-
typescript: "^5.7.0",
|
|
71
|
-
vitest: "^3.0.0"
|
|
72
|
-
},
|
|
73
|
-
publishConfig: {
|
|
74
|
-
access: "public"
|
|
75
|
-
},
|
|
76
|
-
repository: {
|
|
77
|
-
type: "git",
|
|
78
|
-
url: "git+https://github.com/databrainhq/mcp-server.git"
|
|
79
|
-
},
|
|
80
|
-
engines: {
|
|
81
|
-
node: ">=18.0.0"
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// src/version.ts
|
|
86
|
-
var MCP_SERVER_VERSION = package_default.version;
|
|
87
|
-
|
|
88
|
-
// src/utils/cache.ts
|
|
89
|
-
var TtlCache = class {
|
|
90
|
-
store = /* @__PURE__ */ new Map();
|
|
91
|
-
get(key) {
|
|
92
|
-
const entry = this.store.get(key);
|
|
93
|
-
if (!entry) return void 0;
|
|
94
|
-
if (Date.now() > entry.expiresAt) {
|
|
95
|
-
this.store.delete(key);
|
|
96
|
-
return void 0;
|
|
97
|
-
}
|
|
98
|
-
return entry.value;
|
|
99
|
-
}
|
|
100
|
-
set(key, value, ttlMs) {
|
|
101
|
-
this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
102
|
-
}
|
|
103
|
-
invalidateByPrefix(prefix) {
|
|
104
|
-
for (const key of this.store.keys()) {
|
|
105
|
-
if (key.startsWith(prefix)) {
|
|
106
|
-
this.store.delete(key);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
clear() {
|
|
111
|
-
this.store.clear();
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// src/utils/errors.ts
|
|
116
|
-
var DatabrainApiError = class extends Error {
|
|
117
|
-
constructor(status, code, message, details) {
|
|
118
|
-
super(message);
|
|
119
|
-
this.status = status;
|
|
120
|
-
this.code = code;
|
|
121
|
-
this.details = details;
|
|
122
|
-
this.name = "DatabrainApiError";
|
|
123
|
-
}
|
|
124
|
-
toToolResponse() {
|
|
125
|
-
const parts = [`Error: ${this.message}`];
|
|
126
|
-
const detailDump = this.formatDetails();
|
|
127
|
-
if (detailDump) parts.push(detailDump);
|
|
128
|
-
const hint = this.hint();
|
|
129
|
-
if (hint) parts.push(hint);
|
|
130
|
-
return {
|
|
131
|
-
content: [{ type: "text", text: parts.join("\n\n") }],
|
|
132
|
-
isError: true
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Surface API-returned details for 4xx errors so the LLM can recover or
|
|
137
|
-
* report the real reason (validation messages, field names, etc.). Skips
|
|
138
|
-
* fields already collapsed into `message`/`code`, and skips 5xx where the
|
|
139
|
-
* payload usually isn't actionable.
|
|
140
|
-
*/
|
|
141
|
-
formatDetails() {
|
|
142
|
-
if (this.status < 400 || this.status >= 500) return "";
|
|
143
|
-
if (!this.details || typeof this.details !== "object") return "";
|
|
144
|
-
const skipKeys = /* @__PURE__ */ new Set(["message", "detail", "error", "code"]);
|
|
145
|
-
const extra = {};
|
|
146
|
-
for (const [k, v] of Object.entries(this.details)) {
|
|
147
|
-
if (skipKeys.has(k)) continue;
|
|
148
|
-
if (v === null || v === void 0) continue;
|
|
149
|
-
extra[k] = v;
|
|
150
|
-
}
|
|
151
|
-
if (Object.keys(extra).length === 0) return "";
|
|
152
|
-
try {
|
|
153
|
-
return `API details: ${JSON.stringify(extra)}`;
|
|
154
|
-
} catch {
|
|
155
|
-
return "";
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
hint() {
|
|
159
|
-
if (this.status === 401 || this.status === 403) {
|
|
160
|
-
if (this.code === "OAUTH_API_ERROR") {
|
|
161
|
-
return "Hint: Check that the backend accepts the configured OAuth issuer and resource, and that the service principal has permission for this operation. Static fallback applies only to token acquisition failures.";
|
|
162
|
-
}
|
|
163
|
-
if (this.code === "INVALID_SERVICE_TOKEN" || this.code === "MISSING_SERVICE_TOKEN" || this.message.toLowerCase().includes("service token")) {
|
|
164
|
-
return "Hint: The service token (DATABRAIN_SERVICE_TOKEN) is invalid, expired, or missing. It must be set in the MCP server config (not at runtime). Check Settings > Service Tokens in the Databrain UI.";
|
|
165
|
-
}
|
|
166
|
-
return "Hint: The per-data-app API token is invalid or expired. Use `create_api_token` to generate a new one. Do not ask the user \u2014 manage tokens via MCP tools.";
|
|
167
|
-
}
|
|
168
|
-
if (this.status === 400) {
|
|
169
|
-
return "Hint: The request was invalid. Check parameter values match expected types and formats. Use the corresponding list tool to verify IDs and names.";
|
|
170
|
-
}
|
|
171
|
-
if (this.status === 404) {
|
|
172
|
-
return "Hint: The requested resource was not found. Verify the ID or name is correct using the corresponding list tool.";
|
|
173
|
-
}
|
|
174
|
-
if (this.status === 409) {
|
|
175
|
-
return "Hint: A resource with this name already exists. Use a different name or list existing resources first.";
|
|
176
|
-
}
|
|
177
|
-
if (this.status === 422) {
|
|
178
|
-
return "Hint: The request parameters are invalid. Check the required fields and their formats.";
|
|
179
|
-
}
|
|
180
|
-
if (this.status === 429) {
|
|
181
|
-
return "Hint: API rate limit reached. Wait a moment and try again.";
|
|
182
|
-
}
|
|
183
|
-
if (this.status >= 500) {
|
|
184
|
-
return "Hint: The Databrain API is experiencing issues. Try again in a few moments.";
|
|
185
|
-
}
|
|
186
|
-
return "";
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
function toolError(message) {
|
|
190
|
-
return {
|
|
191
|
-
content: [{ type: "text", text: message }],
|
|
192
|
-
isError: true
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function stringify(value) {
|
|
196
|
-
if (typeof value === "string") return value;
|
|
197
|
-
try {
|
|
198
|
-
return JSON.stringify(value, null, 2);
|
|
199
|
-
} catch {
|
|
200
|
-
return String(value);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function toolSuccess(text) {
|
|
204
|
-
return {
|
|
205
|
-
content: [{ type: "text", text }]
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// src/utils/tool-context.ts
|
|
210
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
211
|
-
var toolContext = new AsyncLocalStorage();
|
|
212
|
-
|
|
213
|
-
// src/client/oauth-client-credentials.ts
|
|
214
|
-
import { z } from "zod";
|
|
215
|
-
var oauthHttpsUrl = z.string().refine((value) => {
|
|
216
|
-
try {
|
|
217
|
-
const url = new URL(value);
|
|
218
|
-
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
219
|
-
} catch {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
}, "OAuth URLs must use HTTPS without userinfo or fragments");
|
|
223
|
-
var oauthConfigSchema = z.object({
|
|
224
|
-
issuer: oauthHttpsUrl.refine((value) => {
|
|
225
|
-
try {
|
|
226
|
-
return !new URL(value).search;
|
|
227
|
-
} catch {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}, "OAuth issuer must not contain a query"),
|
|
231
|
-
clientId: z.string().min(1),
|
|
232
|
-
clientSecret: z.string().min(1),
|
|
233
|
-
resource: oauthHttpsUrl,
|
|
234
|
-
scope: z.string().min(1).optional(),
|
|
235
|
-
authMethod: z.enum(["client_secret_basic", "client_secret_post"]).default("client_secret_basic")
|
|
236
|
-
});
|
|
237
|
-
var metadataSchema = z.object({
|
|
238
|
-
issuer: z.string(),
|
|
239
|
-
token_endpoint: oauthHttpsUrl,
|
|
240
|
-
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
|
241
|
-
grant_types_supported: z.array(z.string()).optional()
|
|
242
|
-
});
|
|
243
|
-
var tokenSchema = z.object({
|
|
244
|
-
access_token: z.string().regex(/^[A-Za-z0-9\-._~+/]+=*$/),
|
|
245
|
-
token_type: z.string().refine((value) => value.toLowerCase() === "bearer"),
|
|
246
|
-
expires_in: z.number().finite().positive().optional()
|
|
247
|
-
});
|
|
248
|
-
var TOKEN_TIMEOUT_MS = 1e4;
|
|
249
|
-
var FAILURE_COOLDOWN_MS = 3e4;
|
|
250
|
-
var MAX_REFRESH_SKEW_MS = 6e4;
|
|
251
|
-
function authError() {
|
|
252
|
-
return new Error("OAuth token acquisition failed. Check the configured issuer, client credentials, resource and scopes.");
|
|
253
|
-
}
|
|
254
|
-
var OAuthClientCredentials = class {
|
|
255
|
-
config;
|
|
256
|
-
tokenEndpoint;
|
|
257
|
-
cached;
|
|
258
|
-
pending;
|
|
259
|
-
retryAt = 0;
|
|
260
|
-
constructor(config) {
|
|
261
|
-
const parsed = oauthConfigSchema.safeParse(config);
|
|
262
|
-
if (!parsed.success) throw authError();
|
|
263
|
-
this.config = Object.freeze(parsed.data);
|
|
264
|
-
}
|
|
265
|
-
getStatus() {
|
|
266
|
-
if (this.cached && Date.now() < this.cached.renewAt) return "ready";
|
|
267
|
-
if (this.retryAt > 0) return "unavailable";
|
|
268
|
-
return "configured";
|
|
269
|
-
}
|
|
270
|
-
async getToken() {
|
|
271
|
-
if (this.cached && Date.now() < this.cached.renewAt) return this.cached.value;
|
|
272
|
-
if (this.pending) return this.pending;
|
|
273
|
-
if (Date.now() < this.retryAt) throw authError();
|
|
274
|
-
this.cached = void 0;
|
|
275
|
-
this.pending = this.acquire().catch(() => {
|
|
276
|
-
this.retryAt = Date.now() + FAILURE_COOLDOWN_MS;
|
|
277
|
-
throw authError();
|
|
278
|
-
}).finally(() => {
|
|
279
|
-
this.pending = void 0;
|
|
280
|
-
});
|
|
281
|
-
return this.pending;
|
|
282
|
-
}
|
|
283
|
-
async discoverTokenEndpoint(signal) {
|
|
284
|
-
if (this.tokenEndpoint) return this.tokenEndpoint;
|
|
285
|
-
const issuer = new URL(this.config.issuer);
|
|
286
|
-
const path = issuer.pathname.replace(/\/$/, "");
|
|
287
|
-
const candidates = [.../* @__PURE__ */ new Set([
|
|
288
|
-
`${issuer.origin}/.well-known/oauth-authorization-server${path}`,
|
|
289
|
-
`${issuer.origin}/.well-known/openid-configuration${path}`,
|
|
290
|
-
`${issuer.origin}${path}/.well-known/openid-configuration`
|
|
291
|
-
])];
|
|
292
|
-
for (const url of candidates) {
|
|
293
|
-
const response = await fetch(url, {
|
|
294
|
-
headers: { Accept: "application/json" },
|
|
295
|
-
redirect: "error",
|
|
296
|
-
signal
|
|
297
|
-
});
|
|
298
|
-
if (response.status === 404 || response.status === 405) continue;
|
|
299
|
-
if (!response.ok) throw authError();
|
|
300
|
-
const metadata = metadataSchema.parse(await response.json());
|
|
301
|
-
if (metadata.issuer !== this.config.issuer) throw authError();
|
|
302
|
-
const methods = metadata.token_endpoint_auth_methods_supported ?? ["client_secret_basic"];
|
|
303
|
-
if (!methods.includes(this.config.authMethod)) throw authError();
|
|
304
|
-
if (metadata.grant_types_supported && !metadata.grant_types_supported.includes("client_credentials")) {
|
|
305
|
-
throw authError();
|
|
306
|
-
}
|
|
307
|
-
this.tokenEndpoint = metadata.token_endpoint;
|
|
308
|
-
return this.tokenEndpoint;
|
|
309
|
-
}
|
|
310
|
-
throw authError();
|
|
311
|
-
}
|
|
312
|
-
async acquire() {
|
|
313
|
-
const signal = AbortSignal.timeout(TOKEN_TIMEOUT_MS);
|
|
314
|
-
const endpoint = await this.discoverTokenEndpoint(signal);
|
|
315
|
-
const body = new URLSearchParams({
|
|
316
|
-
grant_type: "client_credentials",
|
|
317
|
-
resource: this.config.resource
|
|
318
|
-
});
|
|
319
|
-
if (this.config.scope) body.set("scope", this.config.scope);
|
|
320
|
-
const headers = {
|
|
321
|
-
Accept: "application/json",
|
|
322
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
323
|
-
};
|
|
324
|
-
if (this.config.authMethod === "client_secret_post") {
|
|
325
|
-
body.set("client_id", this.config.clientId);
|
|
326
|
-
body.set("client_secret", this.config.clientSecret);
|
|
327
|
-
} else {
|
|
328
|
-
const encode = (value) => new URLSearchParams({ value }).toString().slice(6);
|
|
329
|
-
headers.Authorization = `Basic ${Buffer.from(`${encode(this.config.clientId)}:${encode(this.config.clientSecret)}`).toString("base64")}`;
|
|
330
|
-
}
|
|
331
|
-
const startedAt = Date.now();
|
|
332
|
-
const response = await fetch(endpoint, {
|
|
333
|
-
method: "POST",
|
|
334
|
-
headers,
|
|
335
|
-
body: body.toString(),
|
|
336
|
-
redirect: "error",
|
|
337
|
-
signal
|
|
338
|
-
});
|
|
339
|
-
if (!response.ok) throw authError();
|
|
340
|
-
const token = tokenSchema.parse(await response.json());
|
|
341
|
-
if (token.expires_in !== void 0) {
|
|
342
|
-
const lifetimeMs = token.expires_in * 1e3;
|
|
343
|
-
const expiresAt = startedAt + lifetimeMs;
|
|
344
|
-
if (!Number.isFinite(expiresAt) || Date.now() >= expiresAt) throw authError();
|
|
345
|
-
this.cached = {
|
|
346
|
-
value: token.access_token,
|
|
347
|
-
renewAt: expiresAt - Math.min(MAX_REFRESH_SKEW_MS, lifetimeMs * 0.1)
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
this.retryAt = 0;
|
|
351
|
-
return token.access_token;
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
// src/client/databrain-client.ts
|
|
356
|
-
var MCP_USER_AGENT = `databrain-mcp-server/${MCP_SERVER_VERSION}`;
|
|
357
|
-
var MAX_RETRIES = 3;
|
|
358
|
-
var REQUEST_TIMEOUT_MS = 3e4;
|
|
359
|
-
var NON_TRANSIENT_PATTERNS = [
|
|
360
|
-
"out of context",
|
|
361
|
-
"Definition not found",
|
|
362
|
-
"not a data question",
|
|
363
|
-
"Query generation failed",
|
|
364
|
-
"not found",
|
|
365
|
-
"not authenticated",
|
|
366
|
-
"Invalid Service Token",
|
|
367
|
-
"service token"
|
|
368
|
-
];
|
|
369
|
-
var DatabrainClient = class {
|
|
370
|
-
constructor(config) {
|
|
371
|
-
this.config = config;
|
|
372
|
-
if (config.oauth) {
|
|
373
|
-
if (!oauthHttpsUrl.safeParse(config.apiUrl).success) {
|
|
374
|
-
throw new Error("OAuth requires an HTTPS Databrain API URL without userinfo or fragments.");
|
|
375
|
-
}
|
|
376
|
-
this.oauth = new OAuthClientCredentials(config.oauth);
|
|
377
|
-
this.config = Object.freeze({ ...config });
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
cache = new TtlCache();
|
|
381
|
-
runtimeApiToken;
|
|
382
|
-
oauth;
|
|
383
|
-
oauthFallback = false;
|
|
384
|
-
hasApiToken() {
|
|
385
|
-
return !!(this.runtimeApiToken || this.config.apiToken);
|
|
386
|
-
}
|
|
387
|
-
setRuntimeApiToken(token) {
|
|
388
|
-
this.runtimeApiToken = token;
|
|
389
|
-
logger.info("Runtime API token set for this session");
|
|
390
|
-
}
|
|
391
|
-
getServiceToken() {
|
|
392
|
-
return this.config.serviceToken;
|
|
393
|
-
}
|
|
394
|
-
getApiUrl() {
|
|
395
|
-
return this.config.apiUrl;
|
|
396
|
-
}
|
|
397
|
-
getDemoDomain() {
|
|
398
|
-
return this.config.demoDomain;
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Describes which credential is currently used as the bearer for outgoing
|
|
402
|
-
* API calls. Used by `whoami` so the LLM can give the user a truthful answer.
|
|
403
|
-
*/
|
|
404
|
-
getActiveAuth() {
|
|
405
|
-
if (this.oauth) {
|
|
406
|
-
if (this.oauthFallback) return { source: "service-token", oauthFallback: true };
|
|
407
|
-
return { source: "oauth-client-credentials", status: this.oauth.getStatus() };
|
|
408
|
-
}
|
|
409
|
-
if (this.config.serviceToken) return { source: "service-token" };
|
|
410
|
-
if (this.runtimeApiToken || this.config.apiToken) return { source: "api-token" };
|
|
411
|
-
return { source: "none" };
|
|
412
|
-
}
|
|
413
|
-
async getToken(auth) {
|
|
414
|
-
if (auth === "none") return void 0;
|
|
415
|
-
if (auth === "service") {
|
|
416
|
-
if (this.oauth) {
|
|
417
|
-
try {
|
|
418
|
-
const token = await this.oauth.getToken();
|
|
419
|
-
this.oauthFallback = false;
|
|
420
|
-
return token;
|
|
421
|
-
} catch {
|
|
422
|
-
if (!this.config.serviceToken) {
|
|
423
|
-
throw new DatabrainApiError(
|
|
424
|
-
0,
|
|
425
|
-
"OAUTH_TOKEN_ERROR",
|
|
426
|
-
"OAuth token acquisition failed. Check the configured issuer, client credentials, resource and scopes."
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
if (!this.oauthFallback) logger.warn("OAuth unavailable; using the configured static service token.");
|
|
430
|
-
this.oauthFallback = true;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return this.config.serviceToken;
|
|
434
|
-
}
|
|
435
|
-
return this.runtimeApiToken || this.config.apiToken;
|
|
436
|
-
}
|
|
437
|
-
async request(options) {
|
|
438
|
-
return this.requestInternal(options);
|
|
439
|
-
}
|
|
440
|
-
async requestInternal(options) {
|
|
441
|
-
const usesOAuth = !!this.oauth && options.auth === "service" && !options.token;
|
|
442
|
-
const cacheAllowed = !usesOAuth;
|
|
443
|
-
if (options.invalidateCache) {
|
|
444
|
-
for (const prefix of options.invalidateCache) {
|
|
445
|
-
this.cache.invalidateByPrefix(prefix);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (cacheAllowed && options.cacheKey && options.cacheTtlMs) {
|
|
449
|
-
const cached = this.cache.get(options.cacheKey);
|
|
450
|
-
if (cached !== void 0) {
|
|
451
|
-
logger.debug(`Cache hit: ${options.cacheKey}`);
|
|
452
|
-
return cached;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
let token = options.token ?? await this.getToken(options.auth);
|
|
456
|
-
if (options.auth !== "none" && !options.token && !token) {
|
|
457
|
-
if (options.auth === "service") {
|
|
458
|
-
throw new DatabrainApiError(
|
|
459
|
-
401,
|
|
460
|
-
"MISSING_SERVICE_TOKEN",
|
|
461
|
-
"DATABRAIN_SERVICE_TOKEN is required for this operation (org-level token for discovery and data app management). It must be set in the MCP server configuration \u2014 do not ask the user for it at runtime."
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
throw new DatabrainApiError(
|
|
465
|
-
401,
|
|
466
|
-
"MISSING_API_TOKEN",
|
|
467
|
-
"A per-data-app API token is required for this embedding operation but none is configured. Call `create_api_token` to generate one, or `set_api_token` if you already have the value. Do not ask the user for this token \u2014 create it via the MCP tools."
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
let url = `${this.config.apiUrl}${options.path}`;
|
|
471
|
-
if (options.query) {
|
|
472
|
-
const params = new URLSearchParams(options.query);
|
|
473
|
-
url += `?${params.toString()}`;
|
|
474
|
-
}
|
|
475
|
-
let lastError;
|
|
476
|
-
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
477
|
-
try {
|
|
478
|
-
if (usesOAuth && attempt > 1) token = await this.getToken(options.auth);
|
|
479
|
-
const headers = {
|
|
480
|
-
"Content-Type": "application/json",
|
|
481
|
-
Accept: "application/json",
|
|
482
|
-
"User-Agent": MCP_USER_AGENT,
|
|
483
|
-
"X-Databrain-Client": MCP_USER_AGENT,
|
|
484
|
-
...options.headers
|
|
485
|
-
};
|
|
486
|
-
const ctx = toolContext.getStore();
|
|
487
|
-
if (ctx) {
|
|
488
|
-
headers["X-Databrain-MCP-Tool"] = ctx.tool;
|
|
489
|
-
headers["X-Databrain-MCP-Tool-Category"] = ctx.category;
|
|
490
|
-
headers["X-Databrain-MCP-Workflow-Intent"] = ctx.workflowIntent;
|
|
491
|
-
headers["X-Databrain-MCP-Input-Mode"] = ctx.inputMode;
|
|
492
|
-
}
|
|
493
|
-
if (token) {
|
|
494
|
-
if (options.auth === "none") {
|
|
495
|
-
headers["x-plugin-token"] = token;
|
|
496
|
-
headers["Origin"] = "https://mcp.usedatabrain.com";
|
|
497
|
-
} else {
|
|
498
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
const response = await fetch(url, {
|
|
502
|
-
method: options.method,
|
|
503
|
-
headers,
|
|
504
|
-
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
505
|
-
signal: AbortSignal.timeout(options.timeoutMs || REQUEST_TIMEOUT_MS),
|
|
506
|
-
...usesOAuth ? { redirect: "error" } : {}
|
|
507
|
-
});
|
|
508
|
-
if (!response.ok) {
|
|
509
|
-
if (usesOAuth) {
|
|
510
|
-
throw new DatabrainApiError(
|
|
511
|
-
response.status,
|
|
512
|
-
"OAUTH_API_ERROR",
|
|
513
|
-
`Databrain API request failed with status ${response.status} using configured service credentials.`
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
const errorBody = await response.text();
|
|
517
|
-
logger.warn(`[API Error] ${options.method} ${options.path} status=${response.status} body=${errorBody.slice(0, 500)}`);
|
|
518
|
-
let parsed = {};
|
|
519
|
-
try {
|
|
520
|
-
parsed = JSON.parse(errorBody);
|
|
521
|
-
} catch {
|
|
522
|
-
}
|
|
523
|
-
const errorMsg = parsed.message || parsed.detail || (typeof parsed.error === "string" ? parsed.error : JSON.stringify(parsed.error)) || `API request failed with status ${response.status}`;
|
|
524
|
-
const error = new DatabrainApiError(
|
|
525
|
-
response.status,
|
|
526
|
-
parsed.code || `HTTP_${response.status}`,
|
|
527
|
-
errorMsg,
|
|
528
|
-
parsed
|
|
529
|
-
);
|
|
530
|
-
const isTransient = response.status === 429 || response.status >= 500 && !NON_TRANSIENT_PATTERNS.some((p) => errorMsg.includes(p));
|
|
531
|
-
if (isTransient && attempt < MAX_RETRIES) {
|
|
532
|
-
const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 8e3);
|
|
533
|
-
logger.warn(`Retrying in ${delay}ms (attempt ${attempt}/${MAX_RETRIES})`, {
|
|
534
|
-
status: response.status,
|
|
535
|
-
path: options.path
|
|
536
|
-
});
|
|
537
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
538
|
-
lastError = error;
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
throw error;
|
|
542
|
-
}
|
|
543
|
-
const text = await response.text();
|
|
544
|
-
let data = text ? JSON.parse(text) : {};
|
|
545
|
-
if (!options.skipDataUnwrap && data && typeof data === "object" && !Array.isArray(data) && "data" in data) {
|
|
546
|
-
const inner = data.data;
|
|
547
|
-
if (inner !== null && inner !== void 0) {
|
|
548
|
-
data = inner;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
if (cacheAllowed && options.cacheKey && options.cacheTtlMs) {
|
|
552
|
-
this.cache.set(options.cacheKey, data, options.cacheTtlMs);
|
|
553
|
-
}
|
|
554
|
-
return data;
|
|
555
|
-
} catch (err) {
|
|
556
|
-
if (err instanceof DatabrainApiError) throw err;
|
|
557
|
-
if (attempt < MAX_RETRIES && err?.name === "TimeoutError" && !options.noRetryOnTimeout) {
|
|
558
|
-
const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 8e3);
|
|
559
|
-
logger.warn(`Request timed out, retrying in ${delay}ms`, { path: options.path });
|
|
560
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
561
|
-
lastError = err;
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
throw new DatabrainApiError(
|
|
565
|
-
0,
|
|
566
|
-
"NETWORK_ERROR",
|
|
567
|
-
usesOAuth ? "Failed to connect to Databrain API using configured service credentials." : `Failed to connect to Databrain API: ${err.message}`
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
throw lastError || new DatabrainApiError(0, "UNKNOWN", "Request failed after retries");
|
|
572
|
-
}
|
|
573
|
-
};
|
|
2
|
+
import {
|
|
3
|
+
CustomChartsApi,
|
|
4
|
+
DatabrainApiError,
|
|
5
|
+
apiProblems,
|
|
6
|
+
buildProject,
|
|
7
|
+
bundleSizeBytes,
|
|
8
|
+
contentHash,
|
|
9
|
+
createCustomChartsApi,
|
|
10
|
+
logger,
|
|
11
|
+
optionSchema,
|
|
12
|
+
planUpload,
|
|
13
|
+
readDist,
|
|
14
|
+
scaffoldProject,
|
|
15
|
+
slotSchema,
|
|
16
|
+
stringify,
|
|
17
|
+
toolContext,
|
|
18
|
+
toolError,
|
|
19
|
+
toolSuccess,
|
|
20
|
+
validateBundle
|
|
21
|
+
} from "./chunk-PRE4CGOV.js";
|
|
574
22
|
|
|
575
23
|
// src/apps/contract.ts
|
|
576
|
-
import { z
|
|
24
|
+
import { z } from "zod";
|
|
577
25
|
var CHART_URI = "ui://databrain/chart-tile";
|
|
578
26
|
var KPI_URI = "ui://databrain/kpi-tile";
|
|
579
27
|
var APP_MIME = "text/html;profile=mcp-app";
|
|
580
28
|
var RESOURCE_URI_META_KEY = "ui/resourceUri";
|
|
581
29
|
var PAGE_SIZE = 50;
|
|
582
30
|
var MAX_RESULT_CHARS = 125e3;
|
|
583
|
-
var id =
|
|
584
|
-
var cell =
|
|
585
|
-
var rowsSchema =
|
|
586
|
-
var referenceSchema =
|
|
587
|
-
var point =
|
|
31
|
+
var id = z.string().trim().min(1).max(256);
|
|
32
|
+
var cell = z.union([z.string().max(2e3), z.number().finite(), z.boolean(), z.null()]);
|
|
33
|
+
var rowsSchema = z.array(z.record(cell)).max(PAGE_SIZE);
|
|
34
|
+
var referenceSchema = z.object({ embedId: id, clientId: id, widgetId: id }).strict();
|
|
35
|
+
var point = z.object({
|
|
588
36
|
key: id,
|
|
589
|
-
label:
|
|
37
|
+
label: z.string().max(2e3),
|
|
590
38
|
value: cell,
|
|
591
|
-
x:
|
|
592
|
-
y:
|
|
593
|
-
width:
|
|
594
|
-
height:
|
|
39
|
+
x: z.number().min(0),
|
|
40
|
+
y: z.number().min(0),
|
|
41
|
+
width: z.number().positive(),
|
|
42
|
+
height: z.number().positive(),
|
|
595
43
|
drillId: id.optional()
|
|
596
44
|
}).strict();
|
|
597
|
-
var dateRangeSchema =
|
|
598
|
-
from:
|
|
599
|
-
to:
|
|
45
|
+
var dateRangeSchema = z.object({
|
|
46
|
+
from: z.string().date(),
|
|
47
|
+
to: z.string().date()
|
|
600
48
|
}).strict().refine((value) => value.from <= value.to, "Date range must be ordered");
|
|
601
|
-
var viewSchema =
|
|
602
|
-
cursor:
|
|
49
|
+
var viewSchema = z.object({
|
|
50
|
+
cursor: z.string().min(1).max(2048).optional(),
|
|
603
51
|
dateRange: dateRangeSchema.optional(),
|
|
604
52
|
drillId: id.optional()
|
|
605
53
|
}).strict();
|
|
606
|
-
var pageSchema =
|
|
607
|
-
kind:
|
|
608
|
-
title:
|
|
609
|
-
chartSpec:
|
|
54
|
+
var pageSchema = z.object({
|
|
55
|
+
kind: z.enum(["chart", "kpi"]),
|
|
56
|
+
title: z.string().max(500),
|
|
57
|
+
chartSpec: z.object({ type: z.string().max(80) }).strict(),
|
|
610
58
|
rows: rowsSchema,
|
|
611
|
-
columns:
|
|
612
|
-
svg:
|
|
613
|
-
width:
|
|
614
|
-
height:
|
|
615
|
-
points:
|
|
59
|
+
columns: z.array(z.string().max(128)).max(20),
|
|
60
|
+
svg: z.string().max(1e5).optional(),
|
|
61
|
+
width: z.number().int().min(240).max(1200),
|
|
62
|
+
height: z.number().int().min(120).max(800),
|
|
63
|
+
points: z.array(point).max(PAGE_SIZE),
|
|
616
64
|
value: cell.optional(),
|
|
617
|
-
nextCursor:
|
|
65
|
+
nextCursor: z.string().min(1).max(2048).optional()
|
|
618
66
|
}).strict().superRefine((page, ctx) => {
|
|
619
67
|
if (page.kind === "chart" && !page.svg) ctx.addIssue({ code: "custom", message: "SVG required" });
|
|
620
68
|
if (page.kind === "kpi" && (page.svg || page.points.length || page.value === void 0)) {
|
|
@@ -664,70 +112,95 @@ function chartResult(summary, payload) {
|
|
|
664
112
|
// src/tools/tool-metadata.ts
|
|
665
113
|
var DEFAULT_TOOL_META = {
|
|
666
114
|
category: "discovery",
|
|
667
|
-
workflowIntent: "other"
|
|
115
|
+
workflowIntent: "other",
|
|
116
|
+
// Fail closed: an unregistered/unclassified tool is treated as a commit
|
|
117
|
+
// until someone deliberately marks it read or view.
|
|
118
|
+
effect: "commit"
|
|
668
119
|
};
|
|
669
120
|
var TOOL_METADATA = {
|
|
670
121
|
// Identity / discovery
|
|
671
|
-
whoami: { category: "discovery", workflowIntent: "discovery" },
|
|
122
|
+
whoami: { category: "discovery", workflowIntent: "discovery", effect: "read" },
|
|
672
123
|
// Infrastructure / data setup
|
|
673
|
-
list_datasources: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
674
|
-
discover_datasource_schema: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
675
|
-
list_datamarts: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
676
|
-
list_workspaces: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
677
|
-
|
|
678
|
-
|
|
124
|
+
list_datasources: { category: "infrastructure", workflowIntent: "setup_data", effect: "read" },
|
|
125
|
+
discover_datasource_schema: { category: "infrastructure", workflowIntent: "setup_data", effect: "read" },
|
|
126
|
+
list_datamarts: { category: "infrastructure", workflowIntent: "setup_data", effect: "read" },
|
|
127
|
+
list_workspaces: { category: "infrastructure", workflowIntent: "setup_data", effect: "read" },
|
|
128
|
+
// Triggers a backend sync job that refreshes stored schema metadata — a real mutation, not a read.
|
|
129
|
+
sync_datasource: { category: "infrastructure", workflowIntent: "setup_data", effect: "commit" },
|
|
130
|
+
// dry_run is the default, but mode:"apply" performs a real POST that creates the datamart.
|
|
131
|
+
create_datamart: { category: "infrastructure", workflowIntent: "setup_data", effect: "commit" },
|
|
679
132
|
// Embedding — data apps & tokens
|
|
680
|
-
list_data_apps: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
681
|
-
list_tenants: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
682
|
-
create_data_app: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
683
|
-
list_api_tokens: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
684
|
-
create_api_token: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
685
|
-
rotate_api_token: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
133
|
+
list_data_apps: { category: "embedding", workflowIntent: "embed_dashboard", effect: "read" },
|
|
134
|
+
list_tenants: { category: "embedding", workflowIntent: "embed_dashboard", effect: "read" },
|
|
135
|
+
create_data_app: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
136
|
+
list_api_tokens: { category: "embedding", workflowIntent: "embed_dashboard", effect: "read" },
|
|
137
|
+
create_api_token: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
138
|
+
rotate_api_token: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
686
139
|
// Embedding — embed management
|
|
687
|
-
create_embed: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
688
|
-
list_embeds: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
689
|
-
get_embed_details: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
690
|
-
update_embed: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
691
|
-
|
|
692
|
-
|
|
140
|
+
create_embed: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
141
|
+
list_embeds: { category: "embedding", workflowIntent: "embed_dashboard", effect: "read" },
|
|
142
|
+
get_embed_details: { category: "embedding", workflowIntent: "embed_dashboard", effect: "read" },
|
|
143
|
+
update_embed: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
144
|
+
// Persists theme/access config on the embed record — a real write, not a display-only tweak.
|
|
145
|
+
customize_embed_theme: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
146
|
+
// Mints and returns a live guest token embedded in the demo URL.
|
|
147
|
+
get_demo_link: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
693
148
|
// Embedding — frontend
|
|
694
|
-
generate_guest_token: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
695
|
-
generate_embed_code: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
149
|
+
generate_guest_token: { category: "embedding", workflowIntent: "embed_dashboard", effect: "commit" },
|
|
150
|
+
generate_embed_code: { category: "embedding", workflowIntent: "embed_dashboard", effect: "read" },
|
|
696
151
|
// Embedding — customer dashboards & widgets
|
|
697
|
-
list_dashboards: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
698
|
-
create_dashboard: { category: "embedding", workflowIntent: "author_dashboard" },
|
|
699
|
-
get_dashboard: { category: "embedding", workflowIntent: "author_dashboard" },
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
152
|
+
list_dashboards: { category: "embedding", workflowIntent: "embedded_widget", effect: "read" },
|
|
153
|
+
create_dashboard: { category: "embedding", workflowIntent: "author_dashboard", effect: "commit" },
|
|
154
|
+
get_dashboard: { category: "embedding", workflowIntent: "author_dashboard", effect: "read" },
|
|
155
|
+
// Does not persist a dashboard, but mints an internal guest token to render tile previews.
|
|
156
|
+
generate_dashboard_tiles: { category: "embedding", workflowIntent: "author_dashboard", effect: "commit" },
|
|
157
|
+
list_widgets: { category: "embedding", workflowIntent: "embedded_widget", effect: "read" },
|
|
158
|
+
add_widget: { category: "embedding", workflowIntent: "embedded_widget", effect: "commit" },
|
|
159
|
+
update_widget: { category: "embedding", workflowIntent: "embedded_widget", effect: "commit" },
|
|
160
|
+
remove_widget: { category: "embedding", workflowIntent: "embedded_widget", effect: "commit" },
|
|
161
|
+
// Returns an unsaved draft, but mints an internal write-capable guest token to generate it.
|
|
162
|
+
generate_widget: { category: "embedding", workflowIntent: "embedded_widget", effect: "commit" },
|
|
163
|
+
// Dry-run validation only, but mints an internal write-capable guest token to check conflicts.
|
|
164
|
+
validate_metric_spec: { category: "embedding", workflowIntent: "sql_to_embedded_metric", effect: "commit" },
|
|
707
165
|
create_or_update_metric_from_query: {
|
|
708
166
|
category: "embedding",
|
|
709
|
-
workflowIntent: "sql_to_embedded_metric"
|
|
167
|
+
workflowIntent: "sql_to_embedded_metric",
|
|
168
|
+
effect: "commit"
|
|
710
169
|
},
|
|
711
170
|
// Internal analytics
|
|
712
171
|
create_workspace_metric: {
|
|
713
172
|
category: "internal_analytics",
|
|
714
|
-
workflowIntent: "sql_to_workspace_metric"
|
|
173
|
+
workflowIntent: "sql_to_workspace_metric",
|
|
174
|
+
effect: "commit"
|
|
715
175
|
},
|
|
716
|
-
|
|
176
|
+
// Runs a query and returns rows; no dashboard/widget/row is persisted.
|
|
177
|
+
ask_question: { category: "internal_analytics", workflowIntent: "query_data", effect: "read" },
|
|
178
|
+
preview_chart: { category: "internal_analytics", workflowIntent: "query_data", effect: "view" },
|
|
179
|
+
save_chart_as_dashboard: { category: "internal_analytics", workflowIntent: "author_dashboard", effect: "commit" },
|
|
717
180
|
// Semantic layer
|
|
718
|
-
get_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data" },
|
|
719
|
-
update_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data" },
|
|
720
|
-
start_semantic_layer_generation: { category: "semantic_layer", workflowIntent: "query_data" },
|
|
181
|
+
get_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
|
|
182
|
+
update_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data", effect: "commit" },
|
|
183
|
+
start_semantic_layer_generation: { category: "semantic_layer", workflowIntent: "query_data", effect: "commit" },
|
|
721
184
|
get_semantic_layer_generation_status: {
|
|
722
185
|
category: "semantic_layer",
|
|
723
|
-
workflowIntent: "query_data"
|
|
186
|
+
workflowIntent: "query_data",
|
|
187
|
+
effect: "read"
|
|
724
188
|
},
|
|
725
189
|
// Reports
|
|
726
|
-
list_scheduled_reports: { category: "reports", workflowIntent: "reports" }
|
|
190
|
+
list_scheduled_reports: { category: "reports", workflowIntent: "reports", effect: "read" },
|
|
191
|
+
// Custom chart types (DAT-925)
|
|
192
|
+
// Returns template files; nothing is written or persisted.
|
|
193
|
+
custom_chart_scaffold: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
|
|
194
|
+
// Local rules plus POST /validate, which never persists.
|
|
195
|
+
custom_chart_validate: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
|
|
196
|
+
// dry_run is the default, but mode:"apply" creates a chart or a version (and may publish).
|
|
197
|
+
custom_chart_upload: { category: "custom_charts", workflowIntent: "custom_chart", effect: "commit" },
|
|
198
|
+
list_custom_charts: { category: "custom_charts", workflowIntent: "custom_chart", effect: "read" },
|
|
199
|
+
publish_custom_chart: { category: "custom_charts", workflowIntent: "custom_chart", effect: "commit" }
|
|
727
200
|
};
|
|
728
201
|
|
|
729
202
|
// src/tools/data-app/list-data-apps.ts
|
|
730
|
-
import { z as
|
|
203
|
+
import { z as z2 } from "zod";
|
|
731
204
|
|
|
732
205
|
// src/api/dashboards-api.ts
|
|
733
206
|
var DashboardsApi = class {
|
|
@@ -1022,7 +495,7 @@ var QueryApi = class {
|
|
|
1022
495
|
async startSemanticLayerGeneration(datamartId) {
|
|
1023
496
|
return this.client.request({
|
|
1024
497
|
method: "POST",
|
|
1025
|
-
path: `/
|
|
498
|
+
path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId)}/semantic-layer/generate`,
|
|
1026
499
|
auth: "service",
|
|
1027
500
|
invalidateCache: ["semantic_layer_"]
|
|
1028
501
|
});
|
|
@@ -1030,7 +503,7 @@ var QueryApi = class {
|
|
|
1030
503
|
async getSemanticLayerGenerationStatus(datamartId) {
|
|
1031
504
|
return this.client.request({
|
|
1032
505
|
method: "GET",
|
|
1033
|
-
path: `/
|
|
506
|
+
path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId)}/semantic-layer/generation-status`,
|
|
1034
507
|
auth: "service"
|
|
1035
508
|
});
|
|
1036
509
|
}
|
|
@@ -1999,6 +1472,7 @@ function createServiceBundle(client) {
|
|
|
1999
1472
|
const guestTokenApi = new GuestTokenApi(client);
|
|
2000
1473
|
const queryApi = new QueryApi(client);
|
|
2001
1474
|
const scheduledReportsApi = new ScheduledReportsApi(client);
|
|
1475
|
+
const customChartsApi = new CustomChartsApi(client);
|
|
2002
1476
|
const tenantsApi = new TenantsApi(client);
|
|
2003
1477
|
const widgetsApi = new WidgetsApi(client);
|
|
2004
1478
|
const workspacesApi = new WorkspacesApi(client);
|
|
@@ -2013,6 +1487,7 @@ function createServiceBundle(client) {
|
|
|
2013
1487
|
workspacesApi,
|
|
2014
1488
|
queryApi,
|
|
2015
1489
|
scheduledReportsApi,
|
|
1490
|
+
customChartsApi,
|
|
2016
1491
|
tenantsApi,
|
|
2017
1492
|
widgetsApi,
|
|
2018
1493
|
embedService: new EmbedService(embedsApi, datamartsApi),
|
|
@@ -2063,7 +1538,7 @@ function registerListDataApps(server, client) {
|
|
|
2063
1538
|
"list_data_apps",
|
|
2064
1539
|
"List data apps in your organization. Start here for embed setup.",
|
|
2065
1540
|
{
|
|
2066
|
-
workspaceName:
|
|
1541
|
+
workspaceName: z2.string().optional().describe("Filter by workspace name")
|
|
2067
1542
|
},
|
|
2068
1543
|
READ_ONLY,
|
|
2069
1544
|
async (params) => {
|
|
@@ -2092,7 +1567,7 @@ function registerListDataApps(server, client) {
|
|
|
2092
1567
|
}
|
|
2093
1568
|
|
|
2094
1569
|
// src/tools/data-app/list-tenants.ts
|
|
2095
|
-
import { z as
|
|
1570
|
+
import { z as z3 } from "zod";
|
|
2096
1571
|
var isNonEmptyString4 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2097
1572
|
function workspaceIdFrom(workspace) {
|
|
2098
1573
|
return isNonEmptyString4(workspace.id) && workspace.id || isNonEmptyString4(workspace._id) && workspace._id || isNonEmptyString4(workspace.workspaceId) && workspace.workspaceId || null;
|
|
@@ -2130,12 +1605,12 @@ function registerListTenants(server, client) {
|
|
|
2130
1605
|
"list_tenants",
|
|
2131
1606
|
"List available tenants/clients from the workspace tenancy database. Use this to pick a valid clientId before creating guest tokens or editing customer dashboards.",
|
|
2132
1607
|
{
|
|
2133
|
-
workspaceName:
|
|
2134
|
-
workspaceId:
|
|
2135
|
-
searchValue:
|
|
2136
|
-
limit:
|
|
2137
|
-
datasourceId:
|
|
2138
|
-
datamartId:
|
|
1608
|
+
workspaceName: z3.string().optional().describe("Workspace name. Preferred for non-technical callers; the tool resolves it to a workspaceId."),
|
|
1609
|
+
workspaceId: z3.string().optional().describe("Technical workspace UUID. Provide this instead of workspaceName if already known."),
|
|
1610
|
+
searchValue: z3.string().optional().describe("Optional case-insensitive search string for tenant/client labels."),
|
|
1611
|
+
limit: z3.number().int().positive().max(500).optional().describe("Maximum number of tenants to return. Backend may apply connector-specific limits."),
|
|
1612
|
+
datasourceId: z3.string().optional().describe("Optional datasource UUID override when the workspace has multiple datasource contexts."),
|
|
1613
|
+
datamartId: z3.string().optional().describe("Optional datamart UUID override when tenant lookup should use a specific datamart.")
|
|
2139
1614
|
},
|
|
2140
1615
|
READ_ONLY,
|
|
2141
1616
|
async (params) => {
|
|
@@ -2229,15 +1704,15 @@ function registerListTenants(server, client) {
|
|
|
2229
1704
|
}
|
|
2230
1705
|
|
|
2231
1706
|
// src/tools/data-app/create-data-app.ts
|
|
2232
|
-
import { z as
|
|
1707
|
+
import { z as z4 } from "zod";
|
|
2233
1708
|
function registerCreateDataApp(server, client) {
|
|
2234
1709
|
server.tool(
|
|
2235
1710
|
"create_data_app",
|
|
2236
1711
|
"Create a new data app.",
|
|
2237
1712
|
{
|
|
2238
|
-
name:
|
|
2239
|
-
workspaceName:
|
|
2240
|
-
body:
|
|
1713
|
+
name: z4.string().describe("Data app name"),
|
|
1714
|
+
workspaceName: z4.string().optional().describe("Optional workspace name if required by backend"),
|
|
1715
|
+
body: z4.record(z4.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/")
|
|
2241
1716
|
},
|
|
2242
1717
|
WRITE_CREATE,
|
|
2243
1718
|
async (params) => {
|
|
@@ -2271,7 +1746,7 @@ function registerCreateDataApp(server, client) {
|
|
|
2271
1746
|
}
|
|
2272
1747
|
|
|
2273
1748
|
// src/tools/data-app/list-api-tokens.ts
|
|
2274
|
-
import { z as
|
|
1749
|
+
import { z as z5 } from "zod";
|
|
2275
1750
|
|
|
2276
1751
|
// src/utils/resolve-resource.ts
|
|
2277
1752
|
function resolveResource(options) {
|
|
@@ -2340,7 +1815,7 @@ function registerListApiTokens(server, client) {
|
|
|
2340
1815
|
"list_api_tokens",
|
|
2341
1816
|
"List API tokens for a data app. Omit dataAppName to auto-resolve.",
|
|
2342
1817
|
{
|
|
2343
|
-
dataAppName:
|
|
1818
|
+
dataAppName: z5.string().optional().describe("Data app name. Omit to list available data apps.")
|
|
2344
1819
|
},
|
|
2345
1820
|
READ_ONLY,
|
|
2346
1821
|
async (params) => {
|
|
@@ -2385,16 +1860,16 @@ function registerListApiTokens(server, client) {
|
|
|
2385
1860
|
}
|
|
2386
1861
|
|
|
2387
1862
|
// src/tools/data-app/create-api-token.ts
|
|
2388
|
-
import { z as
|
|
1863
|
+
import { z as z6 } from "zod";
|
|
2389
1864
|
function registerCreateApiToken(server, client) {
|
|
2390
1865
|
const services = createServiceBundle(client);
|
|
2391
1866
|
server.tool(
|
|
2392
1867
|
"create_api_token",
|
|
2393
1868
|
"Create an API token for a data app and activate it for this session. Omit dataAppName to auto-resolve.",
|
|
2394
1869
|
{
|
|
2395
|
-
dataAppName:
|
|
2396
|
-
name:
|
|
2397
|
-
body:
|
|
1870
|
+
dataAppName: z6.string().optional().describe("Data app name. Omit to list available data apps."),
|
|
1871
|
+
name: z6.string().optional().describe("Optional token name"),
|
|
1872
|
+
body: z6.record(z6.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/api-tokens")
|
|
2398
1873
|
},
|
|
2399
1874
|
WRITE_CREATE,
|
|
2400
1875
|
async (params) => {
|
|
@@ -2446,16 +1921,16 @@ Save this in backend secrets immediately. This value may not be returned again.`
|
|
|
2446
1921
|
}
|
|
2447
1922
|
|
|
2448
1923
|
// src/tools/data-app/rotate-api-token.ts
|
|
2449
|
-
import { z as
|
|
1924
|
+
import { z as z7 } from "zod";
|
|
2450
1925
|
function registerRotateApiToken(server, client) {
|
|
2451
1926
|
const services = createServiceBundle(client);
|
|
2452
1927
|
server.tool(
|
|
2453
1928
|
"rotate_api_token",
|
|
2454
1929
|
"Rotate (invalidate and replace) an API token. Previous token becomes stale.",
|
|
2455
1930
|
{
|
|
2456
|
-
dataAppName:
|
|
2457
|
-
tokenId:
|
|
2458
|
-
body:
|
|
1931
|
+
dataAppName: z7.string().optional().describe("Data app name. Omit to list available data apps."),
|
|
1932
|
+
tokenId: z7.string().optional().describe("Optional token ID to rotate"),
|
|
1933
|
+
body: z7.record(z7.unknown()).optional().describe("Raw API body passthrough for rotate endpoint")
|
|
2459
1934
|
},
|
|
2460
1935
|
DESTRUCTIVE,
|
|
2461
1936
|
async (params) => {
|
|
@@ -2574,14 +2049,14 @@ function registerListDatasources(server, client) {
|
|
|
2574
2049
|
}
|
|
2575
2050
|
|
|
2576
2051
|
// src/tools/infrastructure/discover-datasource-schema.ts
|
|
2577
|
-
import { z as
|
|
2052
|
+
import { z as z8 } from "zod";
|
|
2578
2053
|
function registerDiscoverDatasourceSchema(server, client) {
|
|
2579
2054
|
const services = createServiceBundle(client);
|
|
2580
2055
|
server.tool(
|
|
2581
2056
|
"discover_datasource_schema",
|
|
2582
2057
|
"Inspect tables and columns in a datasource. Omit datasourceName to list available datasources.",
|
|
2583
2058
|
{
|
|
2584
|
-
datasourceName:
|
|
2059
|
+
datasourceName: z8.string().optional().describe(
|
|
2585
2060
|
"Name of the datasource to inspect. Omit to list available datasources."
|
|
2586
2061
|
)
|
|
2587
2062
|
},
|
|
@@ -2664,13 +2139,13 @@ Fix the connection or add a new database in the **Databrain UI**.`
|
|
|
2664
2139
|
}
|
|
2665
2140
|
|
|
2666
2141
|
// src/tools/infrastructure/list-datamarts.ts
|
|
2667
|
-
import { z as
|
|
2142
|
+
import { z as z9 } from "zod";
|
|
2668
2143
|
function registerListDatamarts(server, client) {
|
|
2669
2144
|
server.tool(
|
|
2670
2145
|
"list_datamarts",
|
|
2671
2146
|
"List datamarts with table counts and semantic layer scores.",
|
|
2672
2147
|
{
|
|
2673
|
-
dataAppName:
|
|
2148
|
+
dataAppName: z9.string().optional().describe("Filter by data app name")
|
|
2674
2149
|
},
|
|
2675
2150
|
READ_ONLY,
|
|
2676
2151
|
async (params) => {
|
|
@@ -2759,7 +2234,7 @@ function registerListWorkspaces(server, client) {
|
|
|
2759
2234
|
}
|
|
2760
2235
|
|
|
2761
2236
|
// src/tools/infrastructure/create-datamart.ts
|
|
2762
|
-
import { z as
|
|
2237
|
+
import { z as z10 } from "zod";
|
|
2763
2238
|
|
|
2764
2239
|
// src/tools/widgets/production-guards.ts
|
|
2765
2240
|
var APPLY_TO_PRODUCTION_CONFIRMATION = "APPLY_TO_PRODUCTION";
|
|
@@ -2777,29 +2252,29 @@ function genericProductionSafetyNote() {
|
|
|
2777
2252
|
}
|
|
2778
2253
|
|
|
2779
2254
|
// src/tools/infrastructure/create-datamart.ts
|
|
2780
|
-
var columnSchema =
|
|
2781
|
-
name:
|
|
2782
|
-
alias:
|
|
2783
|
-
label:
|
|
2784
|
-
isHide:
|
|
2785
|
-
isCustomColumn:
|
|
2786
|
-
sql:
|
|
2255
|
+
var columnSchema = z10.object({
|
|
2256
|
+
name: z10.string(),
|
|
2257
|
+
alias: z10.string().optional(),
|
|
2258
|
+
label: z10.string().optional(),
|
|
2259
|
+
isHide: z10.boolean().optional(),
|
|
2260
|
+
isCustomColumn: z10.boolean().optional(),
|
|
2261
|
+
sql: z10.string().optional()
|
|
2787
2262
|
});
|
|
2788
|
-
var tableSchema =
|
|
2789
|
-
name:
|
|
2790
|
-
schemaName:
|
|
2791
|
-
clientColumn:
|
|
2792
|
-
label:
|
|
2793
|
-
columns:
|
|
2263
|
+
var tableSchema = z10.object({
|
|
2264
|
+
name: z10.string(),
|
|
2265
|
+
schemaName: z10.string().optional(),
|
|
2266
|
+
clientColumn: z10.string().optional(),
|
|
2267
|
+
label: z10.string().optional(),
|
|
2268
|
+
columns: z10.array(columnSchema)
|
|
2794
2269
|
});
|
|
2795
|
-
var relationshipSchema =
|
|
2796
|
-
parentTableName:
|
|
2797
|
-
parentColumnName:
|
|
2798
|
-
childTableName:
|
|
2799
|
-
childColumnName:
|
|
2800
|
-
cardinality:
|
|
2801
|
-
join:
|
|
2802
|
-
relationshipName:
|
|
2270
|
+
var relationshipSchema = z10.object({
|
|
2271
|
+
parentTableName: z10.string(),
|
|
2272
|
+
parentColumnName: z10.string(),
|
|
2273
|
+
childTableName: z10.string(),
|
|
2274
|
+
childColumnName: z10.string(),
|
|
2275
|
+
cardinality: z10.enum(["OneToOne", "OneToMany", "ManyToOne", "ManyToMany"]),
|
|
2276
|
+
join: z10.enum(["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN"]),
|
|
2277
|
+
relationshipName: z10.string()
|
|
2803
2278
|
});
|
|
2804
2279
|
function datamartSuccessMessage(name, result) {
|
|
2805
2280
|
return `Datamart "${name}" created successfully (ID: ${result.id || result._id || "UNKNOWN"}).
|
|
@@ -2820,21 +2295,21 @@ function registerCreateDatamart(server, client) {
|
|
|
2820
2295
|
"create_datamart",
|
|
2821
2296
|
"Create a production datamart from selected tables and relationships. Defaults to dry-run; apply requires explicit confirmation.",
|
|
2822
2297
|
{
|
|
2823
|
-
mode:
|
|
2824
|
-
confirm:
|
|
2825
|
-
name:
|
|
2826
|
-
datasourceName:
|
|
2827
|
-
tableList:
|
|
2828
|
-
relationships:
|
|
2829
|
-
tenancySettings:
|
|
2830
|
-
tenancyLevel:
|
|
2831
|
-
clientColumnType:
|
|
2832
|
-
schemaName:
|
|
2833
|
-
tableName:
|
|
2834
|
-
tableClientNameColumn:
|
|
2835
|
-
tablePrimaryKeyColumn:
|
|
2298
|
+
mode: z10.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run returns the plan only; apply creates the datamart"),
|
|
2299
|
+
confirm: z10.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
2300
|
+
name: z10.string().describe("Unique datamart name"),
|
|
2301
|
+
datasourceName: z10.string().optional().describe("Name of the datasource to use. Omit to list available datasources."),
|
|
2302
|
+
tableList: z10.array(tableSchema).optional().describe("Array of tables with their columns"),
|
|
2303
|
+
relationships: z10.array(relationshipSchema).optional().describe("Join definitions between tables"),
|
|
2304
|
+
tenancySettings: z10.object({
|
|
2305
|
+
tenancyLevel: z10.enum(["TABLE", "DATABASE"]).optional(),
|
|
2306
|
+
clientColumnType: z10.string().optional(),
|
|
2307
|
+
schemaName: z10.string().optional(),
|
|
2308
|
+
tableName: z10.string().optional(),
|
|
2309
|
+
tableClientNameColumn: z10.string().optional(),
|
|
2310
|
+
tablePrimaryKeyColumn: z10.string().optional()
|
|
2836
2311
|
}).optional().describe("Multi-tenant configuration"),
|
|
2837
|
-
body:
|
|
2312
|
+
body: z10.record(z10.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/datamarts")
|
|
2838
2313
|
},
|
|
2839
2314
|
WRITE_CREATE,
|
|
2840
2315
|
async (params) => {
|
|
@@ -2929,14 +2404,14 @@ function registerCreateDatamart(server, client) {
|
|
|
2929
2404
|
}
|
|
2930
2405
|
|
|
2931
2406
|
// src/tools/infrastructure/sync-datasource.ts
|
|
2932
|
-
import { z as
|
|
2407
|
+
import { z as z11 } from "zod";
|
|
2933
2408
|
function registerSyncDatasource(server, client) {
|
|
2934
2409
|
const datasourcesApi = new DatasourcesApi(client);
|
|
2935
2410
|
server.tool(
|
|
2936
2411
|
"sync_datasource",
|
|
2937
2412
|
"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.",
|
|
2938
2413
|
{
|
|
2939
|
-
datasourceName:
|
|
2414
|
+
datasourceName: z11.string().optional().describe("Name of the datasource to sync. Omit to list available datasources.")
|
|
2940
2415
|
},
|
|
2941
2416
|
WRITE_IDEMPOTENT,
|
|
2942
2417
|
async (params) => {
|
|
@@ -2985,7 +2460,7 @@ The schema metadata will be refreshed. You can now use \`discover_datasource_sch
|
|
|
2985
2460
|
}
|
|
2986
2461
|
|
|
2987
2462
|
// src/tools/embed/create-embed.ts
|
|
2988
|
-
import { z as
|
|
2463
|
+
import { z as z12 } from "zod";
|
|
2989
2464
|
var DEFAULT_ACCESS_SETTINGS2 = {
|
|
2990
2465
|
isAllowMetricCreation: false,
|
|
2991
2466
|
isAllowMetricDeletion: false,
|
|
@@ -2998,18 +2473,18 @@ var DEFAULT_ACCESS_SETTINGS2 = {
|
|
|
2998
2473
|
isAllowCreateDashboardView: false,
|
|
2999
2474
|
metricCreationMode: "DRAG_DROP"
|
|
3000
2475
|
};
|
|
3001
|
-
var accessSettingsSchema =
|
|
3002
|
-
datamartName:
|
|
3003
|
-
isAllowMetricCreation:
|
|
3004
|
-
metricCreationMode:
|
|
3005
|
-
isAllowMetricDeletion:
|
|
3006
|
-
isAllowMetricUpdate:
|
|
3007
|
-
isAllowMetricLayoutChange:
|
|
3008
|
-
isAllowManageMetrics:
|
|
3009
|
-
isAllowUnderlyingData:
|
|
3010
|
-
isAllowAiPilot:
|
|
3011
|
-
isAllowEmailReports:
|
|
3012
|
-
isAllowCreateDashboardView:
|
|
2476
|
+
var accessSettingsSchema = z12.object({
|
|
2477
|
+
datamartName: z12.string().describe("Name of the datamart (required)"),
|
|
2478
|
+
isAllowMetricCreation: z12.boolean().optional().describe("Allow end-users to create metrics"),
|
|
2479
|
+
metricCreationMode: z12.enum(["DRAG_DROP", "CHAT"]).optional().describe("Metric creation mode"),
|
|
2480
|
+
isAllowMetricDeletion: z12.boolean().optional().describe("Allow metric deletion"),
|
|
2481
|
+
isAllowMetricUpdate: z12.boolean().optional().describe("Allow metric editing"),
|
|
2482
|
+
isAllowMetricLayoutChange: z12.boolean().optional().describe("Allow layout changes"),
|
|
2483
|
+
isAllowManageMetrics: z12.boolean().optional().describe("Show manage metrics UI"),
|
|
2484
|
+
isAllowUnderlyingData: z12.boolean().optional().describe("Allow viewing underlying data"),
|
|
2485
|
+
isAllowAiPilot: z12.boolean().optional().describe("Enable AI pilot features"),
|
|
2486
|
+
isAllowEmailReports: z12.boolean().optional().describe("Allow email report scheduling"),
|
|
2487
|
+
isAllowCreateDashboardView: z12.boolean().optional().describe("Allow creating dashboard views")
|
|
3013
2488
|
}).describe("Embed access settings. Only datamartName is required \u2014 all flags default to false.");
|
|
3014
2489
|
function registerCreateEmbed(server, client) {
|
|
3015
2490
|
const services = createServiceBundle(client);
|
|
@@ -3017,23 +2492,23 @@ function registerCreateEmbed(server, client) {
|
|
|
3017
2492
|
"create_embed",
|
|
3018
2493
|
"Create an embed. Provide dashboardId, workspaceName, accessSettings.datamartName. Add clientId for multi-tenant per-client dashboards. All isAllow* flags default to false.",
|
|
3019
2494
|
{
|
|
3020
|
-
body:
|
|
2495
|
+
body: z12.record(z12.unknown()).optional().describe(
|
|
3021
2496
|
"Raw API body passthrough. If provided, all other fields are ignored."
|
|
3022
2497
|
),
|
|
3023
|
-
dashboardId:
|
|
3024
|
-
workspaceName:
|
|
3025
|
-
embedType:
|
|
3026
|
-
metricId:
|
|
3027
|
-
name:
|
|
2498
|
+
dashboardId: z12.string().optional().describe("Dashboard identifier for embed creation"),
|
|
2499
|
+
workspaceName: z12.string().optional().describe("Workspace name. Omit to auto-resolve if only one exists."),
|
|
2500
|
+
embedType: z12.enum(["dashboard", "metric"]).optional().describe("Embed type"),
|
|
2501
|
+
metricId: z12.string().optional().describe("Required for metric embed type"),
|
|
2502
|
+
name: z12.string().optional().describe("Human-readable embed config name"),
|
|
3028
2503
|
accessSettings: accessSettingsSchema.optional(),
|
|
3029
2504
|
// Multi-tenant (dashboard embed) params
|
|
3030
|
-
clientId:
|
|
2505
|
+
clientId: z12.string().optional().describe(
|
|
3031
2506
|
"Client identifier for multi-tenant isolation. When provided, creates a dashboard embed (each client gets their own dashboard copy)."
|
|
3032
2507
|
),
|
|
3033
|
-
templateDashboardId:
|
|
3034
|
-
isImportMetrics:
|
|
3035
|
-
metricImportMode:
|
|
3036
|
-
isRenameDashboard:
|
|
2508
|
+
templateDashboardId: z12.string().optional().describe("Template dashboard ID to clone metrics from"),
|
|
2509
|
+
isImportMetrics: z12.boolean().optional().describe("Import metrics from template dashboard"),
|
|
2510
|
+
metricImportMode: z12.enum(["COPY", "LINK"]).optional().describe("COPY duplicates metrics, LINK shares them"),
|
|
2511
|
+
isRenameDashboard: z12.boolean().optional().describe("Rename the dashboard for this client")
|
|
3037
2512
|
},
|
|
3038
2513
|
WRITE_CREATE,
|
|
3039
2514
|
async (params) => {
|
|
@@ -3135,14 +2610,14 @@ Next: run \`get_embed_details\` to verify the embed configuration.`
|
|
|
3135
2610
|
}
|
|
3136
2611
|
|
|
3137
2612
|
// src/tools/embed/list-embeds.ts
|
|
3138
|
-
import { z as
|
|
2613
|
+
import { z as z13 } from "zod";
|
|
3139
2614
|
function registerListEmbeds(server, client) {
|
|
3140
2615
|
const services = createServiceBundle(client);
|
|
3141
2616
|
server.tool(
|
|
3142
2617
|
"list_embeds",
|
|
3143
2618
|
"List embed configurations. Requires an API token (use create_api_token first). Use `list_tenants` for authoritative client discovery from the tenancy database.",
|
|
3144
2619
|
{
|
|
3145
|
-
dataAppName:
|
|
2620
|
+
dataAppName: z13.string().optional().describe("Filter by data app name")
|
|
3146
2621
|
},
|
|
3147
2622
|
READ_ONLY,
|
|
3148
2623
|
async (params) => {
|
|
@@ -3193,14 +2668,14 @@ function registerListEmbeds(server, client) {
|
|
|
3193
2668
|
}
|
|
3194
2669
|
|
|
3195
2670
|
// src/tools/embed/get-embed-details.ts
|
|
3196
|
-
import { z as
|
|
2671
|
+
import { z as z14 } from "zod";
|
|
3197
2672
|
function registerGetEmbedDetails(server, client) {
|
|
3198
2673
|
const services = createServiceBundle(client);
|
|
3199
2674
|
server.tool(
|
|
3200
2675
|
"get_embed_details",
|
|
3201
2676
|
"Get full embed configuration including access settings and theme.",
|
|
3202
2677
|
{
|
|
3203
|
-
embedId:
|
|
2678
|
+
embedId: z14.string().describe("ID of the embed")
|
|
3204
2679
|
},
|
|
3205
2680
|
READ_ONLY,
|
|
3206
2681
|
async (params) => {
|
|
@@ -3216,7 +2691,7 @@ function registerGetEmbedDetails(server, client) {
|
|
|
3216
2691
|
}
|
|
3217
2692
|
|
|
3218
2693
|
// src/tools/embed/update-embed.ts
|
|
3219
|
-
import { z as
|
|
2694
|
+
import { z as z15 } from "zod";
|
|
3220
2695
|
|
|
3221
2696
|
// src/tools/embed/helpers.ts
|
|
3222
2697
|
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.";
|
|
@@ -3254,15 +2729,15 @@ function registerUpdateEmbed(server, client) {
|
|
|
3254
2729
|
"update_embed",
|
|
3255
2730
|
"Update embed access settings, permissions, theme, or name.",
|
|
3256
2731
|
{
|
|
3257
|
-
embedId:
|
|
3258
|
-
body:
|
|
2732
|
+
embedId: z15.string().describe("ID of the embed to update"),
|
|
2733
|
+
body: z15.record(z15.unknown()).optional().describe(
|
|
3259
2734
|
"Raw API body for PUT /api/v2/data-app/embeds. If provided, it is sent directly (passthrough mode)."
|
|
3260
2735
|
),
|
|
3261
|
-
name:
|
|
3262
|
-
accessSettings:
|
|
2736
|
+
name: z15.string().optional().describe("New name for the embed"),
|
|
2737
|
+
accessSettings: z15.record(z15.unknown()).optional().describe(
|
|
3263
2738
|
"Partial update of access settings. See `get_embed_details` for current settings."
|
|
3264
2739
|
),
|
|
3265
|
-
theme:
|
|
2740
|
+
theme: z15.record(z15.unknown()).optional().describe(
|
|
3266
2741
|
"Theme configuration object. See databrain://theme-reference for the full schema."
|
|
3267
2742
|
)
|
|
3268
2743
|
},
|
|
@@ -3308,7 +2783,7 @@ function registerUpdateEmbed(server, client) {
|
|
|
3308
2783
|
}
|
|
3309
2784
|
|
|
3310
2785
|
// src/tools/embed/customize-theme.ts
|
|
3311
|
-
import { z as
|
|
2786
|
+
import { z as z16 } from "zod";
|
|
3312
2787
|
var THEME_PRESETS = {
|
|
3313
2788
|
light: {
|
|
3314
2789
|
general: {
|
|
@@ -3442,7 +2917,7 @@ var ACCESS_PRESETS = {
|
|
|
3442
2917
|
metricCreationMode: "CHAT"
|
|
3443
2918
|
}
|
|
3444
2919
|
};
|
|
3445
|
-
var fontFamilyEnum =
|
|
2920
|
+
var fontFamilyEnum = z16.enum([
|
|
3446
2921
|
"Inter",
|
|
3447
2922
|
"Roboto",
|
|
3448
2923
|
"Open Sans",
|
|
@@ -3454,17 +2929,17 @@ var fontFamilyEnum = z17.enum([
|
|
|
3454
2929
|
"Nunito",
|
|
3455
2930
|
"system-ui"
|
|
3456
2931
|
]).describe("Font family");
|
|
3457
|
-
var tooltipFontSchema =
|
|
3458
|
-
fontFamily:
|
|
3459
|
-
fontSize:
|
|
3460
|
-
fontWeight:
|
|
3461
|
-
fontColor:
|
|
2932
|
+
var tooltipFontSchema = z16.object({
|
|
2933
|
+
fontFamily: z16.string().optional(),
|
|
2934
|
+
fontSize: z16.number().optional(),
|
|
2935
|
+
fontWeight: z16.number().optional(),
|
|
2936
|
+
fontColor: z16.string().optional()
|
|
3462
2937
|
}).optional();
|
|
3463
|
-
var fontConfigInputSchema =
|
|
3464
|
-
fontSize:
|
|
3465
|
-
fontFamily:
|
|
3466
|
-
fontWeight:
|
|
3467
|
-
color:
|
|
2938
|
+
var fontConfigInputSchema = z16.object({
|
|
2939
|
+
fontSize: z16.number().optional(),
|
|
2940
|
+
fontFamily: z16.string().optional(),
|
|
2941
|
+
fontWeight: z16.number().optional(),
|
|
2942
|
+
color: z16.string().optional()
|
|
3468
2943
|
}).optional();
|
|
3469
2944
|
function mapToFrontendFontConfig(input) {
|
|
3470
2945
|
if (!input) return void 0;
|
|
@@ -3475,23 +2950,23 @@ function mapToFrontendFontConfig(input) {
|
|
|
3475
2950
|
...input.color && { color: input.color }
|
|
3476
2951
|
};
|
|
3477
2952
|
}
|
|
3478
|
-
var axisSchema =
|
|
3479
|
-
hideAxisLines:
|
|
3480
|
-
hideSplitLines:
|
|
3481
|
-
hideAxisLabels:
|
|
3482
|
-
hideAxisTicks:
|
|
3483
|
-
axisName:
|
|
3484
|
-
axisNameOffset:
|
|
3485
|
-
axisLabelMargin:
|
|
3486
|
-
fontSize:
|
|
3487
|
-
fontFamily:
|
|
3488
|
-
fontWeight:
|
|
3489
|
-
color:
|
|
3490
|
-
axisColor:
|
|
3491
|
-
axisNameFontConfig:
|
|
3492
|
-
fontFamily:
|
|
3493
|
-
fontSize:
|
|
3494
|
-
fontWeight:
|
|
2953
|
+
var axisSchema = z16.object({
|
|
2954
|
+
hideAxisLines: z16.boolean().optional().describe("Hide axis lines"),
|
|
2955
|
+
hideSplitLines: z16.boolean().optional().describe("Hide grid/split lines"),
|
|
2956
|
+
hideAxisLabels: z16.boolean().optional().describe("Hide axis labels"),
|
|
2957
|
+
hideAxisTicks: z16.boolean().optional().describe("Hide axis tick marks"),
|
|
2958
|
+
axisName: z16.string().optional().describe("Axis title text"),
|
|
2959
|
+
axisNameOffset: z16.number().optional().describe("Axis title offset from axis"),
|
|
2960
|
+
axisLabelMargin: z16.number().optional().describe("Margin between axis labels and chart area in pixels"),
|
|
2961
|
+
fontSize: z16.number().optional().describe("Axis label font size"),
|
|
2962
|
+
fontFamily: z16.string().optional().describe("Axis label font family"),
|
|
2963
|
+
fontWeight: z16.number().optional().describe("Axis label font weight"),
|
|
2964
|
+
color: z16.string().optional().describe("Axis label color (hex)"),
|
|
2965
|
+
axisColor: z16.string().optional().describe("Axis line color (hex)"),
|
|
2966
|
+
axisNameFontConfig: z16.object({
|
|
2967
|
+
fontFamily: z16.string().optional(),
|
|
2968
|
+
fontSize: z16.number().optional(),
|
|
2969
|
+
fontWeight: z16.number().optional()
|
|
3495
2970
|
}).optional().describe("Axis title font configuration")
|
|
3496
2971
|
}).optional();
|
|
3497
2972
|
function buildAdminTheme(p, presetBase) {
|
|
@@ -3647,94 +3122,94 @@ function registerCustomizeTheme(server, client) {
|
|
|
3647
3122
|
"customize_embed_theme",
|
|
3648
3123
|
`Theme an embed: presets (${Object.keys(THEME_PRESETS).join("/")}), colors, fonts, cards, chart appearance, responsive layout, or access presets (${Object.keys(ACCESS_PRESETS).join("/")}).`,
|
|
3649
3124
|
{
|
|
3650
|
-
embedId:
|
|
3651
|
-
body:
|
|
3125
|
+
embedId: z16.string().describe("ID of the embed to theme"),
|
|
3126
|
+
body: z16.record(z16.unknown()).optional().describe(
|
|
3652
3127
|
"Raw API body for PUT /api/v2/data-app/embeds. If provided, sent directly and all convenience fields are ignored."
|
|
3653
3128
|
),
|
|
3654
3129
|
// --- Presets ---
|
|
3655
|
-
themePreset:
|
|
3656
|
-
accessPreset:
|
|
3130
|
+
themePreset: z16.enum(["light", "dark", "corporate", "minimal"]).optional().describe("Apply a theme preset as the base. Granular params below override preset values."),
|
|
3131
|
+
accessPreset: z16.enum(["view-only", "power-user", "export-only", "ai-enabled"]).optional().describe("Apply an access settings preset"),
|
|
3657
3132
|
// --- General ---
|
|
3658
3133
|
fontFamily: fontFamilyEnum.optional().describe("Global font family"),
|
|
3659
|
-
datePickerFormat:
|
|
3134
|
+
datePickerFormat: z16.string().optional().describe("Date picker display format"),
|
|
3660
3135
|
// --- Colors ---
|
|
3661
|
-
primaryColor:
|
|
3662
|
-
backgroundColor:
|
|
3663
|
-
textColor:
|
|
3664
|
-
ctaColor:
|
|
3665
|
-
ctaTextColor:
|
|
3136
|
+
primaryColor: z16.string().optional().describe("Primary accent color (hex)"),
|
|
3137
|
+
backgroundColor: z16.string().optional().describe("Overall background color (hex)"),
|
|
3138
|
+
textColor: z16.string().optional().describe("Default text color (hex)"),
|
|
3139
|
+
ctaColor: z16.string().optional().describe("Call-to-action button color (hex)"),
|
|
3140
|
+
ctaTextColor: z16.string().optional().describe("CTA button text color (hex)"),
|
|
3666
3141
|
// --- Dashboard ---
|
|
3667
|
-
dashboardBackgroundColor:
|
|
3668
|
-
selectBoxSize:
|
|
3669
|
-
selectBoxVariant:
|
|
3670
|
-
selectBoxBorderRadius:
|
|
3671
|
-
selectBoxTextColor:
|
|
3672
|
-
metricCardColor:
|
|
3142
|
+
dashboardBackgroundColor: z16.string().optional().describe("Dashboard container background (hex)"),
|
|
3143
|
+
selectBoxSize: z16.enum(["small", "medium", "large"]).optional().describe("Filter select box size"),
|
|
3144
|
+
selectBoxVariant: z16.enum(["floating", "outlined", "standard"]).optional().describe("Filter select box style"),
|
|
3145
|
+
selectBoxBorderRadius: z16.string().optional().describe('Select box border radius (e.g. "20px")'),
|
|
3146
|
+
selectBoxTextColor: z16.string().optional().describe("Select box text color (hex)"),
|
|
3147
|
+
metricCardColor: z16.string().optional().describe("Metric card background color (hex)"),
|
|
3673
3148
|
// --- Card Title ---
|
|
3674
|
-
cardTitleFontSize:
|
|
3675
|
-
cardTitleFontWeight:
|
|
3676
|
-
cardTitleColor:
|
|
3149
|
+
cardTitleFontSize: z16.string().optional().describe('Card title font size (e.g. "16px")'),
|
|
3150
|
+
cardTitleFontWeight: z16.string().optional().describe('Card title font weight (e.g. "600")'),
|
|
3151
|
+
cardTitleColor: z16.string().optional().describe("Card title text color (hex)"),
|
|
3677
3152
|
// --- Card Description ---
|
|
3678
|
-
cardDescriptionFontSize:
|
|
3679
|
-
cardDescriptionFontWeight:
|
|
3680
|
-
cardDescriptionColor:
|
|
3153
|
+
cardDescriptionFontSize: z16.string().optional().describe("Card description font size"),
|
|
3154
|
+
cardDescriptionFontWeight: z16.string().optional().describe("Card description font weight"),
|
|
3155
|
+
cardDescriptionColor: z16.string().optional().describe("Card description text color (hex)"),
|
|
3681
3156
|
// --- Card Customization ---
|
|
3682
|
-
cardPadding:
|
|
3683
|
-
cardBorderRadius:
|
|
3684
|
-
cardShadow:
|
|
3685
|
-
disableShadowOnHover:
|
|
3686
|
-
disableCardStroke:
|
|
3687
|
-
cardStrokeColor:
|
|
3157
|
+
cardPadding: z16.string().optional().describe("Card internal padding"),
|
|
3158
|
+
cardBorderRadius: z16.string().optional().describe("Card border radius"),
|
|
3159
|
+
cardShadow: z16.string().optional().describe("Card box-shadow CSS value"),
|
|
3160
|
+
disableShadowOnHover: z16.boolean().optional().describe("Disable shadow on card hover"),
|
|
3161
|
+
disableCardStroke: z16.boolean().optional().describe("Remove card border stroke"),
|
|
3162
|
+
cardStrokeColor: z16.string().optional().describe("Card border stroke color (hex)"),
|
|
3688
3163
|
// --- Chart Colors (admin theme level) ---
|
|
3689
|
-
chartFontFamily:
|
|
3690
|
-
chartFontColor:
|
|
3691
|
-
chartColors:
|
|
3692
|
-
chartPaletteName:
|
|
3693
|
-
chartPaletteColors:
|
|
3164
|
+
chartFontFamily: z16.string().optional().describe("Font family for chart text"),
|
|
3165
|
+
chartFontColor: z16.string().optional().describe("Font color for chart text (hex)"),
|
|
3166
|
+
chartColors: z16.array(z16.string()).optional().describe("Array of chart series colors (hex)"),
|
|
3167
|
+
chartPaletteName: z16.string().optional().describe("Name for a custom palette"),
|
|
3168
|
+
chartPaletteColors: z16.array(z16.string()).optional().describe("9 colors for a custom palette"),
|
|
3694
3169
|
// --- Chart Tooltip (admin theme level) ---
|
|
3695
3170
|
tooltipHeader: tooltipFontSchema.describe("Tooltip header font config"),
|
|
3696
3171
|
tooltipLabel: tooltipFontSchema.describe("Tooltip label font config"),
|
|
3697
3172
|
tooltipValue: tooltipFontSchema.describe("Tooltip value font config"),
|
|
3698
3173
|
// --- Component Theme ---
|
|
3699
|
-
buttonPrimaryColor:
|
|
3700
|
-
buttonPrimaryTextColor:
|
|
3701
|
-
buttonSecondaryColor:
|
|
3702
|
-
buttonSecondaryTextColor:
|
|
3703
|
-
checkboxCheckedColor:
|
|
3704
|
-
checkboxUncheckedColor:
|
|
3705
|
-
switchEnabledColor:
|
|
3706
|
-
switchDisabledColor:
|
|
3707
|
-
breadcrumbFontFamily:
|
|
3708
|
-
breadcrumbFontColor:
|
|
3709
|
-
breadcrumbActiveColor:
|
|
3710
|
-
filterBadgeColor:
|
|
3711
|
-
filterBadgeTextColor:
|
|
3712
|
-
datePickerColor:
|
|
3174
|
+
buttonPrimaryColor: z16.string().optional().describe("Primary button background (hex)"),
|
|
3175
|
+
buttonPrimaryTextColor: z16.string().optional().describe("Primary button text (hex)"),
|
|
3176
|
+
buttonSecondaryColor: z16.string().optional().describe("Secondary button background (hex)"),
|
|
3177
|
+
buttonSecondaryTextColor: z16.string().optional().describe("Secondary button text (hex)"),
|
|
3178
|
+
checkboxCheckedColor: z16.string().optional().describe("Checked checkbox color (hex)"),
|
|
3179
|
+
checkboxUncheckedColor: z16.string().optional().describe("Unchecked checkbox color (hex)"),
|
|
3180
|
+
switchEnabledColor: z16.string().optional().describe("Enabled switch color (hex)"),
|
|
3181
|
+
switchDisabledColor: z16.string().optional().describe("Disabled switch color (hex)"),
|
|
3182
|
+
breadcrumbFontFamily: z16.string().optional().describe("Drill breadcrumb font family"),
|
|
3183
|
+
breadcrumbFontColor: z16.string().optional().describe("Drill breadcrumb font color (hex)"),
|
|
3184
|
+
breadcrumbActiveColor: z16.string().optional().describe("Active breadcrumb color (hex)"),
|
|
3185
|
+
filterBadgeColor: z16.string().optional().describe("Multi-select filter badge color (hex)"),
|
|
3186
|
+
filterBadgeTextColor: z16.string().optional().describe("Filter badge text color (hex)"),
|
|
3187
|
+
datePickerColor: z16.string().optional().describe("Date picker accent color (hex)"),
|
|
3713
3188
|
// --- Responsive Breakpoints ---
|
|
3714
|
-
breakpointXl:
|
|
3715
|
-
breakpointLg:
|
|
3716
|
-
breakpointMd:
|
|
3717
|
-
breakpointSm:
|
|
3718
|
-
breakpointXs:
|
|
3189
|
+
breakpointXl: z16.number().optional().describe("XL breakpoint width in px"),
|
|
3190
|
+
breakpointLg: z16.number().optional().describe("LG breakpoint width in px"),
|
|
3191
|
+
breakpointMd: z16.number().optional().describe("MD breakpoint width in px"),
|
|
3192
|
+
breakpointSm: z16.number().optional().describe("SM breakpoint width in px"),
|
|
3193
|
+
breakpointXs: z16.number().optional().describe("XS breakpoint width in px"),
|
|
3719
3194
|
// --- Metric Layout Columns ---
|
|
3720
|
-
layoutColsXl:
|
|
3721
|
-
layoutColsLg:
|
|
3722
|
-
layoutColsMd:
|
|
3723
|
-
layoutColsSm:
|
|
3724
|
-
layoutColsXs:
|
|
3725
|
-
layoutColsXxs:
|
|
3195
|
+
layoutColsXl: z16.number().optional().describe("Grid columns at XL breakpoint"),
|
|
3196
|
+
layoutColsLg: z16.number().optional().describe("Grid columns at LG breakpoint"),
|
|
3197
|
+
layoutColsMd: z16.number().optional().describe("Grid columns at MD breakpoint"),
|
|
3198
|
+
layoutColsSm: z16.number().optional().describe("Grid columns at SM breakpoint"),
|
|
3199
|
+
layoutColsXs: z16.number().optional().describe("Grid columns at XS breakpoint"),
|
|
3200
|
+
layoutColsXxs: z16.number().optional().describe("Grid columns at XXS breakpoint"),
|
|
3726
3201
|
// --- Chart Appearance (detailed) ---
|
|
3727
3202
|
tooltipHeaderStyle: fontConfigInputSchema.describe("Tooltip header: fontSize, fontFamily, fontWeight, color"),
|
|
3728
3203
|
tooltipLabelStyle: fontConfigInputSchema.describe("Tooltip label: fontSize, fontFamily, fontWeight, color"),
|
|
3729
3204
|
tooltipValueStyle: fontConfigInputSchema.describe("Tooltip value: fontSize, fontFamily, fontWeight, color"),
|
|
3730
|
-
labelPosition:
|
|
3731
|
-
radialLabelPosition:
|
|
3732
|
-
marginTop:
|
|
3733
|
-
marginLeft:
|
|
3734
|
-
marginRight:
|
|
3735
|
-
marginBottom:
|
|
3736
|
-
legendShow:
|
|
3737
|
-
legendPosition:
|
|
3205
|
+
labelPosition: z16.enum(["hidden", "top", "left", "right", "bottom", "inside"]).optional().describe("Data label position on charts"),
|
|
3206
|
+
radialLabelPosition: z16.enum(["outside", "inside"]).optional().describe("Label position for radial/pie charts"),
|
|
3207
|
+
marginTop: z16.number().optional().describe("Chart top margin in pixels"),
|
|
3208
|
+
marginLeft: z16.number().optional().describe("Chart left margin in pixels"),
|
|
3209
|
+
marginRight: z16.number().optional().describe("Chart right margin in pixels"),
|
|
3210
|
+
marginBottom: z16.number().optional().describe("Chart bottom margin in pixels"),
|
|
3211
|
+
legendShow: z16.boolean().optional().describe("Show or hide chart legend"),
|
|
3212
|
+
legendPosition: z16.enum([
|
|
3738
3213
|
"top-left",
|
|
3739
3214
|
"top-center",
|
|
3740
3215
|
"top-right",
|
|
@@ -3744,17 +3219,17 @@ function registerCustomizeTheme(server, client) {
|
|
|
3744
3219
|
"bottom-center",
|
|
3745
3220
|
"bottom-right"
|
|
3746
3221
|
]).optional().describe("Legend position"),
|
|
3747
|
-
legendLayout:
|
|
3748
|
-
legendShape:
|
|
3749
|
-
legendFontSize:
|
|
3750
|
-
legendFontWeight:
|
|
3751
|
-
legendFontFamily:
|
|
3752
|
-
legendColor:
|
|
3753
|
-
legendTruncateLength:
|
|
3754
|
-
disableLegendScrolling:
|
|
3755
|
-
enableVariableLegendPosition:
|
|
3756
|
-
legendTop:
|
|
3757
|
-
legendLeft:
|
|
3222
|
+
legendLayout: z16.enum(["horizontal", "vertical"]).optional().describe("Legend layout direction"),
|
|
3223
|
+
legendShape: z16.enum(["circle", "rect", "roundRect", "triangle", "diamond", "arrow", "none"]).optional().describe("Legend icon shape"),
|
|
3224
|
+
legendFontSize: z16.number().optional().describe("Legend font size"),
|
|
3225
|
+
legendFontWeight: z16.number().optional().describe("Legend font weight"),
|
|
3226
|
+
legendFontFamily: z16.string().optional().describe("Legend font family"),
|
|
3227
|
+
legendColor: z16.string().optional().describe("Legend text color (hex)"),
|
|
3228
|
+
legendTruncateLength: z16.number().optional().describe("Max characters before truncating legend"),
|
|
3229
|
+
disableLegendScrolling: z16.boolean().optional().describe("Disable scrolling in legend"),
|
|
3230
|
+
enableVariableLegendPosition: z16.boolean().optional().describe("Allow user to drag legend"),
|
|
3231
|
+
legendTop: z16.number().optional().describe("Custom legend top offset"),
|
|
3232
|
+
legendLeft: z16.number().optional().describe("Custom legend left offset"),
|
|
3758
3233
|
verticalAxis: axisSchema.describe("Vertical (Y) axis configuration"),
|
|
3759
3234
|
horizontalAxis: axisSchema.describe("Horizontal (X) axis configuration")
|
|
3760
3235
|
},
|
|
@@ -3829,7 +3304,7 @@ Use \`get_embed_details\` to view the full current theme configuration.` + GUEST
|
|
|
3829
3304
|
}
|
|
3830
3305
|
|
|
3831
3306
|
// src/tools/embed/get-demo-link.ts
|
|
3832
|
-
import { z as
|
|
3307
|
+
import { z as z17 } from "zod";
|
|
3833
3308
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
3834
3309
|
var DEFAULT_EXPIRY_HOURS = 24;
|
|
3835
3310
|
var MAX_EXPIRY_HOURS = 24 * 30;
|
|
@@ -3839,11 +3314,11 @@ function registerGetDemoLink(server, client) {
|
|
|
3839
3314
|
"get_demo_link",
|
|
3840
3315
|
`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.`,
|
|
3841
3316
|
{
|
|
3842
|
-
clientId:
|
|
3843
|
-
dashboardName:
|
|
3844
|
-
workspaceName:
|
|
3845
|
-
embedId:
|
|
3846
|
-
expiryHours:
|
|
3317
|
+
clientId: z17.string().describe("End-customer identifier to scope the preview to."),
|
|
3318
|
+
dashboardName: z17.string().optional().describe("Dashboard name (non-technical). Preferred \u2014 pass with optional `workspaceName`."),
|
|
3319
|
+
workspaceName: z17.string().optional().describe("Workspace name. Only needed if `dashboardName` is ambiguous."),
|
|
3320
|
+
embedId: z17.string().optional().describe("Technical alternative to `dashboardName`."),
|
|
3321
|
+
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).`)
|
|
3847
3322
|
},
|
|
3848
3323
|
WRITE_CREATE,
|
|
3849
3324
|
async (params) => {
|
|
@@ -3899,70 +3374,70 @@ function registerGetDemoLink(server, client) {
|
|
|
3899
3374
|
}
|
|
3900
3375
|
|
|
3901
3376
|
// src/tools/frontend/generate-guest-token.ts
|
|
3902
|
-
import { z as
|
|
3377
|
+
import { z as z18 } from "zod";
|
|
3903
3378
|
function registerGenerateGuestToken(server, client) {
|
|
3904
3379
|
const services = createServiceBundle(client);
|
|
3905
3380
|
server.tool(
|
|
3906
3381
|
"generate_guest_token",
|
|
3907
3382
|
"Generate a guest token for frontend embedding. Requires clientId. Omit dataAppName to auto-resolve.",
|
|
3908
3383
|
{
|
|
3909
|
-
clientId:
|
|
3910
|
-
dataAppName:
|
|
3911
|
-
body:
|
|
3384
|
+
clientId: z18.string().describe('Unique end-user identifier for multi-tenant filtering. Use "None" if no tenancy.'),
|
|
3385
|
+
dataAppName: z18.string().optional().describe("Name of the data app. Omit to list available data apps."),
|
|
3386
|
+
body: z18.record(z18.unknown()).optional().describe(
|
|
3912
3387
|
"Raw API body for POST /api/v2/guest-token/create. If provided, sent directly and convenience fields are ignored."
|
|
3913
3388
|
),
|
|
3914
|
-
params:
|
|
3915
|
-
allowedEmbeds:
|
|
3916
|
-
rlsSettings:
|
|
3917
|
-
metricId:
|
|
3918
|
-
values:
|
|
3389
|
+
params: z18.object({
|
|
3390
|
+
allowedEmbeds: z18.array(z18.string()).optional().describe("Restrict token to specific embed IDs"),
|
|
3391
|
+
rlsSettings: z18.array(z18.object({
|
|
3392
|
+
metricId: z18.string().describe("Metric ID"),
|
|
3393
|
+
values: z18.record(z18.unknown()).describe("RLS values by column name")
|
|
3919
3394
|
})).optional().describe("Legacy alias used in docs/examples; forwarded as params.rlsSettings"),
|
|
3920
|
-
appFilters:
|
|
3921
|
-
metricId:
|
|
3922
|
-
values:
|
|
3395
|
+
appFilters: z18.array(z18.object({
|
|
3396
|
+
metricId: z18.string().describe("Metric ID"),
|
|
3397
|
+
values: z18.record(z18.unknown()).describe("Filter values by column name")
|
|
3923
3398
|
})).optional().describe("Metric-level app filters"),
|
|
3924
|
-
dashboardAppFilters:
|
|
3925
|
-
dashboardId:
|
|
3926
|
-
values:
|
|
3927
|
-
isShowOnUrl:
|
|
3399
|
+
dashboardAppFilters: z18.array(z18.object({
|
|
3400
|
+
dashboardId: z18.string().describe("Dashboard ID"),
|
|
3401
|
+
values: z18.record(z18.unknown()).describe("Filter values by dashboard filter name"),
|
|
3402
|
+
isShowOnUrl: z18.boolean().optional().describe("Whether filters are shown in URL params")
|
|
3928
3403
|
})).optional().describe("Dashboard-level app filters"),
|
|
3929
|
-
hideDashboardFilters:
|
|
3930
|
-
userIdentifier:
|
|
3931
|
-
timezone:
|
|
3404
|
+
hideDashboardFilters: z18.array(z18.string()).optional().describe("Dashboard filter names to hide"),
|
|
3405
|
+
userIdentifier: z18.string().optional().describe("End-user identifier for private metrics support"),
|
|
3406
|
+
timezone: z18.string().optional().describe('IANA timezone, e.g. "America/New_York"')
|
|
3932
3407
|
}).optional().describe("Advanced token params"),
|
|
3933
3408
|
// Legacy convenience fields (auto-mapped into params for backward compatibility)
|
|
3934
|
-
timezone:
|
|
3935
|
-
allowedEmbeds:
|
|
3936
|
-
rlsSettings:
|
|
3937
|
-
metricId:
|
|
3938
|
-
values:
|
|
3409
|
+
timezone: z18.string().optional().describe("Legacy: moved under params.timezone"),
|
|
3410
|
+
allowedEmbeds: z18.array(z18.string()).optional().describe("Legacy: moved under params.allowedEmbeds"),
|
|
3411
|
+
rlsSettings: z18.array(z18.object({
|
|
3412
|
+
metricId: z18.string().describe("Metric ID"),
|
|
3413
|
+
values: z18.record(z18.unknown()).describe("RLS values by column name")
|
|
3939
3414
|
})).optional().describe("Legacy: moved under params.rlsSettings"),
|
|
3940
|
-
appFilters:
|
|
3941
|
-
metricId:
|
|
3942
|
-
values:
|
|
3415
|
+
appFilters: z18.array(z18.object({
|
|
3416
|
+
metricId: z18.string().describe("Metric ID"),
|
|
3417
|
+
values: z18.record(z18.unknown()).describe("Filter values by column name")
|
|
3943
3418
|
})).optional().describe("Legacy: moved under params.appFilters"),
|
|
3944
|
-
dashboardAppFilters:
|
|
3945
|
-
dashboardId:
|
|
3946
|
-
values:
|
|
3947
|
-
isShowOnUrl:
|
|
3419
|
+
dashboardAppFilters: z18.array(z18.object({
|
|
3420
|
+
dashboardId: z18.string().describe("Dashboard ID to apply filters to"),
|
|
3421
|
+
values: z18.record(z18.unknown()).describe("Filter values for this dashboard"),
|
|
3422
|
+
isShowOnUrl: z18.boolean().optional().describe("Whether filters are shown in URL params")
|
|
3948
3423
|
})).optional().describe("Legacy: moved under params.dashboardAppFilters"),
|
|
3949
|
-
permissions:
|
|
3950
|
-
isEnableArchiveMetrics:
|
|
3951
|
-
isEnableManageMetrics:
|
|
3952
|
-
isEnableCreateDashboardView:
|
|
3953
|
-
isEnableMetricUpdation:
|
|
3954
|
-
isEnableCustomizeLayout:
|
|
3955
|
-
isEnableUnderlyingData:
|
|
3956
|
-
isEnableDownloadMetrics:
|
|
3957
|
-
isShowSideBar:
|
|
3958
|
-
isShowDashboardName:
|
|
3959
|
-
isDisableMetricCreation:
|
|
3424
|
+
permissions: z18.object({
|
|
3425
|
+
isEnableArchiveMetrics: z18.boolean().optional(),
|
|
3426
|
+
isEnableManageMetrics: z18.boolean().optional(),
|
|
3427
|
+
isEnableCreateDashboardView: z18.boolean().optional(),
|
|
3428
|
+
isEnableMetricUpdation: z18.boolean().optional(),
|
|
3429
|
+
isEnableCustomizeLayout: z18.boolean().optional(),
|
|
3430
|
+
isEnableUnderlyingData: z18.boolean().optional(),
|
|
3431
|
+
isEnableDownloadMetrics: z18.boolean().optional(),
|
|
3432
|
+
isShowSideBar: z18.boolean().optional(),
|
|
3433
|
+
isShowDashboardName: z18.boolean().optional(),
|
|
3434
|
+
isDisableMetricCreation: z18.boolean().optional()
|
|
3960
3435
|
}).optional().describe("Runtime permission overrides (merge with and override embed-level settings)"),
|
|
3961
|
-
theme:
|
|
3962
|
-
hideDashboardFilters:
|
|
3963
|
-
userIdentifier:
|
|
3964
|
-
expiryTime:
|
|
3965
|
-
datasourceName:
|
|
3436
|
+
theme: z18.record(z18.unknown()).optional().describe("Runtime theme overrides (overrides embed-level theme)"),
|
|
3437
|
+
hideDashboardFilters: z18.array(z18.string()).optional().describe("Legacy: moved under params.hideDashboardFilters"),
|
|
3438
|
+
userIdentifier: z18.string().optional().describe("Legacy: moved under params.userIdentifier"),
|
|
3439
|
+
expiryTime: z18.number().optional().describe("Token TTL in milliseconds"),
|
|
3440
|
+
datasourceName: z18.string().optional().describe("Datasource name for multi-datasource routing")
|
|
3966
3441
|
},
|
|
3967
3442
|
WRITE_CREATE,
|
|
3968
3443
|
async (params) => {
|
|
@@ -4015,7 +3490,7 @@ Next step: Use \`generate_embed_code\` to get copy-paste-ready frontend code.`
|
|
|
4015
3490
|
}
|
|
4016
3491
|
|
|
4017
3492
|
// src/tools/frontend/generate-embed-code.ts
|
|
4018
|
-
import { z as
|
|
3493
|
+
import { z as z19 } from "zod";
|
|
4019
3494
|
|
|
4020
3495
|
// src/templates/react.ts
|
|
4021
3496
|
function kebabCase(str) {
|
|
@@ -5011,22 +4486,22 @@ function registerGenerateEmbedCode(server, _client) {
|
|
|
5011
4486
|
"generate_embed_code",
|
|
5012
4487
|
"Generate framework-specific embed code (React, Next.js, Vue, Angular, Svelte, SolidJS, vanilla HTML).",
|
|
5013
4488
|
{
|
|
5014
|
-
embedId:
|
|
5015
|
-
framework:
|
|
5016
|
-
embedType:
|
|
5017
|
-
options:
|
|
5018
|
-
enableDownloadCsv:
|
|
5019
|
-
enableEmailCsv:
|
|
5020
|
-
enableDownloadAllMetrics:
|
|
5021
|
-
enableDownloadAllPdf:
|
|
5022
|
-
disableFullscreen:
|
|
5023
|
-
isHideTablePreview:
|
|
5024
|
-
isHideChartSettings:
|
|
5025
|
-
isStickyDashboardFilters:
|
|
5026
|
-
enableMultiMetricFilters:
|
|
5027
|
-
themeName:
|
|
5028
|
-
customTheme:
|
|
5029
|
-
optionsIcon:
|
|
4489
|
+
embedId: z19.string().describe("The embed ID to generate code for"),
|
|
4490
|
+
framework: z19.enum(FRAMEWORKS).describe("Frontend framework"),
|
|
4491
|
+
embedType: z19.enum(["dashboard", "metric"]).optional().default("dashboard"),
|
|
4492
|
+
options: z19.object({
|
|
4493
|
+
enableDownloadCsv: z19.boolean().optional(),
|
|
4494
|
+
enableEmailCsv: z19.boolean().optional(),
|
|
4495
|
+
enableDownloadAllMetrics: z19.boolean().optional(),
|
|
4496
|
+
enableDownloadAllPdf: z19.boolean().optional(),
|
|
4497
|
+
disableFullscreen: z19.boolean().optional(),
|
|
4498
|
+
isHideTablePreview: z19.boolean().optional(),
|
|
4499
|
+
isHideChartSettings: z19.boolean().optional(),
|
|
4500
|
+
isStickyDashboardFilters: z19.boolean().optional(),
|
|
4501
|
+
enableMultiMetricFilters: z19.boolean().optional(),
|
|
4502
|
+
themeName: z19.string().optional(),
|
|
4503
|
+
customTheme: z19.record(z19.unknown()).optional(),
|
|
4504
|
+
optionsIcon: z19.enum(["kebab-menu-vertical", "download"]).optional()
|
|
5030
4505
|
}).optional()
|
|
5031
4506
|
},
|
|
5032
4507
|
READ_ONLY,
|
|
@@ -5066,17 +4541,17 @@ ${generated.instructions}`;
|
|
|
5066
4541
|
}
|
|
5067
4542
|
|
|
5068
4543
|
// src/tools/analytics/list-dashboards.ts
|
|
5069
|
-
import { z as
|
|
4544
|
+
import { z as z20 } from "zod";
|
|
5070
4545
|
function registerListDashboards(server, client) {
|
|
5071
4546
|
const services = createServiceBundle(client);
|
|
5072
4547
|
server.tool(
|
|
5073
4548
|
"list_dashboards",
|
|
5074
4549
|
"List dashboards globally or for a specific data app.",
|
|
5075
4550
|
{
|
|
5076
|
-
scope:
|
|
4551
|
+
scope: z20.enum(["auto", "global", "data_app"]).optional().describe(
|
|
5077
4552
|
"Dashboard listing scope. auto: use data_app when dataAppName + API token are available, otherwise global."
|
|
5078
4553
|
),
|
|
5079
|
-
dataAppName:
|
|
4554
|
+
dataAppName: z20.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
|
|
5080
4555
|
},
|
|
5081
4556
|
READ_ONLY,
|
|
5082
4557
|
async (params) => {
|
|
@@ -5111,36 +4586,36 @@ function registerListDashboards(server, client) {
|
|
|
5111
4586
|
}
|
|
5112
4587
|
|
|
5113
4588
|
// src/tools/dashboard/schema.ts
|
|
5114
|
-
import { z as
|
|
5115
|
-
var dashboardPositionSchema =
|
|
5116
|
-
x:
|
|
5117
|
-
y:
|
|
5118
|
-
w:
|
|
5119
|
-
h:
|
|
4589
|
+
import { z as z21 } from "zod";
|
|
4590
|
+
var dashboardPositionSchema = z21.object({
|
|
4591
|
+
x: z21.number().int().min(0).max(23),
|
|
4592
|
+
y: z21.number().int().min(0),
|
|
4593
|
+
w: z21.number().int().min(1).max(24),
|
|
4594
|
+
h: z21.number().int().min(1).max(40)
|
|
5120
4595
|
}).strict();
|
|
5121
|
-
var dashboardChartSchema =
|
|
5122
|
-
type:
|
|
5123
|
-
metricRef:
|
|
5124
|
-
query:
|
|
5125
|
-
companyIntegrationId:
|
|
5126
|
-
chartOptions:
|
|
4596
|
+
var dashboardChartSchema = z21.object({
|
|
4597
|
+
type: z21.string(),
|
|
4598
|
+
metricRef: z21.string().optional(),
|
|
4599
|
+
query: z21.string().optional(),
|
|
4600
|
+
companyIntegrationId: z21.string().optional(),
|
|
4601
|
+
chartOptions: z21.record(z21.unknown()).optional()
|
|
5127
4602
|
}).passthrough();
|
|
5128
|
-
var dashboardTileSchema =
|
|
5129
|
-
id:
|
|
5130
|
-
kind:
|
|
4603
|
+
var dashboardTileSchema = z21.object({
|
|
4604
|
+
id: z21.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
|
|
4605
|
+
kind: z21.literal("databrain-chart"),
|
|
5131
4606
|
position: dashboardPositionSchema,
|
|
5132
|
-
title:
|
|
4607
|
+
title: z21.string().min(1).max(200),
|
|
5133
4608
|
chart: dashboardChartSchema.optional()
|
|
5134
4609
|
}).strict();
|
|
5135
4610
|
var dashboardDocumentShape = {
|
|
5136
|
-
schemaVersion:
|
|
5137
|
-
title:
|
|
5138
|
-
description:
|
|
5139
|
-
layout:
|
|
5140
|
-
tiles:
|
|
4611
|
+
schemaVersion: z21.literal(1),
|
|
4612
|
+
title: z21.string().min(1).max(200),
|
|
4613
|
+
description: z21.string().max(2e3).optional(),
|
|
4614
|
+
layout: z21.object({ columns: z21.union([z21.literal(12), z21.literal(24)]).default(24) }).strict().optional(),
|
|
4615
|
+
tiles: z21.array(dashboardTileSchema).min(1).max(50)
|
|
5141
4616
|
};
|
|
5142
|
-
var workspaceNameSchema =
|
|
5143
|
-
var idempotencyKeySchema =
|
|
4617
|
+
var workspaceNameSchema = z21.string().min(1).describe("Target workspace name");
|
|
4618
|
+
var idempotencyKeySchema = z21.string().regex(
|
|
5144
4619
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
5145
4620
|
"idempotencyKey must be a UUID v4"
|
|
5146
4621
|
).describe("UUID v4 unique to this workspace dashboard create request");
|
|
@@ -5193,7 +4668,7 @@ ${stringify(validation)}`
|
|
|
5193
4668
|
}
|
|
5194
4669
|
|
|
5195
4670
|
// src/tools/dashboard/generate-dashboard-tiles.ts
|
|
5196
|
-
import { z as
|
|
4671
|
+
import { z as z22 } from "zod";
|
|
5197
4672
|
function registerGenerateDashboardTiles(server, client) {
|
|
5198
4673
|
const services = createServiceBundle(client);
|
|
5199
4674
|
server.tool(
|
|
@@ -5201,15 +4676,15 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
5201
4676
|
"Generate multiple workspace dashboard tile drafts without persisting them.",
|
|
5202
4677
|
{
|
|
5203
4678
|
workspaceName: workspaceNameSchema,
|
|
5204
|
-
dataAppName:
|
|
5205
|
-
integrationId:
|
|
5206
|
-
integrationName:
|
|
5207
|
-
tiles:
|
|
5208
|
-
|
|
5209
|
-
id:
|
|
5210
|
-
title:
|
|
4679
|
+
dataAppName: z22.string().optional().describe("Data app in the workspace; auto-resolved when unique"),
|
|
4680
|
+
integrationId: z22.string().optional().describe("Datasource UUID override"),
|
|
4681
|
+
integrationName: z22.string().optional().describe("Datasource name override"),
|
|
4682
|
+
tiles: z22.array(
|
|
4683
|
+
z22.object({
|
|
4684
|
+
id: z22.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
|
|
4685
|
+
title: z22.string().min(1).max(200),
|
|
5211
4686
|
position: dashboardPositionSchema,
|
|
5212
|
-
userInputs:
|
|
4687
|
+
userInputs: z22.record(z22.unknown())
|
|
5213
4688
|
}).strict()
|
|
5214
4689
|
).min(1).max(50)
|
|
5215
4690
|
},
|
|
@@ -5293,14 +4768,14 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
5293
4768
|
}
|
|
5294
4769
|
|
|
5295
4770
|
// src/tools/dashboard/get-dashboard.ts
|
|
5296
|
-
import { z as
|
|
4771
|
+
import { z as z23 } from "zod";
|
|
5297
4772
|
function registerGetDashboard(server, client) {
|
|
5298
4773
|
server.tool(
|
|
5299
4774
|
"get_dashboard",
|
|
5300
4775
|
"Retrieve a workspace dashboard projected back into the versioned tile-list contract.",
|
|
5301
4776
|
{
|
|
5302
4777
|
workspaceName: workspaceNameSchema,
|
|
5303
|
-
dashboardId:
|
|
4778
|
+
dashboardId: z23.string().min(1).describe("Dashboard identifier returned by create_dashboard")
|
|
5304
4779
|
},
|
|
5305
4780
|
READ_ONLY,
|
|
5306
4781
|
async (params) => {
|
|
@@ -5323,8 +4798,174 @@ function registerGetDashboard(server, client) {
|
|
|
5323
4798
|
);
|
|
5324
4799
|
}
|
|
5325
4800
|
|
|
4801
|
+
// src/apps/preview.ts
|
|
4802
|
+
import { createHash } from "crypto";
|
|
4803
|
+
import { z as z24 } from "zod";
|
|
4804
|
+
var draftSchema = z24.object({
|
|
4805
|
+
question: z24.string().min(1).max(2e3),
|
|
4806
|
+
sql: z24.string().min(1).max(2e4),
|
|
4807
|
+
datamartName: z24.string().min(1).max(256),
|
|
4808
|
+
dimensions: z24.array(z24.string().max(128)).max(20).default([]),
|
|
4809
|
+
measures: z24.array(z24.string().max(128)).max(20).default([]),
|
|
4810
|
+
chartType: z24.string().min(1).max(80),
|
|
4811
|
+
title: z24.string().min(1).max(200),
|
|
4812
|
+
sessionId: z24.string().max(256).optional(),
|
|
4813
|
+
/** Workspaces built on this datamart. One means Save needs no choice. */
|
|
4814
|
+
workspaces: z24.array(z24.string().max(256)).max(50).default([])
|
|
4815
|
+
}).strict();
|
|
4816
|
+
var PREVIEW_PATH = "/api/v2/data-app/chart-preview";
|
|
4817
|
+
var METRIC_PATH = "/api/mcp/metrics/create";
|
|
4818
|
+
var DASHBOARD_PATH = "/api/v2/workspace/dashboard";
|
|
4819
|
+
var PREVIEW_TYPES = ["bar", "row", "line", "area", "pie", "doughnut", "table"];
|
|
4820
|
+
var RENDER_TYPES = [...PREVIEW_TYPES, "single value"];
|
|
4821
|
+
async function previewPage(client, draft, rows, chartType) {
|
|
4822
|
+
const page = await client.request({
|
|
4823
|
+
method: "POST",
|
|
4824
|
+
path: PREVIEW_PATH,
|
|
4825
|
+
auth: "service",
|
|
4826
|
+
skipDataUnwrap: true,
|
|
4827
|
+
body: {
|
|
4828
|
+
rows: firstRows(rows),
|
|
4829
|
+
chartType,
|
|
4830
|
+
title: draft.title,
|
|
4831
|
+
dimensions: draft.dimensions,
|
|
4832
|
+
measures: draft.measures,
|
|
4833
|
+
width: 600,
|
|
4834
|
+
height: 320
|
|
4835
|
+
}
|
|
4836
|
+
});
|
|
4837
|
+
return pageSchema.parse(page);
|
|
4838
|
+
}
|
|
4839
|
+
function tablePage(draft, rows) {
|
|
4840
|
+
const page = firstRows(rows);
|
|
4841
|
+
return {
|
|
4842
|
+
kind: "table",
|
|
4843
|
+
title: draft.title,
|
|
4844
|
+
chartSpec: { type: "table" },
|
|
4845
|
+
rows: page,
|
|
4846
|
+
columns: Object.keys(page[0] ?? {})
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
async function draftPage(client, draft, rows, chartType) {
|
|
4850
|
+
const data = rowsSchema.parse(firstRows(rows));
|
|
4851
|
+
const carry = { draft: { ...draft, chartType }, previewTypes: PREVIEW_TYPES };
|
|
4852
|
+
if (chartType === "table" || !data.length) return { ...tablePage(draft, data), ...carry };
|
|
4853
|
+
try {
|
|
4854
|
+
return { ...await previewPage(client, draft, data, chartType), ...carry };
|
|
4855
|
+
} catch (error) {
|
|
4856
|
+
const previewError = error instanceof DatabrainApiError ? error.code : "PREVIEW_UNAVAILABLE";
|
|
4857
|
+
return { ...tablePage(draft, data), ...carry, draft: { ...draft, chartType: "table" }, previewError };
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
var sameName = (a, b) => typeof a === "string" && a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
4861
|
+
async function workspacesForDatamart(client, datamartName, mode = "hint") {
|
|
4862
|
+
const cached = (key) => mode === "hint" ? { cacheKey: key, cacheTtlMs: 5 * 60 * 1e3 } : {};
|
|
4863
|
+
try {
|
|
4864
|
+
const [workspaces, datamarts] = await Promise.all([
|
|
4865
|
+
client.request({ method: "GET", path: "/api/v2/workspace", auth: "service", ...cached("list_workspaces") }),
|
|
4866
|
+
client.request({ method: "GET", path: "/api/v2/data-app/datamarts", auth: "service", ...cached("list_datamarts") })
|
|
4867
|
+
]);
|
|
4868
|
+
const datasource = (Array.isArray(datamarts) ? datamarts : []).find((d) => sameName(d?.name ?? d?.datamartName, datamartName))?.companyIntegration?.id;
|
|
4869
|
+
const all = (Array.isArray(workspaces) ? workspaces : []).filter((w) => typeof w?.name === "string");
|
|
4870
|
+
const onDatamart = all.filter((w) => sameName(w.datamart?.name ?? w.datamartName, datamartName));
|
|
4871
|
+
const onDatasource = datasource ? all.filter((w) => !w.datamart && w.companyIntegration?.id === datasource) : [];
|
|
4872
|
+
return [...new Set([...onDatamart, ...onDatasource].map((w) => w.name))];
|
|
4873
|
+
} catch {
|
|
4874
|
+
return [];
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
function saveKey(draft, input) {
|
|
4878
|
+
const h = createHash("sha256").update(JSON.stringify({
|
|
4879
|
+
sql: draft.sql,
|
|
4880
|
+
datamartName: draft.datamartName,
|
|
4881
|
+
workspaceName: input.workspaceName,
|
|
4882
|
+
dashboardTitle: input.dashboardTitle,
|
|
4883
|
+
chartType: input.chartType,
|
|
4884
|
+
// One attempt, many retries: the tile mints an id when the form opens and re-sends it on
|
|
4885
|
+
// retry, so a retry replays and a fresh Save is a fresh key. Without one, content is the key.
|
|
4886
|
+
attemptId: input.attemptId ?? null
|
|
4887
|
+
})).digest();
|
|
4888
|
+
h[6] = h[6] & 15 | 64;
|
|
4889
|
+
h[8] = h[8] & 63 | 128;
|
|
4890
|
+
const x = h.subarray(0, 16).toString("hex");
|
|
4891
|
+
return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20, 32)}`;
|
|
4892
|
+
}
|
|
4893
|
+
async function saveDraftAsDashboard(client, draft, input) {
|
|
4894
|
+
const metric = await client.request({
|
|
4895
|
+
method: "POST",
|
|
4896
|
+
path: METRIC_PATH,
|
|
4897
|
+
auth: "service",
|
|
4898
|
+
body: {
|
|
4899
|
+
workspaceName: input.workspaceName,
|
|
4900
|
+
name: input.dashboardTitle,
|
|
4901
|
+
query: draft.sql,
|
|
4902
|
+
description: draft.question,
|
|
4903
|
+
isPublished: false,
|
|
4904
|
+
validateOnly: false
|
|
4905
|
+
}
|
|
4906
|
+
});
|
|
4907
|
+
const metricId = metric?.metricId || metric?.id;
|
|
4908
|
+
if (!metricId) throw new DatabrainApiError(502, "METRIC_ID_MISSING", "The metric was created but returned no id.");
|
|
4909
|
+
const slug = input.dashboardTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63) || "chart";
|
|
4910
|
+
const body = {
|
|
4911
|
+
schemaVersion: 1,
|
|
4912
|
+
title: input.dashboardTitle,
|
|
4913
|
+
description: draft.question,
|
|
4914
|
+
layout: { columns: 24 },
|
|
4915
|
+
tiles: [{
|
|
4916
|
+
id: slug,
|
|
4917
|
+
kind: "databrain-chart",
|
|
4918
|
+
title: input.dashboardTitle,
|
|
4919
|
+
position: { x: 0, y: 0, w: 12, h: 8 },
|
|
4920
|
+
chart: { type: input.chartType, metricRef: metricId }
|
|
4921
|
+
}]
|
|
4922
|
+
};
|
|
4923
|
+
const dashboard = await client.request({
|
|
4924
|
+
method: "POST",
|
|
4925
|
+
path: DASHBOARD_PATH,
|
|
4926
|
+
auth: "service",
|
|
4927
|
+
query: { workspaceName: input.workspaceName },
|
|
4928
|
+
headers: { "Idempotency-Key": saveKey(draft, input) },
|
|
4929
|
+
body,
|
|
4930
|
+
invalidateCache: ["list_dashboards"],
|
|
4931
|
+
// A metadata insert: 15s keeps the attempt and its retries inside the host's 60s window.
|
|
4932
|
+
timeoutMs: 15e3
|
|
4933
|
+
});
|
|
4934
|
+
if (!dashboard?.dashboardId) {
|
|
4935
|
+
throw new DatabrainApiError(502, "DASHBOARD_ID_MISSING", `Metric ${metricId} was saved but the dashboard returned no id.`);
|
|
4936
|
+
}
|
|
4937
|
+
return { dashboardId: dashboard.dashboardId, metricId, workspaceName: input.workspaceName, title: input.dashboardTitle };
|
|
4938
|
+
}
|
|
4939
|
+
|
|
5326
4940
|
// src/tools/query/ask-question.ts
|
|
5327
4941
|
import { z as z25 } from "zod";
|
|
4942
|
+
|
|
4943
|
+
// src/tools/query/answer-format.ts
|
|
4944
|
+
function formatConfidence(value) {
|
|
4945
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
4946
|
+
const percent = value > 1 ? value : value * 100;
|
|
4947
|
+
return `${Math.round(Math.min(Math.max(percent, 0), 100))}%`;
|
|
4948
|
+
}
|
|
4949
|
+
function formatProse(value) {
|
|
4950
|
+
if (Array.isArray(value)) {
|
|
4951
|
+
const items = value.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => `- ${item.trim()}`);
|
|
4952
|
+
return items.length ? items.join("\n") : null;
|
|
4953
|
+
}
|
|
4954
|
+
if (typeof value === "string" && value.trim() !== "") return value.trim();
|
|
4955
|
+
return null;
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
// src/tools/query/ask-question.ts
|
|
4959
|
+
function normalizePreviewType(type) {
|
|
4960
|
+
const t = (type || "").toLowerCase().trim();
|
|
4961
|
+
if (RENDER_TYPES.includes(t)) return t;
|
|
4962
|
+
if (t === "single value" || t === "kpi" || t === "number") return "single value";
|
|
4963
|
+
if (t === "time series" || t === "stepped") return "line";
|
|
4964
|
+
if (t === "stack" || t === "column") return "bar";
|
|
4965
|
+
if (t === "horizontal stack" || t === "horizontal bar") return "row";
|
|
4966
|
+
if (t === "stacked area") return "area";
|
|
4967
|
+
return "table";
|
|
4968
|
+
}
|
|
5328
4969
|
var MAX_DISPLAY_ROWS = 50;
|
|
5329
4970
|
function formatDataTable(data, columns) {
|
|
5330
4971
|
if (!Array.isArray(data) || data.length === 0) return "_No data returned._";
|
|
@@ -5468,15 +5109,22 @@ You can verify with \`get_semantic_layer\` using datamartName "${datamartName}".
|
|
|
5468
5109
|
});
|
|
5469
5110
|
const data = Array.isArray(result.data) ? result.data : [];
|
|
5470
5111
|
if (hasChartApps(server)) {
|
|
5112
|
+
const chartType = normalizePreviewType(result.suggestedChartType);
|
|
5113
|
+
const draft = {
|
|
5114
|
+
question: params.question,
|
|
5115
|
+
sql: result.query || "",
|
|
5116
|
+
datamartName,
|
|
5117
|
+
dimensions: result.columns?.dimensions || [],
|
|
5118
|
+
measures: result.columns?.measures || [],
|
|
5119
|
+
chartType,
|
|
5120
|
+
title: params.question.slice(0, 200),
|
|
5121
|
+
sessionId: result.sessionId || void 0,
|
|
5122
|
+
workspaces: await workspacesForDatamart(client, datamartName)
|
|
5123
|
+
};
|
|
5471
5124
|
const rows = firstRows(data);
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
chartSpec: { type: result.suggestedChartType || "table" },
|
|
5476
|
-
rows,
|
|
5477
|
-
columns: Object.keys(rows[0] ?? {}),
|
|
5478
|
-
description: "Showing the first page. Interactive chart preview is not available for this query yet."
|
|
5479
|
-
});
|
|
5125
|
+
const page = draft.sql ? await draftPage(client, draft, rows, chartType) : { ...tablePage(draft, rows), previewTypes: [] };
|
|
5126
|
+
const shown = rows.length === data.length ? `${data.length} rows` : `first ${rows.length} of ${data.length} rows`;
|
|
5127
|
+
return chartResult(`${page.kind === "table" ? "Table" : "Chart"} ready (${page.chartSpec && page.chartSpec.type}): ${shown}.` + (result.summary ? ` ${result.summary}` : ""), { ...page, rowsTotal: data.length });
|
|
5480
5128
|
}
|
|
5481
5129
|
const table = formatDataTable(data, result.columns);
|
|
5482
5130
|
const parts = [];
|
|
@@ -5488,23 +5136,24 @@ You can verify with \`get_semantic_layer\` using datamartName "${datamartName}".
|
|
|
5488
5136
|
parts.push(
|
|
5489
5137
|
`**Suggested chart type:** ${result.suggestedChartType}`
|
|
5490
5138
|
);
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
);
|
|
5139
|
+
const confidence = formatConfidence(result.confidence);
|
|
5140
|
+
if (confidence) {
|
|
5141
|
+
parts.push(`**Confidence:** ${confidence}`);
|
|
5495
5142
|
}
|
|
5496
5143
|
parts.push(
|
|
5497
5144
|
`**Rows returned:** ${data.length} | **Time:** ${result.timeTaken}ms`
|
|
5498
5145
|
);
|
|
5499
|
-
|
|
5146
|
+
const reasoning = formatProse(result.reasoning);
|
|
5147
|
+
if (reasoning) {
|
|
5500
5148
|
parts.push(`
|
|
5501
5149
|
### Reasoning
|
|
5502
|
-
${
|
|
5150
|
+
${reasoning}`);
|
|
5503
5151
|
}
|
|
5504
|
-
|
|
5152
|
+
const summary = formatProse(result.summary);
|
|
5153
|
+
if (summary) {
|
|
5505
5154
|
parts.push(`
|
|
5506
5155
|
### Summary
|
|
5507
|
-
${
|
|
5156
|
+
${summary}`);
|
|
5508
5157
|
}
|
|
5509
5158
|
parts.push(`
|
|
5510
5159
|
### Data
|
|
@@ -5687,10 +5336,17 @@ var columnSchema2 = z27.object({
|
|
|
5687
5336
|
"Identifier",
|
|
5688
5337
|
"Number",
|
|
5689
5338
|
"JSON"
|
|
5690
|
-
]).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.')
|
|
5339
|
+
]).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.'),
|
|
5340
|
+
// The public PUT accepts these three as well (docs: Update Semantic Layer).
|
|
5341
|
+
// They were missing here, and zod strips unknown keys, so a caller who set
|
|
5342
|
+
// `isIdentifier: true` got a success message and a row with `false`.
|
|
5343
|
+
columnTypeConfig: z27.union([z27.record(z27.string(), z27.string()), z27.string(), z27.object({ lowerLimit: z27.number(), upperLimit: z27.number() }), z27.null()]).optional().describe("Shape depends on columnType: value-to-description object for String / String (Custom) / ENUM / Mapper, { lowerLimit, upperLimit } for Range, a string for Expression and JSON. Omit for Identifier, Number and Long String."),
|
|
5344
|
+
isIdentifier: z27.boolean().optional().describe("Mark the column as an identifier (primary or foreign key)."),
|
|
5345
|
+
isNotIndexed: z27.boolean().optional().describe("Exclude the column from AI indexing.")
|
|
5691
5346
|
});
|
|
5692
5347
|
var tableSchema2 = z27.object({
|
|
5693
5348
|
name: z27.string().describe("Table name (must exist in the datamart)"),
|
|
5349
|
+
schemaName: z27.string().optional().describe("Schema the table lives in, when the datamart has the same table name in more than one schema."),
|
|
5694
5350
|
description: z27.string().optional().describe(
|
|
5695
5351
|
"Business-friendly description of what this table represents"
|
|
5696
5352
|
),
|
|
@@ -5730,6 +5386,7 @@ function normalizeTablesForApi(tables, datatypesByColumn) {
|
|
|
5730
5386
|
if (!tables) return void 0;
|
|
5731
5387
|
return tables.map((table) => {
|
|
5732
5388
|
const normalized = { name: table.name };
|
|
5389
|
+
if (table.schemaName !== void 0) normalized.schemaName = table.schemaName;
|
|
5733
5390
|
if (table.description !== void 0) normalized.description = table.description;
|
|
5734
5391
|
if (table.synonyms !== void 0) normalized.synonyms = table.synonyms;
|
|
5735
5392
|
if (table.miscellaneousInfo !== void 0) {
|
|
@@ -5753,6 +5410,15 @@ function normalizeTablesForApi(tables, datatypesByColumn) {
|
|
|
5753
5410
|
normalizedColumn.columnType = column.columnType;
|
|
5754
5411
|
}
|
|
5755
5412
|
}
|
|
5413
|
+
if (column.columnTypeConfig !== void 0) {
|
|
5414
|
+
normalizedColumn.columnTypeConfig = column.columnTypeConfig;
|
|
5415
|
+
}
|
|
5416
|
+
if (column.isIdentifier !== void 0) {
|
|
5417
|
+
normalizedColumn.isIdentifier = column.isIdentifier;
|
|
5418
|
+
}
|
|
5419
|
+
if (column.isNotIndexed !== void 0) {
|
|
5420
|
+
normalizedColumn.isNotIndexed = column.isNotIndexed;
|
|
5421
|
+
}
|
|
5756
5422
|
return normalizedColumn;
|
|
5757
5423
|
});
|
|
5758
5424
|
}
|
|
@@ -5890,35 +5556,6 @@ async function resolveDatamartFromWorkspace(params) {
|
|
|
5890
5556
|
workspaceName: resolution.name
|
|
5891
5557
|
};
|
|
5892
5558
|
}
|
|
5893
|
-
async function findDatamartIdInLinkedWorkspaces(params) {
|
|
5894
|
-
const services = createServiceBundle(params.client);
|
|
5895
|
-
const workspaces = await services.workspacesApi.listWorkspaces();
|
|
5896
|
-
const matches = workspaces.map((workspace) => {
|
|
5897
|
-
const record = workspace;
|
|
5898
|
-
return {
|
|
5899
|
-
name: nonEmptyString(record.name),
|
|
5900
|
-
datamartName: workspaceDatamartNameFrom(record),
|
|
5901
|
-
datamartId: workspaceDatamartIdFrom(record)
|
|
5902
|
-
};
|
|
5903
|
-
}).filter(
|
|
5904
|
-
(workspace) => workspace.datamartName?.toLowerCase() === params.datamartName.toLowerCase() && workspace.datamartId
|
|
5905
|
-
);
|
|
5906
|
-
const uniqueIds = [...new Set(matches.map((workspace) => workspace.datamartId))];
|
|
5907
|
-
if (uniqueIds.length === 1 && uniqueIds[0]) {
|
|
5908
|
-
return {
|
|
5909
|
-
kind: "resolved",
|
|
5910
|
-
datamartId: uniqueIds[0],
|
|
5911
|
-
workspaceName: matches.find((workspace) => workspace.datamartId === uniqueIds[0])?.name || void 0
|
|
5912
|
-
};
|
|
5913
|
-
}
|
|
5914
|
-
if (uniqueIds.length > 1) {
|
|
5915
|
-
return {
|
|
5916
|
-
kind: "ambiguous",
|
|
5917
|
-
workspaceNames: matches.map((workspace) => workspace.name).filter((name) => !!name)
|
|
5918
|
-
};
|
|
5919
|
-
}
|
|
5920
|
-
return { kind: "none" };
|
|
5921
|
-
}
|
|
5922
5559
|
async function resolveDatamart(params) {
|
|
5923
5560
|
if (params.datamartId) {
|
|
5924
5561
|
return {
|
|
@@ -5949,30 +5586,10 @@ async function resolveDatamart(params) {
|
|
|
5949
5586
|
if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
|
|
5950
5587
|
const datamartId = datamartIdFrom(resolution.selected);
|
|
5951
5588
|
if (!datamartId) {
|
|
5952
|
-
const workspaceMatch = await findDatamartIdInLinkedWorkspaces({
|
|
5953
|
-
client: params.client,
|
|
5954
|
-
datamartName: resolution.name
|
|
5955
|
-
});
|
|
5956
|
-
if (workspaceMatch.kind === "resolved") {
|
|
5957
|
-
return {
|
|
5958
|
-
ok: true,
|
|
5959
|
-
datamartName: resolution.name,
|
|
5960
|
-
datamartId: workspaceMatch.datamartId,
|
|
5961
|
-
workspaceName: workspaceMatch.workspaceName
|
|
5962
|
-
};
|
|
5963
|
-
}
|
|
5964
|
-
if (workspaceMatch.kind === "ambiguous") {
|
|
5965
|
-
return {
|
|
5966
|
-
ok: false,
|
|
5967
|
-
response: toolError(
|
|
5968
|
-
`Datamart "${resolution.name}" is linked from multiple workspaces with different IDs. Call ${params.toolName} again with workspaceName. Matching workspaces: ${workspaceMatch.workspaceNames.join(", ")}`
|
|
5969
|
-
)
|
|
5970
|
-
};
|
|
5971
|
-
}
|
|
5972
5589
|
return {
|
|
5973
5590
|
ok: false,
|
|
5974
5591
|
response: toolError(
|
|
5975
|
-
`Datamart "${resolution.name}"
|
|
5592
|
+
`Datamart "${resolution.name}" did not include an ID in the datamart list. Refresh the list and try again, or pass datamartId directly.`
|
|
5976
5593
|
)
|
|
5977
5594
|
};
|
|
5978
5595
|
}
|
|
@@ -7064,6 +6681,400 @@ function registerWhoami(server, client) {
|
|
|
7064
6681
|
);
|
|
7065
6682
|
}
|
|
7066
6683
|
|
|
6684
|
+
// src/tools/custom-chart/scaffold.ts
|
|
6685
|
+
import { z as z39 } from "zod";
|
|
6686
|
+
|
|
6687
|
+
// src/tools/custom-chart/shared.ts
|
|
6688
|
+
import { z as z38 } from "zod";
|
|
6689
|
+
var slotInputSchema = slotSchema;
|
|
6690
|
+
var optionInputSchema = optionSchema;
|
|
6691
|
+
var manifestInputSchema = z38.record(z38.unknown()).describe("manifest.json as an object (schemaVersion 2). Validated with the same rules as the server.");
|
|
6692
|
+
var bundleInputShape = {
|
|
6693
|
+
manifest: manifestInputSchema.optional(),
|
|
6694
|
+
bundleJs: z38.string().optional().describe("dist/index.js from `databrain custom-chart build` (the shim plus the chart module)."),
|
|
6695
|
+
bundleCss: z38.string().optional().describe("The raw chart.css, kept for provenance."),
|
|
6696
|
+
iconSvg: z38.string().optional().describe("icon.svg, at most 16 KiB, no script, no foreignObject, no external href."),
|
|
6697
|
+
projectDir: z38.string().optional().describe("Local only: a chart project folder. The tool reads it instead of manifest/bundleJs/iconSvg.")
|
|
6698
|
+
};
|
|
6699
|
+
function formatProblems(problems) {
|
|
6700
|
+
if (problems.length === 0) return "No problems.";
|
|
6701
|
+
return problems.map((p) => `${p.path || "/"} ${p.code} ${p.message}`).join("\n");
|
|
6702
|
+
}
|
|
6703
|
+
function projectDirRefusal() {
|
|
6704
|
+
if (process.env.DATABRAIN_HOSTED_PUBLIC_URL) {
|
|
6705
|
+
return "projectDir is not available on the hosted server. Pass manifest, bundleJs, bundleCss and iconSvg instead.";
|
|
6706
|
+
}
|
|
6707
|
+
return null;
|
|
6708
|
+
}
|
|
6709
|
+
async function resolveBundleInput(params, options) {
|
|
6710
|
+
if (params.projectDir) {
|
|
6711
|
+
const refusal = projectDirRefusal();
|
|
6712
|
+
if (refusal) return { problems: [], error: refusal, source: "projectDir" };
|
|
6713
|
+
if (params.manifest || params.bundleJs || params.iconSvg) {
|
|
6714
|
+
return { problems: [], error: "Pass either projectDir or manifest/bundleJs/iconSvg, not both.", source: "projectDir" };
|
|
6715
|
+
}
|
|
6716
|
+
if (options.build) {
|
|
6717
|
+
const result = await buildProject(params.projectDir, { write: false });
|
|
6718
|
+
if (!result.ok || !result.outputs) return { problems: result.problems, source: "projectDir" };
|
|
6719
|
+
const { manifest, bundleJs, bundleCss, iconSvg } = result.outputs;
|
|
6720
|
+
return { bundle: { manifest, bundleJs, bundleCss, iconSvg }, problems: [], source: "projectDir" };
|
|
6721
|
+
}
|
|
6722
|
+
const dist = readDist(params.projectDir);
|
|
6723
|
+
if (!dist.bundle) return { problems: dist.problems, source: "projectDir" };
|
|
6724
|
+
return { bundle: dist.bundle, problems: [], source: "projectDir" };
|
|
6725
|
+
}
|
|
6726
|
+
if (!params.manifest) {
|
|
6727
|
+
return { problems: [], error: "manifest is required (or pass projectDir when running locally).", source: "arguments" };
|
|
6728
|
+
}
|
|
6729
|
+
return {
|
|
6730
|
+
bundle: {
|
|
6731
|
+
manifest: params.manifest,
|
|
6732
|
+
bundleJs: params.bundleJs,
|
|
6733
|
+
bundleCss: params.bundleCss,
|
|
6734
|
+
iconSvg: params.iconSvg
|
|
6735
|
+
},
|
|
6736
|
+
problems: [],
|
|
6737
|
+
source: "arguments"
|
|
6738
|
+
};
|
|
6739
|
+
}
|
|
6740
|
+
async function remoteValidate(client, bundle) {
|
|
6741
|
+
try {
|
|
6742
|
+
const result = await createCustomChartsApi(client).validate(bundle);
|
|
6743
|
+
return { status: "ok", result };
|
|
6744
|
+
} catch (err) {
|
|
6745
|
+
if (err instanceof DatabrainApiError) {
|
|
6746
|
+
if (err.code === "MISSING_SERVICE_TOKEN") {
|
|
6747
|
+
return { status: "skipped", reason: "No service token is configured, so only the local validator ran." };
|
|
6748
|
+
}
|
|
6749
|
+
return { status: "failed", reason: err.message, problems: apiProblems(err) };
|
|
6750
|
+
}
|
|
6751
|
+
return { status: "failed", reason: err.message, problems: [] };
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
function isCompleteBundle(bundle) {
|
|
6755
|
+
return typeof bundle.bundleJs === "string" && typeof bundle.iconSvg === "string";
|
|
6756
|
+
}
|
|
6757
|
+
|
|
6758
|
+
// src/tools/custom-chart/scaffold.ts
|
|
6759
|
+
function registerCustomChartScaffold(server, _client) {
|
|
6760
|
+
server.tool(
|
|
6761
|
+
"custom_chart_scaffold",
|
|
6762
|
+
"Return the files of a new custom chart project (manifest.json, chart.ts, chart.css, icon.svg, sample data, guide). Nothing is written or persisted; write the files yourself.",
|
|
6763
|
+
{
|
|
6764
|
+
key: z39.string().describe("Chart key, becomes chart type custom:<key>. Lowercase letters, digits, _ and -, 64 max."),
|
|
6765
|
+
name: z39.string().describe("Display name shown in the chart picker, 1 to 256 characters."),
|
|
6766
|
+
description: z39.string().optional().describe("Optional description, up to 500 characters."),
|
|
6767
|
+
slots: z39.array(slotInputSchema).optional().describe("Data slots. Defaults to category (dimension), value (measure), target (optional measure)."),
|
|
6768
|
+
options: z39.array(optionInputSchema).optional().describe("Chart options. Defaults to the template options (labels, subtitle, radius, colour, sort).")
|
|
6769
|
+
},
|
|
6770
|
+
READ_ONLY,
|
|
6771
|
+
async (params) => {
|
|
6772
|
+
try {
|
|
6773
|
+
const result = scaffoldProject({
|
|
6774
|
+
key: params.key,
|
|
6775
|
+
name: params.name,
|
|
6776
|
+
description: params.description,
|
|
6777
|
+
slots: params.slots,
|
|
6778
|
+
options: params.options
|
|
6779
|
+
});
|
|
6780
|
+
if (!result.ok) {
|
|
6781
|
+
return toolError(`The requested manifest is not valid:
|
|
6782
|
+
${formatProblems(result.problems)}`);
|
|
6783
|
+
}
|
|
6784
|
+
return toolSuccess(
|
|
6785
|
+
JSON.stringify(
|
|
6786
|
+
{
|
|
6787
|
+
status: "scaffold_only",
|
|
6788
|
+
key: params.key,
|
|
6789
|
+
chartType: `custom:${params.key}`,
|
|
6790
|
+
files: result.files,
|
|
6791
|
+
nextStep: result.nextStep
|
|
6792
|
+
},
|
|
6793
|
+
null,
|
|
6794
|
+
2
|
|
6795
|
+
)
|
|
6796
|
+
);
|
|
6797
|
+
} catch (err) {
|
|
6798
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
);
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6804
|
+
// src/tools/custom-chart/validate.ts
|
|
6805
|
+
function registerCustomChartValidate(server, client) {
|
|
6806
|
+
server.tool(
|
|
6807
|
+
"custom_chart_validate",
|
|
6808
|
+
"Validate a custom chart manifest and bundle with the local rules and, when a service token is configured, the server rules. Never persists anything.",
|
|
6809
|
+
bundleInputShape,
|
|
6810
|
+
READ_ONLY,
|
|
6811
|
+
async (params) => {
|
|
6812
|
+
try {
|
|
6813
|
+
const resolved = await resolveBundleInput(params, { build: true });
|
|
6814
|
+
if (resolved.error) return toolError(resolved.error);
|
|
6815
|
+
if (!resolved.bundle) {
|
|
6816
|
+
return toolSuccess(JSON.stringify({ ok: false, problems: resolved.problems, source: resolved.source }, null, 2));
|
|
6817
|
+
}
|
|
6818
|
+
const { bundle } = resolved;
|
|
6819
|
+
const problems = validateBundle(bundle);
|
|
6820
|
+
const complete = isCompleteBundle(bundle);
|
|
6821
|
+
const result = {
|
|
6822
|
+
ok: problems.length === 0,
|
|
6823
|
+
problems,
|
|
6824
|
+
source: resolved.source,
|
|
6825
|
+
contentHash: complete && problems.length === 0 ? contentHash(bundle) : void 0,
|
|
6826
|
+
sizeBytes: complete && problems.length === 0 ? bundleSizeBytes(bundle) : void 0
|
|
6827
|
+
};
|
|
6828
|
+
if (!complete) {
|
|
6829
|
+
result.note = "Only the manifest was checked. Pass bundleJs and iconSvg (or projectDir) to check the whole package.";
|
|
6830
|
+
} else if (problems.length === 0) {
|
|
6831
|
+
const remote = await remoteValidate(client, bundle);
|
|
6832
|
+
if (remote.status === "ok") {
|
|
6833
|
+
result.remote = remote.result;
|
|
6834
|
+
if (!remote.result.ok) {
|
|
6835
|
+
result.ok = false;
|
|
6836
|
+
result.problems = remote.result.problems;
|
|
6837
|
+
}
|
|
6838
|
+
if (remote.result.contentHash) result.contentHash = remote.result.contentHash;
|
|
6839
|
+
if (typeof remote.result.sizeBytes === "number") result.sizeBytes = remote.result.sizeBytes;
|
|
6840
|
+
} else if (remote.status === "skipped") {
|
|
6841
|
+
result.remote = { status: "skipped", reason: remote.reason };
|
|
6842
|
+
} else {
|
|
6843
|
+
result.remote = { status: "failed", reason: remote.reason };
|
|
6844
|
+
if (remote.problems.length > 0) {
|
|
6845
|
+
result.ok = false;
|
|
6846
|
+
result.problems = remote.problems;
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
result.nextStep = result.ok ? "Upload with custom_chart_upload (mode dry_run first) or `databrain custom-chart upload`." : "Fix every problem (path, code, message), then validate again.";
|
|
6851
|
+
return toolSuccess(JSON.stringify(result, null, 2));
|
|
6852
|
+
} catch (err) {
|
|
6853
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
);
|
|
6857
|
+
}
|
|
6858
|
+
|
|
6859
|
+
// src/tools/custom-chart/upload.ts
|
|
6860
|
+
import { z as z40 } from "zod";
|
|
6861
|
+
function registerCustomChartUpload(server, client) {
|
|
6862
|
+
const api = createCustomChartsApi(client);
|
|
6863
|
+
server.tool(
|
|
6864
|
+
"custom_chart_upload",
|
|
6865
|
+
"Upload a built custom chart bundle to the company. Creates the chart (private, version 1) or a new preview version. Defaults to dry_run; apply requires explicit confirmation and can publish in the same call.",
|
|
6866
|
+
{
|
|
6867
|
+
mode: z40.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run validates and returns the plan; apply uploads."),
|
|
6868
|
+
confirm: z40.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6869
|
+
publish: z40.boolean().default(false).describe("After a successful apply, publish the uploaded version so every builder can use it."),
|
|
6870
|
+
name: z40.string().optional().describe("Display name override. Defaults to manifest.name."),
|
|
6871
|
+
description: z40.string().optional().describe("Description override. Defaults to manifest.description."),
|
|
6872
|
+
...bundleInputShape
|
|
6873
|
+
},
|
|
6874
|
+
WRITE_CREATE,
|
|
6875
|
+
async (params) => {
|
|
6876
|
+
try {
|
|
6877
|
+
const mode = params.mode || "dry_run";
|
|
6878
|
+
const resolved = await resolveBundleInput(params, { build: false });
|
|
6879
|
+
if (resolved.error) return toolError(resolved.error);
|
|
6880
|
+
if (!resolved.bundle) {
|
|
6881
|
+
return toolError(`The project could not be read:
|
|
6882
|
+
${formatProblems(resolved.problems)}`);
|
|
6883
|
+
}
|
|
6884
|
+
const { bundle } = resolved;
|
|
6885
|
+
if (!isCompleteBundle(bundle)) {
|
|
6886
|
+
return toolError("manifest, bundleJs and iconSvg are all required to upload. Run `databrain custom-chart build` and pass dist/index.js, dist/manifest.json and dist/icon.svg (or projectDir when local).");
|
|
6887
|
+
}
|
|
6888
|
+
const problems = validateBundle(bundle);
|
|
6889
|
+
if (problems.length > 0) {
|
|
6890
|
+
return toolError(`The bundle is not valid. Fix these and try again:
|
|
6891
|
+
${formatProblems(problems)}`);
|
|
6892
|
+
}
|
|
6893
|
+
const manifest = bundle.manifest;
|
|
6894
|
+
const key = manifest.id;
|
|
6895
|
+
const body = {
|
|
6896
|
+
manifest,
|
|
6897
|
+
bundleJs: bundle.bundleJs,
|
|
6898
|
+
bundleCss: bundle.bundleCss,
|
|
6899
|
+
iconSvg: bundle.iconSvg,
|
|
6900
|
+
...params.name ? { name: params.name } : {},
|
|
6901
|
+
...params.description ? { description: params.description } : {}
|
|
6902
|
+
};
|
|
6903
|
+
const localHash = contentHash(bundle);
|
|
6904
|
+
const localSize = bundleSizeBytes(bundle);
|
|
6905
|
+
if (mode === "dry_run") {
|
|
6906
|
+
const remote = await remoteValidate(client, bundle);
|
|
6907
|
+
let existing;
|
|
6908
|
+
let planNote;
|
|
6909
|
+
try {
|
|
6910
|
+
existing = (await api.list()).find((chart) => chart.key === key);
|
|
6911
|
+
} catch (err) {
|
|
6912
|
+
planNote = err instanceof DatabrainApiError && err.code === "MISSING_SERVICE_TOKEN" ? "No service token is configured, so the plan could not check whether the chart exists." : `Could not list custom charts: ${err.message}`;
|
|
6913
|
+
}
|
|
6914
|
+
const plan = planUpload(key, existing);
|
|
6915
|
+
const remoteOk = remote.status !== "ok" || remote.result.ok;
|
|
6916
|
+
return toolSuccess(
|
|
6917
|
+
JSON.stringify(
|
|
6918
|
+
{
|
|
6919
|
+
status: "dry_run_only",
|
|
6920
|
+
safety: genericProductionSafetyNote(),
|
|
6921
|
+
ok: remoteOk,
|
|
6922
|
+
key,
|
|
6923
|
+
chartType: `custom:${key}`,
|
|
6924
|
+
contentHash: remote.status === "ok" && remote.result.contentHash ? remote.result.contentHash : localHash,
|
|
6925
|
+
sizeBytes: remote.status === "ok" && typeof remote.result.sizeBytes === "number" ? remote.result.sizeBytes : localSize,
|
|
6926
|
+
problems: remote.status === "ok" ? remote.result.problems : remote.status === "failed" ? remote.problems : [],
|
|
6927
|
+
remote: remote.status === "ok" ? "validated by the server" : remote.reason,
|
|
6928
|
+
plan: plan.summary,
|
|
6929
|
+
...planNote ? { planNote } : {},
|
|
6930
|
+
publishAfterUpload: params.publish === true,
|
|
6931
|
+
requiredApplyConfirmation: APPLY_TO_PRODUCTION_CONFIRMATION,
|
|
6932
|
+
nextStep: remoteOk ? `Review this plan. To execute, call again with mode="apply" and confirm="${APPLY_TO_PRODUCTION_CONFIRMATION}"${params.publish ? " (publish=true will publish the new version right away)" : ""}.` : "Fix every problem, rebuild, then run dry_run again."
|
|
6933
|
+
},
|
|
6934
|
+
null,
|
|
6935
|
+
2
|
|
6936
|
+
)
|
|
6937
|
+
);
|
|
6938
|
+
}
|
|
6939
|
+
const guardError = validateProductionApply({ confirm: params.confirm });
|
|
6940
|
+
if (guardError) return toolError(guardError);
|
|
6941
|
+
const uploaded = await api.upload(body);
|
|
6942
|
+
const versionNumber = uploaded.version?.versionNumber;
|
|
6943
|
+
const lines = [];
|
|
6944
|
+
if (uploaded.created === "none") {
|
|
6945
|
+
lines.push(`This exact bundle is already v${versionNumber ?? "?"} of custom:${key}. Nothing was created.`);
|
|
6946
|
+
} else if (uploaded.created === "chart") {
|
|
6947
|
+
lines.push(`Created custom chart custom:${key} (v${versionNumber ?? 1}, status private).`);
|
|
6948
|
+
} else {
|
|
6949
|
+
lines.push(`Uploaded v${versionNumber ?? "?"} of custom:${key}. It is in preview until you publish it.`);
|
|
6950
|
+
}
|
|
6951
|
+
lines.push(`contentHash: ${uploaded.version?.contentHash ?? localHash}`);
|
|
6952
|
+
if (params.publish) {
|
|
6953
|
+
const published = await api.publish(key, uploaded.version?.id);
|
|
6954
|
+
lines.push(`Published v${versionNumber ?? "?"} of custom:${key}. Status: ${published.chart?.status ?? "published"}. Every dashboard using custom:${key} now renders this version.`);
|
|
6955
|
+
} else if (uploaded.created !== "none" || uploaded.chart?.status !== "published") {
|
|
6956
|
+
lines.push(`To make it available to builders: publish_custom_chart with key "${key}" and confirm="${APPLY_TO_PRODUCTION_CONFIRMATION}", or Settings > Developers > Custom charts.`);
|
|
6957
|
+
}
|
|
6958
|
+
return toolSuccess(lines.join("\n"));
|
|
6959
|
+
} catch (err) {
|
|
6960
|
+
if (err instanceof DatabrainApiError) {
|
|
6961
|
+
const problems = apiProblems(err);
|
|
6962
|
+
if (problems.length > 0) {
|
|
6963
|
+
return toolError(`The server refused the bundle: ${err.message}
|
|
6964
|
+
${formatProblems(problems)}`);
|
|
6965
|
+
}
|
|
6966
|
+
return err.toToolResponse();
|
|
6967
|
+
}
|
|
6968
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
6969
|
+
}
|
|
6970
|
+
}
|
|
6971
|
+
);
|
|
6972
|
+
}
|
|
6973
|
+
|
|
6974
|
+
// src/tools/custom-chart/list.ts
|
|
6975
|
+
function registerListCustomCharts(server, client) {
|
|
6976
|
+
server.tool(
|
|
6977
|
+
"list_custom_charts",
|
|
6978
|
+
"List the custom chart types of the company (key, name, status, published and preview versions). Uses the service token.",
|
|
6979
|
+
{},
|
|
6980
|
+
READ_ONLY,
|
|
6981
|
+
async () => {
|
|
6982
|
+
try {
|
|
6983
|
+
const charts = await createCustomChartsApi(client).list();
|
|
6984
|
+
const rows = charts.map((chart) => ({
|
|
6985
|
+
key: chart.key,
|
|
6986
|
+
chartType: `custom:${chart.key}`,
|
|
6987
|
+
name: chart.name,
|
|
6988
|
+
status: chart.status,
|
|
6989
|
+
publishedVersionId: chart.publishedVersionId ?? null,
|
|
6990
|
+
publishedVersionNumber: typeof chart.publishedVersionNumber === "number" ? chart.publishedVersionNumber : void 0,
|
|
6991
|
+
previewVersionId: chart.previewVersionId ?? null,
|
|
6992
|
+
previewVersionNumber: typeof chart.previewVersionNumber === "number" ? chart.previewVersionNumber : void 0,
|
|
6993
|
+
versionsCount: typeof chart.versionsCount === "number" ? chart.versionsCount : void 0,
|
|
6994
|
+
updatedAt: chart.updatedAt,
|
|
6995
|
+
createdBy: chart.createdBy ?? null
|
|
6996
|
+
}));
|
|
6997
|
+
if (rows.length === 0) {
|
|
6998
|
+
return toolSuccess(
|
|
6999
|
+
"No custom charts yet. Scaffold one with custom_chart_scaffold, build it with `databrain custom-chart build`, then upload with custom_chart_upload."
|
|
7000
|
+
);
|
|
7001
|
+
}
|
|
7002
|
+
return toolSuccess(JSON.stringify({ count: rows.length, charts: rows }, null, 2));
|
|
7003
|
+
} catch (err) {
|
|
7004
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
7005
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
7006
|
+
}
|
|
7007
|
+
}
|
|
7008
|
+
);
|
|
7009
|
+
}
|
|
7010
|
+
|
|
7011
|
+
// src/tools/custom-chart/publish.ts
|
|
7012
|
+
import { z as z41 } from "zod";
|
|
7013
|
+
function registerPublishCustomChart(server, client) {
|
|
7014
|
+
const api = createCustomChartsApi(client);
|
|
7015
|
+
server.tool(
|
|
7016
|
+
"publish_custom_chart",
|
|
7017
|
+
"Publish a custom chart version so every builder in the company can use chart type custom:<key>. Defaults to the preview version. Defaults to dry_run; apply requires explicit confirmation.",
|
|
7018
|
+
{
|
|
7019
|
+
key: z41.string().describe("The chart key (manifest.id), for example revenue-bars."),
|
|
7020
|
+
versionId: z41.string().optional().describe("A specific version id to publish. Defaults to the preview version."),
|
|
7021
|
+
mode: z41.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run shows what would be published; apply publishes."),
|
|
7022
|
+
confirm: z41.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`)
|
|
7023
|
+
},
|
|
7024
|
+
WRITE_IDEMPOTENT,
|
|
7025
|
+
async (params) => {
|
|
7026
|
+
try {
|
|
7027
|
+
const mode = params.mode || "dry_run";
|
|
7028
|
+
const detail = await api.get(params.key);
|
|
7029
|
+
const versions = Array.isArray(detail.versions) ? detail.versions : [];
|
|
7030
|
+
const targetId = params.versionId ?? detail.previewVersionId ?? null;
|
|
7031
|
+
const target = versions.find((version) => version.id === targetId);
|
|
7032
|
+
if (!targetId) {
|
|
7033
|
+
return toolError(
|
|
7034
|
+
`custom:${params.key} has no preview version to publish${detail.publishedVersionId ? ` (v published: ${detail.publishedVersionId})` : ""}. Upload a new version first, or pass versionId.`
|
|
7035
|
+
);
|
|
7036
|
+
}
|
|
7037
|
+
if (params.versionId && versions.length > 0 && !target) {
|
|
7038
|
+
return toolError(`custom:${params.key} has no version with id ${params.versionId}. Versions: ${versions.map((v) => `${v.id} (v${v.versionNumber})`).join(", ")}.`);
|
|
7039
|
+
}
|
|
7040
|
+
if (detail.publishedVersionId === targetId) {
|
|
7041
|
+
return toolSuccess(`custom:${params.key} already has ${target ? `v${target.versionNumber}` : targetId} published. Nothing to do.`);
|
|
7042
|
+
}
|
|
7043
|
+
if (mode === "dry_run") {
|
|
7044
|
+
return toolSuccess(
|
|
7045
|
+
JSON.stringify(
|
|
7046
|
+
{
|
|
7047
|
+
status: "dry_run_only",
|
|
7048
|
+
safety: genericProductionSafetyNote(),
|
|
7049
|
+
key: params.key,
|
|
7050
|
+
chartType: `custom:${params.key}`,
|
|
7051
|
+
name: detail.name,
|
|
7052
|
+
currentStatus: detail.status,
|
|
7053
|
+
currentPublishedVersionId: detail.publishedVersionId ?? null,
|
|
7054
|
+
willPublish: { versionId: targetId, versionNumber: target?.versionNumber, contentHash: target?.contentHash },
|
|
7055
|
+
impact: `Every dashboard using custom:${params.key} will render this version.`,
|
|
7056
|
+
requiredApplyConfirmation: APPLY_TO_PRODUCTION_CONFIRMATION,
|
|
7057
|
+
nextStep: `Ask the user to confirm, then call again with mode="apply" and confirm="${APPLY_TO_PRODUCTION_CONFIRMATION}".`
|
|
7058
|
+
},
|
|
7059
|
+
null,
|
|
7060
|
+
2
|
|
7061
|
+
)
|
|
7062
|
+
);
|
|
7063
|
+
}
|
|
7064
|
+
const guardError = validateProductionApply({ confirm: params.confirm });
|
|
7065
|
+
if (guardError) return toolError(guardError);
|
|
7066
|
+
const result = await api.publish(params.key, targetId);
|
|
7067
|
+
return toolSuccess(
|
|
7068
|
+
`Published ${target ? `v${target.versionNumber}` : targetId} of custom:${params.key} (${result.chart?.name ?? detail.name}). Status: ${result.chart?.status ?? "published"}. Every dashboard using custom:${params.key} now renders this version.`
|
|
7069
|
+
);
|
|
7070
|
+
} catch (err) {
|
|
7071
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
7072
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
);
|
|
7076
|
+
}
|
|
7077
|
+
|
|
7067
7078
|
// src/tools/registry.ts
|
|
7068
7079
|
function instrumentToolRegistration(server) {
|
|
7069
7080
|
const original = server.tool.bind(server);
|
|
@@ -7128,6 +7139,11 @@ function registerAllTools(server, client) {
|
|
|
7128
7139
|
registerRemoveWidget(server, client);
|
|
7129
7140
|
registerGenerateWidget(server, client);
|
|
7130
7141
|
registerListScheduledReports(server, client);
|
|
7142
|
+
registerCustomChartScaffold(server, client);
|
|
7143
|
+
registerCustomChartValidate(server, client);
|
|
7144
|
+
registerCustomChartUpload(server, client);
|
|
7145
|
+
registerListCustomCharts(server, client);
|
|
7146
|
+
registerPublishCustomChart(server, client);
|
|
7131
7147
|
}
|
|
7132
7148
|
|
|
7133
7149
|
// src/resources/content/getting-started.ts
|
|
@@ -10436,28 +10452,27 @@ function registerAllPrompts(server) {
|
|
|
10436
10452
|
}
|
|
10437
10453
|
|
|
10438
10454
|
export {
|
|
10439
|
-
oauthHttpsUrl,
|
|
10440
|
-
oauthConfigSchema,
|
|
10441
|
-
DatabrainApiError,
|
|
10442
|
-
toolError,
|
|
10443
|
-
toolSuccess,
|
|
10444
|
-
logger,
|
|
10445
|
-
MCP_SERVER_VERSION,
|
|
10446
|
-
toolContext,
|
|
10447
|
-
DatabrainClient,
|
|
10448
10455
|
createServiceBundle,
|
|
10449
10456
|
READ_ONLY,
|
|
10457
|
+
WRITE_CREATE,
|
|
10458
|
+
APPLY_TO_PRODUCTION_CONFIRMATION,
|
|
10450
10459
|
CHART_URI,
|
|
10451
10460
|
KPI_URI,
|
|
10452
10461
|
APP_MIME,
|
|
10462
|
+
rowsSchema,
|
|
10453
10463
|
referenceSchema,
|
|
10454
10464
|
viewSchema,
|
|
10455
10465
|
pageSchema,
|
|
10456
10466
|
appMeta,
|
|
10457
10467
|
enableChartApps,
|
|
10458
10468
|
chartResult,
|
|
10469
|
+
draftSchema,
|
|
10470
|
+
RENDER_TYPES,
|
|
10471
|
+
draftPage,
|
|
10472
|
+
workspacesForDatamart,
|
|
10473
|
+
saveDraftAsDashboard,
|
|
10459
10474
|
registerAllTools,
|
|
10460
10475
|
registerAllResources,
|
|
10461
10476
|
registerAllPrompts
|
|
10462
10477
|
};
|
|
10463
|
-
//# sourceMappingURL=chunk-
|
|
10478
|
+
//# sourceMappingURL=chunk-5IQ7MVUH.js.map
|