@databrainhq/mcp-server 0.3.0-rc.10 → 0.3.0-rc.11

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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/utils/analytics.ts"],"sourcesContent":["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './utils/config.js';\nimport { createServer } from './server.js';\nimport { setupAnalytics } from './utils/analytics.js';\nimport { logger } from './utils/logger.js';\nimport { MCP_SERVER_VERSION } from './version.js';\n\nfunction banner(msg: string): void {\n process.stderr.write(`[databrain] ${msg}\\n`);\n}\n\nasync function main(): Promise<void> {\n try {\n if (process.argv.includes('--http')) {\n const { startHosted } = await import('./hosted/main.js');\n await startHosted();\n return;\n }\n const config = loadConfig();\n const server = createServer(config);\n const posthog = setupAnalytics(server, config);\n const transport = new StdioServerTransport();\n\n let shuttingDown = false;\n const flushAnalytics = async (): Promise<void> => {\n if (shuttingDown || !posthog) return;\n shuttingDown = true;\n await posthog.shutdown();\n };\n // StdioServerTransport never observes stdin ending, so the MCP client\n // disconnecting drains the event loop and exits the process without ever\n // calling transport/server onclose. `beforeExit` is what actually fires\n // in that path; posthog-node's flush timers are unref'd so they don't\n // keep the process alive on their own.\n process.on('beforeExit', () => void flushAnalytics());\n process.on('SIGINT', () => void flushAnalytics().then(() => process.exit(0)));\n process.on('SIGTERM', () => void flushAnalytics().then(() => process.exit(0)));\n\n await server.connect(transport);\n banner(`v${MCP_SERVER_VERSION} ready`);\n } catch (error) {\n // An Error serializes to {} in JSON, which left a self-hosted operator with no\n // reason. Config errors name the setting, never its value; the logger also\n // scrubs registered secrets.\n logger.error(`Failed to start server: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n}\n\nmain();\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { DatabrainClient } from './client/databrain-client.js';\nimport type { Config } from './utils/config.js';\nimport { registerAllTools } from './tools/registry.js';\nimport { registerAllResources } from './resources/registry.js';\nimport { registerAllPrompts } from './prompts/registry.js';\nimport { MCP_SERVER_VERSION } from './version.js';\n\nexport function createServer(config: Config): McpServer {\n const client = new DatabrainClient(config);\n const auth = client.getActiveAuth();\n let operatorBlurb: string;\n if (auth.source === 'oauth-client-credentials') {\n operatorBlurb =\n 'OPERATOR IDENTITY: OAuth client-credentials mode is configured for org-level calls. Tokens identify a service application, not a human operator. The backend must validate the token and map its issuer/subject to an authorized principal. Configuration alone does not prove backend acceptance. Do NOT ask the user for credentials or tokens.\\n\\n';\n } else if (auth.source === 'service-token') {\n operatorBlurb =\n 'OPERATOR IDENTITY: service-token mode. Calls are authenticated at org level — actions in the audit log are tagged actor=system, not a specific user. Per-user identity binding will be added when the OAuth-based login flow ships. Do NOT ask the user for credentials or tokens.\\n\\n';\n } else if (auth.source === 'api-token') {\n operatorBlurb =\n 'OPERATOR IDENTITY: API-token-only mode. A per-data-app API token is configured, but DATABRAIN_SERVICE_TOKEN is not. Embedding operations can use the configured API token, but org-level setup tools require DATABRAIN_SERVICE_TOKEN in the MCP config.\\n\\n';\n } else {\n operatorBlurb =\n 'OPERATOR IDENTITY: no usable token is configured. The MCP server normally refuses this config at startup. Ask the user to add DATABRAIN_SERVICE_TOKEN or DATABRAIN_API_TOKEN to the MCP config before proceeding.\\n\\n';\n }\n\n const serviceTokenStatus = config.oauth\n ? 'OAuth client credentials are configured for service-level operations. A configured DATABRAIN_SERVICE_TOKEN is used only if token acquisition fails.'\n : config.serviceToken\n ? 'This MCP server has DATABRAIN_SERVICE_TOKEN configured; never ask the user for it.'\n : 'DATABRAIN_SERVICE_TOKEN is not configured. Ask the user to add it to the MCP config only if org-level setup is needed.';\n const apiTokenStatus = client.hasApiToken()\n ? 'This MCP server has a per-data-app API token configured.'\n : config.oauth\n ? 'Created/rotated via create_api_token or rotate_api_token using configured OAuth service credentials, once the backend supports them.'\n : 'Created/rotated via create_api_token or rotate_api_token during setup when DATABRAIN_SERVICE_TOKEN is available.';\n\n const server = new McpServer(\n {\n name: 'databrain',\n version: MCP_SERVER_VERSION,\n description:\n 'Databrain MCP Server — build customer-facing embedded analytics AND run internal analytics on your own data. Supports embed setup, semantic-layer maintenance, natural-language querying, SQL→metric creation (embedded production metrics and unpublished internal workspace metrics), theming, filters, permissions, and guest token generation.',\n },\n {\n instructions:\n operatorBlurb +\n 'Databrain uses two token types — never confuse them:\\n' +\n `• 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, custom_chart_validate (server check), custom_chart_upload, list_custom_charts, publish_custom_chart. ${serviceTokenStatus}\\n` +\n `• 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}\\n\\n` +\n 'FIRST classify the request before calling tools — this avoids wasted turns:\\n' +\n '• (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.\\n' +\n '• (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 — it is NOT embedding.\\n\\n' +\n '5 core use cases — match the user\\'s scenario to the right prompt:\\n' +\n '1. Embed existing dashboard → prompt: embed-existing-dashboard (list_data_apps → create_api_token → list_dashboards → create_embed → generate_guest_token → generate_embed_code)\\n' +\n '2. Blank dashboard per client → prompt: embed-blank-dashboard (list_data_apps → list_datamarts → create_embed with clientId per client → generate_guest_token → generate_embed_code)\\n' +\n '3. Explore & query data → prompt: query-data (list_datamarts → get_semantic_layer → if empty, auto-generate descriptions with update_semantic_layer or use populate-semantic-layer prompt → ask_question)\\n' +\n '4. SQL → embedded production metric on a customer dashboard (EMBEDDING, requires clientId) → prompt: sql-to-metric-migration (validate_metric_spec → create_or_update_metric_from_query dry_run → apply only with explicit production confirmation)\\n' +\n '5. SQL → unpublished workspace metric (INTERNAL ANALYTICS, no clientId, not attached to any dashboard) → prompt: sql-to-workspace-metric (discover_datasource_schema → create_workspace_metric dry_run → apply with explicit confirmation)\\n\\n' +\n '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.\\n\\n' +\n 'Semantic models: search_semantic_model, get_semantic_entity, propose_semantic_change, apply_semantic_change, and eval_semantic_model use the configured service credential and datamartId from list_datamarts. For a v2 model, use these tools instead of update_semantic_layer. Search and inspect first. Propose a change, review its errors and plan, then evaluate its returned source. Apply that exact source with its expected_content_hash only after the user supplies confirm=\"APPLY_TO_PRODUCTION\" and dry_run=false. Never supply confirmation yourself. Apply changes the live model for every consumer. A conflict requires a new proposal and review. dry_run defaults to true and never writes.\\n\\n' +\n 'Custom chart types (chart type custom:<key>): custom_chart_scaffold returns a chart project the agent writes to disk, custom_chart_validate checks a manifest and bundle without persisting, custom_chart_upload creates the chart or a preview version only with mode=\"apply\" and confirm=\"APPLY_TO_PRODUCTION\" (dry_run shows the plan first), and publish_custom_chart makes a version live for every builder only with the same confirmation because every dashboard using that key re-renders. Build the bundle with `databrain custom-chart build` (the CLI in this package), never by hand.\\n\\n' +\n 'Infrastructure creation is intentionally narrow in MCP: datasource and workspace creation still happen outside this workflow; sync_datasource and dry-run/apply create_datamart are available. The semantic layer is populated and maintained via MCP tools (get_semantic_layer, update_semantic_layer, start_semantic_layer_generation, get_semantic_layer_generation_status). When the semantic layer is empty, use these tools — do NOT send the user to the UI for semantic metadata.\\n\\n' +\n 'After embed setup, customize with: brand-my-embed prompt for theming. Use update_embed (body passthrough) for filters, localization, and permissions.\\n' +\n 'Use generate_guest_token for frontend auth and generate_embed_code for framework snippets.\\n' +\n 'For advanced embed configuration (UI toggles, filters, i18n), use update_embed with a body passthrough.\\n' +\n 'To diagnose embed issues, use get_embed_details and reason about the response.\\n' +\n 'Do NOT ask the user for any tokens — manage them via MCP tools.\\n\\n' +\n 'IMPORTANT — accessSettings defaults:\\n' +\n 'When calling create_embed, only pass accessSettings: { datamartName: \"...\" } plus any flags the user explicitly wants enabled. For multi-tenant dashboard embeds, also pass clientId. The tool auto-fills all required isAllow* fields with secure defaults (all false). Do NOT guess or enumerate accessSettings fields.\\n\\n' +\n 'IMPORTANT — create_data_app does NOT accept workspaceName. Only pass { name: \"...\" }.\\n\\n' +\n 'IMPORTANT — Be interactive. Present results from each tool call and ask the user before proceeding to the next step. Never batch multiple mutating operations without user confirmation.\\n' +\n 'Trigger keywords: embed, dashboard, analytics, Databrain, guest token, theme, filters, permissions, datasource, datamart, workspace, metric creation.',\n },\n );\n\n registerAllTools(server, client);\n registerAllResources(server);\n registerAllPrompts(server);\n\n return server;\n}\n","import { createHash } from 'node:crypto';\nimport { PostHog } from 'posthog-node';\nimport { instrument } from '@posthog/mcp';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Config } from './config.js';\nimport { DEFAULT_POSTHOG_API_KEY, DEFAULT_POSTHOG_HOST } from './config.js';\nimport { logger } from './logger.js';\n\n// Derives a stable per-install distinct_id from the active auth token without\n// ever sending the token itself to PostHog.\nfunction distinctIdFromToken(token: string | undefined): string | null {\n if (!token) return null;\n return createHash('sha256').update(token).digest('hex').slice(0, 32);\n}\n\n/**\n * Wires PostHog MCP analytics (tool calls, initialize, exceptions) into the\n * server. Always on — not user-configurable. Returns the PostHog client so\n * the caller can flush/shutdown it on exit, or null when no auth token is\n * configured yet (nothing to derive a distinct_id from).\n */\nexport function setupAnalytics(server: McpServer, config: Config): PostHog | null {\n const distinctId = distinctIdFromToken(config.serviceToken || config.apiToken);\n if (!distinctId) {\n return null;\n }\n\n const posthog = new PostHog(config.posthogApiKey || DEFAULT_POSTHOG_API_KEY, {\n host: config.posthogHost || DEFAULT_POSTHOG_HOST,\n });\n\n instrument(server, posthog, {\n logger: (msg) => logger.debug(`[posthog] ${msg}`),\n identify: { distinctId },\n });\n\n return posthog;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/utils/analytics.ts"],"sourcesContent":["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadConfig } from './utils/config.js';\nimport { createServer } from './server.js';\nimport { setupAnalytics } from './utils/analytics.js';\nimport { logger } from './utils/logger.js';\nimport { MCP_SERVER_VERSION } from './version.js';\n\nfunction banner(msg: string): void {\n process.stderr.write(`[databrain] ${msg}\\n`);\n}\n\nasync function main(): Promise<void> {\n try {\n if (process.argv.includes('--http')) {\n const { startHosted } = await import('./hosted/main.js');\n await startHosted();\n return;\n }\n const config = loadConfig();\n const server = createServer(config);\n const posthog = setupAnalytics(server, config);\n const transport = new StdioServerTransport();\n\n let shuttingDown = false;\n const flushAnalytics = async (): Promise<void> => {\n if (shuttingDown || !posthog) return;\n shuttingDown = true;\n await posthog.shutdown();\n };\n // StdioServerTransport never observes stdin ending, so the MCP client\n // disconnecting drains the event loop and exits the process without ever\n // calling transport/server onclose. `beforeExit` is what actually fires\n // in that path; posthog-node's flush timers are unref'd so they don't\n // keep the process alive on their own.\n process.on('beforeExit', () => void flushAnalytics());\n process.on('SIGINT', () => void flushAnalytics().then(() => process.exit(0)));\n process.on('SIGTERM', () => void flushAnalytics().then(() => process.exit(0)));\n\n await server.connect(transport);\n banner(`v${MCP_SERVER_VERSION} ready`);\n } catch (error) {\n // An Error serializes to {} in JSON, which left a self-hosted operator with no\n // reason. Config errors name the setting, never its value; the logger also\n // scrubs registered secrets.\n logger.error(`Failed to start server: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n}\n\nmain();\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { DatabrainClient } from './client/databrain-client.js';\nimport type { Config } from './utils/config.js';\nimport { registerAllTools } from './tools/registry.js';\nimport { registerAllResources } from './resources/registry.js';\nimport { registerAllPrompts } from './prompts/registry.js';\nimport { MCP_SERVER_VERSION } from './version.js';\n\nexport function createServer(config: Config): McpServer {\n const client = new DatabrainClient(config);\n const auth = client.getActiveAuth();\n let operatorBlurb: string;\n if (auth.source === 'oauth-client-credentials') {\n operatorBlurb =\n 'OPERATOR IDENTITY: OAuth client-credentials mode is configured for org-level calls. Tokens identify a service application, not a human operator. The backend must validate the token and map its issuer/subject to an authorized principal. Configuration alone does not prove backend acceptance. Do NOT ask the user for credentials or tokens.\\n\\n';\n } else if (auth.source === 'service-token') {\n operatorBlurb =\n 'OPERATOR IDENTITY: service-token mode. Calls are authenticated at org level — actions in the audit log are tagged actor=system, not a specific user. Per-user identity binding will be added when the OAuth-based login flow ships. Do NOT ask the user for credentials or tokens.\\n\\n';\n } else if (auth.source === 'api-token') {\n operatorBlurb =\n 'OPERATOR IDENTITY: API-token-only mode. A per-data-app API token is configured, but DATABRAIN_SERVICE_TOKEN is not. Embedding operations can use the configured API token, but org-level setup tools require DATABRAIN_SERVICE_TOKEN in the MCP config.\\n\\n';\n } else {\n operatorBlurb =\n 'OPERATOR IDENTITY: no usable token is configured. The MCP server normally refuses this config at startup. Ask the user to add DATABRAIN_SERVICE_TOKEN or DATABRAIN_API_TOKEN to the MCP config before proceeding.\\n\\n';\n }\n\n const serviceTokenStatus = config.oauth\n ? 'OAuth client credentials are configured for service-level operations. A configured DATABRAIN_SERVICE_TOKEN is used only if token acquisition fails.'\n : config.serviceToken\n ? 'This MCP server has DATABRAIN_SERVICE_TOKEN configured; never ask the user for it.'\n : 'DATABRAIN_SERVICE_TOKEN is not configured. Ask the user to add it to the MCP config only if org-level setup is needed.';\n const apiTokenStatus = client.hasApiToken()\n ? 'This MCP server has a per-data-app API token configured.'\n : config.oauth\n ? 'Created/rotated via create_api_token or rotate_api_token using configured OAuth service credentials, once the backend supports them.'\n : 'Created/rotated via create_api_token or rotate_api_token during setup when DATABRAIN_SERVICE_TOKEN is available.';\n\n const server = new McpServer(\n {\n name: 'databrain',\n version: MCP_SERVER_VERSION,\n description:\n 'Databrain MCP Server — build customer-facing embedded analytics AND run internal analytics on your own data. Supports embed setup, semantic-layer maintenance, natural-language querying, SQL→metric creation (embedded production metrics and unpublished internal workspace metrics), theming, filters, permissions, and guest token generation.',\n },\n {\n instructions:\n operatorBlurb +\n 'Databrain uses two token types — never confuse them:\\n' +\n `• 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, custom_chart_validate (server check), custom_chart_upload, list_custom_charts, publish_custom_chart. ${serviceTokenStatus}\\n` +\n `• 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}\\n\\n` +\n 'FIRST classify the request before calling tools — this avoids wasted turns:\\n' +\n '• (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.\\n' +\n '• (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 — it is NOT embedding.\\n\\n' +\n '5 core use cases — match the user\\'s scenario to the right prompt:\\n' +\n '1. Embed existing dashboard → prompt: embed-existing-dashboard (list_data_apps → create_api_token → list_dashboards → create_embed → generate_guest_token → generate_embed_code)\\n' +\n '2. Blank dashboard per client → prompt: embed-blank-dashboard (list_data_apps → list_datamarts → create_embed with clientId per client → generate_guest_token → generate_embed_code)\\n' +\n '3. Explore & query data → prompt: query-data (list_datamarts → get_semantic_layer → if empty, auto-generate descriptions with update_semantic_layer or use populate-semantic-layer prompt → ask_question)\\n' +\n '4. SQL → embedded production metric on a customer dashboard (EMBEDDING, requires clientId) → prompt: sql-to-metric-migration (validate_metric_spec → create_or_update_metric_from_query dry_run → apply only with explicit production confirmation)\\n' +\n '5. SQL → unpublished workspace metric (INTERNAL ANALYTICS, no clientId, not attached to any dashboard) → prompt: sql-to-workspace-metric (discover_datasource_schema → create_workspace_metric dry_run → apply with explicit confirmation)\\n\\n' +\n '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.\\n\\n' +\n 'Semantic models: search_semantic_model, get_semantic_entity, propose_semantic_change, apply_semantic_change, and eval_semantic_model use the configured service credential and datamartId from list_datamarts. For a v2 model, use these tools instead of update_semantic_layer. Search and inspect first. Propose a change, review its errors and plan, then evaluate its returned source. Apply that exact source with its expected_content_hash only after the user supplies confirm=\"APPLY_TO_PRODUCTION\" and dry_run=false. Never supply confirmation yourself. Apply changes the live model for every consumer. A conflict requires a new proposal and review. dry_run defaults to true and never writes.\\n\\n' +\n 'Custom chart types (chart type custom:<key>): custom_chart_scaffold returns a draft and project files. In hosted agents, custom_chart_edit sends the description and draft to Chart studio Bryn when installed; if unavailable, edit draft.code yourself. Pass chartTs, manifest, bundleCss and iconSvg for server-side compilation without files or imports. custom_chart_preview shows validated draft code on bounded real sample rows without saving. custom_chart_validate checks a manifest and bundle without persisting, custom_chart_upload creates the chart or a preview version only with mode=\"apply\" and confirm=\"APPLY_TO_PRODUCTION\" (dry_run shows the plan first), and publish_custom_chart makes a version live for every builder only with the same confirmation because every dashboard using that key re-renders. For local projects only, build with `databrain custom-chart build`. A custom metric uses chartType=\"custom:<key>\" and customChart={key,bindings,options}; its MCP App tile loads only the company published bundle in the app/embed sandbox. Hosts without MCP Apps show rows and a clear fallback.\\n\\n' +\n 'Infrastructure creation is intentionally narrow in MCP: datasource and workspace creation still happen outside this workflow; sync_datasource and dry-run/apply create_datamart are available. The semantic layer is populated and maintained via MCP tools (get_semantic_layer, update_semantic_layer, start_semantic_layer_generation, get_semantic_layer_generation_status). When the semantic layer is empty, use these tools — do NOT send the user to the UI for semantic metadata.\\n\\n' +\n 'After embed setup, customize with: brand-my-embed prompt for theming. Use update_embed (body passthrough) for filters, localization, and permissions.\\n' +\n 'Use generate_guest_token for frontend auth and generate_embed_code for framework snippets.\\n' +\n 'For advanced embed configuration (UI toggles, filters, i18n), use update_embed with a body passthrough.\\n' +\n 'To diagnose embed issues, use get_embed_details and reason about the response.\\n' +\n 'Do NOT ask the user for any tokens — manage them via MCP tools.\\n\\n' +\n 'IMPORTANT — accessSettings defaults:\\n' +\n 'When calling create_embed, only pass accessSettings: { datamartName: \"...\" } plus any flags the user explicitly wants enabled. For multi-tenant dashboard embeds, also pass clientId. The tool auto-fills all required isAllow* fields with secure defaults (all false). Do NOT guess or enumerate accessSettings fields.\\n\\n' +\n 'IMPORTANT — create_data_app does NOT accept workspaceName. Only pass { name: \"...\" }.\\n\\n' +\n 'IMPORTANT — Be interactive. Present results from each tool call and ask the user before proceeding to the next step. Never batch multiple mutating operations without user confirmation.\\n' +\n 'Trigger keywords: embed, dashboard, analytics, Databrain, guest token, theme, filters, permissions, datasource, datamart, workspace, metric creation.',\n },\n );\n\n registerAllTools(server, client);\n registerAllResources(server);\n registerAllPrompts(server);\n\n return server;\n}\n","import { createHash } from 'node:crypto';\nimport { PostHog } from 'posthog-node';\nimport { instrument } from '@posthog/mcp';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Config } from './config.js';\nimport { DEFAULT_POSTHOG_API_KEY, DEFAULT_POSTHOG_HOST } from './config.js';\nimport { logger } from './logger.js';\n\n// Derives a stable per-install distinct_id from the active auth token without\n// ever sending the token itself to PostHog.\nfunction distinctIdFromToken(token: string | undefined): string | null {\n if (!token) return null;\n return createHash('sha256').update(token).digest('hex').slice(0, 32);\n}\n\n/**\n * Wires PostHog MCP analytics (tool calls, initialize, exceptions) into the\n * server. Always on — not user-configurable. Returns the PostHog client so\n * the caller can flush/shutdown it on exit, or null when no auth token is\n * configured yet (nothing to derive a distinct_id from).\n */\nexport function setupAnalytics(server: McpServer, config: Config): PostHog | null {\n const distinctId = distinctIdFromToken(config.serviceToken || config.apiToken);\n if (!distinctId) {\n return null;\n }\n\n const posthog = new PostHog(config.posthogApiKey || DEFAULT_POSTHOG_API_KEY, {\n host: config.posthogHost || DEFAULT_POSTHOG_HOST,\n });\n\n instrument(server, posthog, {\n logger: (msg) => logger.debug(`[posthog] ${msg}`),\n identify: { distinctId },\n });\n\n return posthog;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  registerChartApps
4
- } from "./chunk-5IGP6A6T.js";
4
+ } from "./chunk-NBFUKVWP.js";
5
5
  import {
6
6
  PREVIEW_PATH,
7
7
  WRITE_CREATE,
@@ -9,17 +9,19 @@ import {
9
9
  registerAllPrompts,
10
10
  registerAllResources,
11
11
  registerAllTools
12
- } from "./chunk-ERTAKB2H.js";
12
+ } from "./chunk-ES2LUP4U.js";
13
13
  import {
14
14
  DatabrainApiError,
15
15
  DatabrainClient,
16
16
  MCP_SERVER_VERSION,
17
17
  parseErrorBody,
18
+ parseStudioStream,
18
19
  stringify,
19
20
  toolContext,
20
21
  toolError,
21
22
  toolSuccess
22
- } from "./chunk-OCBTNAAO.js";
23
+ } from "./chunk-RHK62ZJ6.js";
24
+ import "./chunk-Q5EYDKSA.js";
23
25
  import {
24
26
  FilePatStore,
25
27
  HOSTED_SCOPES,
@@ -176,6 +178,10 @@ var TOOL_SCOPES = {
176
178
  remove_widget: ["mcp:dashboards:write"],
177
179
  generate_widget: ["mcp:dashboards:write", "mcp:query"],
178
180
  list_scheduled_reports: ["mcp:dashboards:read"],
181
+ custom_chart_edit: ["mcp:custom-charts:write"],
182
+ custom_chart_preview: ["mcp:custom-charts:read"],
183
+ load_custom_chart_bundle: ["mcp:custom-charts:read"],
184
+ load_custom_chart_runtime: ["mcp:custom-charts:read"],
179
185
  custom_chart_scaffold: [],
180
186
  custom_chart_validate: ["mcp:custom-charts:read"],
181
187
  custom_chart_upload: ["mcp:custom-charts:write"],
@@ -203,7 +209,9 @@ var TOOL_ARGUMENT_VALUES = {
203
209
  list_dashboards: { scope: ["auto", "data_app", "global"] }
204
210
  };
205
211
  var TOOL_OPAQUE_ARGUMENTS = {
206
- preview_chart: ["rows"]
212
+ preview_chart: ["rows"],
213
+ custom_chart_preview: ["rows", "manifest"],
214
+ custom_chart_edit: ["draft"]
207
215
  };
208
216
  function assertToolArguments(name, value, principal, delegated = false) {
209
217
  const allowed = Object.hasOwn(TOOL_ARGUMENT_VALUES, name) ? TOOL_ARGUMENT_VALUES[name] : {};
@@ -229,6 +237,9 @@ var WORKSPACE_FREE_TOOLS = /* @__PURE__ */ new Set([
229
237
  "list_workspaces",
230
238
  "create_workspace",
231
239
  "generate_embed_code",
240
+ "custom_chart_preview",
241
+ "load_custom_chart_bundle",
242
+ "load_custom_chart_runtime",
232
243
  "custom_chart_scaffold",
233
244
  "custom_chart_validate",
234
245
  "custom_chart_upload",
@@ -363,6 +374,9 @@ var HostedClient = class extends DatabrainClient {
363
374
  // A generated API credential must never replace the authorizing hosted principal.
364
375
  setRuntimeApiToken(_token) {
365
376
  }
377
+ isHosted() {
378
+ return true;
379
+ }
366
380
  async request(options) {
367
381
  try {
368
382
  const name = toolContext.getStore()?.tool;
@@ -371,7 +385,7 @@ var HostedClient = class extends DatabrainClient {
371
385
  const named = callWorkspace.getStore()?.target;
372
386
  const target = named ?? (selectWorkspace(options.body, this.principal, ["workspaceName", "workspaceId"]) ?? selectWorkspace(options.query, this.principal, ["workspaceName", "workspaceId"]));
373
387
  const delegated = this.delegation !== void 0;
374
- const opaque = options.method === "POST" && options.path === PREVIEW_PATH ? ["rows"] : void 0;
388
+ const opaque = options.method === "POST" && options.path === PREVIEW_PATH ? ["rows"] : options.method === "POST" && /^\/api\/v2\/custom-charts\/[a-z][a-z0-9_-]*\/bryn\/turn$/.test(options.path) ? ["draft"] : void 0;
375
389
  assertBoundArguments(options.body, this.principal, { target, delegated, opaque });
376
390
  assertBoundArguments(options.query, this.principal, { target, delegated });
377
391
  let raw = await this.adapter.executeRequest(
@@ -467,6 +481,7 @@ function createHostedToolServer(principal, adapter, delegation) {
467
481
  const target = assertToolArguments(name, values[0], bound, delegated);
468
482
  if (name === "whoami") return whoami(bound);
469
483
  const input = values[0] && typeof values[0] === "object" ? values[0] : void 0;
484
+ if (input?.projectDir !== void 0) return toolError("projectDir is not available on the hosted server. Pass chartTs, manifest, bundleCss and iconSvg to compile in memory.");
470
485
  if (!target && !WORKSPACE_FREE_TOOLS.has(name) && !WORKSPACE_SPANNING_TOOLS.has(name) && !namesWorkspace(input)) {
471
486
  throw new WorkspaceSelectionError(
472
487
  400,
@@ -795,12 +810,12 @@ function createProviderAdapter(config) {
795
810
  if (forbidden?.code && GRANT_DENIALS.has(forbidden.code)) {
796
811
  throw new HostedAuthError(403, "delegated_request_denied", forbidden);
797
812
  }
798
- const status = response.status === 401 ? 401 : response.status >= 500 ? 503 : response.status === 403 || response.status === 404 ? 404 : 400;
813
+ const status = response.status === 403 && options.responseFormat === "studio-sse" ? 403 : response.status === 401 ? 401 : response.status >= 500 ? 503 : response.status === 403 || response.status === 404 ? 404 : 400;
799
814
  const detail = status === 401 || status === 503 || response.status === 403 ? void 0 : errorDetail(response.status, text, bearer);
800
815
  throw new HostedAuthError(status, "delegated_request_denied", detail);
801
816
  }
802
817
  try {
803
- return text ? JSON.parse(text) : null;
818
+ return options.responseFormat === "studio-sse" ? parseStudioStream(text) : text ? JSON.parse(text) : null;
804
819
  } catch {
805
820
  throw new HostedAuthError(503, "delegated_response_invalid");
806
821
  }
@@ -854,4 +869,4 @@ async function startHosted() {
854
869
  export {
855
870
  startHosted
856
871
  };
857
- //# sourceMappingURL=main-KZRHWE3K.js.map
872
+ //# sourceMappingURL=main-JEYYFGJM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hosted/main.ts","../src/hosted/config.ts","../src/hosted/http.ts","../src/hosted/server.ts","../src/tools/infrastructure/create-workspace.ts","../src/hosted/authorization.ts","../src/hosted/provider.ts"],"sourcesContent":["import { isAbsolute } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadHostedConfig } from './config.js';\nimport { FilePatStore } from './auth.js';\nimport { createHostedHttpServer } from './http.js';\nimport type { HostedAdapter } from './types.js';\nimport { createProviderAdapter } from './provider.js';\n\nexport async function startHosted(): Promise<void> {\n const config = loadHostedConfig();\n let adapter: HostedAdapter;\n if (config.testOnly) {\n const file = process.env.DATABRAIN_HOSTED_TEST_FIXTURES;\n if (!file || !isAbsolute(file)) throw new Error('Hosted test mode requires an absolute fixture store path.');\n const { createTestOnlyAdapter } = await import('./test-only.js');\n adapter = createTestOnlyAdapter(file);\n } else {\n const file = process.env.DATABRAIN_HOSTED_ADAPTER_MODULE;\n if (!file) {\n adapter = createProviderAdapter(config);\n process.stderr.write('[databrain] OAuth configured: data tools run as the signed-in user through the issuer; PAT membership requires a trusted adapter.\\n');\n } else {\n if (!isAbsolute(file)) throw new Error('DATABRAIN_HOSTED_ADAPTER_MODULE must be an absolute path.');\n try { adapter = (await import(pathToFileURL(file).href)).adapter; }\n catch { throw new Error('Could not load the trusted hosted adapter.'); }\n }\n if (!adapter || ['verifyOAuth', 'resolvePatGrant', 'executeRequest'].some(key =>\n typeof adapter[key as keyof HostedAdapter] !== 'function')) throw new Error('Invalid trusted hosted adapter.');\n }\n const patPath = process.env.DATABRAIN_HOSTED_PAT_STORE;\n if (patPath && !isAbsolute(patPath)) throw new Error('Hosted PAT store must be an absolute path.');\n const server = createHostedHttpServer(config, adapter, patPath ? new FilePatStore(patPath) : undefined);\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(config.port, config.bind, resolve);\n });\n process.stderr.write(`[databrain] hosted HTTP ready on ${config.bind}:${config.port}/mcp${config.testOnly ? ' — TEST ONLY: synthetic identity and data; no OAuth consent' : ''}\\n`);\n const shutdown = () => {\n server.close(() => process.exit(0));\n setTimeout(() => { server.closeAllConnections(); process.exit(0); }, 5000).unref();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n}\n","import type { HostedConfig } from './types.js';\n\nexport function loadHostedConfig(env: NodeJS.ProcessEnv = process.env): HostedConfig {\n const testOnly = env.DATABRAIN_HOSTED_TEST_ONLY === '1';\n if (testOnly && env.NODE_ENV === 'production') throw new Error('Hosted test mode is forbidden in production.');\n if (env.DATABRAIN_HOSTED_TEST_ONLY && !testOnly) throw new Error('Invalid DATABRAIN_HOSTED_TEST_ONLY.');\n const validUrl = (value: string | undefined, field: string) => {\n try {\n if (!value) throw new Error();\n const url = new URL(value);\n if (url.username || url.password || url.search || url.hash || url.protocol !== 'https:') throw new Error();\n return url;\n } catch { throw new Error(`Configure ${field} as an HTTPS URL without credentials, query or fragment.`); }\n };\n // Self-hosted installs set no new variable: with neither URL given, both come from the\n // HOSTED_APP_URL origin the image already has (app, API and /mcp on one origin). HTTPS only.\n let publicValue = env.DATABRAIN_HOSTED_PUBLIC_URL;\n let issuerValue = env.DATABRAIN_HOSTED_ISSUER;\n if (!publicValue && !issuerValue && env.HOSTED_APP_URL) {\n const origin = validUrl(env.HOSTED_APP_URL, 'HOSTED_APP_URL').origin;\n publicValue = `${origin}/mcp`;\n issuerValue = origin;\n }\n const publicUrl = validUrl(publicValue, 'DATABRAIN_HOSTED_PUBLIC_URL');\n validUrl(issuerValue, 'DATABRAIN_HOSTED_ISSUER');\n if (publicUrl.pathname !== '/mcp') throw new Error('DATABRAIN_HOSTED_PUBLIC_URL must end in /mcp.');\n const port = Number(env.DATABRAIN_HOSTED_PORT || '3144');\n // Loopback by default so a laptop run is not silently on the network. A\n // container has to be told, because binding loopback inside a task means the\n // load balancer health check can never reach it and the service never\n // stabilises. Set DATABRAIN_HOSTED_BIND=0.0.0.0 in ECS.\n const bind = env.DATABRAIN_HOSTED_BIND || '127.0.0.1';\n if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Invalid DATABRAIN_HOSTED_PORT.');\n return { publicUrl: publicValue!, issuer: issuerValue!, port,\n bind, testOnly };\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { createHostedToolServer, scopeError, workspaceError } from './server.js';\nimport { createBearerVerifier } from './auth.js';\nimport { WorkspaceSelectionError, authorizeTool, assertToolArguments, requiredScopes } from './authorization.js';\nimport { HOSTED_SCOPES, HostedAuthError } from './types.js';\nimport type { HostedAdapter, HostedConfig, PatStore } from './types.js';\n\nconst HEALTH_BODY = JSON.stringify({ status: 'ok' });\n\n/** Exactly `/health`, with or without a query string; `/health/x` is not health. */\nfunction healthPath(url: string | undefined): boolean {\n const path = (url || '/').split('?')[0];\n return path === '/health';\n}\n\nfunction reply(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });\n res.end(JSON.stringify(body));\n}\n\nasync function readBody(req: IncomingMessage): Promise<unknown> {\n let size = 0;\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n size += chunk.length;\n if (size > 1_048_576) throw new Error('body_too_large');\n chunks.push(Buffer.from(chunk));\n }\n return JSON.parse(Buffer.concat(chunks).toString('utf8'));\n}\n\nexport function createHostedHttpServer(config: HostedConfig, adapter: HostedAdapter, pats?: PatStore) {\n const publicUrl = new URL(config.publicUrl);\n const metadataPath = `/.well-known/oauth-protected-resource${publicUrl.pathname === '/' ? '' : publicUrl.pathname}`;\n const metadataUrl = `${publicUrl.origin}${metadataPath}`;\n const verify = createBearerVerifier(adapter, config.publicUrl, pats);\n let active = 0;\n\n const server = createServer(async (req, res) => {\n // Liveness for the load balancer. It arrives with the target IP:port as Host,\n // so it runs before the Host/Origin checks and sets no CORS headers. It is\n // unauthenticated, reads no body and never echoes configuration.\n if (healthPath(req.url)) {\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n res.writeHead(405, { Allow: 'GET, HEAD', 'Cache-Control': 'no-store' }); res.end(); return;\n }\n res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store',\n 'Content-Length': Buffer.byteLength(HEALTH_BODY) });\n res.end(req.method === 'HEAD' ? undefined : HEALTH_BODY); return;\n }\n res.setHeader('X-Content-Type-Options', 'nosniff');\n if (config.testOnly) res.setHeader('X-Databrain-Test-Only', 'true');\n try {\n // Tailscale Serve may preserve the public Host or use the loopback destination.\n // Never use Host/Forwarded headers to construct discovery URLs or trust an identity.\n const localPort = (server.address() as { port: number } | null)?.port;\n if (![publicUrl.host, `127.0.0.1:${localPort}`, `localhost:${localPort}`].includes(req.headers.host || '')) {\n reply(res, 403, { error: 'invalid_host' }); return;\n }\n if (req.headers.origin && req.headers.origin !== publicUrl.origin) {\n reply(res, 403, { error: 'invalid_origin' }); return;\n }\n if (req.headers.origin) {\n res.setHeader('Access-Control-Allow-Origin', publicUrl.origin);\n res.setHeader('Vary', 'Origin');\n res.setHeader('Access-Control-Expose-Headers', 'WWW-Authenticate, MCP-Protocol-Version');\n }\n const url = new URL(req.url || '/', publicUrl.origin);\n if (req.method === 'GET' && [metadataPath, '/.well-known/oauth-protected-resource'].includes(url.pathname)) {\n reply(res, 200, {\n resource: config.publicUrl, authorization_servers: [config.issuer],\n scopes_supported: HOSTED_SCOPES, bearer_methods_supported: ['header'],\n resource_name: config.testOnly ? 'Databrain (TEST ONLY; fixture identity)' : 'Databrain',\n }); return;\n }\n if (url.pathname !== publicUrl.pathname) { reply(res, 404, { error: 'not_found' }); return; }\n if (url.search) { reply(res, 400, { error: 'query_parameters_not_supported' }); return; }\n if (req.method === 'OPTIONS') {\n res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, MCP-Protocol-Version, MCP-Session-Id');\n res.writeHead(204); res.end(); return;\n }\n // Duplicate Authorization fields and cookies never select a principal.\n const authFields = req.rawHeaders.filter((_, i) => i % 2 === 0 && req.rawHeaders[i].toLowerCase() === 'authorization');\n const match = /^Bearer ([^\\s]+)$/i.exec(req.headers.authorization || '');\n if (authFields.length !== 1 || !match) throw new HostedAuthError(401, 'invalid_token');\n const principal = await verify(match[1]);\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST, OPTIONS');\n reply(res, 405, { error: 'method_not_allowed' }); return;\n }\n if (!req.headers['content-type']?.toLowerCase().startsWith('application/json')) {\n reply(res, 415, { error: 'unsupported_media_type' }); return;\n }\n if (active >= 32) { reply(res, 503, { error: 'server_busy' }); return; }\n active++;\n let body: any;\n try { body = await readBody(req); }\n catch { active--; reply(res, 400, { error: 'invalid_request' }); return; }\n try {\n // The installed SDK accepts batches; this endpoint does not. A batch must\n // never skip the per-call HTTP authorization/challenge path below.\n if (Array.isArray(body)) { reply(res, 400, { error: 'batch_not_supported' }); return; }\n // Only an OAuth bearer delegates; a PAT never leaves this process.\n const delegation = match[1].startsWith('dbp_') ? undefined : adapter.delegate?.(match[1], principal);\n if (body?.method === 'tools/call' && typeof body.params?.name === 'string') {\n try {\n authorizeTool(body.params.name, principal);\n assertToolArguments(body.params.name, body.params.arguments, principal, delegation !== undefined);\n } catch (error) {\n // A workspace the grant does not hold, or two that disagree: answer the call as a\n // tool error with the granted names, which an AI can act on. No tool runs.\n if (error instanceof WorkspaceSelectionError) {\n reply(res, 200, { jsonrpc: '2.0', id: body.id ?? null, result: workspaceError(error) }); return;\n }\n // A scope the grant lacks: consent already trimmed the scopes to the role, so a\n // step-up challenge (403 insufficient_scope) cannot get more. It made Claude Code\n // drop the whole connection to \"needs authentication\". Answer the call instead.\n if (error instanceof HostedAuthError && error.code === 'insufficient_scope') {\n const missing = requiredScopes(body.params.name).filter(scope => !principal.scopes.includes(scope));\n reply(res, 200, { jsonrpc: '2.0', id: body.id ?? null, result: scopeError(missing) }); return;\n }\n throw error;\n }\n }\n const mcp = createHostedToolServer(principal, adapter, delegation);\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });\n let closed = false;\n const close = () => { if (!closed) { closed = true; void mcp.close(); } };\n res.once('close', close);\n try {\n await mcp.connect(transport);\n await transport.handleRequest(req, res, body);\n } finally { if (res.writableEnded) close(); }\n } finally { active--; }\n } catch (error) {\n if (res.headersSent) { res.end(); return; }\n const status = error instanceof HostedAuthError ? error.status : 503;\n if (status === 401) res.setHeader('WWW-Authenticate', `Bearer error=\"invalid_token\", resource_metadata=\"${metadataUrl}\"`);\n reply(res, status, { error: error instanceof HostedAuthError ? error.code : 'temporarily_unavailable' });\n }\n });\n server.requestTimeout = 30_000;\n server.headersTimeout = 10_000;\n server.keepAliveTimeout = 5_000;\n return server;\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { z } from 'zod';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { instrument } from '@posthog/mcp';\nimport { PostHog } from 'posthog-node';\nimport { DatabrainClient } from '../client/databrain-client.js';\nimport type { RequestOptions } from '../client/types.js';\nimport { registerAllTools } from '../tools/registry.js';\nimport { registerCreateWorkspace } from '../tools/infrastructure/create-workspace.js';\nimport { PREVIEW_PATH } from '../apps/preview.js';\nimport { enableChartApps } from '../apps/feature.js';\nimport { registerChartApps } from '../apps/registry.js';\nimport { registerAllResources } from '../resources/registry.js';\nimport { registerAllPrompts } from '../prompts/registry.js';\nimport { MCP_SERVER_VERSION } from '../version.js';\nimport { DatabrainApiError, toolError, toolSuccess } from '../utils/errors.js';\nimport { toolContext } from '../utils/tool-context.js';\nimport {\n WORKSPACE_FREE_TOOLS, WORKSPACE_INPUT, WORKSPACE_SPANNING_TOOLS, WorkspaceSelectionError, assertBoundArguments,\n assertToolArguments, authorizeTool, grantedNames, grantedWorkspaces, namesWorkspace, selectWorkspace,\n} from './authorization.js';\nimport { HostedAuthError } from './types.js';\nimport type { GrantedWorkspace, HostedAdapter, HostedDelegation, HostedPrincipal } from './types.js';\n\n// Stdio's instrumentation changes the wire schema (required `context`). Use that\n// same implementation, with capture disabled until hosted telemetry has an owner.\n// No bearer, request headers, tool arguments or results are sent to analytics.\nconst schemaInstrumentation = new PostHog('hosted-contract-only', { disabled: true });\n\n/** The granted workspace the running tool call acts in. Set by the tool wrapper only. */\nconst callWorkspace = new AsyncLocalStorage<{ target?: GrantedWorkspace }>();\n\nconst WORKSPACE_HINT = 'Call whoami to see the workspaces this sign-in granted, then name one.';\n\n/** A workspace selection problem, as the tool result the AI reads: reason, code, next step. */\nexport function workspaceError(error: WorkspaceSelectionError) {\n return toolError(`Error: ${error.reason} (${error.code})\\n\\nHint: ${WORKSPACE_HINT}`);\n}\n\n/** A tool this sign-in's scopes do not cover, as a tool result: the connection stays up. */\nexport function scopeError(missing: readonly string[]) {\n return toolError(`Error: This connection cannot do this. It needs ${missing.join(', ')}, which your role did not allow ` +\n 'when you connected. (INSUFFICIENT_SCOPE)\\n\\nHint: Ask a Databrain admin for a role that allows it, then connect ' +\n 'again. If your role allows it in a workspace you did not choose, connect again and choose that workspace.');\n}\n\n/** The workspace list answer (bare or under `data`) cut to one workspace. */\nfunction onlyWorkspace(raw: unknown, target: GrantedWorkspace): unknown {\n const keep = (items: unknown) => Array.isArray(items) ? items.filter(item => {\n const record = item && typeof item === 'object' ? item as Record<string, unknown> : {};\n return record.id === target.id || record._id === target.id ||\n (record.id === undefined && record._id === undefined && target.name !== undefined && record.name === target.name);\n }) : items;\n if (Array.isArray(raw)) return keep(raw);\n if (raw && typeof raw === 'object' && 'data' in raw) return { ...raw, data: keep((raw as { data: unknown }).data) };\n return raw;\n}\n\n/** A request-local client: shared APIs/tools run unchanged, through a delegated executor. */\nclass HostedClient extends DatabrainClient {\n constructor(private readonly adapter: HostedAdapter, private readonly principal: HostedPrincipal,\n private readonly delegation?: HostedDelegation) {\n super({ apiUrl: 'https://api.usedatabrain.com', demoDomain: 'https://demo.usedatabrain.com' });\n }\n // A generated API credential must never replace the authorizing hosted principal.\n override setRuntimeApiToken(_token: string): void {}\n override isHosted(): boolean { return true; }\n\n override async request<T>(options: RequestOptions): Promise<T> {\n try {\n const name = toolContext.getStore()?.tool;\n if (!name) throw new HostedAuthError(403, 'tool_not_authorized');\n authorizeTool(name, this.principal);\n const named = callWorkspace.getStore()?.target;\n // With no workspace for the call (a tool that walks every granted workspace), a request\n // that names one top-level workspace acts in it, and says so in the header too.\n const target = named ?? (selectWorkspace(options.body, this.principal, ['workspaceName', 'workspaceId'])\n ?? selectWorkspace(options.query, this.principal, ['workspaceName', 'workspaceId']));\n const delegated = this.delegation !== undefined;\n // Chart preview rows are query output (a row may have a userId column); the issuer\n // treats them as data too. Every other body field is still checked.\n const opaque = options.method === 'POST' && options.path === PREVIEW_PATH ? ['rows']\n : options.method === 'POST' && /^\\/api\\/v2\\/custom-charts\\/[a-z][a-z0-9_-]*\\/bryn\\/turn$/.test(options.path) ? ['draft'] : undefined;\n assertBoundArguments(options.body, this.principal, { target, delegated, opaque });\n assertBoundArguments(options.query, this.principal, { target, delegated });\n let raw = await this.adapter.executeRequest(structuredClone(options), structuredClone(this.principal),\n this.delegation, target && structuredClone(target));\n // A call that named its workspace reads the workspace list as that workspace only, so a\n // tool that walks the list (list_dashboards) stays in it.\n if (named && options.method === 'GET' && options.path === '/api/v2/workspace') raw = onlyWorkspace(raw, named);\n if (!options.skipDataUnwrap && raw && typeof raw === 'object' && !Array.isArray(raw) &&\n 'data' in raw && raw.data !== null && raw.data !== undefined) return raw.data as T;\n return raw as T;\n } catch (error) {\n if (error instanceof WorkspaceSelectionError) {\n throw new DatabrainApiError(error.status, error.code, `${error.reason} (${error.code})`, undefined, WORKSPACE_HINT);\n }\n // The adapter may encounter secret-bearing remote bodies. Do not pass them to tools/logs.\n const status = error instanceof HostedAuthError ? error.status : 503;\n const detail = error instanceof HostedAuthError ? error.detail : undefined;\n // A 403 reaches the tool only with a reason about this user's own grant (the adapter\n // keeps no other 403 body): the role in that workspace, or workspace creation.\n if (status === 403 && detail?.code) {\n throw new DatabrainApiError(0, detail.code, `${detail.message ?? 'Your role does not allow this here.'} (${detail.code})`,\n undefined, 'Ask a Databrain admin for access, or pick a workspace where your role allows it.');\n }\n if (status === 401 || status === 403) {\n // Status 0 carries no API-key hint: a hosted user has no key to rotate, only a sign-in.\n throw new DatabrainApiError(0, 'HOSTED_REQUEST_DENIED',\n 'The signed-in Databrain user cannot do this: the sign-in expired or was revoked, or the granted scopes, role or workspace do not allow it. Sign in again or ask an admin.');\n }\n if (detail && (detail.code || detail.message)) {\n // The code rides with the message: a short message (\"The workspace does not allow\n // this.\") only makes sense next to its reason (DATA_APP_REQUIRED).\n const message = detail.message ?? (status === 404 ? 'Not found in the workspace this sign-in approved.'\n : 'The backend refused this request.');\n throw new DatabrainApiError(detail.status, detail.code ?? 'HOSTED_REQUEST_FAILED',\n detail.code && !message.includes(detail.code) ? `${message} (${detail.code})` : message,\n { error: { code: detail.code, message: detail.message } });\n }\n throw new DatabrainApiError(status, 'HOSTED_REQUEST_DENIED',\n status === 404\n ? 'Not found in the workspace this sign-in approved.'\n : 'The authorized backend could not complete this request. Check workspace access and hosted integration.');\n }\n }\n}\n\n/** The hosted description line for a tool that works in one workspace. */\nfunction workspaceLine(name: string, input: string): string {\n return WORKSPACE_SPANNING_TOOLS.has(name)\n ? ` With more than one granted workspace, name one in \\`${input}\\` to list only that one; leave it out to list them all.`\n : ` Needed when you granted more than one workspace: name it in \\`${input}\\`.`;\n}\n\nfunction whoami(principal: HostedPrincipal) {\n const granted = grantedWorkspaces(principal);\n return toolSuccess(JSON.stringify({\n status: 'authenticated', activeBearer: principal.credentialType,\n actorKind: principal.actorKind, userId: principal.userId, companyId: principal.companyId,\n workspaceGrant: principal.workspaces?.mode === 'all'\n ? 'All workspaces I can access (new ones are included)' : 'Only the workspaces chosen at sign-in',\n grantedWorkspaces: granted.map(item => ({ id: item.id, name: item.name })),\n ...(granted.length === 1 ? { workspaceId: granted[0].id, workspaceName: granted[0].name } : {}),\n note: granted.length === 1\n ? 'Every call works in this workspace.'\n : `Name the workspace on each call that works in one (the \\`${WORKSPACE_INPUT}\\` or \\`workspaceName\\` input): ${grantedNames(principal)}.`,\n oauthClientId: principal.clientId, scopes: principal.scopes,\n }, null, 2));\n}\n\nexport function createHostedToolServer(principal: HostedPrincipal, adapter: HostedAdapter,\n delegation?: HostedDelegation): McpServer {\n const bound = structuredClone(principal);\n const delegated = delegation !== undefined;\n const server = new McpServer({ name: 'databrain', version: MCP_SERVER_VERSION }, {\n instructions: 'Hosted Databrain: each request is authenticated as the signed-in user, with that user\\'s own role in each workspace. ' +\n 'Use whoami to see the verified identity, scopes and the workspaces this sign-in granted. ' +\n 'When more than one workspace is granted, name the workspace on each call (the workspace or workspaceName input). ' +\n 'Never ask for a service token or put credentials in tool arguments. ' +\n 'Workspace and tenant access come from the bearer principal. Existing dry-run and production-confirmation rules still apply. ' +\n 'Use ask_question for inline chart or KPI answers. Chart controls and save_chart_as_dashboard use the same workspace and role; ' +\n 'saving requires the existing production confirmation. Hosts without MCP Apps can read the text answer.',\n });\n\n // The existing shared registry already wraps server.tool for intent telemetry. Layer\n // hosted policy underneath it; keep every handler and annotation. A tool that works in one\n // workspace says so, and gains a `workspace` input when its own schema names none.\n const register = server.tool.bind(server) as (...args: any[]) => unknown;\n server.tool = ((...args: any[]) => {\n const name = args[0] as string;\n const index = args.length - 1;\n const handler = args[index] as (...values: any[]) => CallToolResult | Promise<CallToolResult>;\n const shape = typeof args[1] === 'string' && args[2] && typeof args[2] === 'object' &&\n !('readOnlyHint' in args[2]) ? args[2] as Record<string, unknown> : undefined;\n const ownSelector = shape && (['workspaceName', 'workspaceId'] as const).find(key => Object.hasOwn(shape, key));\n const addsInput = shape !== undefined && !ownSelector && !WORKSPACE_FREE_TOOLS.has(name) &&\n !Object.hasOwn(shape, WORKSPACE_INPUT);\n if (shape && !WORKSPACE_FREE_TOOLS.has(name)) {\n if (addsInput) {\n args[2] = { ...shape, [WORKSPACE_INPUT]: z.string().min(1).max(512).optional()\n .describe(WORKSPACE_SPANNING_TOOLS.has(name)\n ? 'Workspace id or name. Leave it out to cover every granted workspace.'\n : 'Workspace id or name. Needed when you granted more than one workspace.') };\n }\n args[1] = `${args[1]}${workspaceLine(name, addsInput ? WORKSPACE_INPUT : ownSelector!)}`;\n }\n args[index] = async (...values: any[]) => {\n try {\n authorizeTool(name, bound);\n const target = assertToolArguments(name, values[0], bound, delegated);\n if (name === 'whoami') return whoami(bound);\n const input = values[0] && typeof values[0] === 'object' ? values[0] as Record<string, unknown> : undefined;\n if (input?.projectDir !== undefined) return toolError('projectDir is not available on the hosted server. Pass chartTs, manifest, bundleCss and iconSvg to compile in memory.');\n if (!target && !WORKSPACE_FREE_TOOLS.has(name) && !WORKSPACE_SPANNING_TOOLS.has(name) && !namesWorkspace(input)) {\n throw new WorkspaceSelectionError(400, 'WORKSPACE_REQUIRED',\n `Name the workspace for this call. Granted: ${grantedNames(bound)}.`);\n }\n if (input) {\n // The new input is a header, never a body field. A selector the tool declares\n // gets the call's workspace when it was left out, as for one granted workspace before.\n if (addsInput) delete input[WORKSPACE_INPUT];\n if (shape && target) {\n if (Object.hasOwn(shape, 'workspaceId')) input.workspaceId = target.id;\n if (Object.hasOwn(shape, 'workspaceName') && target.name !== undefined) input.workspaceName = target.name;\n }\n }\n const result = await callWorkspace.run({ target }, () => handler(...values));\n // The iframe's follow-up calls have no model to repeat the workspace selector.\n // Carry only this verified call's workspace, never a draft's claimed choices.\n if (target && result?._meta?.['ui/resourceUri']) {\n return { ...result, _meta: { ...result._meta, 'databrain/workspace': target } };\n }\n return result;\n } catch (error) {\n if (error instanceof WorkspaceSelectionError) return workspaceError(error);\n return toolError('Hosted authorization denied this tool call. Check the granted scopes, workspace and tenant access.');\n }\n };\n return register(...args);\n }) as McpServer['tool'];\n\n const client = new HostedClient(adapter, bound, delegation);\n enableChartApps(server);\n registerAllTools(server, client);\n // Hosted only: a grant of \"All workspaces I can access\" may create one (the issuer decides).\n registerCreateWorkspace(server, client);\n registerAllResources(server);\n registerAllPrompts(server);\n // Keep Apps on the principal-bound client and under the same registration policy.\n registerChartApps(server, client);\n instrument(server, schemaInstrumentation, { beforeSend: () => null });\n return server;\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { DatabrainClient } from '../../client/databrain-client.js';\nimport { DatabrainApiError, toolError, toolSuccess, stringify } from '../../utils/errors.js';\nimport { WRITE_CREATE } from '../annotations.js';\n\n/**\n * Registered by the hosted server only (stdio dropped it in v0.2.0). The issuer allows it\n * only for a sign-in granted \"All workspaces I can access\" and a role that may create one.\n */\nexport function registerCreateWorkspace(server: McpServer, client: DatabrainClient): void {\n server.tool(\n 'create_workspace',\n 'Create a new workspace to connect a datasource or datamart for dashboard building. ' +\n 'Works only when this sign-in granted \"All workspaces I can access\" and your role may create workspaces; ' +\n 'the new workspace is usable on the next call.',\n {\n name: z.string().describe('Unique workspace name'),\n connectionType: z\n .enum(['DATASOURCE', 'DATAMART', 'MULTI_DATASOURCE'])\n .optional()\n .describe('Connection type — DATASOURCE, DATAMART, or MULTI_DATASOURCE'),\n datasourceName: z\n .string()\n .optional()\n .describe('Datasource name (required when connectionType is DATASOURCE)'),\n datamartName: z\n .string()\n .optional()\n .describe('Datamart name (required when connectionType is DATAMART)'),\n body: z\n .record(z.unknown())\n .optional()\n .describe('Raw API body passthrough for POST /api/v2/workspace'),\n },\n WRITE_CREATE,\n async (params) => {\n try {\n const body: Record<string, unknown> = params.body\n ? { ...params.body }\n : {\n name: params.name,\n connectionType: params.connectionType,\n datasourceName: params.datasourceName,\n datamartName: params.datamartName,\n };\n\n const result = await client.request<{ name?: string; id?: string; _id?: string; error?: unknown }>({\n method: 'POST',\n path: '/api/v2/workspace',\n body,\n auth: 'service',\n invalidateCache: ['list_workspaces'],\n });\n\n if (result.error) {\n return toolError(`Workspace creation failed: ${stringify(result.error)}`);\n }\n\n return toolSuccess(\n `Workspace \"${result.name || params.name}\" created successfully.\\n\\n` +\n 'Next steps:\\n' +\n '1. Use `create_data_app` to create a data app linked to this workspace\\n' +\n '2. Build dashboards in the Databrain UI using this workspace\\n' +\n '3. Use `create_embed` to embed dashboards',\n );\n } catch (err) {\n if (err instanceof DatabrainApiError) return err.toToolResponse();\n return toolError(`Unexpected error: ${(err as Error).message}`);\n }\n },\n );\n}\n","import { HostedAuthError } from './types.js';\nimport type { GrantedWorkspace, HostedPrincipal, HostedScope } from './types.js';\n\n/** Explicit exhaustive policy: a newly registered tool is denied until it gets a policy. */\nexport const TOOL_SCOPES: Readonly<Record<string, readonly HostedScope[]>> = {\n whoami: [],\n list_datasources: ['mcp:datamarts:read'], discover_datasource_schema: ['mcp:datamarts:read'],\n list_datamarts: ['mcp:datamarts:read'], list_workspaces: ['mcp:datamarts:read'],\n sync_datasource: ['mcp:datamarts:write'], create_datamart: ['mcp:datamarts:write'],\n list_data_apps: ['mcp:dashboards:read'], list_tenants: ['mcp:dashboards:read'],\n create_data_app: ['mcp:embeds:write'], list_api_tokens: ['mcp:embeds:write'],\n create_api_token: ['mcp:embeds:write'], rotate_api_token: ['mcp:embeds:write'],\n create_embed: ['mcp:embeds:write'], list_embeds: ['mcp:dashboards:read'],\n get_embed_details: ['mcp:dashboards:read'], update_embed: ['mcp:embeds:write'],\n customize_embed_theme: ['mcp:embeds:write'], get_demo_link: ['mcp:embeds:write'],\n generate_guest_token: ['mcp:embeds:write'], generate_embed_code: ['mcp:dashboards:read'],\n create_workspace: ['mcp:datamarts:write'],\n list_dashboards: ['mcp:dashboards:read'], create_dashboard: ['mcp:dashboards:write'],\n get_dashboard: ['mcp:dashboards:read'], dashboard_snapshot: ['mcp:dashboards:read'], generate_dashboard_tiles: ['mcp:dashboards:write', 'mcp:query'],\n ask_question: ['mcp:query'], get_semantic_layer: ['mcp:datamarts:read'],\n update_semantic_layer: ['mcp:datamarts:write'], start_semantic_layer_generation: ['mcp:datamarts:write'],\n get_semantic_layer_generation_status: ['mcp:datamarts:read'],\n search_semantic_model: ['mcp:datamarts:read'], get_semantic_entity: ['mcp:datamarts:read'],\n propose_semantic_change: ['mcp:datamarts:read'], apply_semantic_change: ['mcp:datamarts:write'],\n eval_semantic_model: ['mcp:datamarts:read', 'mcp:query'],\n list_widgets: ['mcp:dashboards:read'], validate_metric_spec: ['mcp:dashboards:read', 'mcp:query'],\n create_or_update_metric_from_query: ['mcp:dashboards:write', 'mcp:query'],\n create_workspace_metric: ['mcp:dashboards:write', 'mcp:query'],\n add_widget: ['mcp:dashboards:write'], update_widget: ['mcp:dashboards:write'],\n remove_widget: ['mcp:dashboards:write'], generate_widget: ['mcp:dashboards:write', 'mcp:query'],\n list_scheduled_reports: ['mcp:dashboards:read'],\n custom_chart_edit: ['mcp:custom-charts:write'], custom_chart_preview: ['mcp:custom-charts:read'],\n load_custom_chart_bundle: ['mcp:custom-charts:read'],\n load_custom_chart_runtime: ['mcp:custom-charts:read'],\n custom_chart_scaffold: [], custom_chart_validate: ['mcp:custom-charts:read'],\n custom_chart_upload: ['mcp:custom-charts:write'], list_custom_charts: ['mcp:custom-charts:read'],\n publish_custom_chart: ['mcp:custom-charts:write'],\n // Chart tile controls (the chart-apps composition). The same routes as ask_question and a\n // dashboard save; listed so a hosted composition is not refused for want of a policy.\n preview_chart: ['mcp:query'], save_chart_as_dashboard: ['mcp:dashboards:write', 'mcp:query'],\n // Saved-widget rendering mints a guest token; retain the issuer's embed-write boundary.\n render_chart: ['mcp:dashboards:read', 'mcp:query', 'mcp:embeds:write'],\n refresh_widget: ['mcp:dashboards:read', 'mcp:query', 'mcp:embeds:write'],\n drill_down: ['mcp:dashboards:read', 'mcp:query', 'mcp:embeds:write'],\n change_date_range: ['mcp:dashboards:read', 'mcp:query', 'mcp:embeds:write'],\n};\n\nexport function requiredScopes(name: string): readonly HostedScope[] {\n if (!Object.hasOwn(TOOL_SCOPES, name)) throw new HostedAuthError(403, 'tool_not_authorized');\n return TOOL_SCOPES[name];\n}\n\nexport function authorizeTool(name: string, principal: HostedPrincipal): void {\n if (principal.expiresAt !== undefined && principal.expiresAt <= Date.now() / 1000) throw new HostedAuthError(401, 'invalid_token');\n if (requiredScopes(name).some(scope => !principal.scopes.includes(scope))) throw new HostedAuthError(403, 'insufficient_scope');\n}\n\n/**\n * A tool's own top-level argument whose name is also a forbidden identity key, with the\n * only values it may take. `list_dashboards.scope` picks the listing, not an OAuth scope.\n */\nconst TOOL_ARGUMENT_VALUES: Readonly<Record<string, Readonly<Record<string, readonly string[]>>>> = {\n list_dashboards: { scope: ['auto', 'data_app', 'global'] },\n};\n\n/**\n * Top-level tool arguments that carry query rows, not selectors. A row may have a\n * `userId` or `workspaceId` column; the issuer treats the rows as data.\n */\nconst TOOL_OPAQUE_ARGUMENTS: Readonly<Record<string, readonly string[]>> = {\n preview_chart: ['rows'],\n custom_chart_preview: ['rows', 'manifest'],\n custom_chart_edit: ['draft'],\n};\n\n/**\n * Tool-call arguments: the allowed own arguments by exact value, then the shared binding rules\n * against the call's workspace. Returns that workspace (see selectWorkspace).\n */\nexport function assertToolArguments(name: string, value: unknown, principal: HostedPrincipal,\n delegated = false): GrantedWorkspace | undefined {\n const allowed = Object.hasOwn(TOOL_ARGUMENT_VALUES, name) ? TOOL_ARGUMENT_VALUES[name] : {};\n const opaque = Object.hasOwn(TOOL_OPAQUE_ARGUMENTS, name) ? TOOL_OPAQUE_ARGUMENTS[name] : undefined;\n const target = selectWorkspace(value, principal);\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n assertBoundArguments(value, principal, { target, delegated });\n return target;\n }\n const rest: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n if (!Object.hasOwn(allowed, key)) { rest[key] = child; continue; }\n if (typeof child !== 'string' || !allowed[key].includes(child)) throw new HostedAuthError(403, 'identity_argument_forbidden');\n }\n assertBoundArguments(rest, principal, { target, delegated, opaque });\n return target;\n}\n\n/**\n * Tools that act on no single workspace: identity, the granted list, company-level\n * custom charts, local code generation, and workspace creation. Every other tool\n * reaches a workspace-scoped route, so it can name its workspace.\n */\nexport const WORKSPACE_FREE_TOOLS: ReadonlySet<string> = new Set([\n 'whoami', 'list_workspaces', 'create_workspace', 'generate_embed_code',\n 'custom_chart_preview', 'load_custom_chart_bundle', 'load_custom_chart_runtime',\n 'custom_chart_scaffold', 'custom_chart_validate', 'custom_chart_upload', 'list_custom_charts', 'publish_custom_chart',\n]);\n\n/**\n * Tools that walk every granted workspace when the call names none (each request then names\n * its own workspace), and only the named one when it does. They never ask for a workspace.\n */\nexport const WORKSPACE_SPANNING_TOOLS: ReadonlySet<string> = new Set(['list_dashboards']);\n\n/** The workspace selector a hosted tool gains when its own schema has none. Sent only as a header. */\nexport const WORKSPACE_INPUT = 'workspace';\n\n/** Raised before any request when a call names its workspace wrongly; the tool shows the message. */\nexport class WorkspaceSelectionError extends HostedAuthError {\n constructor(status: 400 | 404, code: string, public readonly reason: string) {\n super(status, code);\n }\n}\n\nexport function grantedWorkspaces(principal: HostedPrincipal): GrantedWorkspace[] {\n if (principal.grantedWorkspaces) return principal.grantedWorkspaces;\n return principal.workspaceId ? [{ id: principal.workspaceId, name: principal.workspaceName }] : [];\n}\n\n/** \"A, B, C\", at most 20 names, then \"and N more\". Only the user's own granted names. */\nexport function grantedNames(principal: HostedPrincipal): string {\n const names = grantedWorkspaces(principal).map(item => item.name ?? item.id);\n return names.length > 20 ? `${names.slice(0, 20).join(', ')} and ${names.length - 20} more` : names.join(', ');\n}\n\n/** A selector is a granted workspace's id, else its exact name. Nothing else, and never two. */\nfunction findGranted(principal: HostedPrincipal, key: string, value: unknown): GrantedWorkspace {\n const granted = grantedWorkspaces(principal);\n const matches = typeof value !== 'string' ? []\n : key === 'workspaceId' ? granted.filter(item => item.id === value)\n : key === 'workspaceName' ? granted.filter(item => item.name === value)\n : granted.some(item => item.id === value) ? granted.filter(item => item.id === value)\n : granted.filter(item => item.name === value);\n if (matches.length !== 1) {\n throw new WorkspaceSelectionError(404, 'NOT_FOUND_IN_GRANT',\n `Workspace ${JSON.stringify(String(value).slice(0, 100))} is not one this sign-in granted. Granted: ${grantedNames(principal)}.`);\n }\n return matches[0];\n}\n\n/**\n * The one workspace a tool call acts in: the top-level `workspace`, `workspaceName` or\n * `workspaceId` inputs (all must name the same granted workspace), else the only granted\n * workspace, else none (the issuer then asks for one, or the route is company-level).\n */\nexport function selectWorkspace(args: unknown, principal: HostedPrincipal,\n keys: readonly string[] = [WORKSPACE_INPUT, 'workspaceName', 'workspaceId']): GrantedWorkspace | undefined {\n let target: GrantedWorkspace | undefined;\n if (args && typeof args === 'object' && !Array.isArray(args)) {\n for (const key of keys) {\n const value = (args as Record<string, unknown>)[key];\n if (value === undefined) continue;\n const found = findGranted(principal, key, value);\n if (target && target.id !== found.id) {\n throw new WorkspaceSelectionError(400, 'WORKSPACE_CONFLICT',\n 'These inputs name different workspaces. Name one workspace for this call.');\n }\n target = found;\n }\n }\n const granted = grantedWorkspaces(principal);\n return target ?? (granted.length === 1 ? granted[0] : undefined);\n}\n\n/** True when a workspace selector appears anywhere in the arguments, raw passthroughs included. */\nexport function namesWorkspace(value: unknown): boolean {\n if (!value || typeof value !== 'object') return false;\n return Object.entries(value).some(([key, child]) =>\n ([WORKSPACE_INPUT, 'workspaceName', 'workspaceId'].includes(key) && child !== undefined) || namesWorkspace(child));\n}\n\n/** What one tool call is bound to, beyond the principal. */\nexport interface ArgumentBinding {\n /** The call's workspace (selectWorkspace). Every workspace selector must name it. */\n target?: GrantedWorkspace;\n /**\n * The call runs as a delegated OAuth request: the issuer checks any tenant id against its\n * route policy on that very call, as it does for the app user, and refuses a route that\n * claims none. Without it (a PAT, a non-delegating adapter) a tenant needs `tenantIds`.\n */\n delegated?: boolean;\n /** Top-level keys holding data rather than selectors (query rows), never scanned. */\n opaque?: readonly string[];\n}\n\nfunction tenantAllowed(child: unknown, principal: HostedPrincipal, delegated: boolean): boolean {\n if (typeof child !== 'string') return false;\n if (principal.tenantIds) return principal.tenantIds.includes(child);\n return delegated && principal.credentialType === 'oauth';\n}\n\n/**\n * Reject conflicting selectors even in raw body passthroughs before a handler can use them.\n * A workspace selector must name the call's target when it has one, else a granted workspace.\n */\nexport function assertBoundArguments(value: unknown, principal: HostedPrincipal, binding: ArgumentBinding = {}): void {\n if (!value || typeof value !== 'object') return;\n const { opaque, ...nested } = binding;\n for (const [key, child] of Object.entries(value)) {\n if (opaque?.includes(key) && !Array.isArray(value)) continue;\n if (key === 'workspaceId' || key === 'workspaceName') {\n const target = binding.target;\n if (target ? child !== (key === 'workspaceId' ? target.id : target.name) : !findGranted(principal, key, child)) {\n throw new HostedAuthError(403, 'principal_mismatch');\n }\n }\n if (['companyId', 'userId'].includes(key) && child !== principal[key as 'companyId' | 'userId']) {\n throw new HostedAuthError(403, 'principal_mismatch');\n }\n if (['scope', 'scopes', 'authUser', 'actorKind', 'credentialType', 'oauthClientId', 'operatorEmail',\n 'session_variables', 'isAllClient', 'isAdmin', 'serviceToken', 'apiToken', 'authorization'].includes(key)) {\n throw new HostedAuthError(403, 'identity_argument_forbidden');\n }\n if (['clientId', 'tenantId'].includes(key) && !tenantAllowed(child, principal, binding.delegated === true)) {\n throw new HostedAuthError(403, 'tenant_not_authorized');\n }\n assertBoundArguments(child, principal, nested);\n }\n}\n","import { parseStudioStream } from '../custom-chart/studio.js';\nimport { z } from 'zod';\nimport { MCP_SERVER_VERSION } from '../version.js';\nimport { toolContext } from '../utils/tool-context.js';\nimport { principalSchema } from './auth.js';\nimport { parseErrorBody } from '../client/error-body.js';\nimport { HostedAuthError } from './types.js';\nimport type { HostedAdapter, HostedConfig, HostedDelegation, HostedErrorDetail } from './types.js';\n\nconst USER_AGENT = `databrain-mcp-server/${MCP_SERVER_VERSION} (hosted)`;\nconst REQUEST_TIMEOUT_MS = 30_000;\n/** Request headers a tool may set; nothing that can carry or replace a credential. */\nconst FORWARDED_HEADERS = new Set(['idempotency-key']);\n/** The call's workspace, for the issuer's delegated path. Only this adapter sets it. */\nexport const WORKSPACE_HEADER = 'X-Databrain-Workspace';\n/**\n * 403 codes whose reason is about the caller's own grant in a workspace it named and holds,\n * so they reveal nothing about other workspaces or companies. Every other 403 stays opaque.\n */\nconst GRANT_DENIALS = new Set(['ROLE_NOT_ALLOWED', 'WORKSPACE_CREATE_NOT_ALLOWED']);\n\n/** Code and message only, as short plain text; never other body fields or the bearer. */\nfunction errorDetail(status: number, text: string, bearer: string | undefined): HostedErrorDetail {\n let parsed: unknown;\n try { parsed = JSON.parse(text); } catch { return { status }; }\n const envelope = parseErrorBody(parsed);\n // A dashboard write the API refuses tile by tile (422 `{tileVerdicts}`) carries no\n // envelope; its problems are the only reason a tool can act on.\n const tiles = tileProblems(parsed);\n const code = envelope.code ?? (tiles ? 'TILE_VALIDATION_FAILED' : undefined);\n const message = envelope.message ?? tiles;\n const clean = (value: string | undefined, max: number) => {\n if (!value) return undefined;\n let out = value.replace(/[\\u0000-\\u001f\\u007f]+/g, ' ').trim();\n if (bearer) out = out.split(bearer).join('[redacted]');\n return out.slice(0, max) || undefined;\n };\n // Long enough for the issuer's list of up to 20 granted workspace names.\n return { status, code: code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined,\n message: clean(message, 1200) };\n}\n\n/** \"Tile t1: problem; problem. Tile t2: ...\" for the tiles a dashboard write refused. */\nfunction tileProblems(body: unknown): string | undefined {\n const verdicts = (body as { tileVerdicts?: unknown } | null)?.tileVerdicts;\n if (!Array.isArray(verdicts)) return undefined;\n const lines = verdicts.flatMap((verdict) => {\n const { id, ok, problems } = (verdict ?? {}) as { id?: unknown; ok?: unknown; problems?: unknown };\n if (ok !== false || !Array.isArray(problems)) return [];\n const words = problems.filter((p): p is string => typeof p === 'string');\n return words.length ? [`Tile ${typeof id === 'string' ? id : '?'}: ${words.join(' ')}`] : [];\n });\n return lines.length ? `The dashboard was not saved. ${lines.join(' ')}` : undefined;\n}\n\n/** BE-3's documented principal endpoint is the trust boundary for opaque OAuth tokens. */\nexport function createProviderAdapter(config: HostedConfig): HostedAdapter {\n const issuer = new URL(config.issuer);\n if (issuer.protocol !== 'https:' || issuer.username || issuer.password || issuer.search || issuer.hash) {\n throw new Error('Invalid hosted authorization server configuration.');\n }\n const apiBase = config.issuer.replace(/\\/$/, '');\n const endpoint = `${apiBase}/oauth/principal`;\n const publicOrigin = new URL(config.publicUrl).origin;\n // Handle -> bearer, for this process only. A handle is unreadable and dies with its request.\n const bearers = new WeakMap<HostedDelegation, string>();\n const responseSchema = principalSchema.extend({ issuer: z.literal(config.issuer) }).strip();\n return {\n async verifyOAuth(token, resource) {\n try {\n // Only the explicitly configured issuer ever receives the token: here to verify it,\n // and in executeRequest for its delegated REST path. No redirects, other origins,\n // cache or service-token fallback.\n const response = await fetch(endpoint, {\n headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },\n redirect: 'error', signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) throw new HostedAuthError(response.status >= 500 ? 503 : 401, 'invalid_token');\n const { issuer: _issuer, ...principal } = responseSchema.parse(await response.json());\n if (principal.resource !== resource || principal.credentialType !== 'oauth') throw new HostedAuthError(401, 'invalid_token');\n return principal;\n } catch (error) {\n if (error instanceof HostedAuthError) throw error;\n throw new HostedAuthError(401, 'invalid_token');\n }\n },\n async resolvePatGrant() { throw new HostedAuthError(401, 'pat_membership_adapter_required'); },\n delegate(token) {\n const handle: HostedDelegation = Object.freeze({ kind: 'hosted-delegation' as const });\n bearers.set(handle, token);\n return handle;\n },\n async executeRequest(options, principal, delegation, workspace) {\n // The backend's delegated path re-verifies this bearer for this resource on every\n // call, then binds workspace, objects, role and audit. Only the configured issuer\n // receives it, exactly as for /oauth/principal. A guest-token call carries only the\n // guest token the backend minted for this user's earlier, authorized call.\n const bearer = delegation ? bearers.get(delegation) : undefined;\n if (options.auth !== 'none' && !bearer) throw new HostedAuthError(503, 'delegated_backend_adapter_required');\n if (options.auth === 'none' && !options.token) throw new HostedAuthError(403, 'tool_not_authorized');\n if (!options.path.startsWith('/') || options.path.startsWith('//')) throw new HostedAuthError(403, 'tool_not_authorized');\n const url = new URL(`${apiBase}${options.path}`);\n if (url.origin !== issuer.origin) throw new HostedAuthError(403, 'tool_not_authorized');\n for (const [key, value] of Object.entries(options.query ?? {})) url.searchParams.set(key, value);\n const headers: Record<string, string> = {\n Accept: 'application/json', 'Content-Type': 'application/json', 'User-Agent': USER_AGENT,\n 'X-Databrain-Client': USER_AGENT,\n };\n for (const [key, value] of Object.entries(options.headers ?? {})) {\n if (FORWARDED_HEADERS.has(key.toLowerCase())) headers[key] = value;\n }\n const tool = toolContext.getStore();\n if (tool) {\n headers['X-Databrain-MCP-Tool'] = tool.tool;\n headers['X-Databrain-MCP-Tool-Category'] = tool.category;\n headers['X-Databrain-MCP-Workflow-Intent'] = tool.workflowIntent;\n headers['X-Databrain-MCP-Input-Mode'] = tool.inputMode;\n }\n if (options.auth === 'none') {\n headers['x-plugin-token'] = options.token!;\n headers.Origin = publicOrigin;\n } else {\n headers.Authorization = `Bearer ${bearer}`;\n // Same rule as the bearer: only with it, only to the issuer, and only a workspace\n // this principal holds. The issuer re-checks it against the live grant.\n if (workspace) {\n if (!principal.grantedWorkspaces?.some(item => item.id === workspace.id) &&\n principal.workspaceId !== workspace.id) throw new HostedAuthError(403, 'principal_mismatch');\n headers[WORKSPACE_HEADER] = workspace.id;\n }\n }\n const response = await fetch(url, {\n method: options.method, headers, redirect: 'error',\n body: options.body === undefined ? undefined : JSON.stringify(options.body),\n signal: AbortSignal.timeout(options.timeoutMs ?? REQUEST_TIMEOUT_MS),\n });\n const text = await response.text();\n if (!response.ok) {\n // A wrong-workspace object is a 404, like a missing one. 401, 403 and 5xx keep the\n // status class only. Other 4xx keep the backend's code and message (a validation\n // reason, `layer_not_found`), so a tool can recover as it does on stdio.\n const forbidden = response.status === 403 ? errorDetail(403, text, bearer) : undefined;\n if (forbidden?.code && GRANT_DENIALS.has(forbidden.code)) {\n throw new HostedAuthError(403, 'delegated_request_denied', forbidden);\n }\n const status = response.status === 403 && options.responseFormat === 'studio-sse' ? 403 : response.status === 401 ? 401 : response.status >= 500 ? 503\n : response.status === 403 || response.status === 404 ? 404 : 400;\n const detail = status === 401 || status === 503 || response.status === 403 ? undefined\n : errorDetail(response.status, text, bearer);\n throw new HostedAuthError(status, 'delegated_request_denied', detail);\n }\n try { return options.responseFormat === 'studio-sse' ? parseStudioStream(text) : text ? JSON.parse(text) : null; }\n catch { throw new HostedAuthError(503, 'delegated_response_invalid'); }\n },\n };\n}\n"],"mappings":"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","names":["z","z","z","z"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@databrainhq/mcp-server",
3
3
  "mcpName": "com.usedatabrain/mcp-server",
4
- "version": "0.3.0-rc.10",
4
+ "version": "0.3.0-rc.11",
5
5
  "description": "MCP server for Databrain embedded analytics - connect databases, create datamarts, configure embeds, and generate frontend code",
6
6
  "type": "module",
7
7
  "bin": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/apps/registry.ts","../src/apps/backend.ts","../src/apps/tile.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { DatabrainClient } from '../client/databrain-client.js';\nimport { DatabrainApiError } from '../utils/errors.js';\nimport { READ_ONLY } from '../tools/annotations.js';\nimport { toolContext } from '../utils/tool-context.js';\nimport { createChartBackend, type ChartBackend, type ChartAction } from './backend.js';\nimport { APP_MIME, CHART_URI, KPI_URI, pageSchema, referenceSchema, viewSchema } from './contract.js';\nimport { appMeta, chartResult } from './feature.js';\nimport { tileHtml } from './tile.js';\nimport { draftSchema, draftPage, RENDER_TYPES, saveDraftAsDashboard, workspacesForDatamart } from './preview.js';\nimport { rowsSchema } from './contract.js';\nimport { WRITE_CREATE } from '../tools/annotations.js';\nimport { APPLY_TO_PRODUCTION_CONFIRMATION } from '../tools/widgets/production-guards.js';\n\nconst actions: Record<ChartAction, string> = {\n render_chart: 'Render one authorized chart page as SVG with point metadata. Cursor pagination stays within the same guest scope.',\n refresh_widget: 'Refresh a chart in the current guest scope. Revalidates access before returning data.',\n drill_down: 'Explore a server-issued drill identifier within the current chart and guest scope.',\n change_date_range: 'Render a chart for an ordered date range within the current guest scope.',\n};\n\n/** Call after registerAllTools so existing registration policy/instrumentation remains active. */\nexport function registerChartApps(server: McpServer, client: DatabrainClient, backend: ChartBackend = createChartBackend(client)): void {\n for (const [name, description] of Object.entries(actions)) {\n const action = name as ChartAction;\n const tool = server.tool(name, description, {\n reference: referenceSchema, view: viewSchema.default({}),\n context: z.string().max(200).optional().describe('Chart interaction context'),\n }, READ_ONLY, async ({ reference, view }) => {\n try {\n if ((action === 'drill_down' && !view.drillId) || (action === 'change_date_range' && !view.dateRange)) throw new Error('Missing view selector');\n const page = pageSchema.parse(await toolContext.run({ tool: name, category: 'embedding', workflowIntent: 'chart_app', inputMode: 'app' },\n () => backend.execute(action, reference, view)));\n // SVG is rendered as an isolated image, never inserted into the HTML DOM.\n // Reject active/external/animated constructs as a further contract check.\n if (page.svg && (!/^\\s*<svg\\b/i.test(page.svg) ||\n /<\\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(page.svg))) {\n throw new Error('Unsafe SVG');\n }\n return chartResult(`${page.kind === 'kpi' ? 'KPI' : 'Chart'} ready. ${page.rows.length} rows in this page.`, {\n ...page, reference, view,\n });\n } catch {\n return { isError: true, content: [{ type: 'text' as const,\n text: 'Chart unavailable. Check dashboard access and the chart renderer integration, then retry.' }] };\n }\n });\n tool.update({ _meta: appMeta(['app']) });\n }\n registerDraftTools(server, client);\n for (const [uri, kind] of [[CHART_URI, 'chart'], [KPI_URI, 'kpi']] as const) {\n server.resource(`Databrain ${kind} tile`, uri, { mimeType: APP_MIME, description: `Interactive ${kind} using host theme and scoped server tools` }, async () => ({\n contents: [{ uri, mimeType: APP_MIME, text: tileHtml(kind), _meta: { ui: { prefersBorder: true } } }],\n }));\n }\n}\n\n/** The tile's controls over an unsaved answer. `preview_chart` is the \"show as\" row and\n * is app-only: the model already has ask_question. `save_chart_as_dashboard` is the commit,\n * visible to both, and gated the way every production write here is gated. */\nfunction registerDraftTools(server: McpServer, client: DatabrainClient): void {\n const preview = server.tool('preview_chart',\n 'Redraw an unsaved ask_question answer as another chart type, from the rows already on screen. No query runs.',\n { draft: draftSchema, rows: rowsSchema, chartType: z.enum(RENDER_TYPES),\n context: z.string().max(200).optional().describe('Chart interaction context') },\n READ_ONLY, async ({ draft, rows, chartType }) => {\n try {\n const page = await draftPage(client, draft, rows, chartType);\n return chartResult(`${page.kind === 'table' ? 'Table' : 'Chart'} ready as ${chartType}.`, page);\n } catch {\n return { isError: true, content: [{ type: 'text' as const, text: 'Chart preview unavailable. Ask the question again to retry.' }] };\n }\n });\n preview.update({ _meta: appMeta(['app']) });\n\n const save = server.tool('save_chart_as_dashboard',\n 'Save an ask_question answer: creates a workspace metric from its SQL and a new dashboard holding it. ' +\n `Production write; requires confirm: \"${APPLY_TO_PRODUCTION_CONFIRMATION}\". ` +\n 'A workspaceName is needed when the datamart backs more than one workspace.',\n { draft: draftSchema, chartType: z.enum(RENDER_TYPES),\n dashboardTitle: z.string().trim().min(1).max(200),\n workspaceName: z.string().trim().min(1).max(256).optional(),\n attemptId: z.string().trim().min(1).max(64).optional()\n .describe('Identifies one save attempt. Reuse it to retry safely; omit or change it to save again on purpose.'),\n confirm: z.string().optional(),\n context: z.string().max(200).optional().describe('Chart interaction context') },\n WRITE_CREATE, async ({ draft, chartType, dashboardTitle, workspaceName, attemptId, confirm }) => {\n if (confirm !== APPLY_TO_PRODUCTION_CONFIRMATION) {\n return { isError: true, content: [{ type: 'text' as const,\n text: `Saving writes to production. Retry with confirm: \"${APPLY_TO_PRODUCTION_CONFIRMATION}\".` }] };\n }\n // The draft's workspace list is a hint for the tile. The caller wrote it, so the\n // set a save may target is recomputed here from the datamart, on the server's own credential.\n const choices = await workspacesForDatamart(client, draft.datamartName, 'authorize');\n const refuse = (text: string) => ({ isError: true, content: [{ type: 'text' as const, text }],\n structuredContent: { kind: 'save', ok: false, choices } });\n // A named workspace must be one the draft's datamart can serve, spelled the draft's way.\n const target = workspaceName\n ? choices.find(w => w.trim().toLowerCase() === workspaceName.toLowerCase())\n : choices.length === 1 ? choices[0] : undefined;\n if (workspaceName && !target) {\n return refuse(`Workspace \"${workspaceName}\" cannot run this datamart's SQL. Choose one of: ${choices.join(', ')}.`);\n }\n if (!target) {\n return refuse(choices.length\n ? `Choose a workspace: ${choices.join(', ')}.`\n : `No workspace is built on datamart \"${draft.datamartName}\". Create one in Databrain, then save again.`);\n }\n try {\n const saved = await saveDraftAsDashboard(client, draft, { chartType, dashboardTitle, workspaceName: target, attemptId });\n return chartResult(`Saved \"${saved.title}\" to workspace ${saved.workspaceName} (dashboard ${saved.dashboardId}, metric ${saved.metricId}).`,\n { kind: 'save', ok: true, ...saved, draft: { ...draft, chartType } });\n } catch (error) {\n const code = error instanceof DatabrainApiError ? error.code : 'SAVE_FAILED';\n return { isError: true, content: [{ type: 'text' as const,\n text: `Could not save (${code}). Check the workspace name and your access, then try again.` }],\n structuredContent: { kind: 'save', ok: false, code } };\n }\n });\n save.update({ _meta: appMeta(['model', 'app']) });\n}\n","import { DatabrainClient } from '../client/databrain-client.js';\nimport type { RequestOptions } from '../client/types.js';\nimport { createServiceBundle } from '../services/factory.js';\nimport { pageSchema, referenceSchema, viewSchema, type ChartPage, type ChartReference, type ChartView } from './contract.js';\n\nexport const RENDER_PATH = '/api/v2/dashboard/mcp-chart';\nexport type ChartAction = 'render_chart' | 'refresh_widget' | 'drill_down' | 'change_date_range';\nexport interface ChartBackend {\n execute(action: ChartAction, reference: ChartReference, view: ChartView): Promise<ChartPage>;\n}\n\nclass UncachedChartClient extends DatabrainClient {\n constructor(private readonly delegate: DatabrainClient) {\n super({ apiUrl: delegate.getApiUrl(), demoDomain: delegate.getDemoDomain() });\n }\n override request<T>(options: RequestOptions): Promise<T> {\n return this.delegate.request<T>({ ...options, cacheKey: undefined, cacheTtlMs: undefined });\n }\n}\n\n/** Proposed backend route; no local renderer, ambient service fallback, or UI credentials. */\nexport function createChartBackend(client: DatabrainClient): ChartBackend {\n // The general embed lookup caches for five minutes. Authorization must not.\n const services = createServiceBundle(new UncachedChartClient(client));\n return {\n async execute(action, rawReference, rawView) {\n const reference = referenceSchema.parse(rawReference);\n const view = viewSchema.parse(rawView);\n // Every page/refresh/drill re-reads the embed live, so a revoked tenant stops working at once.\n const app = await services.widgetService.resolveDataAppName(undefined, reference.embedId);\n if (!app.ok) throw new Error('Chart scope unavailable');\n const scope = await services.widgetService.ensureReadableDashboardScope({\n embedId: reference.embedId, clientId: reference.clientId, dataAppName: app.value });\n if (!scope.ok) throw new Error('Chart scope denied');\n const { token } = await services.guestTokenService.generateToken({\n clientId: reference.clientId, dataAppName: app.value, allowedEmbeds: [reference.embedId],\n permissions: { isDisableMetricCreation: true, isEnableManageMetrics: false,\n isEnableMetricUpdation: false, isEnableArchiveMetrics: false },\n });\n if (!token) throw new Error('Guest scope unavailable');\n const page = await client.request<unknown>({\n method: 'POST', path: RENDER_PATH, auth: 'none', token,\n body: { action, embedId: reference.embedId, widgetId: reference.widgetId, ...view,\n pageSize: 50, width: 600, height: 320, animation: false },\n skipDataUnwrap: true,\n });\n return pageSchema.parse(page);\n },\n };\n}\n","/** No iframe imports. All requests use the host bridge. */\nexport const TILE_SCRIPT = String.raw`(()=>{\nconst $=id=>document.getElementById(id),root=document.documentElement,pending=new Map();\nlet seq=0,origin='*',ready=false,closed=false,ref=null,view={},next=null,context={},busy=false,revision=0,rawSvg=null,dialect=null,shown=false,draft=null,rows=[],saved=null,attempt='',workspace=null;\n/* One lifecycle, two sets of names. Hosts ship either or both, so notifications go out\n under both and arrive under either. Same order in both rows: result, input, context. */\nconst DASH=['ui/notifications/tool-result','ui/notifications/tool-input','ui/notifications/host-context-changed'],\nSNAKE=['notifications/tools/call/result','notifications/tools/call/input','notifications/host_context/changed'],\nINIT=['ui/notifications/initialized','notifications/initialized'],\nSIZE=['ui/notifications/size-changed','notifications/size_changed'],\nDISPLAY=['ui/request-display-mode','requestDisplayMode'];\nfunction send(message){if(!closed)parent.postMessage({jsonrpc:'2.0',...message},origin)}\nfunction notify(names,params){for(const method of names)send({method,params})}\nfunction rpc(method,params){return new Promise((resolve,reject)=>{const id=++seq;\nconst timer=setTimeout(()=>{pending.delete(id);reject(Error('timeout'))},65000);\npending.set(id,{resolve,reject,timer});send({id,method,params})})}\n/* Try each alias while the host says it never heard of it; any other error stands. */\nasync function ask(names,params){let last;\nfor(const method of names){try{return await rpc(method,params)}catch(error){last=error;if(error.code!==-32601)throw error}}\nthrow last}\n/* Fixed codes only; never log customer rows. */\nfunction trouble(code){send({method:'notifications/error',params:{code}})}\n/* One formatter for rail, table, KPI and answer line. Compact past 10K, grouped past 100,\n two decimals below; null is an absence, so it prints as a dash and never as 0. */\nfunction fmt(v){if(typeof v!=='number')return v==null||v===''?'\\u2014':String(v);const a=Math.abs(v);\nreturn new Intl.NumberFormat(navigator.language||'en',a>=1e4?{notation:'compact',maximumFractionDigits:1}:a>=100?{maximumFractionDigits:0}:{maximumFractionDigits:2}).format(v)}\nfunction exact(el,v){el.title=typeof v==='number'?String(v):''}\n/* The answer line: what this shows, and the headline value. Gated on carrying a number or\n a count, so it never renders as a second title or an empty reserved slot. */\nfunction answer(p){if(p.description)return p.description;const bits=[];\nif(p.kind==='dashboard'){const n=p.widgets?.length||0;return n?n+(n===1?' chart':' charts')+' on this dashboard':''}\nconst [dim,measure]=p.columns||[];const pts=(p.points||[]).filter(q=>typeof q.value==='number');\nif(pts.length<2)return '';if(dim&&measure&&!p.draft)bits.push(measure+' by '+dim);\nconst top=pts.reduce((a,b)=>b.value>a.value?b:a);bits.push(top.label.split(' \\u2014 ')[0]+' highest at '+fmt(top.value));return bits.join(' \\u00b7 ')}\nfunction status(text,bad,ok){const s=$('status');s.textContent=text;s.toggleAttribute('data-error',!!bad);s.toggleAttribute('data-ok',!!ok);if(bad)$('plot').dataset.state='off'}\nfunction paint(){const img=$('chart-image');if(!img||!rawSvg)return;\nconst c=getComputedStyle(document.body).color,mix=pc=>'color-mix(in srgb,'+c+' '+pc+'%,transparent)';\nimg.src='data:image/svg+xml,'+encodeURIComponent(rawSvg.replace(/<\\/svg>\\s*$/i,'<style>text{fill:'+mix(72)+'!important}'\n+'[stroke=\"#dbdee4\"],[stroke=\"#f1f1f2\" i]{stroke:'+mix(13)+'!important}[stroke=\"#54555a\"],[stroke=\"#d4d4d8\" i]{stroke:'+mix(30)+'!important}'\n+'</style></svg>'))}\nfunction controls(){document.querySelectorAll('button,input,select').forEach(e=>{e.disabled=busy});\n$('refresh').hidden=!ref;$('next').hidden=!next;$('range').hidden=!ref;$('plot').toggleAttribute('data-busy',busy);\n$('save').hidden=!draft||!!saved||!$('form').hidden;\n$('expand').hidden=!shown||root.dataset.kind==='dashboard'||!context.availableDisplayModes?.includes('fullscreen');\nroot.toggleAttribute('data-deep',context.displayMode==='fullscreen'||$('expand').hidden)}\nfunction host(change){context={...context,...change};if(change.theme==='dark'||change.theme==='light')root.style.colorScheme=change.theme;\nconst vars=change.styles?.variables||{};for(const [key,value] of Object.entries(vars)){\nif(/^--(?:color-|font-|border-|shadow-)/.test(key)&&typeof value==='string'&&!/url\\s*\\(|@import/i.test(value))root.style.setProperty(key,value)}\nconst face=change.styles?.css?.fonts;if(typeof face==='string'&&face.length<4000&&!/@import|javascript:/i.test(face))$('hostfont').textContent=face;\nconst insets=change.safeAreaInsets;if(insets)for(const edge of ['top','right','bottom','left']){\nconst n=insets[edge];if(Number.isFinite(n))root.style.setProperty('--safe-'+edge,Math.max(0,Math.min(n,200))+'px')}\nroot.dataset.mode=context.displayMode==='fullscreen'?'full':'inline';\n$('expand').textContent=context.displayMode==='fullscreen'?'Back to conversation':'Expand';paint();controls()}\nfunction scope(p,n){const bits=[];\nif(p.kind==='chart'||p.kind==='table')bits.push(p.rowsTotal>n?'first '+n+' of '+p.rowsTotal+' rows':n?(n===1?'1 row':n+' rows'):'No rows in this view');\nif(view.dateRange)bits.push(view.dateRange.from+' to '+view.dateRange.to);\nif(next)bits.push('more pages available');\n$('scope').textContent=bits.join(' · ')}\nfunction resize(){if(ready)notify(SIZE,{width:document.body.scrollWidth,height:document.body.scrollHeight})}\nfunction table(rows,columns){$('rows').replaceChildren();$('data').hidden=!rows?.length;if(!rows?.length)return;\n$('count').textContent=rows.length===1?'1 row behind this':rows.length+' rows behind this';\nconst t=document.createElement('table');\nconst num=name=>rows.some(r=>typeof r[name]==='number');\nconst header=document.createElement('tr');for(const name of columns){const th=document.createElement('th');th.scope='col';th.textContent=name;if(num(name))th.className='n';header.append(th)}\nconst head=document.createElement('thead');head.append(header);t.append(head);const body=document.createElement('tbody');\nfor(const row of rows.slice(0,50)){const tr=document.createElement('tr');for(const key of columns){const td=document.createElement('td');td.textContent=fmt(row[key]);exact(td,row[key]);if(num(key))td.className='n';tr.append(td)}body.append(tr)}t.append(body);$('rows').append(t)}\nasync function display(mode){try{const answer=await ask(DISPLAY,{mode});\nhost({displayMode:answer.mode||context.displayMode});if(root.dataset.mode==='full'&&!$('data').hidden)$('data').open=true;resize()}\ncatch{status('Expand is unavailable in this host.',1)}}\nfunction wait(){busy=true;if(!$('plot').firstChild)$('plot').dataset.state='wait';status('Loading chart…');controls()}\nasync function call(name,extra={}){if(!ready||!ref||busy)return;run(name,{reference:ref,view:{...view,...extra}})}\n/* The draft tools take the draft and the rows on screen instead of a reference. */\nasync function run(name,args){if(!ready||busy)return;wait();const rev=++revision;\nconst selection=workspace?(name==='save_chart_as_dashboard'?{workspaceName:args.workspaceName||workspace.name}:{workspace:workspace.id}):{};\ntry{const result=await rpc('tools/call',{name,arguments:{...args,...selection,context:'tile'}});\nif(rev===revision)render(result)}catch{if(rev===revision){trouble('tool-call-failed');if(name==='save_chart_as_dashboard'){$('plot').dataset.state='ready';status('Could not save. Try again.',1)}else if(name==='preview_chart'){status('Could not draw that. Pick another type.',1);$('plot').dataset.state='ready'}else stale()}}finally{busy=false;controls()}}\nfunction stale(){if(shown&&$('plot').firstChild){status('Could not refresh. Showing the last chart that loaded. Select Refresh to try again.',1);$('plot').dataset.state='stale'}else status('Could not load the chart. Select Refresh to try again.',1)}\nfunction render(result){busy=false;controls();\nif(result?._meta?.['databrain/workspace'])workspace=result._meta['databrain/workspace'];\nconst sp=result?.structuredContent;if(sp&&sp.kind==='save'){$('plot').dataset.state='ready';if(sp.ok){saved=sp;$('form').hidden=true;status('Saved \"'+sp.title+'\" to workspace '+sp.workspaceName+'.',0,1)}\nelse if(sp.choices?.length){choose(sp.choices);status('Choose a workspace, then save again.',1)}\nelse status((result.content?.[0]?.text)||'Could not save. Try again.',1);controls();resize();return}\nif(result?.isError&&draft&&$('form').hidden===false){$('plot').dataset.state='ready';status((result.content?.[0]?.text)||'Could not save. Try again.',1);controls();return}\nif(result?.isError){trouble('tool-returned-error');\nif(shown&&$('plot').firstChild){stale();controls();return}shown=false;controls();\nstatus(ref?'Chart unavailable. Check your access to this dashboard, then Refresh.'\n:'Chart unavailable. Ask again to retry.',1);return}\nconst p=result?.structuredContent;if(!p||typeof p!=='object'){shown=false;controls();status('No chart data came back for this result.',1);return}\nrevision++;shown=true;rawSvg=null;$('plot').replaceChildren();$('plot').dataset.state='ready';$('choices').replaceChildren();delete $('rail').dataset.value;\nref=p.reference||null;view=p.view||{};next=p.nextCursor||null;draft=p.draft||null;rows=p.rows||[];saved=null;$('form').hidden=true;\n$('title').textContent=p.title||'Analytics';$('description').textContent=answer(p);root.dataset.kind=p.kind;$('choices').toggleAttribute('data-grid',p.kind==='dashboard');\nif(p.kind==='dashboard'){$('plot').dataset.state='off';\nfor(const item of p.widgets||[]){const b=document.createElement('button');b.textContent=item.name||'Untitled widget';b.onclick=()=>{ref=item.reference;view={};call('render_chart')};$('choices').append(b)}\nstatus(p.widgets?.length?'':'No charts on this dashboard yet. Add one in Databrain.');\n}else if(p.kind==='kpi'){\nconst number=document.createElement('p');number.className='kpi';number.textContent=fmt(p.value);exact(number,p.value);$('plot').append(number);status('');\n}else if(p.svg){\nconst box=document.createElement('div');box.className='chart';box.style.aspectRatio=p.width+'/'+p.height;\nconst img=document.createElement('img');img.id='chart-image';rawSvg=p.svg;img.alt=p.title||'Chart';img.onerror=()=>{status('The chart could not be drawn. The rows are below.',1);$('data').open=$('data').toggleAttribute('data-only',true)};box.append(img);\nfor(const point of p.points||[]){const b=document.createElement('button');b.className='point';b.setAttribute('aria-label',point.label+': '+fmt(point.value));b.setAttribute('aria-pressed','false');\nb.style.left=100*point.x/p.width+'%';b.style.top=100*point.y/p.height+'%';b.style.width=100*point.width/p.width+'%';b.style.height=100*point.height/p.height+'%';\nconst hover=()=>{$('vlabel').textContent=point.label;$('vnum').textContent=fmt(point.value);exact($('vnum'),point.value);$('rail').dataset.value='1'};b.onmouseenter=hover;b.onfocus=hover;\nb.onclick=()=>{box.querySelectorAll('.point').forEach(e=>e.setAttribute('aria-pressed','false'));b.setAttribute('aria-pressed','true');hover();\n$('choices').replaceChildren();if(point.drillId){const drill=document.createElement('button');drill.className='primary';drill.textContent='Explore selected data';drill.onclick=()=>call('drill_down',{drillId:point.drillId,cursor:undefined});$('choices').append(drill)}};box.append(b)}$('plot').append(box);\nstatus('');\n}else if(p.kind==='table'){\n// ask_question returns a table by design; calling that a missing chart called success a failure.\n$('plot').dataset.state='off';status('');\n}else{$('plot').dataset.state='off';\nstatus('Nothing to show. Try a wider date range, then Refresh.',1)}\nif(draft&&p.previewTypes?.length){for(const t of p.previewTypes){const b=document.createElement('button');b.textContent=t[0].toUpperCase()+t.slice(1);b.className='chip';b.setAttribute('aria-pressed',String(t===draft.chartType));\nb.onclick=()=>run('preview_chart',{draft,rows,chartType:t});$('choices').append(b)}$('choices').dataset.chips='1'}else delete $('choices').dataset.chips;\nif(p.previewError)status('Cannot draw this as '+(p.draft?.chartType||'a chart')+' ('+p.previewError+'). Showing the rows.',1);\npaint();table(p.rows||[],p.columns||Object.keys(p.rows?.[0]||{}));scope(p,p.rows?.length||0);\nconst only=!p.svg&&p.value===undefined&&p.kind!=='dashboard';$('data').open=only;$('data').toggleAttribute('data-only',only);controls();resize()}\n$('refresh').onclick=()=>call('refresh_widget',{cursor:undefined});$('next').onclick=()=>call('render_chart',{cursor:next});\nfunction choose(names){const w=$('ws');w.replaceChildren();for(const n of names){const o=document.createElement('option');o.value=n;o.textContent=n;w.append(o)}$('wsrow').hidden=names.length<2;$('wsone').textContent=names.length===1?'in workspace '+names[0]:'';$('form').hidden=false;$('dtitle').focus();controls();resize()}\n$('save').onclick=()=>{attempt=crypto.randomUUID();$('dtitle').value=draft?.title||'';choose(draft?.workspaces||[])};\n$('cancel').onclick=()=>{$('form').hidden=true;status('');controls();resize()};\n$('commit').onclick=()=>{const t=$('dtitle').value.trim();if(!t){status('Name the dashboard.',1);return}\nrun('save_chart_as_dashboard',{draft,chartType:draft.chartType,dashboardTitle:t,workspaceName:$('wsrow').hidden?undefined:$('ws').value,attemptId:attempt,confirm:'APPLY_TO_PRODUCTION'})};\n$('expand').onclick=()=>display(context.displayMode==='fullscreen'?'inline':'fullscreen');\n$('dates').onclick=()=>{const from=$('from').value,to=$('to').value;if(!from||!to||from>to){status('Start date must not be after the end date.',1);return}call('change_date_range',{dateRange:{from,to},cursor:undefined,drillId:undefined})};\nfunction receive(event){if(event.source!==parent||closed)return;const m=event.data;\nif(!m||Array.isArray(m)||m.jsonrpc!=='2.0'||(origin!=='*'&&event.origin!==origin))return;\nif(!m.method&&pending.has(m.id)&&('result'in m||'error'in m)){if(origin==='*'&&event.origin!=='null')origin=event.origin;\nconst p=pending.get(m.id);pending.delete(m.id);clearTimeout(p.timer);\nif(m.error){const error=Error('host error');error.code=m.error.code;p.reject(error)}else p.resolve(m.result);return}\nif(m.method==='ping'&&m.id!==undefined){send({id:m.id,result:{}});return}\nif(m.method==='ui/resource-teardown'){send({id:m.id,result:{}});closed=true;revision++;for(const p of pending.values()){clearTimeout(p.timer);p.reject(Error('closed'))}pending.clear();observer.disconnect();removeEventListener('message',receive);return}\nif(!ready||m.id!==undefined)return;\nlet slot=DASH.indexOf(m.method),kind='dashed';\nif(slot<0){slot=SNAKE.indexOf(m.method);kind='snake'}\nif(slot>=0){\n/* A dual host posts each notification twice. Lock to the first name and drop its twin:\n same payload, and a second render is a visible flicker. */\nif(!dialect)dialect=kind;if(dialect!==kind)return;\n// The snake result nests one level deeper.\nif(slot===0)render(kind==='snake'?m.params&&m.params.result:m.params);\nelse if(slot===1)wait();\nelse{host(m.params||{});resize()}\nreturn}\nif(m.method==='ui/notifications/tool-cancelled'){revision++;busy=false;$('plot').dataset.state='off';status('Request cancelled.');controls()}}\naddEventListener('message',receive);const observer=new ResizeObserver(resize);observer.observe(document.body);controls();\nrpc('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'Databrain tile',version:'1.0.0'},appCapabilities:{availableDisplayModes:['inline','fullscreen']}})\n.then(r=>{if(r.protocolVersion!=='2026-01-26')throw Error('version');host(r.hostContext||{});ready=true;notify(INIT,{});resize()})\n.catch(()=>{trouble('initialize-failed');status('This chart needs an MCP Apps host.',1)});\n})();`;\n\n/** Source comments explain the tile; the browser does not need them and the host budget\n * does. Block comments are stripped here, so the served tile stays under the ceiling. */\nconst SERVED_SCRIPT = TILE_SCRIPT.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/^\\s*\\/\\/.*$/gm, '').replace(/\\n{2,}/g, '\\n');\nexport function tileHtml(kind: 'chart' | 'kpi' = 'chart'): string {\n return strip(`<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<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'; base-uri 'none'; form-action 'none'\">\n<title>Databrain ${kind} tile</title><style id=\"hostfont\"></style><style>\n/* Host tokens are the Claude MCP Apps style-variable names. Every one carries a fallback:\n hosts may ship any subset, so nothing here may depend on a token existing. */\n:root{color-scheme:light dark;\n--dbn-bg:var(--color-background-primary,Canvas);--dbn-text:var(--color-text-primary,CanvasText);\n--dbn-mute:var(--color-text-secondary,GrayText);--dbn-faint:var(--color-text-tertiary,GrayText);\n--dbn-edge:var(--color-border-secondary,color-mix(in srgb,currentColor 20%,transparent));\n--dbn-line:var(--color-border-tertiary,color-mix(in srgb,currentColor 9%,transparent));\n--dbn-accent:var(--color-text-info,LinkText);--dbn-ring:var(--color-ring-primary,var(--dbn-accent));\n--dbn-bad:var(--color-text-danger,var(--dbn-text));\n--dbn-quiet:color-mix(in srgb,currentColor 6%,transparent);\n--bw:var(--border-width-regular,1px);--r-s:var(--border-radius-sm,6px);--r-m:var(--border-radius-md,8px);\n--med:var(--font-weight-medium,500);--semi:var(--font-weight-semibold,600);--xs:var(--font-text-xs-size,12px);\n/* One curve. Quick / standard / display-mode. Corporate: no overshoot. */\n--e:cubic-bezier(.2,0,0,1);--q:120ms;--s:300ms;--l:400ms}\n*{box-sizing:border-box}\nbody{margin:0;padding:var(--safe-top,0px) var(--safe-right,0px) var(--safe-bottom,0px) var(--safe-left,0px);background:var(--dbn-bg);color:var(--dbn-text);\nfont:var(--font-text-sm-size,14px)/var(--font-text-sm-line-height,1.4) var(--font-sans,system-ui)}\nmain{padding:20px;max-width:1200px;margin:auto;transition:padding var(--l) var(--e)}\nheader{display:flex;align-items:start;justify-content:space-between;gap:12px}header>div{min-width:0}\n.acts{display:flex;gap:6px;flex:none;align-items:center}\nh1{font-size:var(--font-heading-md-size,18px);line-height:var(--font-heading-md-line-height,1.4);margin:0 0 6px;font-weight:var(--semi);letter-spacing:-.01em;overflow-wrap:anywhere;transition:font-size var(--l) var(--e)}\np{margin:6px 0;color:var(--dbn-mute);overflow-wrap:anywhere}\n#description{font-size:var(--xs);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-top:2px}#description:empty{display:none}\nbutton,input,select{font:inherit;color:inherit;background:var(--dbn-bg);border:var(--bw) solid var(--dbn-edge);border-radius:var(--r-s);padding:7px 10px}\nbutton{cursor:pointer;transition:background-color var(--q) var(--e),border-color var(--q) var(--e)}\ninput{max-width:160px;min-width:0}button:hover:not(:disabled){background:var(--dbn-quiet)}button:disabled{color:var(--dbn-faint);cursor:default}\nbutton:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--dbn-ring);outline-offset:2px}\n/* The picker is a scannable grid of quiet chips: 26 charts fit in nine rows, not a wall. */\n#choices[data-grid]{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:6px;margin-top:12px}\n#choices[data-grid] button{text-align:left;border-color:var(--dbn-line);padding:8px 10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}\n#choices[data-grid] button:hover:not(:disabled){background:var(--dbn-quiet);border-color:var(--dbn-edge)}\n/* One primary per state: the drill pill, and Next page when there is a next page. */\n#choices button.primary,#next{flex:none;text-align:center;border:var(--bw) solid var(--dbn-accent);\nborder-radius:var(--r-s);padding:7px 10px;font-weight:var(--med);color:var(--dbn-accent)}\n#choices button.primary{align-self:flex-start;margin-top:10px}\n/* Refresh is a re-fetch, not a choice. Quiet it so it stops reading as a fifth chart. */\n#refresh{border-color:transparent;color:var(--dbn-mute);padding:7px 8px}\n#refresh:hover:not(:disabled){color:var(--dbn-text);background:none;text-decoration:underline}\n#status:empty{margin:0}\n#status[data-ok],#status[data-error]{font-weight:var(--med);border-left:2px solid;border-radius:0 var(--r-m) var(--r-m) 0;padding:10px 12px;margin:10px 0;background:color-mix(in srgb,currentColor 5%,transparent)}\n#status[data-ok]{color:var(--color-text-success,var(--dbn-text));border-color:var(--color-border-success,color-mix(in srgb,currentColor 40%,transparent))}\n/* \"Show as\": one row of quiet chips, the current type pressed. Never a second title. */\n#choices[data-chips]{flex-direction:row;flex-wrap:wrap;gap:6px;margin-top:10px}\n.chip{border-color:var(--dbn-line);border-radius:999px;padding:5px 11px;font-size:var(--xs);color:var(--dbn-mute)}\n.chip:hover:not(:disabled){color:var(--dbn-text);border-color:var(--dbn-edge)}\n.chip[aria-pressed=true]{color:var(--dbn-text);border-color:var(--dbn-text);background:var(--dbn-quiet)}\n/* Save is the one commit on a draft, so it is the one filled button in the tile. */\n#save,#commit{border-color:var(--dbn-accent);background:var(--dbn-accent);color:var(--dbn-bg);font-weight:var(--med)}\n#save:hover:not(:disabled),#commit:hover:not(:disabled){background:color-mix(in srgb,var(--dbn-accent) 85%,var(--dbn-text))}\n#form{margin-top:12px;padding:12px;border:var(--bw) solid var(--dbn-line);border-radius:var(--r-m);display:flex;flex-direction:column;gap:8px}\n[hidden]{display:none!important}#form label{flex-direction:column;align-items:stretch;gap:4px;font-size:var(--xs)}\n#form input,#form select{max-width:none;width:100%;background:color-mix(in srgb,currentColor 4%,var(--dbn-bg))}\n#wsone{color:var(--dbn-mute);font-size:var(--xs)}#wsone:empty{display:none}\n#status[data-error]{color:var(--dbn-bad);border-color:var(--color-border-danger,color-mix(in srgb,currentColor 40%,transparent));background:var(--color-background-danger,color-mix(in srgb,currentColor 5%,transparent))}\n/* Reserved plot. The skeleton is a static bar silhouette matching the 15:8 backend render:\n Claude's guidance asks for a skeleton that matches the final layout, and bans nothing here,\n but shimmer is banned by our own motion budget - so this paints once and never repaints. */\n#plot{position:relative;transition:opacity var(--q) var(--e)}#plot[data-busy],#plot[data-state=stale]{opacity:.55}\n#plot[data-state=wait]{aspect-ratio:15/8}\n#plot[data-state=wait]::before,#plot[data-state=ready]::before{content:'';position:absolute;pointer-events:none;inset:16% 6% 13%;\nborder-bottom:var(--bw) solid var(--dbn-line);transition:opacity var(--s) var(--e);\n--g:linear-gradient(var(--dbn-quiet),var(--dbn-quiet));background:var(--g) 0 100%/11% 44% no-repeat,var(--g) 22% 100%/11% 71% no-repeat,var(--g) 44% 100%/11% 57% no-repeat,var(--g) 66% 100%/11% 86% no-repeat,var(--g) 88% 100%/11% 33% no-repeat}\n#plot[data-state=ready]::before{opacity:0}\n.chart{position:relative;width:100%;margin:12px 0}.chart img{width:100%;height:100%;display:block}\n/* A point's geometry comes from the renderer and can be 8px; the hit area must not.\n ::after pads it to at least 24px; ::before is the marker that says \"you can pick this\". */\n.point{position:absolute;padding:0;border:0;background:transparent;border-radius:50%}\n.point::after{content:'';position:absolute;inset:-8px}\n.point::before{content:'';position:absolute;left:50%;top:50%;width:8px;height:8px;margin:-4px;border-radius:50%;\nbackground:var(--dbn-accent);box-shadow:0 0 0 2px var(--dbn-bg);opacity:0;transform:scale(.6);transition:opacity var(--q) var(--e),transform var(--q) var(--e)}\n.point:hover::before,.point:focus-visible::before{opacity:.7;transform:none}\n.point[aria-pressed=true]::before{opacity:1;transform:none;box-shadow:0 0 0 2px var(--dbn-bg),0 0 0 4px color-mix(in srgb,var(--dbn-accent) 35%,transparent)}\n.kpi{font-size:clamp(34px,10vw,44px);line-height:1.1;font-weight:var(--semi);letter-spacing:-.025em;font-variant-numeric:tabular-nums;color:var(--dbn-text);margin:14px 0 6px;transition:font-size var(--l) var(--e)}\n/* The readout rail. At rest it states the scope of what is on screen; on selection it\n becomes the value. One slot, two jobs, so selecting a bar never reflows the tile. */\n#rail{display:flex;align-items:center;min-height:2.5em;margin:2px 0 0}\n#scope,#value{margin:0;display:none;overflow-wrap:anywhere}\n#rail:not([data-value]) #scope{display:block;color:var(--dbn-mute);font-size:var(--xs)}\n#rail:not([data-value]):has(#scope:empty){display:none}\n#rail[data-value] #value{display:flex;flex-direction:column;gap:1px;line-height:1.15}\n#vlabel{color:var(--dbn-mute);font-size:var(--xs)}\n#vnum{font-size:var(--font-heading-md-size,18px);font-weight:var(--semi);letter-spacing:-.015em;font-variant-numeric:tabular-nums}\n[data-mode=full] #vnum{font-size:var(--font-heading-xl-size,24px)}\nfooter,.dates{display:flex;gap:8px;flex-wrap:wrap;align-items:center}\n#choices{display:flex;flex-direction:column;align-items:stretch;margin:10px 0 0}footer{margin-top:12px}footer:not(:has(button:not([hidden]))){display:none}.dates{margin:8px 0 2px}\nlabel{display:flex;align-items:center;gap:6px;color:var(--dbn-mute)}\n#range{margin-top:10px}#data{margin-top:2px}\nhtml:not([data-deep]) #range,html:not([data-deep]) #data:not([data-only]){display:none}\nsummary{cursor:pointer;color:var(--dbn-mute);font-size:var(--xs);padding:5px 0;width:fit-content;transition:color var(--q) var(--e)}summary:hover{color:var(--dbn-text)}\n#rows{overflow:auto;max-height:260px;border-radius:var(--r-s)}\ntable{border-collapse:collapse;width:100%;font-size:var(--xs)}\nth,td{text-align:left;padding:8px;border-bottom:var(--bw) solid var(--dbn-line);overflow-wrap:anywhere}\ntd.n,th.n{text-align:right;font-variant-numeric:tabular-nums}\nth{font-weight:var(--semi);position:sticky;top:0;background:var(--dbn-bg);color:var(--dbn-mute);\nfont-size:var(--xs);letter-spacing:.02em;text-transform:uppercase}\n/* Inline to fullscreen is a layout morph, not a new screen: main widens over 400ms on the\n one curve and every dependent box follows it. Fullscreen is the detailed view, so the\n table loses its inline height cap. */\n/* The SVG is rendered at 600x320 and scaled by width, so an unbounded fullscreen column\n makes the chart's own axis type larger than the tile's heading. Hold a measure. */\n[data-mode=full] main{padding:28px 32px;max-width:900px}\n[data-mode=full] h1{font-size:var(--font-heading-xl-size,24px);line-height:var(--font-heading-xl-line-height,1.25)}\n[data-mode=full] .kpi{font-size:clamp(44px,7vw,72px)}\n[data-mode=full] #rows{max-height:none}\n/* Touch: the host gives vertical pan to the conversation, so a nested scroller is a trap. */\n@media(pointer:coarse){button,input{min-height:44px}summary{padding:12px 0}#rows{max-height:none;overflow:visible}}\n@media(max-width:480px){main{padding:14px}}\n/* Entrance. Content first, chrome 60ms later: 300+60 = 360ms, inside the 400ms budget.\n No stagger past two groups - a chat tile that cascades reads as lag, not luxury. */\n@keyframes rise{from{opacity:0;transform:translateY(6px)}}\n@keyframes land{from{opacity:0;transform:translateY(4px) scale(.995)}}\nheader,#status,#plot{animation:rise var(--s) var(--e) both}\n#rail,#choices,footer,#range,#data{animation:rise var(--s) var(--e) 60ms both}\n.chart,.kpi{animation:land var(--s) var(--e) both}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}\n</style></head><body><main id=\"tile\"><header><div><h1 id=\"title\">Analytics</h1><p id=\"description\"></p></div><div class=\"acts\"><button id=\"refresh\" hidden>Refresh</button><button id=\"expand\" hidden>Expand</button></div></header>\n<p id=\"status\" role=\"status\">Loading chart…</p><div id=\"plot\" data-state=\"wait\"></div>\n<div id=\"rail\"><p id=\"scope\"></p><p id=\"value\"><span id=\"vlabel\"></span><b id=\"vnum\"></b></p></div><div id=\"choices\"></div>\n<footer><button id=\"save\" hidden>Save as dashboard</button><button id=\"next\" hidden>Next page</button></footer>\n<form id=\"form\" hidden onsubmit=\"return false\"><label>Dashboard name<input id=\"dtitle\" type=\"text\" maxlength=\"200\" autocomplete=\"off\"></label>\n<label id=\"wsrow\" hidden>Workspace<select id=\"ws\"></select></label><p id=\"wsone\"></p>\n<div class=\"dates\"><button id=\"commit\" type=\"button\">Save to Databrain</button><button id=\"cancel\" type=\"button\">Cancel</button></div></form>\n<details id=\"range\" hidden><summary>Change the date range</summary><div class=\"dates\"><label>From <input id=\"from\" type=\"date\"></label><label>To <input id=\"to\" type=\"date\"></label><button id=\"dates\" type=\"button\">Apply dates</button></div></details>\n<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>`);\n}\nfunction strip(html: string): string {\n return html.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\n{2,}/g, '\\n')\n .replace(/<style>([\\s\\S]*?)<\\/style><\\/head>/, (_m, css: string) => `<style>${css.replace(/;}/g, '}').replace(/\\n/g, '')}</style></head>`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS;;;ACKX,IAAM,cAAc;AAM3B,IAAM,sBAAN,cAAkC,gBAAgB;AAAA,EAChD,YAA6B,UAA2B;AACtD,UAAM,EAAE,QAAQ,SAAS,UAAU,GAAG,YAAY,SAAS,cAAc,EAAE,CAAC;AADjD;AAAA,EAE7B;AAAA,EACS,QAAW,SAAqC;AACvD,WAAO,KAAK,SAAS,QAAW,EAAE,GAAG,SAAS,UAAU,QAAW,YAAY,OAAU,CAAC;AAAA,EAC5F;AACF;AAGO,SAAS,mBAAmB,QAAuC;AAExE,QAAM,WAAW,oBAAoB,IAAI,oBAAoB,MAAM,CAAC;AACpE,SAAO;AAAA,IACL,MAAM,QAAQ,QAAQ,cAAc,SAAS;AAC3C,YAAM,YAAY,gBAAgB,MAAM,YAAY;AACpD,YAAM,OAAO,WAAW,MAAM,OAAO;AAErC,YAAM,MAAM,MAAM,SAAS,cAAc,mBAAmB,QAAW,UAAU,OAAO;AACxF,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,yBAAyB;AACtD,YAAM,QAAQ,MAAM,SAAS,cAAc,6BAA6B;AAAA,QACtE,SAAS,UAAU;AAAA,QAAS,UAAU,UAAU;AAAA,QAAU,aAAa,IAAI;AAAA,MAAM,CAAC;AACpF,UAAI,CAAC,MAAM,GAAI,OAAM,IAAI,MAAM,oBAAoB;AACnD,YAAM,EAAE,MAAM,IAAI,MAAM,SAAS,kBAAkB,cAAc;AAAA,QAC/D,UAAU,UAAU;AAAA,QAAU,aAAa,IAAI;AAAA,QAAO,eAAe,CAAC,UAAU,OAAO;AAAA,QACvF,aAAa;AAAA,UAAE,yBAAyB;AAAA,UAAM,uBAAuB;AAAA,UACnE,wBAAwB;AAAA,UAAO,wBAAwB;AAAA,QAAM;AAAA,MACjE,CAAC;AACD,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,yBAAyB;AACrD,YAAM,OAAO,MAAM,OAAO,QAAiB;AAAA,QACzC,QAAQ;AAAA,QAAQ,MAAM;AAAA,QAAa,MAAM;AAAA,QAAQ;AAAA,QACjD,MAAM;AAAA,UAAE;AAAA,UAAQ,SAAS,UAAU;AAAA,UAAS,UAAU,UAAU;AAAA,UAAU,GAAG;AAAA,UAC3E,UAAU;AAAA,UAAI,OAAO;AAAA,UAAK,QAAQ;AAAA,UAAK,WAAW;AAAA,QAAM;AAAA,QAC1D,gBAAgB;AAAA,MAClB,CAAC;AACD,aAAO,WAAW,MAAM,IAAI;AAAA,IAC9B;AAAA,EACF;AACF;;;AChDO,IAAM,cAAc,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsJlC,IAAM,gBAAgB,YAAY,QAAQ,qBAAqB,EAAE,EAAE,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,WAAW,IAAI;AAChH,SAAS,SAAS,OAAwB,SAAiB;AAChE,SAAO,MAAM;AAAA;AAAA,mBAEI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oLA4H6J,aAAa,yBAAyB;AAC1N;AACA,SAAS,MAAM,MAAsB;AACnC,SAAO,KAAK,QAAQ,qBAAqB,EAAE,EAAE,QAAQ,WAAW,IAAI,EACjE,QAAQ,sCAAsC,CAAC,IAAI,QAAgB,UAAU,IAAI,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE,CAAC,iBAAiB;AAC7I;;;AF7QA,IAAM,UAAuC;AAAA,EAC3C,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,mBAAmB;AACrB;AAGO,SAAS,kBAAkB,QAAmB,QAAyB,UAAwB,mBAAmB,MAAM,GAAS;AACtI,aAAW,CAAC,MAAM,WAAW,KAAK,OAAO,QAAQ,OAAO,GAAG;AACzD,UAAM,SAAS;AACf,UAAM,OAAO,OAAO,KAAK,MAAM,aAAa;AAAA,MAC1C,WAAW;AAAA,MAAiB,MAAM,WAAW,QAAQ,CAAC,CAAC;AAAA,MACvD,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,IAC9E,GAAG,WAAW,OAAO,EAAE,WAAW,KAAK,MAAM;AAC3C,UAAI;AACF,YAAK,WAAW,gBAAgB,CAAC,KAAK,WAAa,WAAW,uBAAuB,CAAC,KAAK,UAAY,OAAM,IAAI,MAAM,uBAAuB;AAC9I,cAAM,OAAO,WAAW,MAAM,MAAM,YAAY;AAAA,UAAI,EAAE,MAAM,MAAM,UAAU,aAAa,gBAAgB,aAAa,WAAW,MAAM;AAAA,UACrI,MAAM,QAAQ,QAAQ,QAAQ,WAAW,IAAI;AAAA,QAAC,CAAC;AAGjD,YAAI,KAAK,QAAQ,CAAC,cAAc,KAAK,KAAK,GAAG,KAC3C,kLAAkL,KAAK,KAAK,GAAG,IAAI;AACnM,gBAAM,IAAI,MAAM,YAAY;AAAA,QAC9B;AACA,eAAO,YAAY,GAAG,KAAK,SAAS,QAAQ,QAAQ,OAAO,WAAW,KAAK,KAAK,MAAM,uBAAuB;AAAA,UAC3G,GAAG;AAAA,UAAM;AAAA,UAAW;AAAA,QACtB,CAAC;AAAA,MACH,QAAQ;AACN,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC;AAAA,UAAE,MAAM;AAAA,UACxC,MAAM;AAAA,QAA4F,CAAC,EAAE;AAAA,MACzG;AAAA,IACF,CAAC;AACD,SAAK,OAAO,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AAAA,EACzC;AACA,qBAAmB,QAAQ,MAAM;AACjC,aAAW,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,WAAW,OAAO,GAAG,CAAC,SAAS,KAAK,CAAC,GAAY;AAC3E,WAAO,SAAS,aAAa,IAAI,SAAS,KAAK,EAAE,UAAU,UAAU,aAAa,eAAe,IAAI,4CAA4C,GAAG,aAAa;AAAA,MAC/J,UAAU,CAAC,EAAE,KAAK,UAAU,UAAU,MAAM,SAAS,IAAI,GAAG,OAAO,EAAE,IAAI,EAAE,eAAe,KAAK,EAAE,EAAE,CAAC;AAAA,IACtG,EAAE;AAAA,EACJ;AACF;AAKA,SAAS,mBAAmB,QAAmB,QAA+B;AAC5E,QAAM,UAAU,OAAO;AAAA,IAAK;AAAA,IAC1B;AAAA,IACA;AAAA,MAAE,OAAO;AAAA,MAAa,MAAM;AAAA,MAAY,WAAW,EAAE,KAAK,YAAY;AAAA,MACpE,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,IAAE;AAAA,IAChF;AAAA,IAAW,OAAO,EAAE,OAAO,MAAM,UAAU,MAAM;AAC/C,UAAI;AACF,cAAM,OAAO,MAAM,UAAU,QAAQ,OAAO,MAAM,SAAS;AAC3D,eAAO,YAAY,GAAG,KAAK,SAAS,UAAU,UAAU,OAAO,aAAa,SAAS,KAAK,IAAI;AAAA,MAChG,QAAQ;AACN,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,8DAA8D,CAAC,EAAE;AAAA,MACpI;AAAA,IACF;AAAA,EAAC;AACH,UAAQ,OAAO,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AAE1C,QAAM,OAAO,OAAO;AAAA,IAAK;AAAA,IACvB,6IACwC,gCAAgC;AAAA,IAExE;AAAA,MAAE,OAAO;AAAA,MAAa,WAAW,EAAE,KAAK,YAAY;AAAA,MAClD,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,MAChD,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,MAC1D,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,EAClD,SAAS,oGAAoG;AAAA,MAChH,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,IAAE;AAAA,IAChF;AAAA,IAAc,OAAO,EAAE,OAAO,WAAW,gBAAgB,eAAe,WAAW,QAAQ,MAAM;AAC/F,UAAI,YAAY,kCAAkC;AAChD,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC;AAAA,UAAE,MAAM;AAAA,UACxC,MAAM,qDAAqD,gCAAgC;AAAA,QAAK,CAAC,EAAE;AAAA,MACvG;AAGA,YAAM,UAAU,MAAM,sBAAsB,QAAQ,MAAM,cAAc,WAAW;AACnF,YAAM,SAAS,CAAC,UAAkB;AAAA,QAAE,SAAS;AAAA,QAAM,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC;AAAA,QAC1F,mBAAmB,EAAE,MAAM,QAAQ,IAAI,OAAO,QAAQ;AAAA,MAAE;AAE1D,YAAM,SAAS,gBACX,QAAQ,KAAK,OAAK,EAAE,KAAK,EAAE,YAAY,MAAM,cAAc,YAAY,CAAC,IACxE,QAAQ,WAAW,IAAI,QAAQ,CAAC,IAAI;AACxC,UAAI,iBAAiB,CAAC,QAAQ;AAC5B,eAAO,OAAO,cAAc,aAAa,oDAAoD,QAAQ,KAAK,IAAI,CAAC,GAAG;AAAA,MACpH;AACA,UAAI,CAAC,QAAQ;AACX,eAAO,OAAO,QAAQ,SAClB,uBAAuB,QAAQ,KAAK,IAAI,CAAC,MACzC,sCAAsC,MAAM,YAAY,8CAA8C;AAAA,MAC5G;AACA,UAAI;AACF,cAAM,QAAQ,MAAM,qBAAqB,QAAQ,OAAO,EAAE,WAAW,gBAAgB,eAAe,QAAQ,UAAU,CAAC;AACvH,eAAO;AAAA,UAAY,UAAU,MAAM,KAAK,kBAAkB,MAAM,aAAa,eAAe,MAAM,WAAW,YAAY,MAAM,QAAQ;AAAA,UACrI,EAAE,MAAM,QAAQ,IAAI,MAAM,GAAG,OAAO,OAAO,EAAE,GAAG,OAAO,UAAU,EAAE;AAAA,QAAC;AAAA,MACxE,SAAS,OAAO;AACd,cAAM,OAAO,iBAAiB,oBAAoB,MAAM,OAAO;AAC/D,eAAO;AAAA,UAAE,SAAS;AAAA,UAAM,SAAS,CAAC;AAAA,YAAE,MAAM;AAAA,YACxC,MAAM,mBAAmB,IAAI;AAAA,UAA+D,CAAC;AAAA,UAC7F,mBAAmB,EAAE,MAAM,QAAQ,IAAI,OAAO,KAAK;AAAA,QAAE;AAAA,MACzD;AAAA,IACF;AAAA,EAAC;AACH,OAAK,OAAO,EAAE,OAAO,QAAQ,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;AAClD;","names":[]}