@databrainhq/mcp-server 0.2.16 → 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 +181 -10
- 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;
|
|
@@ -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
|
|
|
@@ -5868,7 +5941,7 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
5868
5941
|
const services = createServiceBundle(client);
|
|
5869
5942
|
server.tool(
|
|
5870
5943
|
"create_or_update_metric_from_query",
|
|
5871
|
-
"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.",
|
|
5872
5945
|
{
|
|
5873
5946
|
mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
|
|
5874
5947
|
confirm: z30.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
@@ -6051,21 +6124,25 @@ var modeSchema2 = z31.enum(["dry_run", "apply"]).default("dry_run");
|
|
|
6051
6124
|
function registerCreateWorkspaceMetric(server, client) {
|
|
6052
6125
|
server.tool(
|
|
6053
6126
|
"create_workspace_metric",
|
|
6054
|
-
"Create a workspace-level metric from validated SQL
|
|
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.",
|
|
6055
6128
|
{
|
|
6056
6129
|
mode: modeSchema2.describe(
|
|
6057
|
-
"dry_run validates the SQL only (no write); apply creates the metric after confirmation"
|
|
6130
|
+
"dry_run validates the SQL only (no write); apply creates or updates the metric after confirmation"
|
|
6058
6131
|
),
|
|
6059
6132
|
confirm: z31.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6060
6133
|
workspaceName: z31.string().describe("Target workspace name"),
|
|
6061
6134
|
name: z31.string().describe("Metric display name"),
|
|
6062
6135
|
query: z31.string().describe("SQL query to validate and store"),
|
|
6063
|
-
metricId: z31.string().optional().describe(
|
|
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
|
+
),
|
|
6064
6139
|
description: z31.string().optional().describe("Metric description"),
|
|
6065
6140
|
datasourceId: z31.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
|
|
6066
|
-
isPublished: z31.boolean().optional().describe(
|
|
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
|
+
)
|
|
6067
6144
|
},
|
|
6068
|
-
|
|
6145
|
+
WRITE_IDEMPOTENT,
|
|
6069
6146
|
async (params) => {
|
|
6070
6147
|
try {
|
|
6071
6148
|
const mode = params.mode || "dry_run";
|
|
@@ -6102,7 +6179,7 @@ function registerCreateWorkspaceMetric(server, client) {
|
|
|
6102
6179
|
mode,
|
|
6103
6180
|
safety: productionSafetyNote(params.workspaceName),
|
|
6104
6181
|
result,
|
|
6105
|
-
nextStep: isApply ? "
|
|
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.'
|
|
6106
6183
|
},
|
|
6107
6184
|
null,
|
|
6108
6185
|
2
|
|
@@ -6341,7 +6418,30 @@ function registerWhoami(server, client) {
|
|
|
6341
6418
|
}
|
|
6342
6419
|
|
|
6343
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
|
+
}
|
|
6344
6443
|
function registerAllTools(server, client) {
|
|
6444
|
+
instrumentToolRegistration(server);
|
|
6345
6445
|
registerWhoami(server, client);
|
|
6346
6446
|
registerListDatasources(server, client);
|
|
6347
6447
|
registerDiscoverDatasourceSchema(server, client);
|
|
@@ -9565,6 +9665,67 @@ list_scheduled_reports({ clientId, dashboardName, workspaceName })
|
|
|
9565
9665
|
\`\`\`
|
|
9566
9666
|
Clarify that this lists configured schedules only. Delivery success/failure history is not yet persisted in the MCP/API surface.`;
|
|
9567
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
|
+
|
|
9568
9729
|
// src/prompts/registry.ts
|
|
9569
9730
|
var prompts = [
|
|
9570
9731
|
{
|
|
@@ -9601,6 +9762,11 @@ var prompts = [
|
|
|
9601
9762
|
name: "sql-to-metric-migration",
|
|
9602
9763
|
description: "Production-safe SQL-to-metric migration workflow: validate, dry-run, then apply with explicit confirmation.",
|
|
9603
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
|
|
9604
9770
|
}
|
|
9605
9771
|
];
|
|
9606
9772
|
function registerAllPrompts(server) {
|
|
@@ -9637,18 +9803,23 @@ function createServer(config) {
|
|
|
9637
9803
|
{
|
|
9638
9804
|
name: "databrain",
|
|
9639
9805
|
version: MCP_SERVER_VERSION,
|
|
9640
|
-
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."
|
|
9641
9807
|
},
|
|
9642
9808
|
{
|
|
9643
9809
|
instructions: operatorBlurb + `Databrain uses two token types \u2014 never confuse them:
|
|
9644
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}
|
|
9645
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}
|
|
9646
9812
|
|
|
9647
|
-
|
|
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:
|
|
9648
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)
|
|
9649
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)
|
|
9650
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)
|
|
9651
|
-
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)
|
|
9652
9823
|
|
|
9653
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.
|
|
9654
9825
|
|