@databrainhq/mcp-server 0.3.0-rc.13 → 0.3.0-rc.14
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/.mcp.json +6 -1
- package/dist/apps/server.d.ts +102 -389
- package/dist/apps/server.js +5 -5
- package/dist/apps/stdio.d.ts +1 -2
- package/dist/apps/stdio.js +6 -6
- package/dist/{bundle-TDQXWIPM.js → bundle-QJNCUMNY.js} +2 -2
- package/dist/{chunk-Q5EYDKSA.js → chunk-2F7V6RZR.js} +511 -107
- package/dist/chunk-2F7V6RZR.js.map +1 -0
- package/dist/{chunk-AEQTEE5C.js → chunk-AWICHHDU.js} +153 -61
- package/dist/chunk-AWICHHDU.js.map +1 -0
- package/dist/{chunk-X44C5GW2.js → chunk-CEGHG5U3.js} +59 -18
- package/dist/chunk-CEGHG5U3.js.map +1 -0
- package/dist/{chunk-233VGFHY.js → chunk-NIC5MAGC.js} +317 -147
- package/dist/chunk-NIC5MAGC.js.map +1 -0
- package/dist/{chunk-DTFYHLYN.js → chunk-S65KA3Z7.js} +26 -11
- package/dist/chunk-S65KA3Z7.js.map +1 -0
- package/dist/{chunk-PSJVYD4W.js → chunk-UYFFFAYV.js} +11722 -10560
- package/dist/chunk-UYFFFAYV.js.map +1 -0
- package/dist/{chunk-HCYZN7I3.js → chunk-VQ5G5AZV.js} +10 -7
- package/dist/chunk-VQ5G5AZV.js.map +1 -0
- package/dist/cli.d.ts +16 -15
- package/dist/cli.js +134 -36
- package/dist/cli.js.map +1 -1
- package/dist/custom-chart/custom-runtime.js +6 -6
- package/dist/custom-chart/template/chart.ts +116 -28
- package/dist/custom-chart/template/icon.svg +1 -1
- package/dist/custom-chart/template/manifest.json +66 -12
- package/dist/custom-chart/template/sample-data.json +21 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/{main-VEKNKWL5.js → main-QCBVJSSR.js} +348 -155
- package/dist/main-QCBVJSSR.js.map +1 -0
- package/dist/{test-only-LJGOY2QA.js → test-only-6474NMTR.js} +12 -8
- package/dist/test-only-6474NMTR.js.map +1 -0
- package/package.json +20 -9
- package/dist/chunk-233VGFHY.js.map +0 -1
- package/dist/chunk-AEQTEE5C.js.map +0 -1
- package/dist/chunk-DTFYHLYN.js.map +0 -1
- package/dist/chunk-HCYZN7I3.js.map +0 -1
- package/dist/chunk-PSJVYD4W.js.map +0 -1
- package/dist/chunk-Q5EYDKSA.js.map +0 -1
- package/dist/chunk-X44C5GW2.js.map +0 -1
- package/dist/main-VEKNKWL5.js.map +0 -1
- package/dist/test-only-LJGOY2QA.js.map +0 -1
- /package/dist/{bundle-TDQXWIPM.js.map → bundle-QJNCUMNY.js.map} +0 -0
|
@@ -22,84 +22,18 @@ import {
|
|
|
22
22
|
saveDraftAsDashboard,
|
|
23
23
|
viewSchema,
|
|
24
24
|
workspacesForDatamart
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-UYFFFAYV.js";
|
|
26
26
|
import {
|
|
27
27
|
CustomChartsApi,
|
|
28
28
|
DatabrainApiError,
|
|
29
29
|
DatabrainClient,
|
|
30
30
|
toolContext
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-AWICHHDU.js";
|
|
32
32
|
import {
|
|
33
33
|
contentHash,
|
|
34
34
|
readCustomChartBrowserRuntime,
|
|
35
35
|
validateBundle
|
|
36
|
-
} from "./chunk-
|
|
37
|
-
|
|
38
|
-
// src/apps/custom-chart-assets.ts
|
|
39
|
-
import { z } from "zod";
|
|
40
|
-
var assets = /* @__PURE__ */ new Map();
|
|
41
|
-
function registerCustomChartAssets(server, client) {
|
|
42
|
-
const runtime = server.tool(
|
|
43
|
-
"load_custom_chart_runtime",
|
|
44
|
-
"Load the trusted app/embed sandbox adapter for a custom chart tile. App-only; no company data or chart code.",
|
|
45
|
-
{},
|
|
46
|
-
READ_ONLY,
|
|
47
|
-
async () => bounded({
|
|
48
|
-
content: [{ type: "text", text: "Custom chart sandbox runtime." }],
|
|
49
|
-
_meta: { "databrain/custom-chart-runtime": readCustomChartBrowserRuntime() }
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
runtime.update({ _meta: appMeta(["app"]) });
|
|
53
|
-
const api = new CustomChartsApi(client);
|
|
54
|
-
const tool = server.tool(
|
|
55
|
-
"load_custom_chart_bundle",
|
|
56
|
-
"Load one bounded chunk of the company published custom chart bundle for the tile. No draft version, credentials, or query rows.",
|
|
57
|
-
{
|
|
58
|
-
key: z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/),
|
|
59
|
-
offset: z.number().int().min(0).max(MAX_CUSTOM_BUNDLE_CHARS).default(0),
|
|
60
|
-
contentHash: z.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
61
|
-
},
|
|
62
|
-
READ_ONLY,
|
|
63
|
-
async ({ key, offset, contentHash: expected }) => {
|
|
64
|
-
try {
|
|
65
|
-
const detail = await api.get(key);
|
|
66
|
-
if (detail.status !== "published" || !detail.publishedVersionId) throw new Error("not published");
|
|
67
|
-
const publishedHash = detail.versions?.find((version) => version.id === detail.publishedVersionId)?.contentHash;
|
|
68
|
-
const cached = publishedHash && /^[a-f0-9]{64}$/.test(publishedHash) ? assets.get(publishedHash) : void 0;
|
|
69
|
-
let hash = publishedHash, asset = cached && cached.expires > Date.now() ? cached.text : void 0;
|
|
70
|
-
if (asset === void 0) {
|
|
71
|
-
const raw = await api.bundle(key, detail.publishedVersionId);
|
|
72
|
-
const bundle = { ...raw, bundleCss: raw.bundleCss ?? void 0 };
|
|
73
|
-
if (validateBundle(bundle).length || bundle.manifest.id !== key) throw new Error("invalid bundle");
|
|
74
|
-
hash = contentHash(bundle);
|
|
75
|
-
if (publishedHash && publishedHash !== hash) throw new Error("published hash mismatch");
|
|
76
|
-
asset = JSON.stringify({ manifest: bundle.manifest, bundleJs: bundle.bundleJs, bundleCss: bundle.bundleCss ?? "", iconSvg: bundle.iconSvg });
|
|
77
|
-
if (assets.size >= 4) assets.delete(assets.keys().next().value);
|
|
78
|
-
assets.set(hash, { text: asset, expires: Date.now() + 6e4 });
|
|
79
|
-
}
|
|
80
|
-
if (expected && hash !== expected) throw new Error("published version changed");
|
|
81
|
-
if (offset > asset.length) throw new Error("offset");
|
|
82
|
-
const chunk = asset.slice(offset, offset + CUSTOM_BUNDLE_CHUNK_CHARS);
|
|
83
|
-
return bounded({
|
|
84
|
-
content: [{ type: "text", text: "Published custom chart asset chunk." }],
|
|
85
|
-
structuredContent: {
|
|
86
|
-
key,
|
|
87
|
-
contentHash: hash,
|
|
88
|
-
offset,
|
|
89
|
-
chunk,
|
|
90
|
-
nextOffset: offset + chunk.length < asset.length ? offset + chunk.length : null
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
} catch {
|
|
94
|
-
return { isError: true, content: [{
|
|
95
|
-
type: "text",
|
|
96
|
-
text: `Could not load published custom:${key}. It may be unpublished, changed, or unavailable to this sign-in.`
|
|
97
|
-
}] };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
);
|
|
101
|
-
tool.update({ _meta: appMeta(["app"]) });
|
|
102
|
-
}
|
|
36
|
+
} from "./chunk-2F7V6RZR.js";
|
|
103
37
|
|
|
104
38
|
// src/apps/registry.ts
|
|
105
39
|
import { z as z2 } from "zod";
|
|
@@ -108,11 +42,18 @@ import { z as z2 } from "zod";
|
|
|
108
42
|
var RENDER_PATH = "/api/v2/dashboard/mcp-chart";
|
|
109
43
|
var UncachedChartClient = class extends DatabrainClient {
|
|
110
44
|
constructor(delegate) {
|
|
111
|
-
super({
|
|
45
|
+
super({
|
|
46
|
+
apiUrl: delegate.getApiUrl(),
|
|
47
|
+
demoDomain: delegate.getDemoDomain()
|
|
48
|
+
});
|
|
112
49
|
this.delegate = delegate;
|
|
113
50
|
}
|
|
114
51
|
request(options) {
|
|
115
|
-
return this.delegate.request({
|
|
52
|
+
return this.delegate.request({
|
|
53
|
+
...options,
|
|
54
|
+
cacheKey: void 0,
|
|
55
|
+
cacheTtlMs: void 0
|
|
56
|
+
});
|
|
116
57
|
}
|
|
117
58
|
};
|
|
118
59
|
function createChartBackend(client) {
|
|
@@ -123,26 +64,31 @@ function createChartBackend(client) {
|
|
|
123
64
|
const view = viewSchema.parse(rawView);
|
|
124
65
|
if ("dashboardId" in reference) {
|
|
125
66
|
if (!client.isHosted()) throw new Error("Hosted sign-in required");
|
|
126
|
-
return pageSchema.parse(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
67
|
+
return pageSchema.parse(
|
|
68
|
+
await client.request({
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: "/api/v2/workspace/dashboard/chart",
|
|
71
|
+
auth: "service",
|
|
72
|
+
body: {
|
|
73
|
+
action,
|
|
74
|
+
dashboardId: reference.dashboardId,
|
|
75
|
+
widgetId: reference.widgetId,
|
|
76
|
+
...reference.clientId ? { clientId: reference.clientId } : {},
|
|
77
|
+
...reference.display ? { display: reference.display } : {},
|
|
78
|
+
...view,
|
|
79
|
+
pageSize: 50,
|
|
80
|
+
width: 600,
|
|
81
|
+
height: 320,
|
|
82
|
+
animation: false
|
|
83
|
+
},
|
|
84
|
+
skipDataUnwrap: true
|
|
85
|
+
})
|
|
86
|
+
);
|
|
144
87
|
}
|
|
145
|
-
const app = await services.widgetService.resolveDataAppName(
|
|
88
|
+
const app = await services.widgetService.resolveDataAppName(
|
|
89
|
+
reference.dataAppName,
|
|
90
|
+
reference.embedId
|
|
91
|
+
);
|
|
146
92
|
if (!app.ok) throw new Error("Chart scope unavailable");
|
|
147
93
|
const scope = await services.widgetService.ensureReadableDashboardScope({
|
|
148
94
|
embedId: reference.embedId,
|
|
@@ -184,6 +130,97 @@ function createChartBackend(client) {
|
|
|
184
130
|
};
|
|
185
131
|
}
|
|
186
132
|
|
|
133
|
+
// src/apps/custom-chart-assets.ts
|
|
134
|
+
import { z } from "zod";
|
|
135
|
+
var assets = /* @__PURE__ */ new Map();
|
|
136
|
+
function registerCustomChartAssets(server, client) {
|
|
137
|
+
const runtime = server.tool(
|
|
138
|
+
"load_custom_chart_runtime",
|
|
139
|
+
"Load the trusted app/embed sandbox adapter for a custom chart tile. App-only; no company data or chart code.",
|
|
140
|
+
{},
|
|
141
|
+
READ_ONLY,
|
|
142
|
+
async () => bounded({
|
|
143
|
+
content: [
|
|
144
|
+
{ type: "text", text: "Custom chart sandbox runtime." }
|
|
145
|
+
],
|
|
146
|
+
_meta: {
|
|
147
|
+
"databrain/custom-chart-runtime": readCustomChartBrowserRuntime()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
runtime.update({ _meta: appMeta(["app"]) });
|
|
152
|
+
const api = new CustomChartsApi(client);
|
|
153
|
+
const tool = server.tool(
|
|
154
|
+
"load_custom_chart_bundle",
|
|
155
|
+
"Load one bounded chunk of the company published custom chart bundle for the tile. No draft version, credentials, or query rows.",
|
|
156
|
+
{
|
|
157
|
+
key: z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/),
|
|
158
|
+
offset: z.number().int().min(0).max(MAX_CUSTOM_BUNDLE_CHARS).default(0),
|
|
159
|
+
contentHash: z.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
160
|
+
},
|
|
161
|
+
READ_ONLY,
|
|
162
|
+
async ({ key, offset, contentHash: expected }) => {
|
|
163
|
+
try {
|
|
164
|
+
const detail = await api.get(key);
|
|
165
|
+
if (detail.status !== "published" || !detail.publishedVersionId)
|
|
166
|
+
throw new Error("not published");
|
|
167
|
+
const publishedHash = detail.versions?.find(
|
|
168
|
+
(version) => version.id === detail.publishedVersionId
|
|
169
|
+
)?.contentHash;
|
|
170
|
+
const cached = publishedHash && /^[a-f0-9]{64}$/.test(publishedHash) ? assets.get(publishedHash) : void 0;
|
|
171
|
+
let hash = publishedHash, asset = cached && cached.expires > Date.now() ? cached.text : void 0;
|
|
172
|
+
if (asset === void 0) {
|
|
173
|
+
const raw = await api.bundle(key, detail.publishedVersionId);
|
|
174
|
+
const bundle = { ...raw, bundleCss: raw.bundleCss ?? void 0 };
|
|
175
|
+
if (validateBundle(bundle).length || bundle.manifest.id !== key)
|
|
176
|
+
throw new Error("invalid bundle");
|
|
177
|
+
hash = contentHash(bundle);
|
|
178
|
+
if (publishedHash && publishedHash !== hash)
|
|
179
|
+
throw new Error("published hash mismatch");
|
|
180
|
+
asset = JSON.stringify({
|
|
181
|
+
manifest: bundle.manifest,
|
|
182
|
+
bundleJs: bundle.bundleJs,
|
|
183
|
+
bundleCss: bundle.bundleCss ?? "",
|
|
184
|
+
iconSvg: bundle.iconSvg
|
|
185
|
+
});
|
|
186
|
+
if (assets.size >= 4) assets.delete(assets.keys().next().value);
|
|
187
|
+
assets.set(hash, { text: asset, expires: Date.now() + 6e4 });
|
|
188
|
+
}
|
|
189
|
+
if (expected && hash !== expected)
|
|
190
|
+
throw new Error("published version changed");
|
|
191
|
+
if (offset > asset.length) throw new Error("offset");
|
|
192
|
+
const chunk = asset.slice(offset, offset + CUSTOM_BUNDLE_CHUNK_CHARS);
|
|
193
|
+
return bounded({
|
|
194
|
+
content: [
|
|
195
|
+
{
|
|
196
|
+
type: "text",
|
|
197
|
+
text: "Published custom chart asset chunk."
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
structuredContent: {
|
|
201
|
+
key,
|
|
202
|
+
contentHash: hash,
|
|
203
|
+
offset,
|
|
204
|
+
chunk,
|
|
205
|
+
nextOffset: offset + chunk.length < asset.length ? offset + chunk.length : null
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
} catch {
|
|
209
|
+
return {
|
|
210
|
+
isError: true,
|
|
211
|
+
content: [
|
|
212
|
+
{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: `Could not load published custom:${key}. It may be unpublished, changed, or unavailable to this sign-in.`
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
tool.update({ _meta: appMeta(["app"]) });
|
|
222
|
+
}
|
|
223
|
+
|
|
187
224
|
// src/apps/tile.ts
|
|
188
225
|
import { transformSync } from "esbuild";
|
|
189
226
|
var TILE_SCRIPT = String.raw`(()=>{
|
|
@@ -377,7 +414,10 @@ host(r.hostContext||{});ready=true;notify(INIT,{});for(const m of early.splice(0
|
|
|
377
414
|
if(tries<10)setTimeout(start,Math.min(8000,400*2**tries))}
|
|
378
415
|
start();
|
|
379
416
|
})();`;
|
|
380
|
-
var SERVED_SCRIPT = transformSync(TILE_SCRIPT, {
|
|
417
|
+
var SERVED_SCRIPT = transformSync(TILE_SCRIPT, {
|
|
418
|
+
minify: true,
|
|
419
|
+
target: "es2020"
|
|
420
|
+
}).code;
|
|
381
421
|
function tileHtml(kind = "chart") {
|
|
382
422
|
return strip(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
383
423
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'; frame-src blob:; base-uri 'none'; form-action 'none'">
|
|
@@ -509,7 +549,10 @@ header,#status,#plot{animation:rise var(--s) var(--e) both}
|
|
|
509
549
|
<details id="data" hidden><summary id="count">Show the data behind this</summary><div id="rows" tabindex="0" role="region" aria-label="Chart data"></div></details></main><script>${SERVED_SCRIPT}</script></body></html>`);
|
|
510
550
|
}
|
|
511
551
|
function strip(html) {
|
|
512
|
-
return html.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\n{2,}/g, "\n").replace(
|
|
552
|
+
return html.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\n{2,}/g, "\n").replace(
|
|
553
|
+
/<style>([\s\S]*?)<\/style><\/head>/,
|
|
554
|
+
(_m, css) => `<style>${css.replace(/;}/g, "}").replace(/\n/g, "")}</style></head>`
|
|
555
|
+
);
|
|
513
556
|
}
|
|
514
557
|
|
|
515
558
|
// src/apps/registry.ts
|
|
@@ -522,42 +565,93 @@ var actions = {
|
|
|
522
565
|
function registerChartApps(server, client, backend = createChartBackend(client)) {
|
|
523
566
|
for (const [name, description] of Object.entries(actions)) {
|
|
524
567
|
const action = name;
|
|
525
|
-
const tool = server.tool(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
568
|
+
const tool = server.tool(
|
|
569
|
+
name,
|
|
570
|
+
description,
|
|
571
|
+
{
|
|
572
|
+
reference: referenceSchema,
|
|
573
|
+
view: viewSchema.default({}),
|
|
574
|
+
context: z2.string().max(200).optional().describe("Chart interaction context")
|
|
575
|
+
},
|
|
576
|
+
READ_ONLY,
|
|
577
|
+
async ({ reference, view }) => {
|
|
578
|
+
try {
|
|
579
|
+
if (action === "drill_down" && !view.drillId || action === "change_date_range" && !view.dateRange)
|
|
580
|
+
throw new Error("Missing view selector");
|
|
581
|
+
const page = pageSchema.parse(
|
|
582
|
+
await toolContext.run(
|
|
583
|
+
{
|
|
584
|
+
tool: name,
|
|
585
|
+
category: "embedding",
|
|
586
|
+
workflowIntent: "chart_app",
|
|
587
|
+
inputMode: "app"
|
|
588
|
+
},
|
|
589
|
+
() => backend.execute(action, reference, view)
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
if (page.svg && (!/^\s*<svg\b/i.test(page.svg) || /<\s*(?:script|foreignObject|iframe|image|animate\w*|set)\b|<!DOCTYPE|<!ENTITY|\bon\w+\s*=|@(?:import|keyframes)|\banimation\s*:|(?:href\s*=\s*["'](?!#)|url\s*\(\s*["']?(?!#))/i.test(
|
|
593
|
+
page.svg
|
|
594
|
+
))) {
|
|
595
|
+
throw new Error("Unsafe SVG");
|
|
596
|
+
}
|
|
597
|
+
return chartResult(
|
|
598
|
+
`${page.kind === "kpi" ? "KPI" : "Chart"} ready. ${page.rows.length} rows in this page.${page.fallback ? ` ${page.fallback}` : ""}${page.description ? ` ${page.description}` : ""}`,
|
|
599
|
+
{
|
|
600
|
+
...page,
|
|
601
|
+
reference,
|
|
602
|
+
view
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
if (error instanceof DatabrainApiError) return error.toToolResponse();
|
|
607
|
+
return {
|
|
608
|
+
isError: true,
|
|
609
|
+
content: [
|
|
610
|
+
{
|
|
611
|
+
type: "text",
|
|
612
|
+
text: "Chart unavailable. Check dashboard access and the chart renderer integration, then retry."
|
|
613
|
+
}
|
|
614
|
+
]
|
|
615
|
+
};
|
|
538
616
|
}
|
|
539
|
-
return chartResult(`${page.kind === "kpi" ? "KPI" : "Chart"} ready. ${page.rows.length} rows in this page.${page.fallback ? " " + page.fallback : ""}${page.description ? " " + page.description : ""}`, {
|
|
540
|
-
...page,
|
|
541
|
-
reference,
|
|
542
|
-
view
|
|
543
|
-
});
|
|
544
|
-
} catch (error) {
|
|
545
|
-
if (error instanceof DatabrainApiError) return error.toToolResponse();
|
|
546
|
-
return { isError: true, content: [{
|
|
547
|
-
type: "text",
|
|
548
|
-
text: "Chart unavailable. Check dashboard access and the chart renderer integration, then retry."
|
|
549
|
-
}] };
|
|
550
617
|
}
|
|
551
|
-
|
|
618
|
+
);
|
|
552
619
|
tool.update({ _meta: appMeta(["app"]) });
|
|
553
620
|
}
|
|
554
621
|
registerMetricTile(server, client, backend);
|
|
555
622
|
registerDraftTools(server, client);
|
|
556
623
|
registerCustomChartAssets(server, client);
|
|
557
|
-
for (const [uri, kind] of [
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
624
|
+
for (const [uri, kind] of [
|
|
625
|
+
[CHART_URI, "chart"],
|
|
626
|
+
[KPI_URI, "kpi"]
|
|
627
|
+
]) {
|
|
628
|
+
server.resource(
|
|
629
|
+
`Databrain ${kind} tile`,
|
|
630
|
+
uri,
|
|
631
|
+
{
|
|
632
|
+
mimeType: APP_MIME,
|
|
633
|
+
description: `Interactive ${kind} using host theme and scoped server tools`
|
|
634
|
+
},
|
|
635
|
+
async () => ({
|
|
636
|
+
contents: [
|
|
637
|
+
{
|
|
638
|
+
uri,
|
|
639
|
+
mimeType: APP_MIME,
|
|
640
|
+
text: tileHtml(kind),
|
|
641
|
+
_meta: {
|
|
642
|
+
ui: {
|
|
643
|
+
prefersBorder: true,
|
|
644
|
+
csp: {
|
|
645
|
+
resourceDomains: ["data:", "blob:"],
|
|
646
|
+
frameDomains: ["blob:"],
|
|
647
|
+
connectDomains: []
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
]
|
|
653
|
+
})
|
|
654
|
+
);
|
|
561
655
|
}
|
|
562
656
|
}
|
|
563
657
|
function registerDraftTools(server, client) {
|
|
@@ -574,9 +668,20 @@ function registerDraftTools(server, client) {
|
|
|
574
668
|
async ({ draft, rows, chartType }) => {
|
|
575
669
|
try {
|
|
576
670
|
const page = await draftPage(client, draft, rows, chartType);
|
|
577
|
-
return chartResult(
|
|
671
|
+
return chartResult(
|
|
672
|
+
`${page.kind === "table" ? "Table" : "Chart"} ready as ${chartType}.`,
|
|
673
|
+
page
|
|
674
|
+
);
|
|
578
675
|
} catch {
|
|
579
|
-
return {
|
|
676
|
+
return {
|
|
677
|
+
isError: true,
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: "Chart preview unavailable. Ask the question again to retry."
|
|
682
|
+
}
|
|
683
|
+
]
|
|
684
|
+
};
|
|
580
685
|
}
|
|
581
686
|
}
|
|
582
687
|
);
|
|
@@ -589,33 +694,62 @@ function registerDraftTools(server, client) {
|
|
|
589
694
|
chartType: z2.enum(RENDER_TYPES),
|
|
590
695
|
dashboardTitle: z2.string().trim().min(1).max(200),
|
|
591
696
|
workspaceName: z2.string().trim().min(1).max(256).optional(),
|
|
592
|
-
attemptId: z2.string().trim().min(1).max(64).optional().describe(
|
|
697
|
+
attemptId: z2.string().trim().min(1).max(64).optional().describe(
|
|
698
|
+
"Identifies one save attempt. Reuse it to retry safely; omit or change it to save again on purpose."
|
|
699
|
+
),
|
|
593
700
|
confirm: z2.string().optional(),
|
|
594
701
|
context: z2.string().max(200).optional().describe("Chart interaction context")
|
|
595
702
|
},
|
|
596
703
|
WRITE_CREATE,
|
|
597
|
-
async ({
|
|
704
|
+
async ({
|
|
705
|
+
draft,
|
|
706
|
+
chartType,
|
|
707
|
+
dashboardTitle,
|
|
708
|
+
workspaceName,
|
|
709
|
+
attemptId,
|
|
710
|
+
confirm
|
|
711
|
+
}) => {
|
|
598
712
|
if (confirm !== APPLY_TO_PRODUCTION_CONFIRMATION) {
|
|
599
|
-
return {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
713
|
+
return {
|
|
714
|
+
isError: true,
|
|
715
|
+
content: [
|
|
716
|
+
{
|
|
717
|
+
type: "text",
|
|
718
|
+
text: `Saving writes to production. Retry with confirm: "${APPLY_TO_PRODUCTION_CONFIRMATION}".`
|
|
719
|
+
}
|
|
720
|
+
]
|
|
721
|
+
};
|
|
603
722
|
}
|
|
604
|
-
const choices = await workspacesForDatamart(
|
|
723
|
+
const choices = await workspacesForDatamart(
|
|
724
|
+
client,
|
|
725
|
+
draft.datamartName,
|
|
726
|
+
"authorize"
|
|
727
|
+
);
|
|
605
728
|
const refuse = (text) => ({
|
|
606
729
|
isError: true,
|
|
607
730
|
content: [{ type: "text", text }],
|
|
608
731
|
structuredContent: { kind: "save", ok: false, choices }
|
|
609
732
|
});
|
|
610
|
-
const target = workspaceName ? choices.find(
|
|
733
|
+
const target = workspaceName ? choices.find(
|
|
734
|
+
(w) => w.trim().toLowerCase() === workspaceName.toLowerCase()
|
|
735
|
+
) : choices.length === 1 ? choices[0] : void 0;
|
|
611
736
|
if (workspaceName && !target) {
|
|
612
|
-
return refuse(
|
|
737
|
+
return refuse(
|
|
738
|
+
`Workspace "${workspaceName}" cannot run this datamart's SQL. Choose one of: ${choices.join(", ")}.`
|
|
739
|
+
);
|
|
613
740
|
}
|
|
614
741
|
if (!target) {
|
|
615
|
-
return refuse(
|
|
742
|
+
return refuse(
|
|
743
|
+
choices.length ? `Choose a workspace: ${choices.join(", ")}.` : `No workspace is built on datamart "${draft.datamartName}". Create one in Databrain, then save again.`
|
|
744
|
+
);
|
|
616
745
|
}
|
|
617
746
|
try {
|
|
618
|
-
const saved = await saveDraftAsDashboard(client, draft, {
|
|
747
|
+
const saved = await saveDraftAsDashboard(client, draft, {
|
|
748
|
+
chartType,
|
|
749
|
+
dashboardTitle,
|
|
750
|
+
workspaceName: target,
|
|
751
|
+
attemptId
|
|
752
|
+
});
|
|
619
753
|
return chartResult(
|
|
620
754
|
`Saved "${saved.title}" to workspace ${saved.workspaceName} (dashboard ${saved.dashboardId}, metric ${saved.metricId}).`,
|
|
621
755
|
{ kind: "save", ok: true, ...saved, draft: { ...draft, chartType } }
|
|
@@ -624,10 +758,12 @@ function registerDraftTools(server, client) {
|
|
|
624
758
|
const code = error instanceof DatabrainApiError ? error.code : "SAVE_FAILED";
|
|
625
759
|
return {
|
|
626
760
|
isError: true,
|
|
627
|
-
content: [
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
761
|
+
content: [
|
|
762
|
+
{
|
|
763
|
+
type: "text",
|
|
764
|
+
text: `Could not save (${code}). Check the workspace name and your access, then try again.`
|
|
765
|
+
}
|
|
766
|
+
],
|
|
631
767
|
structuredContent: { kind: "save", ok: false, code }
|
|
632
768
|
};
|
|
633
769
|
}
|
|
@@ -650,17 +786,35 @@ function registerMetricTile(server, client, backend) {
|
|
|
650
786
|
context: z2.string().max(200).optional()
|
|
651
787
|
},
|
|
652
788
|
READ_ONLY,
|
|
653
|
-
async ({
|
|
789
|
+
async ({
|
|
790
|
+
workspaceName,
|
|
791
|
+
workspaceId,
|
|
792
|
+
metricId,
|
|
793
|
+
dashboardId,
|
|
794
|
+
display,
|
|
795
|
+
clientId,
|
|
796
|
+
dateRange
|
|
797
|
+
}) => {
|
|
654
798
|
try {
|
|
655
|
-
if (!client.isHosted())
|
|
799
|
+
if (!client.isHosted())
|
|
800
|
+
throw new Error(
|
|
801
|
+
"Use the hosted sign-in for workspace metrics. API-key connections retain list_widgets with an embed and clientId."
|
|
802
|
+
);
|
|
656
803
|
const resolved = await client.request({
|
|
657
804
|
method: "POST",
|
|
658
805
|
path: "/api/v2/workspace/dashboard/widgets",
|
|
659
806
|
auth: "service",
|
|
660
|
-
body: {
|
|
807
|
+
body: {
|
|
808
|
+
workspaceName,
|
|
809
|
+
workspaceId,
|
|
810
|
+
metricId,
|
|
811
|
+
...dashboardId ? { dashboardId } : {}
|
|
812
|
+
}
|
|
661
813
|
});
|
|
662
814
|
if (resolved.clientId && clientId !== resolved.clientId) {
|
|
663
|
-
throw new Error(
|
|
815
|
+
throw new Error(
|
|
816
|
+
`CLIENT_REQUIRED: Pass clientId ${JSON.stringify(resolved.clientId)}, saved on this dashboard/workspace. No unscoped fallback is allowed.`
|
|
817
|
+
);
|
|
664
818
|
}
|
|
665
819
|
const reference = {
|
|
666
820
|
dashboardId: resolved.dashboardId,
|
|
@@ -669,12 +823,28 @@ function registerMetricTile(server, client, backend) {
|
|
|
669
823
|
...clientId ? { clientId } : {}
|
|
670
824
|
};
|
|
671
825
|
const view = dateRange ? { dateRange } : {};
|
|
672
|
-
const page = pageSchema.parse(
|
|
673
|
-
|
|
674
|
-
|
|
826
|
+
const page = pageSchema.parse(
|
|
827
|
+
await backend.execute("render_chart", reference, view)
|
|
828
|
+
);
|
|
829
|
+
if (page.svg && (!/^\s*<svg\b/i.test(page.svg) || /<\s*(?:script|foreignObject|iframe|image|animate\w*|set)\b|<!DOCTYPE|<!ENTITY|\bon\w+\s*=|@(?:import|keyframes)|\banimation\s*:|(?:href\s*=\s*["'](?!#)|url\s*\(\s*["']?(?!#))/i.test(
|
|
830
|
+
page.svg
|
|
831
|
+
)))
|
|
832
|
+
throw new Error("Unsafe SVG");
|
|
833
|
+
return chartResult(
|
|
834
|
+
`${page.kind === "kpi" ? "KPI" : "Chart"} ready. ${page.rows.length} rows in this page.${page.description ? ` ${page.description}` : ""}`,
|
|
835
|
+
{ ...page, reference, view }
|
|
836
|
+
);
|
|
675
837
|
} catch (error) {
|
|
676
838
|
if (error instanceof DatabrainApiError) return error.toToolResponse();
|
|
677
|
-
return {
|
|
839
|
+
return {
|
|
840
|
+
isError: true,
|
|
841
|
+
content: [
|
|
842
|
+
{
|
|
843
|
+
type: "text",
|
|
844
|
+
text: error.message.startsWith("CLIENT_REQUIRED:") || error.message.startsWith("Use the hosted sign-in") ? error.message : "Chart unavailable. Check metric access and the saved tenant settings, then retry."
|
|
845
|
+
}
|
|
846
|
+
]
|
|
847
|
+
};
|
|
678
848
|
}
|
|
679
849
|
}
|
|
680
850
|
);
|
|
@@ -684,4 +854,4 @@ function registerMetricTile(server, client, backend) {
|
|
|
684
854
|
export {
|
|
685
855
|
registerChartApps
|
|
686
856
|
};
|
|
687
|
-
//# sourceMappingURL=chunk-
|
|
857
|
+
//# sourceMappingURL=chunk-NIC5MAGC.js.map
|