@databrainhq/mcp-server 0.2.12 → 0.2.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/README.md +34 -17
- package/dist/index.js +174 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @databrainhq/mcp-server
|
|
2
2
|
|
|
3
|
-
Add embedded analytics to your app using natural language. Connect this MCP server to any AI assistant and describe what you want — it handles the
|
|
3
|
+
Add embedded analytics to your app using natural language. Connect this MCP server to any AI assistant and describe what you want — it handles the setup steps for you.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
"What was total revenue last month?"
|
|
7
7
|
"Embed my sales dashboard in my React app"
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
Works with Claude, Cursor, Windsurf, and any MCP-compatible client.
|
|
10
|
+
Works with Codex, Claude, Cursor, Windsurf, and any MCP-compatible client.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -23,6 +23,24 @@ Works with Claude, Cursor, Windsurf, and any MCP-compatible client.
|
|
|
23
23
|
|
|
24
24
|
> **Self-hosted?** Also set `DATABRAIN_API_URL` in the `env` block — see [Self-hosted Databrain](#self-hosted-databrain) below.
|
|
25
25
|
|
|
26
|
+
<details>
|
|
27
|
+
<summary><b>Codex</b></summary>
|
|
28
|
+
|
|
29
|
+
Add the MCP server with your service token:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
codex mcp add databrain --env DATABRAIN_SERVICE_TOKEN=YOUR_SERVICE_TOKEN -- npx @databrainhq/mcp-server
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Verify the server is configured:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
codex mcp list
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Start a new Codex session after adding the server.
|
|
42
|
+
</details>
|
|
43
|
+
|
|
26
44
|
<details>
|
|
27
45
|
<summary><b>Cursor</b></summary>
|
|
28
46
|
|
|
@@ -119,7 +137,8 @@ Running Databrain on your own infrastructure? Point the MCP server at your insta
|
|
|
119
137
|
"args": ["@databrainhq/mcp-server"],
|
|
120
138
|
"env": {
|
|
121
139
|
"DATABRAIN_SERVICE_TOKEN": "<your-service-token>",
|
|
122
|
-
"DATABRAIN_API_URL": "https://api.your-databrain.example.com"
|
|
140
|
+
"DATABRAIN_API_URL": "https://api.your-databrain.example.com",
|
|
141
|
+
"DATABRAIN_DEMO_DOMAIN": "https://demo.your-databrain.example.com"
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
144
|
}
|
|
@@ -129,7 +148,7 @@ Running Databrain on your own infrastructure? Point the MCP server at your insta
|
|
|
129
148
|
Checklist:
|
|
130
149
|
- `DATABRAIN_API_URL` must be the **API origin** for your self-hosted instance (not the dashboard URL). Typically `https://api.<your-domain>`.
|
|
131
150
|
- The token must be minted from that same self-hosted instance — tokens are not portable across instances.
|
|
132
|
-
- Use `DATABRAIN_SERVICE_TOKEN` (Settings → Service Tokens) for org-level access.
|
|
151
|
+
- Use `DATABRAIN_SERVICE_TOKEN` (Settings → Service Tokens) for org-level access.
|
|
133
152
|
- The MCP client process must be able to reach the URL (VPN, firewall, self-signed certs all apply here).
|
|
134
153
|
- Scaffolded frontend code (React, Next.js, Vue, etc.) reads the same `DATABRAIN_API_URL` for server-side guest-token generation, so the variable flows through to your app.
|
|
135
154
|
|
|
@@ -229,11 +248,11 @@ Let end-users build their own metrics with drag-and-drop or AI-powered chat.
|
|
|
229
248
|
|
|
230
249
|
## Environment Variables
|
|
231
250
|
|
|
232
|
-
At least one of `DATABRAIN_SERVICE_TOKEN` or `DATABRAIN_API_TOKEN` must be set.
|
|
251
|
+
At least one of `DATABRAIN_SERVICE_TOKEN` or `DATABRAIN_API_TOKEN` must be set. Use a service token for full setup and management flows; use an API token only when you are operating inside one data app.
|
|
233
252
|
|
|
234
253
|
| Variable | Required | Default | Description |
|
|
235
254
|
|----------|----------|---------|-------------|
|
|
236
|
-
| `DATABRAIN_SERVICE_TOKEN` | One of two† | — | Org-level token from Settings → Service Tokens. Powers
|
|
255
|
+
| `DATABRAIN_SERVICE_TOKEN` | One of two† | — | Org-level token from Settings → Service Tokens. Powers discovery, setup, data app management, semantic layer, and production-safe datamart operations. |
|
|
237
256
|
| `DATABRAIN_API_TOKEN` | One of two† | — | Per-data-app token. Created automatically during setup via `create_api_token`, or set manually if you already have one. Sufficient for embedding operations on a single data app. |
|
|
238
257
|
| `DATABRAIN_API_URL` | **Required for self-hosted** | `https://api.usedatabrain.com` | API base URL. Set to your instance's API origin (e.g. `https://api.your-databrain.example.com`). |
|
|
239
258
|
| `DATABRAIN_DEMO_DOMAIN` | Optional | `https://demo.usedatabrain.com` | Base host for `get_demo_link` URLs. Override for self-hosted instances or custom preview domains (e.g. `https://demo.your-databrain.example.com`). |
|
|
@@ -297,9 +316,9 @@ Prompts are guided workflows your AI assistant can follow. Ask for them by name
|
|
|
297
316
|
| `create_embed` | Create an embed (standard or multi-tenant with `clientId`) |
|
|
298
317
|
| `list_embeds` | List embed configurations (includes `distinctClientIds` for multi-tenant client discovery) |
|
|
299
318
|
| `get_embed_details` | Get full embed details by ID |
|
|
300
|
-
| `update_embed` | Update embed config — access settings, options, filters, i18n
|
|
319
|
+
| `update_embed` | Update embed config — access settings, options, filters, i18n, and advanced embed options |
|
|
301
320
|
| `customize_embed_theme` | Presets, colors, fonts, cards, chart appearance, responsive layout |
|
|
302
|
-
| `get_demo_link` | Get a shareable demo URL for a `clientId` — same link the UI's "Share Demo Link" generates
|
|
321
|
+
| `get_demo_link` | Get a shareable demo URL for a `clientId` — same link the UI's "Share Demo Link" generates |
|
|
303
322
|
</details>
|
|
304
323
|
|
|
305
324
|
<details>
|
|
@@ -319,7 +338,7 @@ Prompts are guided workflows your AI assistant can follow. Ask for them by name
|
|
|
319
338
|
| `ask_question` | Ask a natural language question — gets converted to SQL, returns data + chart suggestion |
|
|
320
339
|
| `get_semantic_layer` | Inspect semantic layer metadata (table/column descriptions, synonyms, completion score) |
|
|
321
340
|
| `update_semantic_layer` | Improve semantic layer quality (add descriptions, synonyms, example questions) |
|
|
322
|
-
| `start_semantic_layer_generation` | Start
|
|
341
|
+
| `start_semantic_layer_generation` | Start semantic-layer generation for a datamart |
|
|
323
342
|
| `get_semantic_layer_generation_status` | Check semantic-layer generation progress |
|
|
324
343
|
</details>
|
|
325
344
|
|
|
@@ -332,12 +351,12 @@ Prompts are guided workflows your AI assistant can follow. Ask for them by name
|
|
|
332
351
|
| `list_widgets` | List widgets on an embed for a specific client |
|
|
333
352
|
| `validate_metric_spec` | Validate a SQL-backed metric target without saving |
|
|
334
353
|
| `create_or_update_metric_from_query` | Dry-run or apply a SQL-backed metric create/update |
|
|
335
|
-
| `add_widget` | Add a widget — clone from a template (`templateMetricId`), supply a
|
|
354
|
+
| `add_widget` | Add a widget — clone from a template (`templateMetricId`), supply a metric configuration, or use a generated draft |
|
|
336
355
|
| `update_widget` | Patch a widget's fields (name, query, chartOptions, isPublished, etc.) |
|
|
337
356
|
| `remove_widget` | Per-client soft delete — hides the widget for one client only |
|
|
338
357
|
| `generate_widget` | AI-generate a widget draft from a natural-language prompt. Returns a draft; pass to `add_widget` to persist |
|
|
339
358
|
|
|
340
|
-
|
|
359
|
+
Provide `clientId` and `dashboardName`; include `workspaceName` only if dashboard names are ambiguous. The tools resolve the required resources automatically.
|
|
341
360
|
Production writes through `create_or_update_metric_from_query` default to `dry_run`; `apply` requires `confirm: "APPLY_TO_PRODUCTION"`.
|
|
342
361
|
</details>
|
|
343
362
|
|
|
@@ -346,7 +365,7 @@ Production writes through `create_or_update_metric_from_query` default to `dry_r
|
|
|
346
365
|
|
|
347
366
|
| Tool | Description |
|
|
348
367
|
|------|-------------|
|
|
349
|
-
| `list_scheduled_reports` | List configured scheduled reports for a dashboard/client scope
|
|
368
|
+
| `list_scheduled_reports` | List configured scheduled reports for a dashboard/client scope |
|
|
350
369
|
</details>
|
|
351
370
|
|
|
352
371
|
---
|
|
@@ -407,13 +426,11 @@ node --version # Should be v18.0.0 or higher
|
|
|
407
426
|
## How it works
|
|
408
427
|
|
|
409
428
|
```
|
|
410
|
-
Your AI Client
|
|
411
|
-
|
|
412
|
-
Cursor, package) dashboards,
|
|
413
|
-
etc.) embeds)
|
|
429
|
+
Your AI Client MCP Server Databrain API
|
|
430
|
+
(Codex, Claude, Cursor, etc.) ←→ (this package) ←→ (your data, dashboards, embeds)
|
|
414
431
|
```
|
|
415
432
|
|
|
416
|
-
The MCP server translates natural language requests into Databrain
|
|
433
|
+
The MCP server translates natural language requests into Databrain actions. It manages authentication (service tokens, API tokens, guest tokens), validates inputs, and provides guided workflows so you don't need to learn the Databrain API directly.
|
|
417
434
|
|
|
418
435
|
All tokens are managed automatically. You provide your service token once in the config — everything else is created and handled by the server during setup.
|
|
419
436
|
|
package/dist/index.js
CHANGED
|
@@ -646,7 +646,7 @@ var QueryApi = class {
|
|
|
646
646
|
async startSemanticLayerGeneration(datamartId) {
|
|
647
647
|
return this.client.request({
|
|
648
648
|
method: "POST",
|
|
649
|
-
path: `/
|
|
649
|
+
path: `/schema/datamart/${encodeURIComponent(datamartId)}/semantic-layer/auto-generate`,
|
|
650
650
|
auth: "service",
|
|
651
651
|
invalidateCache: ["semantic_layer_"]
|
|
652
652
|
});
|
|
@@ -654,7 +654,7 @@ var QueryApi = class {
|
|
|
654
654
|
async getSemanticLayerGenerationStatus(datamartId) {
|
|
655
655
|
return this.client.request({
|
|
656
656
|
method: "GET",
|
|
657
|
-
path: `/
|
|
657
|
+
path: `/schema/datamart/${encodeURIComponent(datamartId)}/semantic-layer/generation-status`,
|
|
658
658
|
auth: "service"
|
|
659
659
|
});
|
|
660
660
|
}
|
|
@@ -4798,7 +4798,7 @@ var columnSchema2 = z22.object({
|
|
|
4798
4798
|
"Identifier",
|
|
4799
4799
|
"Number",
|
|
4800
4800
|
"JSON"
|
|
4801
|
-
]).nullable().optional().describe('Semantic column type. Use "Identifier" for IDs/foreign keys, "Number" for numeric values, "String" for text. Omit for date columns.')
|
|
4801
|
+
]).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.')
|
|
4802
4802
|
});
|
|
4803
4803
|
var tableSchema2 = z22.object({
|
|
4804
4804
|
name: z22.string().describe("Table name (must exist in the datamart)"),
|
|
@@ -4811,7 +4811,33 @@ var tableSchema2 = z22.object({
|
|
|
4811
4811
|
),
|
|
4812
4812
|
columns: z22.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
|
|
4813
4813
|
});
|
|
4814
|
-
function
|
|
4814
|
+
function columnKey(tableName, columnName) {
|
|
4815
|
+
return `${tableName.toLowerCase()}\0${columnName.toLowerCase()}`;
|
|
4816
|
+
}
|
|
4817
|
+
function buildColumnDatatypeMap(semanticLayer) {
|
|
4818
|
+
const datatypes = /* @__PURE__ */ new Map();
|
|
4819
|
+
for (const table of semanticLayer.tables ?? []) {
|
|
4820
|
+
for (const column of table.columns ?? []) {
|
|
4821
|
+
if (column.datatype) {
|
|
4822
|
+
datatypes.set(columnKey(table.name, column.name), column.datatype);
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return datatypes;
|
|
4827
|
+
}
|
|
4828
|
+
function hasExplicitColumnType(tables) {
|
|
4829
|
+
return tables?.some(
|
|
4830
|
+
(table) => table.columns?.some(
|
|
4831
|
+
(column) => column.columnType !== void 0 && column.columnType !== null
|
|
4832
|
+
)
|
|
4833
|
+
) ?? false;
|
|
4834
|
+
}
|
|
4835
|
+
function shouldOmitColumnTypeForDatatype(datatype) {
|
|
4836
|
+
if (!datatype) return false;
|
|
4837
|
+
const normalized = datatype.trim().toLowerCase();
|
|
4838
|
+
return normalized === "boolean" || normalized === "bool" || normalized === "date" || normalized.startsWith("date(") || normalized.startsWith("datetime") || normalized.startsWith("timestamp") || normalized === "time" || normalized.startsWith("time(") || normalized.startsWith("time ");
|
|
4839
|
+
}
|
|
4840
|
+
function normalizeTablesForApi(tables, datatypesByColumn) {
|
|
4815
4841
|
if (!tables) return void 0;
|
|
4816
4842
|
return tables.map((table) => {
|
|
4817
4843
|
const normalized = { name: table.name };
|
|
@@ -4831,7 +4857,12 @@ function normalizeTablesForApi(tables) {
|
|
|
4831
4857
|
normalizedColumn.miscellaneousInfo = column.miscellaneousInfo;
|
|
4832
4858
|
}
|
|
4833
4859
|
if (column.columnType !== void 0 && column.columnType !== null) {
|
|
4834
|
-
|
|
4860
|
+
const datatype = datatypesByColumn?.get(
|
|
4861
|
+
columnKey(table.name, column.name)
|
|
4862
|
+
);
|
|
4863
|
+
if (!shouldOmitColumnTypeForDatatype(datatype)) {
|
|
4864
|
+
normalizedColumn.columnType = column.columnType;
|
|
4865
|
+
}
|
|
4835
4866
|
}
|
|
4836
4867
|
return normalizedColumn;
|
|
4837
4868
|
});
|
|
@@ -4874,7 +4905,10 @@ function registerUpdateSemanticLayer(server, client) {
|
|
|
4874
4905
|
});
|
|
4875
4906
|
if (resolution.kind !== "resolved") return resolution.response;
|
|
4876
4907
|
const datamartName = resolution.name;
|
|
4877
|
-
const
|
|
4908
|
+
const datatypesByColumn = hasExplicitColumnType(params.tables) ? buildColumnDatatypeMap(
|
|
4909
|
+
await services.queryApi.getSemanticLayer(datamartName)
|
|
4910
|
+
) : void 0;
|
|
4911
|
+
const tables = normalizeTablesForApi(params.tables, datatypesByColumn);
|
|
4878
4912
|
await services.queryApi.updateSemanticLayer({
|
|
4879
4913
|
datamartName,
|
|
4880
4914
|
tables,
|
|
@@ -4911,10 +4945,108 @@ You can now:
|
|
|
4911
4945
|
|
|
4912
4946
|
// src/tools/query/semantic-generation.ts
|
|
4913
4947
|
import { z as z23 } from "zod";
|
|
4948
|
+
function nonEmptyString(value) {
|
|
4949
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
4950
|
+
}
|
|
4914
4951
|
function datamartIdFrom(datamart) {
|
|
4915
|
-
return
|
|
4952
|
+
return nonEmptyString(datamart.id) || nonEmptyString(datamart._id) || nonEmptyString(datamart.datamartId) || nonEmptyString(datamart.dataMartId) || null;
|
|
4953
|
+
}
|
|
4954
|
+
function nestedDatamartFromWorkspace(workspace) {
|
|
4955
|
+
const nested = workspace.datamart;
|
|
4956
|
+
return nested && typeof nested === "object" && !Array.isArray(nested) ? nested : null;
|
|
4957
|
+
}
|
|
4958
|
+
function workspaceDatamartIdFrom(workspace) {
|
|
4959
|
+
const nested = nestedDatamartFromWorkspace(workspace);
|
|
4960
|
+
return nonEmptyString(workspace.dataMartId) || nonEmptyString(workspace.datamartId) || (nested ? datamartIdFrom(nested) : null);
|
|
4961
|
+
}
|
|
4962
|
+
function workspaceDatamartNameFrom(workspace) {
|
|
4963
|
+
const nested = nestedDatamartFromWorkspace(workspace);
|
|
4964
|
+
return (nested ? nonEmptyString(nested.name) || nonEmptyString(nested.datamartName) : null) || nonEmptyString(workspace.datamartName) || null;
|
|
4965
|
+
}
|
|
4966
|
+
async function resolveDatamartFromWorkspace(params) {
|
|
4967
|
+
const services = createServiceBundle(params.client);
|
|
4968
|
+
const workspaces = await services.workspacesApi.listWorkspaces();
|
|
4969
|
+
const resolution = resolveResource({
|
|
4970
|
+
items: workspaces,
|
|
4971
|
+
name: params.workspaceName,
|
|
4972
|
+
getName: (ws) => ws.name,
|
|
4973
|
+
formatItem: formatWorkspace,
|
|
4974
|
+
resourceType: "workspace",
|
|
4975
|
+
toolName: params.toolName
|
|
4976
|
+
});
|
|
4977
|
+
if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
|
|
4978
|
+
const selected = resolution.selected;
|
|
4979
|
+
const datamartId = workspaceDatamartIdFrom(selected);
|
|
4980
|
+
const workspaceDatamartName = workspaceDatamartNameFrom(selected);
|
|
4981
|
+
if (!datamartId) {
|
|
4982
|
+
return {
|
|
4983
|
+
ok: false,
|
|
4984
|
+
response: toolError(
|
|
4985
|
+
`Workspace "${resolution.name}" does not expose a linked dataMartId/datamart.id. Pass datamartId directly or choose a workspace linked to a datamart.`
|
|
4986
|
+
)
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
if (params.datamartName && workspaceDatamartName && workspaceDatamartName.toLowerCase() !== params.datamartName.toLowerCase()) {
|
|
4990
|
+
return {
|
|
4991
|
+
ok: false,
|
|
4992
|
+
response: toolError(
|
|
4993
|
+
`Workspace "${resolution.name}" is linked to datamart "${workspaceDatamartName}", not "${params.datamartName}". Use the linked datamart name or choose another workspace.`
|
|
4994
|
+
)
|
|
4995
|
+
};
|
|
4996
|
+
}
|
|
4997
|
+
return {
|
|
4998
|
+
ok: true,
|
|
4999
|
+
datamartName: workspaceDatamartName || params.datamartName || datamartId,
|
|
5000
|
+
datamartId,
|
|
5001
|
+
workspaceName: resolution.name
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
async function findDatamartIdInLinkedWorkspaces(params) {
|
|
5005
|
+
const services = createServiceBundle(params.client);
|
|
5006
|
+
const workspaces = await services.workspacesApi.listWorkspaces();
|
|
5007
|
+
const matches = workspaces.map((workspace) => {
|
|
5008
|
+
const record = workspace;
|
|
5009
|
+
return {
|
|
5010
|
+
name: nonEmptyString(record.name),
|
|
5011
|
+
datamartName: workspaceDatamartNameFrom(record),
|
|
5012
|
+
datamartId: workspaceDatamartIdFrom(record)
|
|
5013
|
+
};
|
|
5014
|
+
}).filter(
|
|
5015
|
+
(workspace) => workspace.datamartName?.toLowerCase() === params.datamartName.toLowerCase() && workspace.datamartId
|
|
5016
|
+
);
|
|
5017
|
+
const uniqueIds = [...new Set(matches.map((workspace) => workspace.datamartId))];
|
|
5018
|
+
if (uniqueIds.length === 1 && uniqueIds[0]) {
|
|
5019
|
+
return {
|
|
5020
|
+
kind: "resolved",
|
|
5021
|
+
datamartId: uniqueIds[0],
|
|
5022
|
+
workspaceName: matches.find((workspace) => workspace.datamartId === uniqueIds[0])?.name || void 0
|
|
5023
|
+
};
|
|
5024
|
+
}
|
|
5025
|
+
if (uniqueIds.length > 1) {
|
|
5026
|
+
return {
|
|
5027
|
+
kind: "ambiguous",
|
|
5028
|
+
workspaceNames: matches.map((workspace) => workspace.name).filter((name) => !!name)
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
return { kind: "none" };
|
|
4916
5032
|
}
|
|
4917
5033
|
async function resolveDatamart(params) {
|
|
5034
|
+
if (params.datamartId) {
|
|
5035
|
+
return {
|
|
5036
|
+
ok: true,
|
|
5037
|
+
datamartName: params.datamartName || params.datamartId,
|
|
5038
|
+
datamartId: params.datamartId,
|
|
5039
|
+
workspaceName: params.workspaceName
|
|
5040
|
+
};
|
|
5041
|
+
}
|
|
5042
|
+
if (params.workspaceName) {
|
|
5043
|
+
return resolveDatamartFromWorkspace({
|
|
5044
|
+
client: params.client,
|
|
5045
|
+
workspaceName: params.workspaceName,
|
|
5046
|
+
datamartName: params.datamartName,
|
|
5047
|
+
toolName: params.toolName
|
|
5048
|
+
});
|
|
5049
|
+
}
|
|
4918
5050
|
const services = createServiceBundle(params.client);
|
|
4919
5051
|
const datamarts = await services.datamartsApi.listDatamarts();
|
|
4920
5052
|
const resolution = resolveResource({
|
|
@@ -4928,10 +5060,30 @@ async function resolveDatamart(params) {
|
|
|
4928
5060
|
if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
|
|
4929
5061
|
const datamartId = datamartIdFrom(resolution.selected);
|
|
4930
5062
|
if (!datamartId) {
|
|
5063
|
+
const workspaceMatch = await findDatamartIdInLinkedWorkspaces({
|
|
5064
|
+
client: params.client,
|
|
5065
|
+
datamartName: resolution.name
|
|
5066
|
+
});
|
|
5067
|
+
if (workspaceMatch.kind === "resolved") {
|
|
5068
|
+
return {
|
|
5069
|
+
ok: true,
|
|
5070
|
+
datamartName: resolution.name,
|
|
5071
|
+
datamartId: workspaceMatch.datamartId,
|
|
5072
|
+
workspaceName: workspaceMatch.workspaceName
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
if (workspaceMatch.kind === "ambiguous") {
|
|
5076
|
+
return {
|
|
5077
|
+
ok: false,
|
|
5078
|
+
response: toolError(
|
|
5079
|
+
`Datamart "${resolution.name}" is linked from multiple workspaces with different IDs. Call ${params.toolName} again with workspaceName. Matching workspaces: ${workspaceMatch.workspaceNames.join(", ")}`
|
|
5080
|
+
)
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
4931
5083
|
return {
|
|
4932
5084
|
ok: false,
|
|
4933
5085
|
response: toolError(
|
|
4934
|
-
`Datamart "${resolution.name}" resolved by name but no id/_id/datamartId was returned.
|
|
5086
|
+
`Datamart "${resolution.name}" resolved by name but no id/_id/datamartId/dataMartId was returned and no linked workspace exposed its ID. Pass workspaceName for a workspace linked to this datamart, pass datamartId directly, or update the datamart list API to include IDs.`
|
|
4935
5087
|
)
|
|
4936
5088
|
};
|
|
4937
5089
|
}
|
|
@@ -4947,14 +5099,18 @@ function registerStartSemanticLayerGeneration(server, client) {
|
|
|
4947
5099
|
"start_semantic_layer_generation",
|
|
4948
5100
|
"Start backend semantic-layer auto-generation for a datamart. Returns an async job id to poll.",
|
|
4949
5101
|
{
|
|
4950
|
-
datamartName: z23.string().optional().describe("Datamart name. Omit to list available datamarts")
|
|
5102
|
+
datamartName: z23.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
5103
|
+
workspaceName: z23.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
5104
|
+
datamartId: z23.string().optional().describe("Datamart UUID. Use only when already known.")
|
|
4951
5105
|
},
|
|
4952
5106
|
WRITE_CREATE,
|
|
4953
5107
|
async (params) => {
|
|
4954
5108
|
try {
|
|
4955
5109
|
const resolved = await resolveDatamart({
|
|
4956
5110
|
client,
|
|
5111
|
+
datamartId: params.datamartId,
|
|
4957
5112
|
datamartName: params.datamartName,
|
|
5113
|
+
workspaceName: params.workspaceName,
|
|
4958
5114
|
toolName: "start_semantic_layer_generation"
|
|
4959
5115
|
});
|
|
4960
5116
|
if (!resolved.ok) return resolved.response;
|
|
@@ -4966,8 +5122,9 @@ function registerStartSemanticLayerGeneration(server, client) {
|
|
|
4966
5122
|
{
|
|
4967
5123
|
datamartName: resolved.datamartName,
|
|
4968
5124
|
datamartId: resolved.datamartId,
|
|
5125
|
+
workspaceName: resolved.workspaceName,
|
|
4969
5126
|
...result,
|
|
4970
|
-
nextStep: "Call get_semantic_layer_generation_status with the same datamartName until status is complete or failed."
|
|
5127
|
+
nextStep: "Call get_semantic_layer_generation_status with the same datamartName or workspaceName until status is complete or failed."
|
|
4971
5128
|
},
|
|
4972
5129
|
null,
|
|
4973
5130
|
2
|
|
@@ -4986,14 +5143,18 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
4986
5143
|
"get_semantic_layer_generation_status",
|
|
4987
5144
|
"Get semantic-layer auto-generation job status for a datamart.",
|
|
4988
5145
|
{
|
|
4989
|
-
datamartName: z23.string().optional().describe("Datamart name. Omit to list available datamarts")
|
|
5146
|
+
datamartName: z23.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
5147
|
+
workspaceName: z23.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
5148
|
+
datamartId: z23.string().optional().describe("Datamart UUID. Use only when already known.")
|
|
4990
5149
|
},
|
|
4991
5150
|
READ_ONLY,
|
|
4992
5151
|
async (params) => {
|
|
4993
5152
|
try {
|
|
4994
5153
|
const resolved = await resolveDatamart({
|
|
4995
5154
|
client,
|
|
5155
|
+
datamartId: params.datamartId,
|
|
4996
5156
|
datamartName: params.datamartName,
|
|
5157
|
+
workspaceName: params.workspaceName,
|
|
4997
5158
|
toolName: "get_semantic_layer_generation_status"
|
|
4998
5159
|
});
|
|
4999
5160
|
if (!resolved.ok) return resolved.response;
|
|
@@ -5005,6 +5166,7 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
5005
5166
|
{
|
|
5006
5167
|
datamartName: resolved.datamartName,
|
|
5007
5168
|
datamartId: resolved.datamartId,
|
|
5169
|
+
workspaceName: resolved.workspaceName,
|
|
5008
5170
|
...result
|
|
5009
5171
|
},
|
|
5010
5172
|
null,
|
|
@@ -8819,7 +8981,7 @@ Guidelines for generating descriptions:
|
|
|
8819
8981
|
- **Table descriptions:** Explain what the table contains in 1-2 sentences. Example: \`orders\` \u2192 "Contains customer purchase orders with line items, totals, and fulfillment status."
|
|
8820
8982
|
- **Column descriptions:** Explain the business meaning. Example: \`total_amount\` \u2192 "Total order value in the customer's local currency, including tax and shipping."
|
|
8821
8983
|
- **Synonyms:** Add common alternative names users might say. Example: \`total_amount\` \u2192 ["revenue", "order total", "sales amount"]
|
|
8822
|
-
- **Column types:**
|
|
8984
|
+
- **Column types:** Only use supported semantic types: "String", "Long String", "ENUM", "Mapper", "Range", "Expression", "Identifier", "Number", or "JSON". Omit \`columnType\` for date and boolean database columns.
|
|
8823
8985
|
|
|
8824
8986
|
Ask the user if they want to review and refine the descriptions before applying, or if you should go ahead and apply them directly.
|
|
8825
8987
|
|