@databrainhq/mcp-server 0.3.0-rc.4 → 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-NZGQJBJV.js → chunk-4DYPD273.js} +8 -6
- package/dist/{chunk-NZGQJBJV.js.map → chunk-4DYPD273.js.map} +1 -1
- package/dist/{chunk-I35U2LBX.js → chunk-5IQ7MVUH.js} +953 -1118
- 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-3JFMWXRO.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-QHUNQWID.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-CX3CS62P.js.map +0 -1
- package/dist/chunk-I35U2LBX.js.map +0 -1
- package/dist/main-QHUNQWID.js.map +0 -1
- /package/dist/{chunk-3JFMWXRO.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.4",
|
|
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)) {
|
|
@@ -739,11 +187,20 @@ var TOOL_METADATA = {
|
|
|
739
187
|
effect: "read"
|
|
740
188
|
},
|
|
741
189
|
// Reports
|
|
742
|
-
list_scheduled_reports: { category: "reports", workflowIntent: "reports", effect: "read" }
|
|
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" }
|
|
743
200
|
};
|
|
744
201
|
|
|
745
202
|
// src/tools/data-app/list-data-apps.ts
|
|
746
|
-
import { z as
|
|
203
|
+
import { z as z2 } from "zod";
|
|
747
204
|
|
|
748
205
|
// src/api/dashboards-api.ts
|
|
749
206
|
var DashboardsApi = class {
|
|
@@ -1038,7 +495,7 @@ var QueryApi = class {
|
|
|
1038
495
|
async startSemanticLayerGeneration(datamartId) {
|
|
1039
496
|
return this.client.request({
|
|
1040
497
|
method: "POST",
|
|
1041
|
-
path: `/
|
|
498
|
+
path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId)}/semantic-layer/generate`,
|
|
1042
499
|
auth: "service",
|
|
1043
500
|
invalidateCache: ["semantic_layer_"]
|
|
1044
501
|
});
|
|
@@ -1046,7 +503,7 @@ var QueryApi = class {
|
|
|
1046
503
|
async getSemanticLayerGenerationStatus(datamartId) {
|
|
1047
504
|
return this.client.request({
|
|
1048
505
|
method: "GET",
|
|
1049
|
-
path: `/
|
|
506
|
+
path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId)}/semantic-layer/generation-status`,
|
|
1050
507
|
auth: "service"
|
|
1051
508
|
});
|
|
1052
509
|
}
|
|
@@ -2015,6 +1472,7 @@ function createServiceBundle(client) {
|
|
|
2015
1472
|
const guestTokenApi = new GuestTokenApi(client);
|
|
2016
1473
|
const queryApi = new QueryApi(client);
|
|
2017
1474
|
const scheduledReportsApi = new ScheduledReportsApi(client);
|
|
1475
|
+
const customChartsApi = new CustomChartsApi(client);
|
|
2018
1476
|
const tenantsApi = new TenantsApi(client);
|
|
2019
1477
|
const widgetsApi = new WidgetsApi(client);
|
|
2020
1478
|
const workspacesApi = new WorkspacesApi(client);
|
|
@@ -2029,6 +1487,7 @@ function createServiceBundle(client) {
|
|
|
2029
1487
|
workspacesApi,
|
|
2030
1488
|
queryApi,
|
|
2031
1489
|
scheduledReportsApi,
|
|
1490
|
+
customChartsApi,
|
|
2032
1491
|
tenantsApi,
|
|
2033
1492
|
widgetsApi,
|
|
2034
1493
|
embedService: new EmbedService(embedsApi, datamartsApi),
|
|
@@ -2079,7 +1538,7 @@ function registerListDataApps(server, client) {
|
|
|
2079
1538
|
"list_data_apps",
|
|
2080
1539
|
"List data apps in your organization. Start here for embed setup.",
|
|
2081
1540
|
{
|
|
2082
|
-
workspaceName:
|
|
1541
|
+
workspaceName: z2.string().optional().describe("Filter by workspace name")
|
|
2083
1542
|
},
|
|
2084
1543
|
READ_ONLY,
|
|
2085
1544
|
async (params) => {
|
|
@@ -2108,7 +1567,7 @@ function registerListDataApps(server, client) {
|
|
|
2108
1567
|
}
|
|
2109
1568
|
|
|
2110
1569
|
// src/tools/data-app/list-tenants.ts
|
|
2111
|
-
import { z as
|
|
1570
|
+
import { z as z3 } from "zod";
|
|
2112
1571
|
var isNonEmptyString4 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2113
1572
|
function workspaceIdFrom(workspace) {
|
|
2114
1573
|
return isNonEmptyString4(workspace.id) && workspace.id || isNonEmptyString4(workspace._id) && workspace._id || isNonEmptyString4(workspace.workspaceId) && workspace.workspaceId || null;
|
|
@@ -2146,12 +1605,12 @@ function registerListTenants(server, client) {
|
|
|
2146
1605
|
"list_tenants",
|
|
2147
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.",
|
|
2148
1607
|
{
|
|
2149
|
-
workspaceName:
|
|
2150
|
-
workspaceId:
|
|
2151
|
-
searchValue:
|
|
2152
|
-
limit:
|
|
2153
|
-
datasourceId:
|
|
2154
|
-
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.")
|
|
2155
1614
|
},
|
|
2156
1615
|
READ_ONLY,
|
|
2157
1616
|
async (params) => {
|
|
@@ -2245,15 +1704,15 @@ function registerListTenants(server, client) {
|
|
|
2245
1704
|
}
|
|
2246
1705
|
|
|
2247
1706
|
// src/tools/data-app/create-data-app.ts
|
|
2248
|
-
import { z as
|
|
1707
|
+
import { z as z4 } from "zod";
|
|
2249
1708
|
function registerCreateDataApp(server, client) {
|
|
2250
1709
|
server.tool(
|
|
2251
1710
|
"create_data_app",
|
|
2252
1711
|
"Create a new data app.",
|
|
2253
1712
|
{
|
|
2254
|
-
name:
|
|
2255
|
-
workspaceName:
|
|
2256
|
-
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/")
|
|
2257
1716
|
},
|
|
2258
1717
|
WRITE_CREATE,
|
|
2259
1718
|
async (params) => {
|
|
@@ -2287,7 +1746,7 @@ function registerCreateDataApp(server, client) {
|
|
|
2287
1746
|
}
|
|
2288
1747
|
|
|
2289
1748
|
// src/tools/data-app/list-api-tokens.ts
|
|
2290
|
-
import { z as
|
|
1749
|
+
import { z as z5 } from "zod";
|
|
2291
1750
|
|
|
2292
1751
|
// src/utils/resolve-resource.ts
|
|
2293
1752
|
function resolveResource(options) {
|
|
@@ -2356,7 +1815,7 @@ function registerListApiTokens(server, client) {
|
|
|
2356
1815
|
"list_api_tokens",
|
|
2357
1816
|
"List API tokens for a data app. Omit dataAppName to auto-resolve.",
|
|
2358
1817
|
{
|
|
2359
|
-
dataAppName:
|
|
1818
|
+
dataAppName: z5.string().optional().describe("Data app name. Omit to list available data apps.")
|
|
2360
1819
|
},
|
|
2361
1820
|
READ_ONLY,
|
|
2362
1821
|
async (params) => {
|
|
@@ -2401,16 +1860,16 @@ function registerListApiTokens(server, client) {
|
|
|
2401
1860
|
}
|
|
2402
1861
|
|
|
2403
1862
|
// src/tools/data-app/create-api-token.ts
|
|
2404
|
-
import { z as
|
|
1863
|
+
import { z as z6 } from "zod";
|
|
2405
1864
|
function registerCreateApiToken(server, client) {
|
|
2406
1865
|
const services = createServiceBundle(client);
|
|
2407
1866
|
server.tool(
|
|
2408
1867
|
"create_api_token",
|
|
2409
1868
|
"Create an API token for a data app and activate it for this session. Omit dataAppName to auto-resolve.",
|
|
2410
1869
|
{
|
|
2411
|
-
dataAppName:
|
|
2412
|
-
name:
|
|
2413
|
-
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")
|
|
2414
1873
|
},
|
|
2415
1874
|
WRITE_CREATE,
|
|
2416
1875
|
async (params) => {
|
|
@@ -2462,16 +1921,16 @@ Save this in backend secrets immediately. This value may not be returned again.`
|
|
|
2462
1921
|
}
|
|
2463
1922
|
|
|
2464
1923
|
// src/tools/data-app/rotate-api-token.ts
|
|
2465
|
-
import { z as
|
|
1924
|
+
import { z as z7 } from "zod";
|
|
2466
1925
|
function registerRotateApiToken(server, client) {
|
|
2467
1926
|
const services = createServiceBundle(client);
|
|
2468
1927
|
server.tool(
|
|
2469
1928
|
"rotate_api_token",
|
|
2470
1929
|
"Rotate (invalidate and replace) an API token. Previous token becomes stale.",
|
|
2471
1930
|
{
|
|
2472
|
-
dataAppName:
|
|
2473
|
-
tokenId:
|
|
2474
|
-
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")
|
|
2475
1934
|
},
|
|
2476
1935
|
DESTRUCTIVE,
|
|
2477
1936
|
async (params) => {
|
|
@@ -2590,14 +2049,14 @@ function registerListDatasources(server, client) {
|
|
|
2590
2049
|
}
|
|
2591
2050
|
|
|
2592
2051
|
// src/tools/infrastructure/discover-datasource-schema.ts
|
|
2593
|
-
import { z as
|
|
2052
|
+
import { z as z8 } from "zod";
|
|
2594
2053
|
function registerDiscoverDatasourceSchema(server, client) {
|
|
2595
2054
|
const services = createServiceBundle(client);
|
|
2596
2055
|
server.tool(
|
|
2597
2056
|
"discover_datasource_schema",
|
|
2598
2057
|
"Inspect tables and columns in a datasource. Omit datasourceName to list available datasources.",
|
|
2599
2058
|
{
|
|
2600
|
-
datasourceName:
|
|
2059
|
+
datasourceName: z8.string().optional().describe(
|
|
2601
2060
|
"Name of the datasource to inspect. Omit to list available datasources."
|
|
2602
2061
|
)
|
|
2603
2062
|
},
|
|
@@ -2680,13 +2139,13 @@ Fix the connection or add a new database in the **Databrain UI**.`
|
|
|
2680
2139
|
}
|
|
2681
2140
|
|
|
2682
2141
|
// src/tools/infrastructure/list-datamarts.ts
|
|
2683
|
-
import { z as
|
|
2142
|
+
import { z as z9 } from "zod";
|
|
2684
2143
|
function registerListDatamarts(server, client) {
|
|
2685
2144
|
server.tool(
|
|
2686
2145
|
"list_datamarts",
|
|
2687
2146
|
"List datamarts with table counts and semantic layer scores.",
|
|
2688
2147
|
{
|
|
2689
|
-
dataAppName:
|
|
2148
|
+
dataAppName: z9.string().optional().describe("Filter by data app name")
|
|
2690
2149
|
},
|
|
2691
2150
|
READ_ONLY,
|
|
2692
2151
|
async (params) => {
|
|
@@ -2775,7 +2234,7 @@ function registerListWorkspaces(server, client) {
|
|
|
2775
2234
|
}
|
|
2776
2235
|
|
|
2777
2236
|
// src/tools/infrastructure/create-datamart.ts
|
|
2778
|
-
import { z as
|
|
2237
|
+
import { z as z10 } from "zod";
|
|
2779
2238
|
|
|
2780
2239
|
// src/tools/widgets/production-guards.ts
|
|
2781
2240
|
var APPLY_TO_PRODUCTION_CONFIRMATION = "APPLY_TO_PRODUCTION";
|
|
@@ -2793,29 +2252,29 @@ function genericProductionSafetyNote() {
|
|
|
2793
2252
|
}
|
|
2794
2253
|
|
|
2795
2254
|
// src/tools/infrastructure/create-datamart.ts
|
|
2796
|
-
var columnSchema =
|
|
2797
|
-
name:
|
|
2798
|
-
alias:
|
|
2799
|
-
label:
|
|
2800
|
-
isHide:
|
|
2801
|
-
isCustomColumn:
|
|
2802
|
-
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()
|
|
2803
2262
|
});
|
|
2804
|
-
var tableSchema =
|
|
2805
|
-
name:
|
|
2806
|
-
schemaName:
|
|
2807
|
-
clientColumn:
|
|
2808
|
-
label:
|
|
2809
|
-
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)
|
|
2810
2269
|
});
|
|
2811
|
-
var relationshipSchema =
|
|
2812
|
-
parentTableName:
|
|
2813
|
-
parentColumnName:
|
|
2814
|
-
childTableName:
|
|
2815
|
-
childColumnName:
|
|
2816
|
-
cardinality:
|
|
2817
|
-
join:
|
|
2818
|
-
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()
|
|
2819
2278
|
});
|
|
2820
2279
|
function datamartSuccessMessage(name, result) {
|
|
2821
2280
|
return `Datamart "${name}" created successfully (ID: ${result.id || result._id || "UNKNOWN"}).
|
|
@@ -2836,21 +2295,21 @@ function registerCreateDatamart(server, client) {
|
|
|
2836
2295
|
"create_datamart",
|
|
2837
2296
|
"Create a production datamart from selected tables and relationships. Defaults to dry-run; apply requires explicit confirmation.",
|
|
2838
2297
|
{
|
|
2839
|
-
mode:
|
|
2840
|
-
confirm:
|
|
2841
|
-
name:
|
|
2842
|
-
datasourceName:
|
|
2843
|
-
tableList:
|
|
2844
|
-
relationships:
|
|
2845
|
-
tenancySettings:
|
|
2846
|
-
tenancyLevel:
|
|
2847
|
-
clientColumnType:
|
|
2848
|
-
schemaName:
|
|
2849
|
-
tableName:
|
|
2850
|
-
tableClientNameColumn:
|
|
2851
|
-
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()
|
|
2852
2311
|
}).optional().describe("Multi-tenant configuration"),
|
|
2853
|
-
body:
|
|
2312
|
+
body: z10.record(z10.unknown()).optional().describe("Raw API body passthrough for POST /api/v2/data-app/datamarts")
|
|
2854
2313
|
},
|
|
2855
2314
|
WRITE_CREATE,
|
|
2856
2315
|
async (params) => {
|
|
@@ -2945,14 +2404,14 @@ function registerCreateDatamart(server, client) {
|
|
|
2945
2404
|
}
|
|
2946
2405
|
|
|
2947
2406
|
// src/tools/infrastructure/sync-datasource.ts
|
|
2948
|
-
import { z as
|
|
2407
|
+
import { z as z11 } from "zod";
|
|
2949
2408
|
function registerSyncDatasource(server, client) {
|
|
2950
2409
|
const datasourcesApi = new DatasourcesApi(client);
|
|
2951
2410
|
server.tool(
|
|
2952
2411
|
"sync_datasource",
|
|
2953
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.",
|
|
2954
2413
|
{
|
|
2955
|
-
datasourceName:
|
|
2414
|
+
datasourceName: z11.string().optional().describe("Name of the datasource to sync. Omit to list available datasources.")
|
|
2956
2415
|
},
|
|
2957
2416
|
WRITE_IDEMPOTENT,
|
|
2958
2417
|
async (params) => {
|
|
@@ -3001,7 +2460,7 @@ The schema metadata will be refreshed. You can now use \`discover_datasource_sch
|
|
|
3001
2460
|
}
|
|
3002
2461
|
|
|
3003
2462
|
// src/tools/embed/create-embed.ts
|
|
3004
|
-
import { z as
|
|
2463
|
+
import { z as z12 } from "zod";
|
|
3005
2464
|
var DEFAULT_ACCESS_SETTINGS2 = {
|
|
3006
2465
|
isAllowMetricCreation: false,
|
|
3007
2466
|
isAllowMetricDeletion: false,
|
|
@@ -3014,18 +2473,18 @@ var DEFAULT_ACCESS_SETTINGS2 = {
|
|
|
3014
2473
|
isAllowCreateDashboardView: false,
|
|
3015
2474
|
metricCreationMode: "DRAG_DROP"
|
|
3016
2475
|
};
|
|
3017
|
-
var accessSettingsSchema =
|
|
3018
|
-
datamartName:
|
|
3019
|
-
isAllowMetricCreation:
|
|
3020
|
-
metricCreationMode:
|
|
3021
|
-
isAllowMetricDeletion:
|
|
3022
|
-
isAllowMetricUpdate:
|
|
3023
|
-
isAllowMetricLayoutChange:
|
|
3024
|
-
isAllowManageMetrics:
|
|
3025
|
-
isAllowUnderlyingData:
|
|
3026
|
-
isAllowAiPilot:
|
|
3027
|
-
isAllowEmailReports:
|
|
3028
|
-
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")
|
|
3029
2488
|
}).describe("Embed access settings. Only datamartName is required \u2014 all flags default to false.");
|
|
3030
2489
|
function registerCreateEmbed(server, client) {
|
|
3031
2490
|
const services = createServiceBundle(client);
|
|
@@ -3033,23 +2492,23 @@ function registerCreateEmbed(server, client) {
|
|
|
3033
2492
|
"create_embed",
|
|
3034
2493
|
"Create an embed. Provide dashboardId, workspaceName, accessSettings.datamartName. Add clientId for multi-tenant per-client dashboards. All isAllow* flags default to false.",
|
|
3035
2494
|
{
|
|
3036
|
-
body:
|
|
2495
|
+
body: z12.record(z12.unknown()).optional().describe(
|
|
3037
2496
|
"Raw API body passthrough. If provided, all other fields are ignored."
|
|
3038
2497
|
),
|
|
3039
|
-
dashboardId:
|
|
3040
|
-
workspaceName:
|
|
3041
|
-
embedType:
|
|
3042
|
-
metricId:
|
|
3043
|
-
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"),
|
|
3044
2503
|
accessSettings: accessSettingsSchema.optional(),
|
|
3045
2504
|
// Multi-tenant (dashboard embed) params
|
|
3046
|
-
clientId:
|
|
2505
|
+
clientId: z12.string().optional().describe(
|
|
3047
2506
|
"Client identifier for multi-tenant isolation. When provided, creates a dashboard embed (each client gets their own dashboard copy)."
|
|
3048
2507
|
),
|
|
3049
|
-
templateDashboardId:
|
|
3050
|
-
isImportMetrics:
|
|
3051
|
-
metricImportMode:
|
|
3052
|
-
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")
|
|
3053
2512
|
},
|
|
3054
2513
|
WRITE_CREATE,
|
|
3055
2514
|
async (params) => {
|
|
@@ -3151,14 +2610,14 @@ Next: run \`get_embed_details\` to verify the embed configuration.`
|
|
|
3151
2610
|
}
|
|
3152
2611
|
|
|
3153
2612
|
// src/tools/embed/list-embeds.ts
|
|
3154
|
-
import { z as
|
|
2613
|
+
import { z as z13 } from "zod";
|
|
3155
2614
|
function registerListEmbeds(server, client) {
|
|
3156
2615
|
const services = createServiceBundle(client);
|
|
3157
2616
|
server.tool(
|
|
3158
2617
|
"list_embeds",
|
|
3159
2618
|
"List embed configurations. Requires an API token (use create_api_token first). Use `list_tenants` for authoritative client discovery from the tenancy database.",
|
|
3160
2619
|
{
|
|
3161
|
-
dataAppName:
|
|
2620
|
+
dataAppName: z13.string().optional().describe("Filter by data app name")
|
|
3162
2621
|
},
|
|
3163
2622
|
READ_ONLY,
|
|
3164
2623
|
async (params) => {
|
|
@@ -3209,14 +2668,14 @@ function registerListEmbeds(server, client) {
|
|
|
3209
2668
|
}
|
|
3210
2669
|
|
|
3211
2670
|
// src/tools/embed/get-embed-details.ts
|
|
3212
|
-
import { z as
|
|
2671
|
+
import { z as z14 } from "zod";
|
|
3213
2672
|
function registerGetEmbedDetails(server, client) {
|
|
3214
2673
|
const services = createServiceBundle(client);
|
|
3215
2674
|
server.tool(
|
|
3216
2675
|
"get_embed_details",
|
|
3217
2676
|
"Get full embed configuration including access settings and theme.",
|
|
3218
2677
|
{
|
|
3219
|
-
embedId:
|
|
2678
|
+
embedId: z14.string().describe("ID of the embed")
|
|
3220
2679
|
},
|
|
3221
2680
|
READ_ONLY,
|
|
3222
2681
|
async (params) => {
|
|
@@ -3232,7 +2691,7 @@ function registerGetEmbedDetails(server, client) {
|
|
|
3232
2691
|
}
|
|
3233
2692
|
|
|
3234
2693
|
// src/tools/embed/update-embed.ts
|
|
3235
|
-
import { z as
|
|
2694
|
+
import { z as z15 } from "zod";
|
|
3236
2695
|
|
|
3237
2696
|
// src/tools/embed/helpers.ts
|
|
3238
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.";
|
|
@@ -3270,15 +2729,15 @@ function registerUpdateEmbed(server, client) {
|
|
|
3270
2729
|
"update_embed",
|
|
3271
2730
|
"Update embed access settings, permissions, theme, or name.",
|
|
3272
2731
|
{
|
|
3273
|
-
embedId:
|
|
3274
|
-
body:
|
|
2732
|
+
embedId: z15.string().describe("ID of the embed to update"),
|
|
2733
|
+
body: z15.record(z15.unknown()).optional().describe(
|
|
3275
2734
|
"Raw API body for PUT /api/v2/data-app/embeds. If provided, it is sent directly (passthrough mode)."
|
|
3276
2735
|
),
|
|
3277
|
-
name:
|
|
3278
|
-
accessSettings:
|
|
2736
|
+
name: z15.string().optional().describe("New name for the embed"),
|
|
2737
|
+
accessSettings: z15.record(z15.unknown()).optional().describe(
|
|
3279
2738
|
"Partial update of access settings. See `get_embed_details` for current settings."
|
|
3280
2739
|
),
|
|
3281
|
-
theme:
|
|
2740
|
+
theme: z15.record(z15.unknown()).optional().describe(
|
|
3282
2741
|
"Theme configuration object. See databrain://theme-reference for the full schema."
|
|
3283
2742
|
)
|
|
3284
2743
|
},
|
|
@@ -3324,7 +2783,7 @@ function registerUpdateEmbed(server, client) {
|
|
|
3324
2783
|
}
|
|
3325
2784
|
|
|
3326
2785
|
// src/tools/embed/customize-theme.ts
|
|
3327
|
-
import { z as
|
|
2786
|
+
import { z as z16 } from "zod";
|
|
3328
2787
|
var THEME_PRESETS = {
|
|
3329
2788
|
light: {
|
|
3330
2789
|
general: {
|
|
@@ -3458,7 +2917,7 @@ var ACCESS_PRESETS = {
|
|
|
3458
2917
|
metricCreationMode: "CHAT"
|
|
3459
2918
|
}
|
|
3460
2919
|
};
|
|
3461
|
-
var fontFamilyEnum =
|
|
2920
|
+
var fontFamilyEnum = z16.enum([
|
|
3462
2921
|
"Inter",
|
|
3463
2922
|
"Roboto",
|
|
3464
2923
|
"Open Sans",
|
|
@@ -3470,17 +2929,17 @@ var fontFamilyEnum = z17.enum([
|
|
|
3470
2929
|
"Nunito",
|
|
3471
2930
|
"system-ui"
|
|
3472
2931
|
]).describe("Font family");
|
|
3473
|
-
var tooltipFontSchema =
|
|
3474
|
-
fontFamily:
|
|
3475
|
-
fontSize:
|
|
3476
|
-
fontWeight:
|
|
3477
|
-
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()
|
|
3478
2937
|
}).optional();
|
|
3479
|
-
var fontConfigInputSchema =
|
|
3480
|
-
fontSize:
|
|
3481
|
-
fontFamily:
|
|
3482
|
-
fontWeight:
|
|
3483
|
-
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()
|
|
3484
2943
|
}).optional();
|
|
3485
2944
|
function mapToFrontendFontConfig(input) {
|
|
3486
2945
|
if (!input) return void 0;
|
|
@@ -3491,23 +2950,23 @@ function mapToFrontendFontConfig(input) {
|
|
|
3491
2950
|
...input.color && { color: input.color }
|
|
3492
2951
|
};
|
|
3493
2952
|
}
|
|
3494
|
-
var axisSchema =
|
|
3495
|
-
hideAxisLines:
|
|
3496
|
-
hideSplitLines:
|
|
3497
|
-
hideAxisLabels:
|
|
3498
|
-
hideAxisTicks:
|
|
3499
|
-
axisName:
|
|
3500
|
-
axisNameOffset:
|
|
3501
|
-
axisLabelMargin:
|
|
3502
|
-
fontSize:
|
|
3503
|
-
fontFamily:
|
|
3504
|
-
fontWeight:
|
|
3505
|
-
color:
|
|
3506
|
-
axisColor:
|
|
3507
|
-
axisNameFontConfig:
|
|
3508
|
-
fontFamily:
|
|
3509
|
-
fontSize:
|
|
3510
|
-
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()
|
|
3511
2970
|
}).optional().describe("Axis title font configuration")
|
|
3512
2971
|
}).optional();
|
|
3513
2972
|
function buildAdminTheme(p, presetBase) {
|
|
@@ -3663,94 +3122,94 @@ function registerCustomizeTheme(server, client) {
|
|
|
3663
3122
|
"customize_embed_theme",
|
|
3664
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("/")}).`,
|
|
3665
3124
|
{
|
|
3666
|
-
embedId:
|
|
3667
|
-
body:
|
|
3125
|
+
embedId: z16.string().describe("ID of the embed to theme"),
|
|
3126
|
+
body: z16.record(z16.unknown()).optional().describe(
|
|
3668
3127
|
"Raw API body for PUT /api/v2/data-app/embeds. If provided, sent directly and all convenience fields are ignored."
|
|
3669
3128
|
),
|
|
3670
3129
|
// --- Presets ---
|
|
3671
|
-
themePreset:
|
|
3672
|
-
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"),
|
|
3673
3132
|
// --- General ---
|
|
3674
3133
|
fontFamily: fontFamilyEnum.optional().describe("Global font family"),
|
|
3675
|
-
datePickerFormat:
|
|
3134
|
+
datePickerFormat: z16.string().optional().describe("Date picker display format"),
|
|
3676
3135
|
// --- Colors ---
|
|
3677
|
-
primaryColor:
|
|
3678
|
-
backgroundColor:
|
|
3679
|
-
textColor:
|
|
3680
|
-
ctaColor:
|
|
3681
|
-
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)"),
|
|
3682
3141
|
// --- Dashboard ---
|
|
3683
|
-
dashboardBackgroundColor:
|
|
3684
|
-
selectBoxSize:
|
|
3685
|
-
selectBoxVariant:
|
|
3686
|
-
selectBoxBorderRadius:
|
|
3687
|
-
selectBoxTextColor:
|
|
3688
|
-
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)"),
|
|
3689
3148
|
// --- Card Title ---
|
|
3690
|
-
cardTitleFontSize:
|
|
3691
|
-
cardTitleFontWeight:
|
|
3692
|
-
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)"),
|
|
3693
3152
|
// --- Card Description ---
|
|
3694
|
-
cardDescriptionFontSize:
|
|
3695
|
-
cardDescriptionFontWeight:
|
|
3696
|
-
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)"),
|
|
3697
3156
|
// --- Card Customization ---
|
|
3698
|
-
cardPadding:
|
|
3699
|
-
cardBorderRadius:
|
|
3700
|
-
cardShadow:
|
|
3701
|
-
disableShadowOnHover:
|
|
3702
|
-
disableCardStroke:
|
|
3703
|
-
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)"),
|
|
3704
3163
|
// --- Chart Colors (admin theme level) ---
|
|
3705
|
-
chartFontFamily:
|
|
3706
|
-
chartFontColor:
|
|
3707
|
-
chartColors:
|
|
3708
|
-
chartPaletteName:
|
|
3709
|
-
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"),
|
|
3710
3169
|
// --- Chart Tooltip (admin theme level) ---
|
|
3711
3170
|
tooltipHeader: tooltipFontSchema.describe("Tooltip header font config"),
|
|
3712
3171
|
tooltipLabel: tooltipFontSchema.describe("Tooltip label font config"),
|
|
3713
3172
|
tooltipValue: tooltipFontSchema.describe("Tooltip value font config"),
|
|
3714
3173
|
// --- Component Theme ---
|
|
3715
|
-
buttonPrimaryColor:
|
|
3716
|
-
buttonPrimaryTextColor:
|
|
3717
|
-
buttonSecondaryColor:
|
|
3718
|
-
buttonSecondaryTextColor:
|
|
3719
|
-
checkboxCheckedColor:
|
|
3720
|
-
checkboxUncheckedColor:
|
|
3721
|
-
switchEnabledColor:
|
|
3722
|
-
switchDisabledColor:
|
|
3723
|
-
breadcrumbFontFamily:
|
|
3724
|
-
breadcrumbFontColor:
|
|
3725
|
-
breadcrumbActiveColor:
|
|
3726
|
-
filterBadgeColor:
|
|
3727
|
-
filterBadgeTextColor:
|
|
3728
|
-
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)"),
|
|
3729
3188
|
// --- Responsive Breakpoints ---
|
|
3730
|
-
breakpointXl:
|
|
3731
|
-
breakpointLg:
|
|
3732
|
-
breakpointMd:
|
|
3733
|
-
breakpointSm:
|
|
3734
|
-
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"),
|
|
3735
3194
|
// --- Metric Layout Columns ---
|
|
3736
|
-
layoutColsXl:
|
|
3737
|
-
layoutColsLg:
|
|
3738
|
-
layoutColsMd:
|
|
3739
|
-
layoutColsSm:
|
|
3740
|
-
layoutColsXs:
|
|
3741
|
-
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"),
|
|
3742
3201
|
// --- Chart Appearance (detailed) ---
|
|
3743
3202
|
tooltipHeaderStyle: fontConfigInputSchema.describe("Tooltip header: fontSize, fontFamily, fontWeight, color"),
|
|
3744
3203
|
tooltipLabelStyle: fontConfigInputSchema.describe("Tooltip label: fontSize, fontFamily, fontWeight, color"),
|
|
3745
3204
|
tooltipValueStyle: fontConfigInputSchema.describe("Tooltip value: fontSize, fontFamily, fontWeight, color"),
|
|
3746
|
-
labelPosition:
|
|
3747
|
-
radialLabelPosition:
|
|
3748
|
-
marginTop:
|
|
3749
|
-
marginLeft:
|
|
3750
|
-
marginRight:
|
|
3751
|
-
marginBottom:
|
|
3752
|
-
legendShow:
|
|
3753
|
-
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([
|
|
3754
3213
|
"top-left",
|
|
3755
3214
|
"top-center",
|
|
3756
3215
|
"top-right",
|
|
@@ -3760,17 +3219,17 @@ function registerCustomizeTheme(server, client) {
|
|
|
3760
3219
|
"bottom-center",
|
|
3761
3220
|
"bottom-right"
|
|
3762
3221
|
]).optional().describe("Legend position"),
|
|
3763
|
-
legendLayout:
|
|
3764
|
-
legendShape:
|
|
3765
|
-
legendFontSize:
|
|
3766
|
-
legendFontWeight:
|
|
3767
|
-
legendFontFamily:
|
|
3768
|
-
legendColor:
|
|
3769
|
-
legendTruncateLength:
|
|
3770
|
-
disableLegendScrolling:
|
|
3771
|
-
enableVariableLegendPosition:
|
|
3772
|
-
legendTop:
|
|
3773
|
-
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"),
|
|
3774
3233
|
verticalAxis: axisSchema.describe("Vertical (Y) axis configuration"),
|
|
3775
3234
|
horizontalAxis: axisSchema.describe("Horizontal (X) axis configuration")
|
|
3776
3235
|
},
|
|
@@ -3845,7 +3304,7 @@ Use \`get_embed_details\` to view the full current theme configuration.` + GUEST
|
|
|
3845
3304
|
}
|
|
3846
3305
|
|
|
3847
3306
|
// src/tools/embed/get-demo-link.ts
|
|
3848
|
-
import { z as
|
|
3307
|
+
import { z as z17 } from "zod";
|
|
3849
3308
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
3850
3309
|
var DEFAULT_EXPIRY_HOURS = 24;
|
|
3851
3310
|
var MAX_EXPIRY_HOURS = 24 * 30;
|
|
@@ -3855,11 +3314,11 @@ function registerGetDemoLink(server, client) {
|
|
|
3855
3314
|
"get_demo_link",
|
|
3856
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.`,
|
|
3857
3316
|
{
|
|
3858
|
-
clientId:
|
|
3859
|
-
dashboardName:
|
|
3860
|
-
workspaceName:
|
|
3861
|
-
embedId:
|
|
3862
|
-
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).`)
|
|
3863
3322
|
},
|
|
3864
3323
|
WRITE_CREATE,
|
|
3865
3324
|
async (params) => {
|
|
@@ -3915,70 +3374,70 @@ function registerGetDemoLink(server, client) {
|
|
|
3915
3374
|
}
|
|
3916
3375
|
|
|
3917
3376
|
// src/tools/frontend/generate-guest-token.ts
|
|
3918
|
-
import { z as
|
|
3377
|
+
import { z as z18 } from "zod";
|
|
3919
3378
|
function registerGenerateGuestToken(server, client) {
|
|
3920
3379
|
const services = createServiceBundle(client);
|
|
3921
3380
|
server.tool(
|
|
3922
3381
|
"generate_guest_token",
|
|
3923
3382
|
"Generate a guest token for frontend embedding. Requires clientId. Omit dataAppName to auto-resolve.",
|
|
3924
3383
|
{
|
|
3925
|
-
clientId:
|
|
3926
|
-
dataAppName:
|
|
3927
|
-
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(
|
|
3928
3387
|
"Raw API body for POST /api/v2/guest-token/create. If provided, sent directly and convenience fields are ignored."
|
|
3929
3388
|
),
|
|
3930
|
-
params:
|
|
3931
|
-
allowedEmbeds:
|
|
3932
|
-
rlsSettings:
|
|
3933
|
-
metricId:
|
|
3934
|
-
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")
|
|
3935
3394
|
})).optional().describe("Legacy alias used in docs/examples; forwarded as params.rlsSettings"),
|
|
3936
|
-
appFilters:
|
|
3937
|
-
metricId:
|
|
3938
|
-
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")
|
|
3939
3398
|
})).optional().describe("Metric-level app filters"),
|
|
3940
|
-
dashboardAppFilters:
|
|
3941
|
-
dashboardId:
|
|
3942
|
-
values:
|
|
3943
|
-
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")
|
|
3944
3403
|
})).optional().describe("Dashboard-level app filters"),
|
|
3945
|
-
hideDashboardFilters:
|
|
3946
|
-
userIdentifier:
|
|
3947
|
-
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"')
|
|
3948
3407
|
}).optional().describe("Advanced token params"),
|
|
3949
3408
|
// Legacy convenience fields (auto-mapped into params for backward compatibility)
|
|
3950
|
-
timezone:
|
|
3951
|
-
allowedEmbeds:
|
|
3952
|
-
rlsSettings:
|
|
3953
|
-
metricId:
|
|
3954
|
-
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")
|
|
3955
3414
|
})).optional().describe("Legacy: moved under params.rlsSettings"),
|
|
3956
|
-
appFilters:
|
|
3957
|
-
metricId:
|
|
3958
|
-
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")
|
|
3959
3418
|
})).optional().describe("Legacy: moved under params.appFilters"),
|
|
3960
|
-
dashboardAppFilters:
|
|
3961
|
-
dashboardId:
|
|
3962
|
-
values:
|
|
3963
|
-
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")
|
|
3964
3423
|
})).optional().describe("Legacy: moved under params.dashboardAppFilters"),
|
|
3965
|
-
permissions:
|
|
3966
|
-
isEnableArchiveMetrics:
|
|
3967
|
-
isEnableManageMetrics:
|
|
3968
|
-
isEnableCreateDashboardView:
|
|
3969
|
-
isEnableMetricUpdation:
|
|
3970
|
-
isEnableCustomizeLayout:
|
|
3971
|
-
isEnableUnderlyingData:
|
|
3972
|
-
isEnableDownloadMetrics:
|
|
3973
|
-
isShowSideBar:
|
|
3974
|
-
isShowDashboardName:
|
|
3975
|
-
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()
|
|
3976
3435
|
}).optional().describe("Runtime permission overrides (merge with and override embed-level settings)"),
|
|
3977
|
-
theme:
|
|
3978
|
-
hideDashboardFilters:
|
|
3979
|
-
userIdentifier:
|
|
3980
|
-
expiryTime:
|
|
3981
|
-
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")
|
|
3982
3441
|
},
|
|
3983
3442
|
WRITE_CREATE,
|
|
3984
3443
|
async (params) => {
|
|
@@ -4031,7 +3490,7 @@ Next step: Use \`generate_embed_code\` to get copy-paste-ready frontend code.`
|
|
|
4031
3490
|
}
|
|
4032
3491
|
|
|
4033
3492
|
// src/tools/frontend/generate-embed-code.ts
|
|
4034
|
-
import { z as
|
|
3493
|
+
import { z as z19 } from "zod";
|
|
4035
3494
|
|
|
4036
3495
|
// src/templates/react.ts
|
|
4037
3496
|
function kebabCase(str) {
|
|
@@ -5027,22 +4486,22 @@ function registerGenerateEmbedCode(server, _client) {
|
|
|
5027
4486
|
"generate_embed_code",
|
|
5028
4487
|
"Generate framework-specific embed code (React, Next.js, Vue, Angular, Svelte, SolidJS, vanilla HTML).",
|
|
5029
4488
|
{
|
|
5030
|
-
embedId:
|
|
5031
|
-
framework:
|
|
5032
|
-
embedType:
|
|
5033
|
-
options:
|
|
5034
|
-
enableDownloadCsv:
|
|
5035
|
-
enableEmailCsv:
|
|
5036
|
-
enableDownloadAllMetrics:
|
|
5037
|
-
enableDownloadAllPdf:
|
|
5038
|
-
disableFullscreen:
|
|
5039
|
-
isHideTablePreview:
|
|
5040
|
-
isHideChartSettings:
|
|
5041
|
-
isStickyDashboardFilters:
|
|
5042
|
-
enableMultiMetricFilters:
|
|
5043
|
-
themeName:
|
|
5044
|
-
customTheme:
|
|
5045
|
-
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()
|
|
5046
4505
|
}).optional()
|
|
5047
4506
|
},
|
|
5048
4507
|
READ_ONLY,
|
|
@@ -5082,17 +4541,17 @@ ${generated.instructions}`;
|
|
|
5082
4541
|
}
|
|
5083
4542
|
|
|
5084
4543
|
// src/tools/analytics/list-dashboards.ts
|
|
5085
|
-
import { z as
|
|
4544
|
+
import { z as z20 } from "zod";
|
|
5086
4545
|
function registerListDashboards(server, client) {
|
|
5087
4546
|
const services = createServiceBundle(client);
|
|
5088
4547
|
server.tool(
|
|
5089
4548
|
"list_dashboards",
|
|
5090
4549
|
"List dashboards globally or for a specific data app.",
|
|
5091
4550
|
{
|
|
5092
|
-
scope:
|
|
4551
|
+
scope: z20.enum(["auto", "global", "data_app"]).optional().describe(
|
|
5093
4552
|
"Dashboard listing scope. auto: use data_app when dataAppName + API token are available, otherwise global."
|
|
5094
4553
|
),
|
|
5095
|
-
dataAppName:
|
|
4554
|
+
dataAppName: z20.string().optional().describe("Data app name for data_app scope. Ignored for global scope.")
|
|
5096
4555
|
},
|
|
5097
4556
|
READ_ONLY,
|
|
5098
4557
|
async (params) => {
|
|
@@ -5127,36 +4586,36 @@ function registerListDashboards(server, client) {
|
|
|
5127
4586
|
}
|
|
5128
4587
|
|
|
5129
4588
|
// src/tools/dashboard/schema.ts
|
|
5130
|
-
import { z as
|
|
5131
|
-
var dashboardPositionSchema =
|
|
5132
|
-
x:
|
|
5133
|
-
y:
|
|
5134
|
-
w:
|
|
5135
|
-
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)
|
|
5136
4595
|
}).strict();
|
|
5137
|
-
var dashboardChartSchema =
|
|
5138
|
-
type:
|
|
5139
|
-
metricRef:
|
|
5140
|
-
query:
|
|
5141
|
-
companyIntegrationId:
|
|
5142
|
-
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()
|
|
5143
4602
|
}).passthrough();
|
|
5144
|
-
var dashboardTileSchema =
|
|
5145
|
-
id:
|
|
5146
|
-
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"),
|
|
5147
4606
|
position: dashboardPositionSchema,
|
|
5148
|
-
title:
|
|
4607
|
+
title: z21.string().min(1).max(200),
|
|
5149
4608
|
chart: dashboardChartSchema.optional()
|
|
5150
4609
|
}).strict();
|
|
5151
4610
|
var dashboardDocumentShape = {
|
|
5152
|
-
schemaVersion:
|
|
5153
|
-
title:
|
|
5154
|
-
description:
|
|
5155
|
-
layout:
|
|
5156
|
-
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)
|
|
5157
4616
|
};
|
|
5158
|
-
var workspaceNameSchema =
|
|
5159
|
-
var idempotencyKeySchema =
|
|
4617
|
+
var workspaceNameSchema = z21.string().min(1).describe("Target workspace name");
|
|
4618
|
+
var idempotencyKeySchema = z21.string().regex(
|
|
5160
4619
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
5161
4620
|
"idempotencyKey must be a UUID v4"
|
|
5162
4621
|
).describe("UUID v4 unique to this workspace dashboard create request");
|
|
@@ -5209,7 +4668,7 @@ ${stringify(validation)}`
|
|
|
5209
4668
|
}
|
|
5210
4669
|
|
|
5211
4670
|
// src/tools/dashboard/generate-dashboard-tiles.ts
|
|
5212
|
-
import { z as
|
|
4671
|
+
import { z as z22 } from "zod";
|
|
5213
4672
|
function registerGenerateDashboardTiles(server, client) {
|
|
5214
4673
|
const services = createServiceBundle(client);
|
|
5215
4674
|
server.tool(
|
|
@@ -5217,15 +4676,15 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
5217
4676
|
"Generate multiple workspace dashboard tile drafts without persisting them.",
|
|
5218
4677
|
{
|
|
5219
4678
|
workspaceName: workspaceNameSchema,
|
|
5220
|
-
dataAppName:
|
|
5221
|
-
integrationId:
|
|
5222
|
-
integrationName:
|
|
5223
|
-
tiles:
|
|
5224
|
-
|
|
5225
|
-
id:
|
|
5226
|
-
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),
|
|
5227
4686
|
position: dashboardPositionSchema,
|
|
5228
|
-
userInputs:
|
|
4687
|
+
userInputs: z22.record(z22.unknown())
|
|
5229
4688
|
}).strict()
|
|
5230
4689
|
).min(1).max(50)
|
|
5231
4690
|
},
|
|
@@ -5309,14 +4768,14 @@ function registerGenerateDashboardTiles(server, client) {
|
|
|
5309
4768
|
}
|
|
5310
4769
|
|
|
5311
4770
|
// src/tools/dashboard/get-dashboard.ts
|
|
5312
|
-
import { z as
|
|
4771
|
+
import { z as z23 } from "zod";
|
|
5313
4772
|
function registerGetDashboard(server, client) {
|
|
5314
4773
|
server.tool(
|
|
5315
4774
|
"get_dashboard",
|
|
5316
4775
|
"Retrieve a workspace dashboard projected back into the versioned tile-list contract.",
|
|
5317
4776
|
{
|
|
5318
4777
|
workspaceName: workspaceNameSchema,
|
|
5319
|
-
dashboardId:
|
|
4778
|
+
dashboardId: z23.string().min(1).describe("Dashboard identifier returned by create_dashboard")
|
|
5320
4779
|
},
|
|
5321
4780
|
READ_ONLY,
|
|
5322
4781
|
async (params) => {
|
|
@@ -5341,18 +4800,18 @@ function registerGetDashboard(server, client) {
|
|
|
5341
4800
|
|
|
5342
4801
|
// src/apps/preview.ts
|
|
5343
4802
|
import { createHash } from "crypto";
|
|
5344
|
-
import { z as
|
|
5345
|
-
var draftSchema =
|
|
5346
|
-
question:
|
|
5347
|
-
sql:
|
|
5348
|
-
datamartName:
|
|
5349
|
-
dimensions:
|
|
5350
|
-
measures:
|
|
5351
|
-
chartType:
|
|
5352
|
-
title:
|
|
5353
|
-
sessionId:
|
|
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(),
|
|
5354
4813
|
/** Workspaces built on this datamart. One means Save needs no choice. */
|
|
5355
|
-
workspaces:
|
|
4814
|
+
workspaces: z24.array(z24.string().max(256)).max(50).default([])
|
|
5356
4815
|
}).strict();
|
|
5357
4816
|
var PREVIEW_PATH = "/api/v2/data-app/chart-preview";
|
|
5358
4817
|
var METRIC_PATH = "/api/mcp/metrics/create";
|
|
@@ -5479,7 +4938,24 @@ async function saveDraftAsDashboard(client, draft, input) {
|
|
|
5479
4938
|
}
|
|
5480
4939
|
|
|
5481
4940
|
// src/tools/query/ask-question.ts
|
|
5482
|
-
import { z as
|
|
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
|
|
5483
4959
|
function normalizePreviewType(type) {
|
|
5484
4960
|
const t = (type || "").toLowerCase().trim();
|
|
5485
4961
|
if (RENDER_TYPES.includes(t)) return t;
|
|
@@ -5519,16 +4995,16 @@ function registerAskQuestion(server, client) {
|
|
|
5519
4995
|
"ask_question",
|
|
5520
4996
|
"Ask a natural language question \u2014 converts to SQL, returns data + chart suggestion. Requires datamartName with a populated semantic layer.",
|
|
5521
4997
|
{
|
|
5522
|
-
question:
|
|
4998
|
+
question: z25.string().describe(
|
|
5523
4999
|
'The natural language question to ask (e.g. "What was total revenue last month?")'
|
|
5524
5000
|
),
|
|
5525
|
-
datamartName:
|
|
5001
|
+
datamartName: z25.string().optional().describe(
|
|
5526
5002
|
"Name of the datamart to query. Use `list_datamarts` to find available names. REQUIRED unless exactly one datamart exists (auto-resolved). Do NOT pass a workspace name here."
|
|
5527
5003
|
),
|
|
5528
|
-
sessionId:
|
|
5004
|
+
sessionId: z25.string().optional().describe(
|
|
5529
5005
|
"Session ID from a previous ask_question response, for follow-up questions."
|
|
5530
5006
|
),
|
|
5531
|
-
timezone:
|
|
5007
|
+
timezone: z25.string().optional().describe('IANA timezone (e.g. "America/New_York"). Defaults to UTC.')
|
|
5532
5008
|
},
|
|
5533
5009
|
WRITE_CREATE,
|
|
5534
5010
|
async (params) => {
|
|
@@ -5660,23 +5136,24 @@ You can verify with \`get_semantic_layer\` using datamartName "${datamartName}".
|
|
|
5660
5136
|
parts.push(
|
|
5661
5137
|
`**Suggested chart type:** ${result.suggestedChartType}`
|
|
5662
5138
|
);
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
);
|
|
5139
|
+
const confidence = formatConfidence(result.confidence);
|
|
5140
|
+
if (confidence) {
|
|
5141
|
+
parts.push(`**Confidence:** ${confidence}`);
|
|
5667
5142
|
}
|
|
5668
5143
|
parts.push(
|
|
5669
5144
|
`**Rows returned:** ${data.length} | **Time:** ${result.timeTaken}ms`
|
|
5670
5145
|
);
|
|
5671
|
-
|
|
5146
|
+
const reasoning = formatProse(result.reasoning);
|
|
5147
|
+
if (reasoning) {
|
|
5672
5148
|
parts.push(`
|
|
5673
5149
|
### Reasoning
|
|
5674
|
-
${
|
|
5150
|
+
${reasoning}`);
|
|
5675
5151
|
}
|
|
5676
|
-
|
|
5152
|
+
const summary = formatProse(result.summary);
|
|
5153
|
+
if (summary) {
|
|
5677
5154
|
parts.push(`
|
|
5678
5155
|
### Summary
|
|
5679
|
-
${
|
|
5156
|
+
${summary}`);
|
|
5680
5157
|
}
|
|
5681
5158
|
parts.push(`
|
|
5682
5159
|
### Data
|
|
@@ -5723,14 +5200,14 @@ To fix this:
|
|
|
5723
5200
|
}
|
|
5724
5201
|
|
|
5725
5202
|
// src/tools/query/get-semantic-layer.ts
|
|
5726
|
-
import { z as
|
|
5203
|
+
import { z as z26 } from "zod";
|
|
5727
5204
|
function registerGetSemanticLayer(server, client) {
|
|
5728
5205
|
const services = createServiceBundle(client);
|
|
5729
5206
|
server.tool(
|
|
5730
5207
|
"get_semantic_layer",
|
|
5731
5208
|
"Get semantic layer metadata for a datamart: table/column descriptions, synonyms, completion score. Omit datamartName to auto-resolve.",
|
|
5732
5209
|
{
|
|
5733
|
-
datamartName:
|
|
5210
|
+
datamartName: z26.string().optional().describe(
|
|
5734
5211
|
"Name of the datamart to inspect. Omit to list available datamarts."
|
|
5735
5212
|
)
|
|
5736
5213
|
},
|
|
@@ -5840,15 +5317,15 @@ ${result.feedback}`);
|
|
|
5840
5317
|
}
|
|
5841
5318
|
|
|
5842
5319
|
// src/tools/query/update-semantic-layer.ts
|
|
5843
|
-
import { z as
|
|
5844
|
-
var columnSchema2 =
|
|
5845
|
-
name:
|
|
5846
|
-
description:
|
|
5847
|
-
synonyms:
|
|
5320
|
+
import { z as z27 } from "zod";
|
|
5321
|
+
var columnSchema2 = z27.object({
|
|
5322
|
+
name: z27.string().describe("Column name (must exist in the datamart)"),
|
|
5323
|
+
description: z27.string().optional().describe("Business-friendly description of what this column represents"),
|
|
5324
|
+
synonyms: z27.array(z27.string()).optional().describe(
|
|
5848
5325
|
'Alternative names users might use for this column (e.g. ["revenue", "sales amount"])'
|
|
5849
5326
|
),
|
|
5850
|
-
miscellaneousInfo:
|
|
5851
|
-
columnType:
|
|
5327
|
+
miscellaneousInfo: z27.string().optional().describe("Additional context such as valid values, units, or caveats"),
|
|
5328
|
+
columnType: z27.enum([
|
|
5852
5329
|
"String",
|
|
5853
5330
|
"Long String",
|
|
5854
5331
|
"String (Custom)",
|
|
@@ -5859,18 +5336,25 @@ var columnSchema2 = z28.object({
|
|
|
5859
5336
|
"Identifier",
|
|
5860
5337
|
"Number",
|
|
5861
5338
|
"JSON"
|
|
5862
|
-
]).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.")
|
|
5863
5346
|
});
|
|
5864
|
-
var tableSchema2 =
|
|
5865
|
-
name:
|
|
5866
|
-
|
|
5347
|
+
var tableSchema2 = z27.object({
|
|
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."),
|
|
5350
|
+
description: z27.string().optional().describe(
|
|
5867
5351
|
"Business-friendly description of what this table represents"
|
|
5868
5352
|
),
|
|
5869
|
-
synonyms:
|
|
5870
|
-
miscellaneousInfo:
|
|
5353
|
+
synonyms: z27.array(z27.string()).optional().describe("Alternative names for this table"),
|
|
5354
|
+
miscellaneousInfo: z27.string().optional().describe(
|
|
5871
5355
|
"Additional context such as how the table relates to other tables"
|
|
5872
5356
|
),
|
|
5873
|
-
columns:
|
|
5357
|
+
columns: z27.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
|
|
5874
5358
|
});
|
|
5875
5359
|
function columnKey(tableName, columnName) {
|
|
5876
5360
|
return `${tableName.toLowerCase()}\0${columnName.toLowerCase()}`;
|
|
@@ -5902,6 +5386,7 @@ function normalizeTablesForApi(tables, datatypesByColumn) {
|
|
|
5902
5386
|
if (!tables) return void 0;
|
|
5903
5387
|
return tables.map((table) => {
|
|
5904
5388
|
const normalized = { name: table.name };
|
|
5389
|
+
if (table.schemaName !== void 0) normalized.schemaName = table.schemaName;
|
|
5905
5390
|
if (table.description !== void 0) normalized.description = table.description;
|
|
5906
5391
|
if (table.synonyms !== void 0) normalized.synonyms = table.synonyms;
|
|
5907
5392
|
if (table.miscellaneousInfo !== void 0) {
|
|
@@ -5925,6 +5410,15 @@ function normalizeTablesForApi(tables, datatypesByColumn) {
|
|
|
5925
5410
|
normalizedColumn.columnType = column.columnType;
|
|
5926
5411
|
}
|
|
5927
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
|
+
}
|
|
5928
5422
|
return normalizedColumn;
|
|
5929
5423
|
});
|
|
5930
5424
|
}
|
|
@@ -5937,13 +5431,13 @@ function registerUpdateSemanticLayer(server, client) {
|
|
|
5937
5431
|
"update_semantic_layer",
|
|
5938
5432
|
"Populate or update the semantic layer for a datamart. Add table/column descriptions, synonyms, context. Required before ask_question works. Auto-generate descriptions from column names when empty.",
|
|
5939
5433
|
{
|
|
5940
|
-
datamartName:
|
|
5434
|
+
datamartName: z27.string().optional().describe(
|
|
5941
5435
|
"Name of the datamart to update. Omit to list available datamarts."
|
|
5942
5436
|
),
|
|
5943
|
-
tables:
|
|
5437
|
+
tables: z27.array(tableSchema2).optional().describe(
|
|
5944
5438
|
"Table and column definitions to update. Only include tables/columns you want to change."
|
|
5945
5439
|
),
|
|
5946
|
-
feedback:
|
|
5440
|
+
feedback: z27.string().optional().describe(
|
|
5947
5441
|
'General feedback or context about the datamart (e.g. "This datamart contains e-commerce data for the US market").'
|
|
5948
5442
|
)
|
|
5949
5443
|
},
|
|
@@ -6005,7 +5499,7 @@ You can now:
|
|
|
6005
5499
|
}
|
|
6006
5500
|
|
|
6007
5501
|
// src/tools/query/semantic-generation.ts
|
|
6008
|
-
import { z as
|
|
5502
|
+
import { z as z28 } from "zod";
|
|
6009
5503
|
function nonEmptyString(value) {
|
|
6010
5504
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
6011
5505
|
}
|
|
@@ -6062,35 +5556,6 @@ async function resolveDatamartFromWorkspace(params) {
|
|
|
6062
5556
|
workspaceName: resolution.name
|
|
6063
5557
|
};
|
|
6064
5558
|
}
|
|
6065
|
-
async function findDatamartIdInLinkedWorkspaces(params) {
|
|
6066
|
-
const services = createServiceBundle(params.client);
|
|
6067
|
-
const workspaces = await services.workspacesApi.listWorkspaces();
|
|
6068
|
-
const matches = workspaces.map((workspace) => {
|
|
6069
|
-
const record = workspace;
|
|
6070
|
-
return {
|
|
6071
|
-
name: nonEmptyString(record.name),
|
|
6072
|
-
datamartName: workspaceDatamartNameFrom(record),
|
|
6073
|
-
datamartId: workspaceDatamartIdFrom(record)
|
|
6074
|
-
};
|
|
6075
|
-
}).filter(
|
|
6076
|
-
(workspace) => workspace.datamartName?.toLowerCase() === params.datamartName.toLowerCase() && workspace.datamartId
|
|
6077
|
-
);
|
|
6078
|
-
const uniqueIds = [...new Set(matches.map((workspace) => workspace.datamartId))];
|
|
6079
|
-
if (uniqueIds.length === 1 && uniqueIds[0]) {
|
|
6080
|
-
return {
|
|
6081
|
-
kind: "resolved",
|
|
6082
|
-
datamartId: uniqueIds[0],
|
|
6083
|
-
workspaceName: matches.find((workspace) => workspace.datamartId === uniqueIds[0])?.name || void 0
|
|
6084
|
-
};
|
|
6085
|
-
}
|
|
6086
|
-
if (uniqueIds.length > 1) {
|
|
6087
|
-
return {
|
|
6088
|
-
kind: "ambiguous",
|
|
6089
|
-
workspaceNames: matches.map((workspace) => workspace.name).filter((name) => !!name)
|
|
6090
|
-
};
|
|
6091
|
-
}
|
|
6092
|
-
return { kind: "none" };
|
|
6093
|
-
}
|
|
6094
5559
|
async function resolveDatamart(params) {
|
|
6095
5560
|
if (params.datamartId) {
|
|
6096
5561
|
return {
|
|
@@ -6121,30 +5586,10 @@ async function resolveDatamart(params) {
|
|
|
6121
5586
|
if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
|
|
6122
5587
|
const datamartId = datamartIdFrom(resolution.selected);
|
|
6123
5588
|
if (!datamartId) {
|
|
6124
|
-
const workspaceMatch = await findDatamartIdInLinkedWorkspaces({
|
|
6125
|
-
client: params.client,
|
|
6126
|
-
datamartName: resolution.name
|
|
6127
|
-
});
|
|
6128
|
-
if (workspaceMatch.kind === "resolved") {
|
|
6129
|
-
return {
|
|
6130
|
-
ok: true,
|
|
6131
|
-
datamartName: resolution.name,
|
|
6132
|
-
datamartId: workspaceMatch.datamartId,
|
|
6133
|
-
workspaceName: workspaceMatch.workspaceName
|
|
6134
|
-
};
|
|
6135
|
-
}
|
|
6136
|
-
if (workspaceMatch.kind === "ambiguous") {
|
|
6137
|
-
return {
|
|
6138
|
-
ok: false,
|
|
6139
|
-
response: toolError(
|
|
6140
|
-
`Datamart "${resolution.name}" is linked from multiple workspaces with different IDs. Call ${params.toolName} again with workspaceName. Matching workspaces: ${workspaceMatch.workspaceNames.join(", ")}`
|
|
6141
|
-
)
|
|
6142
|
-
};
|
|
6143
|
-
}
|
|
6144
5589
|
return {
|
|
6145
5590
|
ok: false,
|
|
6146
5591
|
response: toolError(
|
|
6147
|
-
`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.`
|
|
6148
5593
|
)
|
|
6149
5594
|
};
|
|
6150
5595
|
}
|
|
@@ -6160,9 +5605,9 @@ function registerStartSemanticLayerGeneration(server, client) {
|
|
|
6160
5605
|
"start_semantic_layer_generation",
|
|
6161
5606
|
"Start backend semantic-layer auto-generation for a datamart. Returns an async job id to poll.",
|
|
6162
5607
|
{
|
|
6163
|
-
datamartName:
|
|
6164
|
-
workspaceName:
|
|
6165
|
-
datamartId:
|
|
5608
|
+
datamartName: z28.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
5609
|
+
workspaceName: z28.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
5610
|
+
datamartId: z28.string().optional().describe("Datamart UUID. Use only when already known.")
|
|
6166
5611
|
},
|
|
6167
5612
|
WRITE_CREATE,
|
|
6168
5613
|
async (params) => {
|
|
@@ -6204,9 +5649,9 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
6204
5649
|
"get_semantic_layer_generation_status",
|
|
6205
5650
|
"Get semantic-layer auto-generation job status for a datamart.",
|
|
6206
5651
|
{
|
|
6207
|
-
datamartName:
|
|
6208
|
-
workspaceName:
|
|
6209
|
-
datamartId:
|
|
5652
|
+
datamartName: z28.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
5653
|
+
workspaceName: z28.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
5654
|
+
datamartId: z28.string().optional().describe("Datamart UUID. Use only when already known.")
|
|
6210
5655
|
},
|
|
6211
5656
|
READ_ONLY,
|
|
6212
5657
|
async (params) => {
|
|
@@ -6243,7 +5688,7 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
6243
5688
|
}
|
|
6244
5689
|
|
|
6245
5690
|
// src/tools/widgets/list-widgets.ts
|
|
6246
|
-
import { z as
|
|
5691
|
+
import { z as z29 } from "zod";
|
|
6247
5692
|
function widgetReferenceId(widget) {
|
|
6248
5693
|
for (const candidate of [widget.externalMetricId, widget.id, widget._id, widget.metricId]) {
|
|
6249
5694
|
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate.trim();
|
|
@@ -6256,10 +5701,10 @@ function registerListWidgets(server, client) {
|
|
|
6256
5701
|
"list_widgets",
|
|
6257
5702
|
"List widgets on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Returns each widget's externalMetricId (uuid, used for update/remove), metricId (slug), name, published state, and creator identifier.",
|
|
6258
5703
|
{
|
|
6259
|
-
clientId:
|
|
6260
|
-
dashboardName:
|
|
6261
|
-
workspaceName:
|
|
6262
|
-
embedId:
|
|
5704
|
+
clientId: z29.string().describe("Client/tenant identifier for this dashboard"),
|
|
5705
|
+
dashboardName: z29.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
5706
|
+
workspaceName: z29.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
5707
|
+
embedId: z29.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
|
|
6263
5708
|
},
|
|
6264
5709
|
READ_ONLY,
|
|
6265
5710
|
async (params) => {
|
|
@@ -6323,29 +5768,29 @@ function registerListWidgets(server, client) {
|
|
|
6323
5768
|
}
|
|
6324
5769
|
|
|
6325
5770
|
// src/tools/widgets/add-widget.ts
|
|
6326
|
-
import { z as
|
|
5771
|
+
import { z as z30 } from "zod";
|
|
6327
5772
|
function registerAddWidget(server, client) {
|
|
6328
5773
|
const services = createServiceBundle(client);
|
|
6329
5774
|
server.tool(
|
|
6330
5775
|
"add_widget",
|
|
6331
5776
|
"Add a widget to a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Cloning via `templateMetricId` is the recommended path \u2014 query, chart config, and datasource are all inherited. Data app and datasource auto-resolve; never ask the user for them.",
|
|
6332
5777
|
{
|
|
6333
|
-
clientId:
|
|
6334
|
-
dashboardName:
|
|
6335
|
-
workspaceName:
|
|
6336
|
-
embedId:
|
|
6337
|
-
dataAppName:
|
|
6338
|
-
templateMetricId:
|
|
6339
|
-
name:
|
|
6340
|
-
metricId:
|
|
6341
|
-
query:
|
|
6342
|
-
chartOptions:
|
|
6343
|
-
description:
|
|
6344
|
-
companyIntegrationId:
|
|
6345
|
-
integrationName:
|
|
6346
|
-
isPublished:
|
|
6347
|
-
body:
|
|
6348
|
-
widgets:
|
|
5778
|
+
clientId: z30.string().describe("Client/tenant identifier (widget is created in their scope)"),
|
|
5779
|
+
dashboardName: z30.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
5780
|
+
workspaceName: z30.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
5781
|
+
embedId: z30.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
5782
|
+
dataAppName: z30.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
|
|
5783
|
+
templateMetricId: z30.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
|
|
5784
|
+
name: z30.string().optional().describe("Widget name (overrides template)"),
|
|
5785
|
+
metricId: z30.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
|
|
5786
|
+
query: z30.string().optional().describe("SQL query body (overrides template)"),
|
|
5787
|
+
chartOptions: z30.record(z30.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
|
|
5788
|
+
description: z30.string().optional(),
|
|
5789
|
+
companyIntegrationId: z30.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
|
|
5790
|
+
integrationName: z30.string().optional().describe('Datasource display name (e.g. "Snowflake"). Use this when the user picks a datasource by name \u2014 the id is looked up automatically.'),
|
|
5791
|
+
isPublished: z30.boolean().optional().describe("Publish immediately (default true)"),
|
|
5792
|
+
body: z30.record(z30.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params."),
|
|
5793
|
+
widgets: z30.array(z30.record(z30.unknown())).min(1).max(50).optional().describe("Batch of raw createMetric bodies. Dashboard attachment is added to each body.")
|
|
6349
5794
|
},
|
|
6350
5795
|
WRITE_CREATE,
|
|
6351
5796
|
async (params) => {
|
|
@@ -6493,20 +5938,20 @@ Use \`list_widgets\` to verify, or \`update_widget\`/\`remove_widget\` to adjust
|
|
|
6493
5938
|
}
|
|
6494
5939
|
|
|
6495
5940
|
// src/tools/widgets/update-widget.ts
|
|
6496
|
-
import { z as
|
|
5941
|
+
import { z as z31 } from "zod";
|
|
6497
5942
|
function registerUpdateWidget(server, client) {
|
|
6498
5943
|
const services = createServiceBundle(client);
|
|
6499
5944
|
server.tool(
|
|
6500
5945
|
"update_widget",
|
|
6501
5946
|
"Update a widget on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId` + `set`. Data app auto-resolves from the dashboard.",
|
|
6502
5947
|
{
|
|
6503
|
-
externalMetricId:
|
|
6504
|
-
clientId:
|
|
6505
|
-
dashboardName:
|
|
6506
|
-
workspaceName:
|
|
6507
|
-
embedId:
|
|
6508
|
-
dataAppName:
|
|
6509
|
-
set:
|
|
5948
|
+
externalMetricId: z31.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
|
|
5949
|
+
clientId: z31.string().describe("Client/tenant identifier"),
|
|
5950
|
+
dashboardName: z31.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
5951
|
+
workspaceName: z31.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
5952
|
+
embedId: z31.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
5953
|
+
dataAppName: z31.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
|
|
5954
|
+
set: z31.record(z31.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
|
|
6510
5955
|
},
|
|
6511
5956
|
WRITE_IDEMPOTENT,
|
|
6512
5957
|
async (params) => {
|
|
@@ -6569,19 +6014,19 @@ Use \`list_widgets\` to verify.`
|
|
|
6569
6014
|
}
|
|
6570
6015
|
|
|
6571
6016
|
// src/tools/widgets/remove-widget.ts
|
|
6572
|
-
import { z as
|
|
6017
|
+
import { z as z32 } from "zod";
|
|
6573
6018
|
function registerRemoveWidget(server, client) {
|
|
6574
6019
|
const services = createServiceBundle(client);
|
|
6575
6020
|
server.tool(
|
|
6576
6021
|
"remove_widget",
|
|
6577
6022
|
"Remove a widget from a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId`. Per-client soft delete \u2014 the widget stays on the template dashboard and on other customers' dashboards.",
|
|
6578
6023
|
{
|
|
6579
|
-
externalMetricId:
|
|
6580
|
-
clientId:
|
|
6581
|
-
dashboardName:
|
|
6582
|
-
workspaceName:
|
|
6583
|
-
embedId:
|
|
6584
|
-
dataAppName:
|
|
6024
|
+
externalMetricId: z32.string().describe("Internal uuid of the widget (externalMetrics.id)"),
|
|
6025
|
+
clientId: z32.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
|
|
6026
|
+
dashboardName: z32.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6027
|
+
workspaceName: z32.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6028
|
+
embedId: z32.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
6029
|
+
dataAppName: z32.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
|
|
6585
6030
|
},
|
|
6586
6031
|
DESTRUCTIVE,
|
|
6587
6032
|
async (params) => {
|
|
@@ -6637,21 +6082,21 @@ Note: this is a per-client soft delete. The widget remains available for other c
|
|
|
6637
6082
|
}
|
|
6638
6083
|
|
|
6639
6084
|
// src/tools/widgets/generate-widget.ts
|
|
6640
|
-
import { z as
|
|
6085
|
+
import { z as z33 } from "zod";
|
|
6641
6086
|
function registerGenerateWidget(server, client) {
|
|
6642
6087
|
const services = createServiceBundle(client);
|
|
6643
6088
|
const tool = server.tool(
|
|
6644
6089
|
"generate_widget",
|
|
6645
6090
|
"AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft to pass to `add_widget`. Non-technical flow: `clientId` + `dashboardName` (+ `workspaceName` if ambiguous); data app and datasource auto-resolve. Never ask the user for data app, datasource, or integration.",
|
|
6646
6091
|
{
|
|
6647
|
-
clientId:
|
|
6648
|
-
userInputs:
|
|
6649
|
-
dashboardName:
|
|
6650
|
-
workspaceName:
|
|
6651
|
-
embedId:
|
|
6652
|
-
dataAppName:
|
|
6653
|
-
integrationId:
|
|
6654
|
-
integrationName:
|
|
6092
|
+
clientId: z33.string().describe("Client/tenant identifier"),
|
|
6093
|
+
userInputs: z33.record(z33.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
|
|
6094
|
+
dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6095
|
+
workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6096
|
+
embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
6097
|
+
dataAppName: z33.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
|
|
6098
|
+
integrationId: z33.string().optional().describe("Technical override \u2014 the datasource UUID. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets."),
|
|
6099
|
+
integrationName: z33.string().optional().describe("Technical override \u2014 datasource display name. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets.")
|
|
6655
6100
|
},
|
|
6656
6101
|
READ_ONLY,
|
|
6657
6102
|
async (params) => {
|
|
@@ -6736,9 +6181,9 @@ function registerGenerateWidget(server, client) {
|
|
|
6736
6181
|
}
|
|
6737
6182
|
|
|
6738
6183
|
// src/tools/widgets/create-or-update-metric-from-query.ts
|
|
6739
|
-
import { z as
|
|
6740
|
-
var modeSchema =
|
|
6741
|
-
var conflictStrategySchema =
|
|
6184
|
+
import { z as z34 } from "zod";
|
|
6185
|
+
var modeSchema = z34.enum(["dry_run", "apply"]).default("dry_run");
|
|
6186
|
+
var conflictStrategySchema = z34.enum(["fail", "update", "create_new"]).default("fail");
|
|
6742
6187
|
function makePlanText(plan) {
|
|
6743
6188
|
return JSON.stringify(plan, null, 2);
|
|
6744
6189
|
}
|
|
@@ -6755,22 +6200,22 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
6755
6200
|
"[EMBEDDING] Create or update a production metric/widget on a customer dashboard from validated SQL. Requires clientId (multi-tenant embed). Defaults to dry-run; apply requires explicit confirmation.",
|
|
6756
6201
|
{
|
|
6757
6202
|
mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
|
|
6758
|
-
confirm:
|
|
6203
|
+
confirm: z34.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6759
6204
|
conflictStrategy: conflictStrategySchema.describe("What to do if metricId already exists on the target dashboard"),
|
|
6760
|
-
clientId:
|
|
6761
|
-
workspaceName:
|
|
6762
|
-
dashboardName:
|
|
6763
|
-
embedId:
|
|
6764
|
-
dataAppName:
|
|
6765
|
-
name:
|
|
6766
|
-
metricId:
|
|
6767
|
-
query:
|
|
6768
|
-
description:
|
|
6769
|
-
chartOptions:
|
|
6770
|
-
companyIntegrationId:
|
|
6771
|
-
integrationName:
|
|
6772
|
-
isPublished:
|
|
6773
|
-
requestedBy:
|
|
6205
|
+
clientId: z34.string().describe("Client/tenant identifier for the dashboard widget scope"),
|
|
6206
|
+
workspaceName: z34.string().describe("Target workspace name"),
|
|
6207
|
+
dashboardName: z34.string().optional().describe("Dashboard name to attach to"),
|
|
6208
|
+
embedId: z34.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6209
|
+
dataAppName: z34.string().optional().describe("Optional data app override"),
|
|
6210
|
+
name: z34.string().describe("Metric/widget display name"),
|
|
6211
|
+
metricId: z34.string().describe("Stable metric slug to create or update"),
|
|
6212
|
+
query: z34.string().describe("SQL query already validated against the warehouse"),
|
|
6213
|
+
description: z34.string().optional().describe("Metric description"),
|
|
6214
|
+
chartOptions: z34.record(z34.unknown()).optional().describe("Chart configuration JSON"),
|
|
6215
|
+
companyIntegrationId: z34.string().optional().describe("Datasource UUID override"),
|
|
6216
|
+
integrationName: z34.string().optional().describe("Datasource display name override"),
|
|
6217
|
+
isPublished: z34.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
|
|
6218
|
+
requestedBy: z34.string().optional().describe("Human operator/request context to store on the metric when supported")
|
|
6774
6219
|
},
|
|
6775
6220
|
WRITE_IDEMPOTENT,
|
|
6776
6221
|
async (params) => {
|
|
@@ -6930,8 +6375,8 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
6930
6375
|
}
|
|
6931
6376
|
|
|
6932
6377
|
// src/tools/widgets/create-workspace-metric.ts
|
|
6933
|
-
import { z as
|
|
6934
|
-
var modeSchema2 =
|
|
6378
|
+
import { z as z35 } from "zod";
|
|
6379
|
+
var modeSchema2 = z35.enum(["dry_run", "apply"]).default("dry_run");
|
|
6935
6380
|
function registerCreateWorkspaceMetric(server, client) {
|
|
6936
6381
|
server.tool(
|
|
6937
6382
|
"create_workspace_metric",
|
|
@@ -6940,16 +6385,16 @@ function registerCreateWorkspaceMetric(server, client) {
|
|
|
6940
6385
|
mode: modeSchema2.describe(
|
|
6941
6386
|
"dry_run validates the SQL only (no write); apply creates or updates the metric after confirmation"
|
|
6942
6387
|
),
|
|
6943
|
-
confirm:
|
|
6944
|
-
workspaceName:
|
|
6945
|
-
name:
|
|
6946
|
-
query:
|
|
6947
|
-
metricId:
|
|
6388
|
+
confirm: z35.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6389
|
+
workspaceName: z35.string().describe("Target workspace name"),
|
|
6390
|
+
name: z35.string().describe("Metric display name"),
|
|
6391
|
+
query: z35.string().describe("SQL query to validate and store"),
|
|
6392
|
+
metricId: z35.string().optional().describe(
|
|
6948
6393
|
"Stable metric slug. Pass an existing slug to UPDATE that metric in place. Omit to create \u2014 the slug is derived deterministically from the name (no random/uniqueness suffix), so re-running with the same name upserts the same metric rather than creating a duplicate."
|
|
6949
6394
|
),
|
|
6950
|
-
description:
|
|
6951
|
-
datasourceId:
|
|
6952
|
-
isPublished:
|
|
6395
|
+
description: z35.string().optional().describe("Metric description"),
|
|
6396
|
+
datasourceId: z35.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
|
|
6397
|
+
isPublished: z35.boolean().optional().describe(
|
|
6953
6398
|
"Publish after apply. Defaults to false. Applies to creates; on updates the existing publish state is preserved."
|
|
6954
6399
|
)
|
|
6955
6400
|
},
|
|
@@ -7005,25 +6450,25 @@ function registerCreateWorkspaceMetric(server, client) {
|
|
|
7005
6450
|
}
|
|
7006
6451
|
|
|
7007
6452
|
// src/tools/widgets/validate-metric-spec.ts
|
|
7008
|
-
import { z as
|
|
6453
|
+
import { z as z36 } from "zod";
|
|
7009
6454
|
function registerValidateMetricSpec(server, client) {
|
|
7010
6455
|
const services = createServiceBundle(client);
|
|
7011
6456
|
server.tool(
|
|
7012
6457
|
"validate_metric_spec",
|
|
7013
6458
|
"Validate a production metric spec from SQL without saving it. Resolves dashboard, data app, datasource, conflicts, and required fields.",
|
|
7014
6459
|
{
|
|
7015
|
-
clientId:
|
|
7016
|
-
workspaceName:
|
|
7017
|
-
dashboardName:
|
|
7018
|
-
embedId:
|
|
7019
|
-
dataAppName:
|
|
7020
|
-
name:
|
|
7021
|
-
metricId:
|
|
7022
|
-
query:
|
|
7023
|
-
description:
|
|
7024
|
-
chartOptions:
|
|
7025
|
-
companyIntegrationId:
|
|
7026
|
-
integrationName:
|
|
6460
|
+
clientId: z36.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
|
|
6461
|
+
workspaceName: z36.string().describe("Target production-backed workspace name"),
|
|
6462
|
+
dashboardName: z36.string().optional().describe("Dashboard name to attach to"),
|
|
6463
|
+
embedId: z36.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6464
|
+
dataAppName: z36.string().optional().describe("Optional data app override"),
|
|
6465
|
+
name: z36.string().describe("Metric/widget display name"),
|
|
6466
|
+
metricId: z36.string().describe("Stable metric slug to create or update"),
|
|
6467
|
+
query: z36.string().describe("SQL query already validated against the warehouse"),
|
|
6468
|
+
description: z36.string().optional().describe("Metric description"),
|
|
6469
|
+
chartOptions: z36.record(z36.unknown()).optional().describe("Chart configuration JSON"),
|
|
6470
|
+
companyIntegrationId: z36.string().optional().describe("Datasource UUID override"),
|
|
6471
|
+
integrationName: z36.string().optional().describe("Datasource display name override")
|
|
7027
6472
|
},
|
|
7028
6473
|
READ_ONLY,
|
|
7029
6474
|
async (params) => {
|
|
@@ -7114,20 +6559,20 @@ function registerValidateMetricSpec(server, client) {
|
|
|
7114
6559
|
}
|
|
7115
6560
|
|
|
7116
6561
|
// src/tools/reports/list-scheduled-reports.ts
|
|
7117
|
-
import { z as
|
|
6562
|
+
import { z as z37 } from "zod";
|
|
7118
6563
|
function registerListScheduledReports(server, client) {
|
|
7119
6564
|
const services = createServiceBundle(client);
|
|
7120
6565
|
server.tool(
|
|
7121
6566
|
"list_scheduled_reports",
|
|
7122
6567
|
"List configured scheduled reports for a specific client/dashboard embed. Shows schedule metadata, not delivery success history.",
|
|
7123
6568
|
{
|
|
7124
|
-
clientId:
|
|
7125
|
-
dashboardName:
|
|
7126
|
-
workspaceName:
|
|
7127
|
-
embedId:
|
|
7128
|
-
userIdentifier:
|
|
7129
|
-
pageNumber:
|
|
7130
|
-
isPagination:
|
|
6569
|
+
clientId: z37.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
|
|
6570
|
+
dashboardName: z37.string().optional().describe("Dashboard name to inspect"),
|
|
6571
|
+
workspaceName: z37.string().optional().describe("Workspace name if dashboardName is ambiguous"),
|
|
6572
|
+
embedId: z37.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6573
|
+
userIdentifier: z37.string().optional().describe("Optional creator/user filter"),
|
|
6574
|
+
pageNumber: z37.number().int().positive().optional().describe("Page number when pagination is enabled"),
|
|
6575
|
+
isPagination: z37.boolean().optional().describe("Enable backend pagination")
|
|
7131
6576
|
},
|
|
7132
6577
|
READ_ONLY,
|
|
7133
6578
|
async (params) => {
|
|
@@ -7236,6 +6681,400 @@ function registerWhoami(server, client) {
|
|
|
7236
6681
|
);
|
|
7237
6682
|
}
|
|
7238
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
|
+
|
|
7239
7078
|
// src/tools/registry.ts
|
|
7240
7079
|
function instrumentToolRegistration(server) {
|
|
7241
7080
|
const original = server.tool.bind(server);
|
|
@@ -7300,6 +7139,11 @@ function registerAllTools(server, client) {
|
|
|
7300
7139
|
registerRemoveWidget(server, client);
|
|
7301
7140
|
registerGenerateWidget(server, client);
|
|
7302
7141
|
registerListScheduledReports(server, client);
|
|
7142
|
+
registerCustomChartScaffold(server, client);
|
|
7143
|
+
registerCustomChartValidate(server, client);
|
|
7144
|
+
registerCustomChartUpload(server, client);
|
|
7145
|
+
registerListCustomCharts(server, client);
|
|
7146
|
+
registerPublishCustomChart(server, client);
|
|
7303
7147
|
}
|
|
7304
7148
|
|
|
7305
7149
|
// src/resources/content/getting-started.ts
|
|
@@ -10608,15 +10452,6 @@ function registerAllPrompts(server) {
|
|
|
10608
10452
|
}
|
|
10609
10453
|
|
|
10610
10454
|
export {
|
|
10611
|
-
oauthHttpsUrl,
|
|
10612
|
-
oauthConfigSchema,
|
|
10613
|
-
DatabrainApiError,
|
|
10614
|
-
toolError,
|
|
10615
|
-
toolSuccess,
|
|
10616
|
-
logger,
|
|
10617
|
-
MCP_SERVER_VERSION,
|
|
10618
|
-
toolContext,
|
|
10619
|
-
DatabrainClient,
|
|
10620
10455
|
createServiceBundle,
|
|
10621
10456
|
READ_ONLY,
|
|
10622
10457
|
WRITE_CREATE,
|
|
@@ -10640,4 +10475,4 @@ export {
|
|
|
10640
10475
|
registerAllResources,
|
|
10641
10476
|
registerAllPrompts
|
|
10642
10477
|
};
|
|
10643
|
-
//# sourceMappingURL=chunk-
|
|
10478
|
+
//# sourceMappingURL=chunk-5IQ7MVUH.js.map
|