@databrainhq/mcp-server 0.2.5 → 0.2.6

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 CHANGED
@@ -4481,26 +4481,39 @@ function registerGenerateWidget(server, client) {
4481
4481
  const services = createServiceBundle(client);
4482
4482
  server.tool(
4483
4483
  "generate_widget",
4484
- "AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft (query, metadata) the caller can review and pass to `add_widget`. Mints a guest token internally.",
4484
+ "AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft the caller can review and pass to `add_widget`. Data app and datasource auto-resolve from `embedId` (preferred) or from the sole workspace defaults. `dataAppName`/`integrationId`/`integrationName` are technical overrides.",
4485
4485
  {
4486
4486
  clientId: z25.string().describe("Client/tenant identifier"),
4487
- dataAppName: z25.string().describe("Data app name (required to mint guest token)"),
4488
- integrationId: z25.string().describe("Datasource ID to generate the widget against. Use `list_datasources` to find."),
4489
- integrationName: z25.string().describe("Datasource name (matches integrationId)"),
4490
4487
  userInputs: z25.record(z25.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
4491
- embedId: z25.string().optional().describe("Optional \u2014 scopes the guest token to this embed.")
4488
+ embedId: z25.string().optional().describe("Optional. When provided, data app and datasource are inherited from the embed's existing widgets \u2014 the preferred path for CS/CX flows."),
4489
+ dataAppName: z25.string().optional().describe("Optional override. Auto-resolved from embedId, or from the sole data app when there is only one."),
4490
+ integrationId: z25.string().optional().describe("Optional datasource override (technical users only). Auto-resolved from the embed's widgets, or from the sole datasource when there is only one."),
4491
+ integrationName: z25.string().optional().describe("Optional datasource name override. Auto-resolved alongside integrationId.")
4492
4492
  },
4493
4493
  READ_ONLY,
4494
4494
  async (params) => {
4495
4495
  try {
4496
+ const dataAppName = await resolveDataAppName(
4497
+ services,
4498
+ params.dataAppName,
4499
+ params.embedId
4500
+ );
4501
+ if (!dataAppName.ok) return toolError(dataAppName.error);
4496
4502
  const guestToken = await services.widgetService.mintGuestToken(
4497
4503
  params.clientId,
4498
- params.dataAppName,
4504
+ dataAppName.value,
4499
4505
  params.embedId ? [params.embedId] : void 0
4500
4506
  );
4501
- const result = await services.widgetsApi.generateMetric(guestToken, {
4507
+ const datasource = await resolveDatasource(services, guestToken, {
4508
+ embedId: params.embedId,
4509
+ clientId: params.clientId,
4502
4510
  integrationId: params.integrationId,
4503
- integrationName: params.integrationName,
4511
+ integrationName: params.integrationName
4512
+ });
4513
+ if (!datasource.ok) return toolError(datasource.error);
4514
+ const result = await services.widgetsApi.generateMetric(guestToken, {
4515
+ integrationId: datasource.value.integrationId,
4516
+ integrationName: datasource.value.integrationName,
4504
4517
  userInputs: params.userInputs
4505
4518
  });
4506
4519
  const resultRec = result;
@@ -4512,6 +4525,10 @@ function registerGenerateWidget(server, client) {
4512
4525
  return toolSuccess(
4513
4526
  JSON.stringify({
4514
4527
  message: "Draft generated. Review the `query` and `result` fields below, then pass to `add_widget` (using raw `body` passthrough) to persist.",
4528
+ resolved: {
4529
+ dataAppName: dataAppName.value,
4530
+ integrationName: datasource.value.integrationName
4531
+ },
4515
4532
  draft: {
4516
4533
  query: resultRec.query ?? null,
4517
4534
  result: resultRec.result ?? null,
@@ -4528,6 +4545,89 @@ function registerGenerateWidget(server, client) {
4528
4545
  }
4529
4546
  );
4530
4547
  }
4548
+ async function resolveDataAppName(services, provided, embedId) {
4549
+ if (provided) return { ok: true, value: provided };
4550
+ if (embedId) {
4551
+ const ctx = await services.widgetService.resolveEmbedContext(embedId);
4552
+ if (ctx.dataAppName) return { ok: true, value: ctx.dataAppName };
4553
+ }
4554
+ const apps = await services.dataAppsApi.listDataApps();
4555
+ const normalized = apps.map((a) => services.discoveryService.normalizeDataApp(a)).filter((a) => a.name && a.name !== "Unnamed Data App");
4556
+ if (normalized.length === 1) return { ok: true, value: normalized[0].name };
4557
+ if (normalized.length === 0) {
4558
+ return {
4559
+ ok: false,
4560
+ error: "No data apps found in this workspace. Create one in the Databrain UI first."
4561
+ };
4562
+ }
4563
+ return {
4564
+ ok: false,
4565
+ error: `Multiple data apps exist (${normalized.map((a) => `"${a.name}"`).join(", ")}). Pass \`dataAppName\` explicitly, or pass \`embedId\` to auto-resolve.`
4566
+ };
4567
+ }
4568
+ async function resolveDatasource(services, guestToken, params) {
4569
+ if (params.integrationId && params.integrationName) {
4570
+ return {
4571
+ ok: true,
4572
+ value: {
4573
+ integrationId: params.integrationId,
4574
+ integrationName: params.integrationName
4575
+ }
4576
+ };
4577
+ }
4578
+ if (params.embedId) {
4579
+ const fromEmbed = await resolveDatasourceFromEmbed(
4580
+ services,
4581
+ guestToken,
4582
+ params.embedId,
4583
+ params.clientId
4584
+ );
4585
+ if (fromEmbed) {
4586
+ return {
4587
+ ok: true,
4588
+ value: {
4589
+ integrationId: params.integrationId || fromEmbed.integrationId,
4590
+ integrationName: params.integrationName || fromEmbed.integrationName
4591
+ }
4592
+ };
4593
+ }
4594
+ }
4595
+ const datasources = await services.datasourcesApi.listDatasources();
4596
+ const named = datasources.filter(
4597
+ (ds) => typeof ds.name === "string" && ds.name.trim().length > 0
4598
+ );
4599
+ if (named.length === 1) {
4600
+ const ds = named[0];
4601
+ const id = ds._id || ds.id || "";
4602
+ const name = ds.name;
4603
+ if (id && name) {
4604
+ return { ok: true, value: { integrationId: id, integrationName: name } };
4605
+ }
4606
+ }
4607
+ if (named.length === 0) {
4608
+ return {
4609
+ ok: false,
4610
+ error: "No datasources found in this workspace. Connect one in the Databrain UI first (technical setup)."
4611
+ };
4612
+ }
4613
+ return {
4614
+ ok: false,
4615
+ error: `Could not auto-resolve a datasource. Options: ${named.map((d) => `"${d.name}"`).join(", ")}. Either pass \`embedId\` (to inherit from the embed's existing widgets), or pass \`integrationId\` + \`integrationName\` explicitly.`
4616
+ };
4617
+ }
4618
+ async function resolveDatasourceFromEmbed(services, guestToken, embedId, clientId) {
4619
+ const widgets = await services.widgetsApi.listWidgets(embedId, clientId);
4620
+ const first = widgets.find((w) => typeof w.metricId === "string" && w.metricId.length > 0);
4621
+ if (!first || !first.metricId) return null;
4622
+ const response = await services.widgetsApi.fetchEmbeddedMetric(guestToken, first.metricId);
4623
+ const metric = response.externalMetric;
4624
+ if (!metric || typeof metric !== "object") return null;
4625
+ const rec = metric;
4626
+ const integrationId = typeof rec.companyIntegrationId === "string" ? rec.companyIntegrationId : null;
4627
+ const integrationName = typeof rec.integrationName === "string" ? rec.integrationName : null;
4628
+ if (!integrationId || !integrationName) return null;
4629
+ return { integrationId, integrationName };
4630
+ }
4531
4631
 
4532
4632
  // src/tools/registry.ts
4533
4633
  function registerAllTools(server, client) {
@@ -7581,21 +7681,23 @@ STOP and present the list: \`{metricId, name, isPublished, createdByUser}\` per
7581
7681
  ASK: "What would you like to change? (add / modify / remove)" \u2014 or more specifically, "Which widgets should I add, update, or remove?"
7582
7682
 
7583
7683
  **Step 3a \u2014 ADD a widget**
7584
- If the user wants to add a widget, STOP and ASK which sourcing mode:
7684
+ This is a business-user flow. NEVER ask the user for a datasource, \`companyIntegrationId\`, \`integrationId\`, or raw SQL unless they explicitly say they want to write SQL. Prefer the two paths below in order.
7585
7685
 
7586
- 1. **Clone from template** (cheapest, most common):
7587
- - ASK for the source \`templateMetricId\` \u2014 or call \`list_widgets\` on the template embed / another customer's embed to help pick.
7588
- - Call \`add_widget({ embedId, clientId, templateMetricId, name? })\`. The tool inherits query, chartOptions, and datasource from the template.
7686
+ 1. **Clone an existing chart** (default \u2014 try this first):
7687
+ - ASK: "Which existing chart should I copy from?" Call \`list_widgets\` on this embed, or on the template embed / another customer's embed, and present the widgets by name. Let the user pick by name.
7688
+ - Call \`add_widget({ embedId, clientId, templateMetricId, name? })\` using the picked widget's \`metricId\`. The tool inherits the query, chart config, and datasource automatically.
7589
7689
 
7590
- 2. **Fresh spec** (SQL + chart config):
7591
- - ASK for: \`name\`, \`query\` (SQL), \`chartOptions\` (chart type + config), \`companyIntegrationId\` (datasource ID \u2014 use \`list_datasources\` if needed).
7592
- - Call \`add_widget({ embedId, clientId, name, query, chartOptions, companyIntegrationId, ... })\`.
7593
-
7594
- 3. **AI generate** (NL prompt):
7595
- - ASK for a prompt describing the widget (e.g., "calls per day over last 30 days") and the \`integrationId\` / \`integrationName\` to query against.
7596
- - Call \`generate_widget({ clientId, dataAppName, integrationId, integrationName, userInputs: { prompt: "..." } })\`.
7690
+ 2. **Describe the chart in plain English** (AI generate \u2014 use if no suitable template exists):
7691
+ - ASK: "Describe the chart you want" (e.g., "calls per day over last 30 days").
7692
+ - Call \`generate_widget({ embedId, clientId, userInputs: { prompt: "..." } })\` \u2014 data app and datasource auto-resolve from the embed's existing widgets. Do NOT ask the user for a datasource or data app.
7693
+ - \`embedId\` is optional on \`generate_widget\` (the tool will fall back to the sole data app / sole datasource if absent), but pass it when you have one \u2014 resolution is more reliable.
7597
7694
  - Review the returned \`draft\` with the user. If they approve, call \`add_widget({ embedId, clientId, body: <draft + externalDashboardIds> })\` to persist.
7598
7695
  - If they want changes, re-run \`generate_widget\` with a refined prompt.
7696
+ - If \`generate_widget\` reports "Multiple data apps" or "Could not auto-resolve a datasource", either pass \`embedId\` or ask the user which data app/chart to mirror \u2014 still avoid technical datasource IDs.
7697
+
7698
+ **Advanced \u2014 write SQL directly** (technical users only; ONLY if the user explicitly asks to write SQL):
7699
+ - ASK for: \`name\`, \`query\` (SQL), \`chartOptions\`, \`companyIntegrationId\` (use \`list_datasources\`).
7700
+ - Call \`add_widget({ embedId, clientId, name, query, chartOptions, companyIntegrationId, ... })\`.
7599
7701
 
7600
7702
  Show the user the new widget's \`externalMetricId\` and \`metricId\` after each add.
7601
7703