@databrainhq/mcp-server 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +271 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -187,6 +187,10 @@ var logger = {
|
|
|
187
187
|
// src/version.ts
|
|
188
188
|
var MCP_SERVER_VERSION = "0.2.10";
|
|
189
189
|
|
|
190
|
+
// src/utils/tool-context.ts
|
|
191
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
192
|
+
var toolContext = new AsyncLocalStorage();
|
|
193
|
+
|
|
190
194
|
// src/client/databrain-client.ts
|
|
191
195
|
var MCP_USER_AGENT = `databrain-mcp-server/${MCP_SERVER_VERSION}`;
|
|
192
196
|
var MAX_RETRIES = 3;
|
|
@@ -282,6 +286,13 @@ var DatabrainClient = class {
|
|
|
282
286
|
"User-Agent": MCP_USER_AGENT,
|
|
283
287
|
"X-Databrain-Client": MCP_USER_AGENT
|
|
284
288
|
};
|
|
289
|
+
const ctx = toolContext.getStore();
|
|
290
|
+
if (ctx) {
|
|
291
|
+
headers["X-Databrain-MCP-Tool"] = ctx.tool;
|
|
292
|
+
headers["X-Databrain-MCP-Tool-Category"] = ctx.category;
|
|
293
|
+
headers["X-Databrain-MCP-Workflow-Intent"] = ctx.workflowIntent;
|
|
294
|
+
headers["X-Databrain-MCP-Input-Mode"] = ctx.inputMode;
|
|
295
|
+
}
|
|
285
296
|
if (token) {
|
|
286
297
|
if (options.auth === "none") {
|
|
287
298
|
headers["x-plugin-token"] = token;
|
|
@@ -326,7 +337,7 @@ var DatabrainClient = class {
|
|
|
326
337
|
}
|
|
327
338
|
const text = await response.text();
|
|
328
339
|
let data = text ? JSON.parse(text) : {};
|
|
329
|
-
if (data && typeof data === "object" && !Array.isArray(data) && "data" in data) {
|
|
340
|
+
if (!options.skipDataUnwrap && data && typeof data === "object" && !Array.isArray(data) && "data" in data) {
|
|
330
341
|
const inner = data.data;
|
|
331
342
|
if (inner !== null && inner !== void 0) {
|
|
332
343
|
data = inner;
|
|
@@ -356,6 +367,68 @@ var DatabrainClient = class {
|
|
|
356
367
|
}
|
|
357
368
|
};
|
|
358
369
|
|
|
370
|
+
// src/tools/tool-metadata.ts
|
|
371
|
+
var DEFAULT_TOOL_META = {
|
|
372
|
+
category: "discovery",
|
|
373
|
+
workflowIntent: "other"
|
|
374
|
+
};
|
|
375
|
+
var TOOL_METADATA = {
|
|
376
|
+
// Identity / discovery
|
|
377
|
+
whoami: { category: "discovery", workflowIntent: "discovery" },
|
|
378
|
+
// Infrastructure / data setup
|
|
379
|
+
list_datasources: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
380
|
+
discover_datasource_schema: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
381
|
+
list_datamarts: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
382
|
+
list_workspaces: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
383
|
+
sync_datasource: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
384
|
+
create_datamart: { category: "infrastructure", workflowIntent: "setup_data" },
|
|
385
|
+
// Embedding — data apps & tokens
|
|
386
|
+
list_data_apps: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
387
|
+
list_tenants: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
388
|
+
create_data_app: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
389
|
+
list_api_tokens: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
390
|
+
create_api_token: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
391
|
+
rotate_api_token: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
392
|
+
// Embedding — embed management
|
|
393
|
+
create_embed: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
394
|
+
list_embeds: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
395
|
+
get_embed_details: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
396
|
+
update_embed: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
397
|
+
customize_embed_theme: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
398
|
+
get_demo_link: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
399
|
+
// Embedding — frontend
|
|
400
|
+
generate_guest_token: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
401
|
+
generate_embed_code: { category: "embedding", workflowIntent: "embed_dashboard" },
|
|
402
|
+
// Embedding — customer dashboards & widgets
|
|
403
|
+
list_dashboards: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
404
|
+
list_widgets: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
405
|
+
add_widget: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
406
|
+
update_widget: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
407
|
+
remove_widget: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
408
|
+
generate_widget: { category: "embedding", workflowIntent: "embedded_widget" },
|
|
409
|
+
validate_metric_spec: { category: "embedding", workflowIntent: "sql_to_embedded_metric" },
|
|
410
|
+
create_or_update_metric_from_query: {
|
|
411
|
+
category: "embedding",
|
|
412
|
+
workflowIntent: "sql_to_embedded_metric"
|
|
413
|
+
},
|
|
414
|
+
// Internal analytics
|
|
415
|
+
create_workspace_metric: {
|
|
416
|
+
category: "internal_analytics",
|
|
417
|
+
workflowIntent: "sql_to_workspace_metric"
|
|
418
|
+
},
|
|
419
|
+
ask_question: { category: "internal_analytics", workflowIntent: "query_data" },
|
|
420
|
+
// Semantic layer
|
|
421
|
+
get_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data" },
|
|
422
|
+
update_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data" },
|
|
423
|
+
start_semantic_layer_generation: { category: "semantic_layer", workflowIntent: "query_data" },
|
|
424
|
+
get_semantic_layer_generation_status: {
|
|
425
|
+
category: "semantic_layer",
|
|
426
|
+
workflowIntent: "query_data"
|
|
427
|
+
},
|
|
428
|
+
// Reports
|
|
429
|
+
list_scheduled_reports: { category: "reports", workflowIntent: "reports" }
|
|
430
|
+
};
|
|
431
|
+
|
|
359
432
|
// src/tools/data-app/list-data-apps.ts
|
|
360
433
|
import { z as z2 } from "zod";
|
|
361
434
|
|
|
@@ -735,9 +808,10 @@ var TenantsApi = class {
|
|
|
735
808
|
}
|
|
736
809
|
return this.client.request({
|
|
737
810
|
method: "POST",
|
|
738
|
-
path: "/
|
|
811
|
+
path: "/api/mcp/getClientData",
|
|
739
812
|
auth: "service",
|
|
740
813
|
body: { input: { input } },
|
|
814
|
+
skipDataUnwrap: true,
|
|
741
815
|
cacheKey: `list_tenants:${JSON.stringify(input)}`,
|
|
742
816
|
cacheTtlMs: 5 * 60 * 1e3
|
|
743
817
|
});
|
|
@@ -5867,7 +5941,7 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
5867
5941
|
const services = createServiceBundle(client);
|
|
5868
5942
|
server.tool(
|
|
5869
5943
|
"create_or_update_metric_from_query",
|
|
5870
|
-
"Create or update a production metric/widget from validated SQL. Defaults to dry-run; apply requires explicit confirmation.",
|
|
5944
|
+
"[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.",
|
|
5871
5945
|
{
|
|
5872
5946
|
mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
|
|
5873
5947
|
confirm: z30.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
@@ -6044,26 +6118,101 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
6044
6118
|
);
|
|
6045
6119
|
}
|
|
6046
6120
|
|
|
6047
|
-
// src/tools/widgets/
|
|
6121
|
+
// src/tools/widgets/create-workspace-metric.ts
|
|
6048
6122
|
import { z as z31 } from "zod";
|
|
6123
|
+
var modeSchema2 = z31.enum(["dry_run", "apply"]).default("dry_run");
|
|
6124
|
+
function registerCreateWorkspaceMetric(server, client) {
|
|
6125
|
+
server.tool(
|
|
6126
|
+
"create_workspace_metric",
|
|
6127
|
+
"[INTERNAL ANALYTICS] Create or update a workspace-level metric from validated SQL (migrating your SQL into Databrain), not embedding. Pass an existing metricId to update in place \u2014 query and Dimensions/Measures re-hydrated, publish state preserved; omit to create. Workspace-scoped, unattached. Uses the org service token. Defaults to dry-run; apply needs confirmation.",
|
|
6128
|
+
{
|
|
6129
|
+
mode: modeSchema2.describe(
|
|
6130
|
+
"dry_run validates the SQL only (no write); apply creates or updates the metric after confirmation"
|
|
6131
|
+
),
|
|
6132
|
+
confirm: z31.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6133
|
+
workspaceName: z31.string().describe("Target workspace name"),
|
|
6134
|
+
name: z31.string().describe("Metric display name"),
|
|
6135
|
+
query: z31.string().describe("SQL query to validate and store"),
|
|
6136
|
+
metricId: z31.string().optional().describe(
|
|
6137
|
+
"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."
|
|
6138
|
+
),
|
|
6139
|
+
description: z31.string().optional().describe("Metric description"),
|
|
6140
|
+
datasourceId: z31.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
|
|
6141
|
+
isPublished: z31.boolean().optional().describe(
|
|
6142
|
+
"Publish after apply. Defaults to false. Applies to creates; on updates the existing publish state is preserved."
|
|
6143
|
+
)
|
|
6144
|
+
},
|
|
6145
|
+
WRITE_IDEMPOTENT,
|
|
6146
|
+
async (params) => {
|
|
6147
|
+
try {
|
|
6148
|
+
const mode = params.mode || "dry_run";
|
|
6149
|
+
if (!params.name.trim()) return toolError("`name` cannot be empty.");
|
|
6150
|
+
if (!params.query.trim()) return toolError("`query` cannot be empty.");
|
|
6151
|
+
const isApply = mode === "apply";
|
|
6152
|
+
if (isApply) {
|
|
6153
|
+
const guardError = validateProductionApply({
|
|
6154
|
+
workspaceName: params.workspaceName,
|
|
6155
|
+
confirm: params.confirm
|
|
6156
|
+
});
|
|
6157
|
+
if (guardError) return toolError(guardError);
|
|
6158
|
+
}
|
|
6159
|
+
const body = {
|
|
6160
|
+
workspaceName: params.workspaceName,
|
|
6161
|
+
name: params.name,
|
|
6162
|
+
query: params.query,
|
|
6163
|
+
metricId: params.metricId,
|
|
6164
|
+
description: params.description,
|
|
6165
|
+
datasourceId: params.datasourceId,
|
|
6166
|
+
isPublished: params.isPublished ?? false,
|
|
6167
|
+
validateOnly: !isApply
|
|
6168
|
+
};
|
|
6169
|
+
const result = await client.request({
|
|
6170
|
+
method: "POST",
|
|
6171
|
+
// MCP-only control-plane route (not the public /api/v2 surface).
|
|
6172
|
+
path: "/api/mcp/metrics/create",
|
|
6173
|
+
auth: "service",
|
|
6174
|
+
body
|
|
6175
|
+
});
|
|
6176
|
+
return toolSuccess(
|
|
6177
|
+
JSON.stringify(
|
|
6178
|
+
{
|
|
6179
|
+
mode,
|
|
6180
|
+
safety: productionSafetyNote(params.workspaceName),
|
|
6181
|
+
result,
|
|
6182
|
+
nextStep: isApply ? "Saved. If the metricId already existed it was UPDATED in place (query and Dimensions/Measures re-derived; publish state preserved); otherwise a new metric was CREATED, unpublished and unattached. Publish/attach it to a dashboard as a separate step when ready." : 'Dry-run only \u2014 no metric written; it validates the SQL and returns the output columns. Re-run with mode "apply" and confirm to create or update the metric.'
|
|
6183
|
+
},
|
|
6184
|
+
null,
|
|
6185
|
+
2
|
|
6186
|
+
)
|
|
6187
|
+
);
|
|
6188
|
+
} catch (err) {
|
|
6189
|
+
if (err instanceof DatabrainApiError) return err.toToolResponse();
|
|
6190
|
+
return toolError(`Unexpected error: ${err.message}`);
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
);
|
|
6194
|
+
}
|
|
6195
|
+
|
|
6196
|
+
// src/tools/widgets/validate-metric-spec.ts
|
|
6197
|
+
import { z as z32 } from "zod";
|
|
6049
6198
|
function registerValidateMetricSpec(server, client) {
|
|
6050
6199
|
const services = createServiceBundle(client);
|
|
6051
6200
|
server.tool(
|
|
6052
6201
|
"validate_metric_spec",
|
|
6053
6202
|
"Validate a production metric spec from SQL without saving it. Resolves dashboard, data app, datasource, conflicts, and required fields.",
|
|
6054
6203
|
{
|
|
6055
|
-
clientId:
|
|
6056
|
-
workspaceName:
|
|
6057
|
-
dashboardName:
|
|
6058
|
-
embedId:
|
|
6059
|
-
dataAppName:
|
|
6060
|
-
name:
|
|
6061
|
-
metricId:
|
|
6062
|
-
query:
|
|
6063
|
-
description:
|
|
6064
|
-
chartOptions:
|
|
6065
|
-
companyIntegrationId:
|
|
6066
|
-
integrationName:
|
|
6204
|
+
clientId: z32.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
|
|
6205
|
+
workspaceName: z32.string().describe("Target production-backed workspace name"),
|
|
6206
|
+
dashboardName: z32.string().optional().describe("Dashboard name to attach to"),
|
|
6207
|
+
embedId: z32.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6208
|
+
dataAppName: z32.string().optional().describe("Optional data app override"),
|
|
6209
|
+
name: z32.string().describe("Metric/widget display name"),
|
|
6210
|
+
metricId: z32.string().describe("Stable metric slug to create or update"),
|
|
6211
|
+
query: z32.string().describe("SQL query already validated against the warehouse"),
|
|
6212
|
+
description: z32.string().optional().describe("Metric description"),
|
|
6213
|
+
chartOptions: z32.record(z32.unknown()).optional().describe("Chart configuration JSON"),
|
|
6214
|
+
companyIntegrationId: z32.string().optional().describe("Datasource UUID override"),
|
|
6215
|
+
integrationName: z32.string().optional().describe("Datasource display name override")
|
|
6067
6216
|
},
|
|
6068
6217
|
READ_ONLY,
|
|
6069
6218
|
async (params) => {
|
|
@@ -6154,20 +6303,20 @@ function registerValidateMetricSpec(server, client) {
|
|
|
6154
6303
|
}
|
|
6155
6304
|
|
|
6156
6305
|
// src/tools/reports/list-scheduled-reports.ts
|
|
6157
|
-
import { z as
|
|
6306
|
+
import { z as z33 } from "zod";
|
|
6158
6307
|
function registerListScheduledReports(server, client) {
|
|
6159
6308
|
const services = createServiceBundle(client);
|
|
6160
6309
|
server.tool(
|
|
6161
6310
|
"list_scheduled_reports",
|
|
6162
6311
|
"List configured scheduled reports for a specific client/dashboard embed. Shows schedule metadata, not delivery success history.",
|
|
6163
6312
|
{
|
|
6164
|
-
clientId:
|
|
6165
|
-
dashboardName:
|
|
6166
|
-
workspaceName:
|
|
6167
|
-
embedId:
|
|
6168
|
-
userIdentifier:
|
|
6169
|
-
pageNumber:
|
|
6170
|
-
isPagination:
|
|
6313
|
+
clientId: z33.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
|
|
6314
|
+
dashboardName: z33.string().optional().describe("Dashboard name to inspect"),
|
|
6315
|
+
workspaceName: z33.string().optional().describe("Workspace name if dashboardName is ambiguous"),
|
|
6316
|
+
embedId: z33.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6317
|
+
userIdentifier: z33.string().optional().describe("Optional creator/user filter"),
|
|
6318
|
+
pageNumber: z33.number().int().positive().optional().describe("Page number when pagination is enabled"),
|
|
6319
|
+
isPagination: z33.boolean().optional().describe("Enable backend pagination")
|
|
6171
6320
|
},
|
|
6172
6321
|
READ_ONLY,
|
|
6173
6322
|
async (params) => {
|
|
@@ -6269,7 +6418,30 @@ function registerWhoami(server, client) {
|
|
|
6269
6418
|
}
|
|
6270
6419
|
|
|
6271
6420
|
// src/tools/registry.ts
|
|
6421
|
+
function instrumentToolRegistration(server) {
|
|
6422
|
+
const original = server.tool.bind(server);
|
|
6423
|
+
server.tool = (...args) => {
|
|
6424
|
+
const name = args[0];
|
|
6425
|
+
const meta = TOOL_METADATA[name] ?? DEFAULT_TOOL_META;
|
|
6426
|
+
const cbIndex = args.length - 1;
|
|
6427
|
+
const cb = args[cbIndex];
|
|
6428
|
+
if (typeof cb === "function") {
|
|
6429
|
+
const handler = cb;
|
|
6430
|
+
args[cbIndex] = (...handlerArgs) => toolContext.run(
|
|
6431
|
+
{
|
|
6432
|
+
tool: name,
|
|
6433
|
+
category: meta.category,
|
|
6434
|
+
workflowIntent: meta.workflowIntent,
|
|
6435
|
+
inputMode: "tool_call"
|
|
6436
|
+
},
|
|
6437
|
+
() => handler(...handlerArgs)
|
|
6438
|
+
);
|
|
6439
|
+
}
|
|
6440
|
+
return original(...args);
|
|
6441
|
+
};
|
|
6442
|
+
}
|
|
6272
6443
|
function registerAllTools(server, client) {
|
|
6444
|
+
instrumentToolRegistration(server);
|
|
6273
6445
|
registerWhoami(server, client);
|
|
6274
6446
|
registerListDatasources(server, client);
|
|
6275
6447
|
registerDiscoverDatasourceSchema(server, client);
|
|
@@ -6300,6 +6472,7 @@ function registerAllTools(server, client) {
|
|
|
6300
6472
|
registerListWidgets(server, client);
|
|
6301
6473
|
registerValidateMetricSpec(server, client);
|
|
6302
6474
|
registerCreateOrUpdateMetricFromQuery(server, client);
|
|
6475
|
+
registerCreateWorkspaceMetric(server, client);
|
|
6303
6476
|
registerAddWidget(server, client);
|
|
6304
6477
|
registerUpdateWidget(server, client);
|
|
6305
6478
|
registerRemoveWidget(server, client);
|
|
@@ -9492,6 +9665,67 @@ list_scheduled_reports({ clientId, dashboardName, workspaceName })
|
|
|
9492
9665
|
\`\`\`
|
|
9493
9666
|
Clarify that this lists configured schedules only. Delivery success/failure history is not yet persisted in the MCP/API surface.`;
|
|
9494
9667
|
|
|
9668
|
+
// src/prompts/sql-to-workspace-metric.ts
|
|
9669
|
+
var sqlToWorkspaceMetricPrompt = `You are helping turn validated warehouse SQL into an INTERNAL Databrain metric \u2014 a workspace-level metric for the team's own analytics. This is NOT embedding: there is no customer, no clientId, and the metric is not attached to any dashboard.
|
|
9670
|
+
|
|
9671
|
+
Use this flow when the user says things like "I have this SQL, make it a metric in Databrain", "migrate my query", "add an unpublished metric", or "update/fix the SQL on an existing workspace metric" \u2014 and they are not setting up a customer-facing embed. If the goal is a widget on a customer's embedded dashboard, use the sql-to-metric-migration prompt instead (that one requires a clientId).
|
|
9672
|
+
|
|
9673
|
+
**Why this path**
|
|
9674
|
+
- \`create_workspace_metric\` is workspace-scoped, unpublished, and uses the org service token. No clientId.
|
|
9675
|
+
- It upserts on \`metricId\`: pass an existing metric's \`metricId\` to UPDATE it in place (the query and its Dimensions/Measures are re-derived; archived/draft and publish state are preserved); omit \`metricId\` to CREATE a new metric.
|
|
9676
|
+
- The metric is intentionally NOT attached to a dashboard \u2014 attaching/publishing is a separate step the user does later.
|
|
9677
|
+
|
|
9678
|
+
**Critical safety rules**
|
|
9679
|
+
- Default to dry-run. Never apply on the first pass.
|
|
9680
|
+
- Applying requires the exact confirmation string: \`APPLY_TO_PRODUCTION\`.
|
|
9681
|
+
- Service-token mode means writes are attributed as system until OAuth ships. State that clearly before apply.
|
|
9682
|
+
- Keep \`isPublished: false\` unless the user explicitly asks to publish.
|
|
9683
|
+
|
|
9684
|
+
**Minimum inputs**
|
|
9685
|
+
- \`workspaceName\`
|
|
9686
|
+
- \`name\`
|
|
9687
|
+
- \`query\`
|
|
9688
|
+
- Optional: \`description\`, \`datasourceId\` (defaults to the workspace datasource), \`metricId\` (a stable slug). **Omit \`metricId\` to create** (it's generated from the name). **To UPDATE an existing metric, pass its exact \`metricId\`** \u2014 carry that same value through BOTH the dry-run and apply calls, or the update won't target the existing metric. If the user is editing a metric and you don't know its \`metricId\`, ask them for the exact slug it was created with (there is no workspace-metric list tool).
|
|
9689
|
+
|
|
9690
|
+
**Step 1 \u2014 Confirm the workspace and datasource**
|
|
9691
|
+
Call:
|
|
9692
|
+
\`\`\`
|
|
9693
|
+
list_workspaces
|
|
9694
|
+
discover_datasource_schema({ datasourceName })
|
|
9695
|
+
\`\`\`
|
|
9696
|
+
Confirm the target workspace and that the columns referenced by the SQL exist. This avoids the most common validation failure.
|
|
9697
|
+
|
|
9698
|
+
**Step 2 \u2014 Dry-run (validate the SQL, no write)**
|
|
9699
|
+
Call:
|
|
9700
|
+
\`\`\`
|
|
9701
|
+
create_workspace_metric({
|
|
9702
|
+
mode: "dry_run",
|
|
9703
|
+
workspaceName,
|
|
9704
|
+
name,
|
|
9705
|
+
query,
|
|
9706
|
+
description,
|
|
9707
|
+
metricId, // include the existing metricId when UPDATING; omit to create
|
|
9708
|
+
isPublished: false
|
|
9709
|
+
})
|
|
9710
|
+
\`\`\`
|
|
9711
|
+
The dry-run validates the SQL against the workspace datasource and returns the output columns \u2014 it does NOT report whether apply will create or update. Show the result and the output columns. Stop and ask the user to review.
|
|
9712
|
+
|
|
9713
|
+
**Step 3 \u2014 Apply only after explicit confirmation**
|
|
9714
|
+
Only after the user approves, call:
|
|
9715
|
+
\`\`\`
|
|
9716
|
+
create_workspace_metric({
|
|
9717
|
+
mode: "apply",
|
|
9718
|
+
confirm: "APPLY_TO_PRODUCTION",
|
|
9719
|
+
workspaceName,
|
|
9720
|
+
name,
|
|
9721
|
+
query,
|
|
9722
|
+
description,
|
|
9723
|
+
metricId, // pass the SAME metricId used in the dry-run when UPDATING
|
|
9724
|
+
isPublished: false
|
|
9725
|
+
})
|
|
9726
|
+
\`\`\`
|
|
9727
|
+
Never invent the confirmation. Pass the same \`metricId\` you used in the dry-run so an update targets the intended metric. On create, the metric is unpublished and unattached; on update, the existing metric is rewritten in place. Either way its Dimensions/Measures are (re-)hydrated from the query, and archived/draft + publish state are preserved on update; tell the user they can publish/attach it to a dashboard as a separate step when ready.`;
|
|
9728
|
+
|
|
9495
9729
|
// src/prompts/registry.ts
|
|
9496
9730
|
var prompts = [
|
|
9497
9731
|
{
|
|
@@ -9528,6 +9762,11 @@ var prompts = [
|
|
|
9528
9762
|
name: "sql-to-metric-migration",
|
|
9529
9763
|
description: "Production-safe SQL-to-metric migration workflow: validate, dry-run, then apply with explicit confirmation.",
|
|
9530
9764
|
content: sqlToMetricMigrationPrompt
|
|
9765
|
+
},
|
|
9766
|
+
{
|
|
9767
|
+
name: "sql-to-workspace-metric",
|
|
9768
|
+
description: "Internal analytics: turn validated SQL into an unpublished workspace metric (no clientId, not embedding). Validate, dry-run, then apply with confirmation.",
|
|
9769
|
+
content: sqlToWorkspaceMetricPrompt
|
|
9531
9770
|
}
|
|
9532
9771
|
];
|
|
9533
9772
|
function registerAllPrompts(server) {
|
|
@@ -9564,18 +9803,23 @@ function createServer(config) {
|
|
|
9564
9803
|
{
|
|
9565
9804
|
name: "databrain",
|
|
9566
9805
|
version: MCP_SERVER_VERSION,
|
|
9567
|
-
description: "Databrain MCP Server \u2014
|
|
9806
|
+
description: "Databrain MCP Server \u2014 build customer-facing embedded analytics AND run internal analytics on your own data. Supports embed setup, semantic-layer maintenance, natural-language querying, SQL\u2192metric creation (embedded production metrics and unpublished internal workspace metrics), theming, filters, permissions, and guest token generation."
|
|
9568
9807
|
},
|
|
9569
9808
|
{
|
|
9570
9809
|
instructions: operatorBlurb + `Databrain uses two token types \u2014 never confuse them:
|
|
9571
9810
|
\u2022 SERVICE TOKEN (DATABRAIN_SERVICE_TOKEN): org-level, pre-configured in MCP server config. Used for: list_datasources, discover_datasource_schema, sync_datasource, list_datamarts, create_datamart, list_workspaces, list_data_apps, list_tenants, create_data_app, list_api_tokens, create_api_token, rotate_api_token, list_dashboards (workspace scope), ask_question, get_semantic_layer, update_semantic_layer, start_semantic_layer_generation, get_semantic_layer_generation_status. ${serviceTokenStatus}
|
|
9572
9811
|
\u2022 API TOKEN (per data app): scoped to one data app, used for embedding operations (list_embeds, get_embed_details, create_embed, update_embed, generate_guest_token), data-app scoped dashboard listing, widget CRUD/metric creation through guest-token APIs, and list_scheduled_reports. ${apiTokenStatus}
|
|
9573
9812
|
|
|
9574
|
-
|
|
9813
|
+
FIRST classify the request before calling tools \u2014 this avoids wasted turns:
|
|
9814
|
+
\u2022 (A) EMBEDDING: putting Databrain dashboards/charts in front of YOUR customers (data apps, embeds, guest tokens, per-customer dashboard widgets). Needs an API token and usually a clientId.
|
|
9815
|
+
\u2022 (B) INTERNAL ANALYTICS: exploring your own data or migrating SQL into your own Databrain metrics for internal use. Workspace-scoped, uses the service token, NO clientId. Creating an unpublished workspace metric (create_workspace_metric) lives here \u2014 it is NOT embedding.
|
|
9816
|
+
|
|
9817
|
+
5 core use cases \u2014 match the user's scenario to the right prompt:
|
|
9575
9818
|
1. Embed existing dashboard \u2192 prompt: embed-existing-dashboard (list_data_apps \u2192 create_api_token \u2192 list_dashboards \u2192 create_embed \u2192 generate_guest_token \u2192 generate_embed_code)
|
|
9576
9819
|
2. Blank dashboard per client \u2192 prompt: embed-blank-dashboard (list_data_apps \u2192 list_datamarts \u2192 create_embed with clientId per client \u2192 generate_guest_token \u2192 generate_embed_code)
|
|
9577
9820
|
3. Explore & query data \u2192 prompt: query-data (list_datamarts \u2192 get_semantic_layer \u2192 if empty, auto-generate descriptions with update_semantic_layer or use populate-semantic-layer prompt \u2192 ask_question)
|
|
9578
|
-
4. SQL
|
|
9821
|
+
4. SQL \u2192 embedded production metric on a customer dashboard (EMBEDDING, requires clientId) \u2192 prompt: sql-to-metric-migration (validate_metric_spec \u2192 create_or_update_metric_from_query dry_run \u2192 apply only with explicit production confirmation)
|
|
9822
|
+
5. SQL \u2192 unpublished workspace metric (INTERNAL ANALYTICS, no clientId, not attached to any dashboard) \u2192 prompt: sql-to-workspace-metric (discover_datasource_schema \u2192 create_workspace_metric dry_run \u2192 apply with explicit confirmation)
|
|
9579
9823
|
|
|
9580
9824
|
Production safety: OAuth may not be available yet. In service-token mode, writes are actor=system. For create_datamart and create_or_update_metric_from_query, dry_run is the default. Apply requires confirm="APPLY_TO_PRODUCTION" after the user reviews the plan. Widget validation and mutation tools must verify the resolved embed is tenant-scoped for the requested clientId; if not, they must refuse to touch it as a likely production/data-app dashboard. Do not apply from a vague request.
|
|
9581
9825
|
|