@kaelio/ktx 0.15.0 → 0.16.0

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.
Files changed (72) hide show
  1. package/assets/python/kaelio_ktx-0.16.0-py3-none-any.whl +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/setup-commands.js +1 -0
  5. package/dist/connection-drivers.d.ts +1 -1
  6. package/dist/connection-drivers.js +2 -0
  7. package/dist/connection.js +1 -0
  8. package/dist/connectors/athena/connector.d.ts +160 -0
  9. package/dist/connectors/athena/connector.js +378 -0
  10. package/dist/connectors/athena/dialect.d.ts +31 -0
  11. package/dist/connectors/athena/dialect.js +146 -0
  12. package/dist/connectors/athena/live-database-introspection.d.ts +10 -0
  13. package/dist/connectors/athena/live-database-introspection.js +24 -0
  14. package/dist/connectors/duckdb/connector.d.ts +60 -0
  15. package/dist/connectors/duckdb/connector.js +282 -0
  16. package/dist/connectors/duckdb/dialect.d.ts +31 -0
  17. package/dist/connectors/duckdb/dialect.js +155 -0
  18. package/dist/connectors/duckdb/federated-attach.js +7 -0
  19. package/dist/connectors/duckdb/federated-executor.js +7 -13
  20. package/dist/connectors/duckdb/live-database-introspection.d.ts +8 -0
  21. package/dist/connectors/duckdb/live-database-introspection.js +24 -0
  22. package/dist/connectors/shared/duckdb-json-safe.d.ts +3 -0
  23. package/dist/connectors/shared/duckdb-json-safe.js +12 -0
  24. package/dist/context/connections/connection-type-dialect.d.ts +2 -0
  25. package/dist/context/connections/connection-type-dialect.js +20 -0
  26. package/dist/context/connections/connection-type.d.ts +6 -15
  27. package/dist/context/connections/connection-type.js +1 -10
  28. package/dist/context/connections/dialects.js +4 -0
  29. package/dist/context/connections/drivers.js +38 -0
  30. package/dist/context/connections/federation.d.ts +1 -1
  31. package/dist/context/connections/federation.js +6 -5
  32. package/dist/context/connections/local-warehouse-descriptor.d.ts +1 -0
  33. package/dist/context/connections/local-warehouse-descriptor.js +10 -0
  34. package/dist/context/connections/project-sql-executor.d.ts +24 -0
  35. package/dist/context/connections/project-sql-executor.js +56 -0
  36. package/dist/context/connections/query-policy.d.ts +12 -0
  37. package/dist/context/connections/query-policy.js +46 -0
  38. package/dist/context/core/config-reference.js +11 -1
  39. package/dist/context/daemon/semantic-layer-compute.d.ts +16 -0
  40. package/dist/context/daemon/semantic-layer-compute.js +44 -2
  41. package/dist/context/ingest/adapters/looker/mapping.d.ts +1 -0
  42. package/dist/context/ingest/adapters/looker/mapping.js +3 -10
  43. package/dist/context/ingest/adapters/looker/types.d.ts +6 -15
  44. package/dist/context/ingest/adapters/metabase/mapping.d.ts +1 -0
  45. package/dist/context/ingest/adapters/metabase/mapping.js +1 -0
  46. package/dist/context/mcp/context-tools.js +1 -0
  47. package/dist/context/mcp/local-project-ports.d.ts +1 -1
  48. package/dist/context/mcp/local-project-ports.js +53 -51
  49. package/dist/context/project/config.d.ts +92 -0
  50. package/dist/context/project/driver-schemas.d.ts +46 -0
  51. package/dist/context/project/driver-schemas.js +8 -0
  52. package/dist/context/scan/local-scan.js +3 -1
  53. package/dist/context/scan/types.d.ts +1 -1
  54. package/dist/context/sl/local-query.js +23 -6
  55. package/dist/context/sl/semantic-layer.service.d.ts +0 -4
  56. package/dist/context/sl/semantic-layer.service.js +3 -20
  57. package/dist/context/sl/tools/sl-warehouse-validation.d.ts +1 -1
  58. package/dist/context/sl/tools/sl-warehouse-validation.js +2 -2
  59. package/dist/context/sl/types.d.ts +1 -0
  60. package/dist/context/sql-analysis/dialect-notes.d.ts +1 -1
  61. package/dist/context/sql-analysis/dialect-notes.js +3 -2
  62. package/dist/context/sql-analysis/dialect.js +1 -0
  63. package/dist/context/sql-analysis/dialects/athena.md +12 -0
  64. package/dist/context/sql-analysis/dialects/duckdb.md +10 -0
  65. package/dist/local-adapters.js +17 -0
  66. package/dist/setup-databases.d.ts +1 -1
  67. package/dist/setup-databases.js +52 -2
  68. package/dist/sql.js +9 -14
  69. package/dist/status-project.js +2 -1
  70. package/dist/telemetry/events.d.ts +1 -1
  71. package/package.json +3 -1
  72. package/assets/python/kaelio_ktx-0.15.0-py3-none-any.whl +0 -0
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { resolve } from 'node:path';
4
+ import { KtxExpectedError } from '../../errors.js';
4
5
  /** @internal */
5
6
  export function resolveKtxHomePath(path) {
6
7
  if (path === '~') {
@@ -22,7 +23,16 @@ export function resolveKtxConfigReference(value, env) {
22
23
  }
23
24
  if (value.startsWith('file:')) {
24
25
  const filePath = resolveKtxHomePath(value.slice('file:'.length).trim());
25
- const fileValue = readFileSync(filePath, 'utf8').trim();
26
+ let fileValue;
27
+ try {
28
+ fileValue = readFileSync(filePath, 'utf8').trim();
29
+ }
30
+ catch (error) {
31
+ // A `file:` secret whose target is missing or unreadable is a config
32
+ // problem (secrets/ is gitignored, so absent after a clone), not a ktx
33
+ // fault — classify it so it stays out of Error Tracking.
34
+ throw new KtxExpectedError(`Could not read the secret file referenced in ktx.yaml: ${filePath}`, { cause: error });
35
+ }
26
36
  return fileValue.length > 0 ? fileValue : undefined;
27
37
  }
28
38
  const trimmed = value.trim();
@@ -1,4 +1,20 @@
1
1
  import type { ResolvedSemanticLayerSource, SemanticLayerQueryInput } from '../sl/types.js';
2
+ /**
3
+ * A ktx-daemon compute call failed. `inputRejected` is true when the daemon
4
+ * refused the request as invalid (a caller-driven outcome) rather than faulting;
5
+ * callers can promote those to an expected KtxQueryError while letting genuine
6
+ * daemon crashes reach Error Tracking. `detail` is the daemon's own message,
7
+ * unwrapped for surfacing to the caller.
8
+ */
9
+ export declare class KtxDaemonComputeError extends Error {
10
+ readonly inputRejected: boolean;
11
+ readonly detail: string;
12
+ constructor(message: string, options: {
13
+ inputRejected: boolean;
14
+ detail: string;
15
+ cause?: unknown;
16
+ });
17
+ }
2
18
  interface KtxSemanticLayerComputeQueryResult {
3
19
  sql: string;
4
20
  dialect: string;
@@ -2,6 +2,41 @@ import { request as httpRequest } from 'node:http';
2
2
  import { request as httpsRequest } from 'node:https';
3
3
  import { URL } from 'node:url';
4
4
  import { spawn } from 'node:child_process';
5
+ // The daemon signals "the caller sent a well-formed request I refused as
6
+ // invalid" with a distinct subprocess exit code (INPUT_REJECTED_EXIT_CODE in
7
+ // python/ktx-daemon/__main__.py) or HTTP 400. Kept in sync across the boundary.
8
+ const DAEMON_INPUT_REJECTED_EXIT_CODE = 3;
9
+ const DAEMON_INPUT_REJECTED_HTTP_STATUS = 400;
10
+ /**
11
+ * A ktx-daemon compute call failed. `inputRejected` is true when the daemon
12
+ * refused the request as invalid (a caller-driven outcome) rather than faulting;
13
+ * callers can promote those to an expected KtxQueryError while letting genuine
14
+ * daemon crashes reach Error Tracking. `detail` is the daemon's own message,
15
+ * unwrapped for surfacing to the caller.
16
+ */
17
+ export class KtxDaemonComputeError extends Error {
18
+ inputRejected;
19
+ detail;
20
+ constructor(message, options) {
21
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
22
+ this.name = 'KtxDaemonComputeError';
23
+ this.inputRejected = options.inputRejected;
24
+ this.detail = options.detail;
25
+ }
26
+ }
27
+ /** The daemon reports HTTP failures as `{ "detail": "…" }`; fall back to the raw body. */
28
+ function daemonHttpErrorDetail(raw) {
29
+ try {
30
+ const parsed = JSON.parse(raw);
31
+ if (parsed && typeof parsed === 'object' && typeof parsed.detail === 'string') {
32
+ return parsed.detail;
33
+ }
34
+ }
35
+ catch {
36
+ // Non-JSON body (e.g. a proxy error page) — surface it verbatim.
37
+ }
38
+ return raw;
39
+ }
5
40
  function parseJsonObject(raw, subcommand) {
6
41
  const parsed = JSON.parse(raw);
7
42
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
@@ -25,7 +60,11 @@ function runProcessJson(options) {
25
60
  const stdoutText = Buffer.concat(stdout).toString('utf8').trim();
26
61
  const stderrText = Buffer.concat(stderr).toString('utf8').trim();
27
62
  if (code !== 0) {
28
- reject(new Error(`ktx-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`));
63
+ const detail = stderrText || `exit code ${code}`;
64
+ reject(new KtxDaemonComputeError(`ktx-daemon ${subcommand} failed: ${detail}`, {
65
+ inputRejected: code === DAEMON_INPUT_REJECTED_EXIT_CODE,
66
+ detail,
67
+ }));
29
68
  return;
30
69
  }
31
70
  try {
@@ -60,7 +99,10 @@ function postJson(baseUrl) {
60
99
  const text = Buffer.concat(chunks).toString('utf8');
61
100
  const statusCode = response.statusCode ?? 0;
62
101
  if (statusCode < 200 || statusCode >= 300) {
63
- reject(new Error(`ktx-daemon HTTP ${path} failed with ${statusCode}: ${text}`));
102
+ reject(new KtxDaemonComputeError(`ktx-daemon HTTP ${path} failed with ${statusCode}: ${text}`, {
103
+ inputRejected: statusCode === DAEMON_INPUT_REJECTED_HTTP_STATUS,
104
+ detail: daemonHttpErrorDetail(text),
105
+ }));
64
106
  return;
65
107
  }
66
108
  try {
@@ -10,6 +10,7 @@ declare const LOOKER_DIALECT_TO_CONNECTION_TYPE: {
10
10
  readonly sqlite: "SQLITE";
11
11
  readonly sqlserver: "SQLSERVER";
12
12
  readonly clickhouse: "CLICKHOUSE";
13
+ readonly awsathena: "ATHENA";
13
14
  };
14
15
  /** @internal */
15
16
  export type LookerWarehouseTargetConnectionType = (typeof LOOKER_DIALECT_TO_CONNECTION_TYPE)[keyof typeof LOOKER_DIALECT_TO_CONNECTION_TYPE];
@@ -1,3 +1,4 @@
1
+ import { warehouseTargetDialect } from '../../../connections/connection-type-dialect.js';
1
2
  const LOOKER_DIALECT_TO_CONNECTION_TYPE = {
2
3
  bigquery: 'BIGQUERY',
3
4
  bigquery_standard_sql: 'BIGQUERY',
@@ -7,15 +8,7 @@ const LOOKER_DIALECT_TO_CONNECTION_TYPE = {
7
8
  sqlite: 'SQLITE',
8
9
  sqlserver: 'SQLSERVER',
9
10
  clickhouse: 'CLICKHOUSE',
10
- };
11
- const SQLGLOT_DIALECT_BY_CONNECTION_TYPE = {
12
- BIGQUERY: 'bigquery',
13
- SNOWFLAKE: 'snowflake',
14
- POSTGRESQL: 'postgres',
15
- MYSQL: 'mysql',
16
- SQLITE: 'sqlite',
17
- SQLSERVER: 'tsql',
18
- CLICKHOUSE: 'clickhouse',
11
+ awsathena: 'ATHENA',
19
12
  };
20
13
  export async function discoverLookerConnections(client) {
21
14
  return client.listLookerConnections();
@@ -29,7 +22,7 @@ export function lookerDialectToConnectionType(dialect) {
29
22
  }
30
23
  /** @internal */
31
24
  export function sqlglotDialectForConnectionType(connectionType) {
32
- return SQLGLOT_DIALECT_BY_CONNECTION_TYPE[connectionType] ?? null;
25
+ return warehouseTargetDialect(connectionType);
33
26
  }
34
27
  /** @internal */
35
28
  export function validateLookerWarehouseTarget(connectionType) {
@@ -12,27 +12,18 @@ export declare const lookerPullConfigSchema: z.ZodObject<{
12
12
  lookUpdatedSince: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
13
13
  connectionMappings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
14
14
  connectionTypes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
15
- PLAIN: "PLAIN";
15
+ POSTGRESQL: "POSTGRESQL";
16
+ SQLITE: "SQLITE";
17
+ DUCKDB: "DUCKDB";
18
+ SQLSERVER: "SQLSERVER";
16
19
  BIGQUERY: "BIGQUERY";
17
20
  SNOWFLAKE: "SNOWFLAKE";
18
- MYSQL: "MYSQL";
19
- SQLSERVER: "SQLSERVER";
20
- SQLITE: "SQLITE";
21
- CLICKHOUSE: "CLICKHOUSE";
22
- POSTGRESQL: "POSTGRESQL";
23
- CENTRALREACH: "CENTRALREACH";
24
- EPIC: "EPIC";
25
- CERNER: "CERNER";
26
21
  ATHENA: "ATHENA";
27
- QUICKBOOKS: "QUICKBOOKS";
28
- WORKDAY: "WORKDAY";
29
- REST: "REST";
30
- S3: "S3";
31
- SLACK: "SLACK";
32
22
  METABASE: "METABASE";
33
23
  LOOKER: "LOOKER";
34
24
  NOTION: "NOTION";
35
- BETTERSTACK: "BETTERSTACK";
25
+ MYSQL: "MYSQL";
26
+ CLICKHOUSE: "CLICKHOUSE";
36
27
  }>>>;
37
28
  parsedTargetTables: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
38
29
  ok: z.ZodLiteral<true>;
@@ -7,6 +7,7 @@ export declare const METABASE_ENGINE_TO_CONNECTION_TYPE: {
7
7
  readonly snowflake: "SNOWFLAKE";
8
8
  readonly sqlserver: "SQLSERVER";
9
9
  readonly mysql: "MYSQL";
10
+ readonly athena: "ATHENA";
10
11
  };
11
12
  export interface DiscoveredMetabaseDatabase {
12
13
  id: number;
@@ -6,6 +6,7 @@ export const METABASE_ENGINE_TO_CONNECTION_TYPE = {
6
6
  snowflake: 'SNOWFLAKE',
7
7
  sqlserver: 'SQLSERVER',
8
8
  mysql: 'MYSQL',
9
+ athena: 'ATHENA',
9
10
  };
10
11
  function isRecord(value) {
11
12
  return value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -171,6 +171,7 @@ const connectionListOutputSchema = z.object({
171
171
  connectionType: z.string(),
172
172
  members: z.array(z.string()).optional(),
173
173
  hint: z.string().optional(),
174
+ queryPolicy: z.literal('semantic-layer-only').optional(),
174
175
  })),
175
176
  });
176
177
  const wikiSearchOutputSchema = z.object({
@@ -1,7 +1,7 @@
1
1
  import type { KtxSqlQueryExecutorPort } from '../../context/connections/query-executor.js';
2
2
  import type { KtxProjectConnectionConfig } from '../../context/project/config.js';
3
3
  import type { KtxEmbeddingPort } from '../../context/core/embedding.js';
4
- import type { KtxSemanticLayerComputePort } from '../../context/daemon/semantic-layer-compute.js';
4
+ import { type KtxSemanticLayerComputePort } from '../../context/daemon/semantic-layer-compute.js';
5
5
  import type { KtxLocalProject } from '../../context/project/project.js';
6
6
  import type { LocalScanMcpOptions } from '../../context/scan/local-scan.js';
7
7
  import type { SqlAnalysisPort } from '../../context/sql-analysis/ports.js';
@@ -1,11 +1,11 @@
1
1
  import { KtxExpectedError, KtxQueryError, isNativeProgrammingFault } from '../../errors.js';
2
2
  import { isDatabaseDriver, normalizeConnectionDriver } from '../../connection-drivers.js';
3
3
  import { sqlDialectNotes } from '../../context/sql-analysis/dialect-notes.js';
4
- import { executeProjectReadOnlySql } from '../../context/connections/project-sql-executor.js';
5
- import { FEDERATED_CONNECTION_ID, federatedConnectionListing } from '../../context/connections/federation.js';
6
- import { assertSqlQueryableConnection } from '../../context/connections/dialects.js';
7
- import { resolveConfiguredConnection } from '../../context/connections/resolve-connection.js';
4
+ import { executeProjectRawSql } from '../../context/connections/project-sql-executor.js';
5
+ import { federatedConnectionListing } from '../../context/connections/federation.js';
6
+ import { projectAllowsRawSql, restrictedFederatedMemberIds } from '../../context/connections/query-policy.js';
8
7
  import { localConnectionInfoFromConfig, } from '../../context/connections/local-warehouse-descriptor.js';
8
+ import { KtxDaemonComputeError } from '../../context/daemon/semantic-layer-compute.js';
9
9
  import { createKtxEntityDetailsService } from '../../context/scan/entity-details.js';
10
10
  import { createKtxDiscoverDataService } from '../../context/search/discover.js';
11
11
  import { sqlAnalysisDialectForDriver } from '../../context/sql-analysis/dialect.js';
@@ -15,8 +15,26 @@ import { readLocalSlSource } from '../../context/sl/local-sl.js';
15
15
  import { assertSafeConnectionId } from '../../context/sl/source-files.js';
16
16
  import { assertConfiguredConnectionId } from '../../context/connections/configured-connections.js';
17
17
  import { readLocalKnowledgePage, searchLocalKnowledgePages } from '../wiki/local-knowledge.js';
18
+ /**
19
+ * Reclassify a query-path failure. Warehouse/driver rejections and daemon
20
+ * input-rejections are caller-driven outcomes (KtxQueryError, kept out of Error
21
+ * Tracking while preserving the underlying diagnostics); native JS faults, daemon
22
+ * crashes, and already-expected errors propagate unchanged so genuine ktx bugs
23
+ * still reach Error Tracking.
24
+ */
25
+ function throwClassifiedQueryError(error) {
26
+ if (error instanceof KtxDaemonComputeError) {
27
+ if (error.inputRejected) {
28
+ throw new KtxQueryError(error.detail, { cause: error });
29
+ }
30
+ throw error;
31
+ }
32
+ if (isNativeProgrammingFault(error) || error instanceof KtxExpectedError) {
33
+ throw error;
34
+ }
35
+ throw new KtxQueryError(error instanceof Error ? error.message : String(error), { cause: error });
36
+ }
18
37
  async function executeValidatedReadOnlySql(project, options, input, onProgress) {
19
- await onProgress?.({ progress: 0, message: 'Validating SQL' });
20
38
  if (!options.sqlAnalysis) {
21
39
  throw new Error('sql_execution requires parser-backed SQL validation.');
22
40
  }
@@ -24,50 +42,22 @@ async function executeValidatedReadOnlySql(project, options, input, onProgress)
24
42
  if (!createConnector) {
25
43
  throw new Error('sql_execution requires a local scan connector factory.');
26
44
  }
27
- const isFederated = input.connectionId === FEDERATED_CONNECTION_ID;
28
- const connectionId = isFederated ? input.connectionId : assertSafeConnectionId(input.connectionId);
29
- const connection = isFederated ? undefined : resolveConfiguredConnection(project.config, connectionId);
30
- if (!isFederated) {
31
- assertSqlQueryableConnection(connectionId, connection.driver);
32
- }
33
- const dialect = sqlAnalysisDialectForDriver(isFederated ? 'duckdb' : connection.driver);
34
- const validation = await options.sqlAnalysis.validateReadOnly(input.sql, dialect);
35
- if (!validation.ok) {
36
- // A read-only guard rejecting the agent's SQL is an expected outcome, not a
37
- // ktx fault: classify it so reportException keeps it out of Error Tracking.
38
- throw new KtxQueryError(validation.error ?? 'SQL is not read-only.');
39
- }
40
- await onProgress?.({ progress: 0.3, message: 'Executing' });
41
- const result = await executeProjectReadOnlySql({
45
+ const result = await executeProjectRawSql({
42
46
  project,
43
- input: {
44
- connectionId,
45
- projectDir: project.projectDir,
46
- connection,
47
- sql: input.sql,
48
- maxRows: input.maxRows,
49
- },
47
+ connectionId: input.connectionId,
48
+ sql: input.sql,
49
+ maxRows: input.maxRows,
50
+ sqlAnalysis: options.sqlAnalysis,
50
51
  createConnector,
51
52
  runId: 'mcp-sql-execution',
52
- }).catch((error) => {
53
- // A warehouse/driver rejection (e.g. the agent's SQL failed to compile) is a
54
- // surfaced operational outcome, not a ktx fault: mark it expected while
55
- // preserving the warehouse's own diagnostics. A native JS error (TypeError,
56
- // etc.) signals a bug in connector code — let it propagate unchanged so Error
57
- // Tracking still sees it.
58
- if (isNativeProgrammingFault(error) || error instanceof KtxExpectedError) {
59
- throw error;
60
- }
61
- throw new KtxQueryError(error instanceof Error ? error.message : String(error), { cause: error });
53
+ onProgress,
62
54
  });
63
- const response = {
55
+ return {
64
56
  headers: result.headers,
65
57
  ...(result.headerTypes ? { headerTypes: result.headerTypes } : {}),
66
58
  rows: result.rows,
67
59
  rowCount: result.rowCount ?? result.rows.length,
68
60
  };
69
- await onProgress?.({ progress: 1, message: `Fetched ${response.rowCount} rows` });
70
- return response;
71
61
  }
72
62
  /** @internal Resolves a connection's dialect SQL notes; throws KtxExpectedError for an unknown or non-SQL-warehouse connection. */
73
63
  export function resolveDialectNotesForConnection(connectionId, connection) {
@@ -92,12 +82,16 @@ export function createLocalProjectMcpContextPorts(project, options) {
92
82
  .sort((a, b) => a.id.localeCompare(b.id));
93
83
  const federated = federatedConnectionListing(project.config.connections, project.projectDir);
94
84
  if (federated) {
85
+ const restricted = restrictedFederatedMemberIds(project.config, project.projectDir);
95
86
  configured.push({
96
87
  id: federated.id,
97
88
  name: federated.id,
98
89
  connectionType: 'DUCKDB',
99
90
  members: federated.members,
100
- hint: federated.hint,
91
+ hint: restricted.length > 0
92
+ ? `Federated SQL is disabled: member connection(s) ${restricted.join(', ')} have query_policy: semantic-layer-only.`
93
+ : federated.hint,
94
+ ...(restricted.length > 0 ? { queryPolicy: 'semantic-layer-only' } : {}),
101
95
  });
102
96
  }
103
97
  return configured;
@@ -158,15 +152,20 @@ export function createLocalProjectMcpContextPorts(project, options) {
158
152
  if (!options.semanticLayerCompute) {
159
153
  throw new Error('sl_query requires a semantic-layer query adapter.');
160
154
  }
161
- return compileLocalSlQuery(project, {
162
- connectionId: input.connectionId,
163
- query: input.query,
164
- compute: options.semanticLayerCompute,
165
- execute: Boolean(options.queryExecutor),
166
- maxRows: input.query.limit,
167
- queryExecutor: options.queryExecutor,
168
- onProgress: executionOptions?.onProgress,
169
- });
155
+ try {
156
+ return await compileLocalSlQuery(project, {
157
+ connectionId: input.connectionId,
158
+ query: input.query,
159
+ compute: options.semanticLayerCompute,
160
+ execute: Boolean(options.queryExecutor),
161
+ maxRows: input.query.limit,
162
+ queryExecutor: options.queryExecutor,
163
+ onProgress: executionOptions?.onProgress,
164
+ });
165
+ }
166
+ catch (error) {
167
+ throwClassifiedQueryError(error);
168
+ }
170
169
  },
171
170
  },
172
171
  entityDetails: {
@@ -191,7 +190,10 @@ export function createLocalProjectMcpContextPorts(project, options) {
191
190
  },
192
191
  },
193
192
  };
194
- if (options.sqlAnalysis && options.localScan?.createConnector) {
193
+ // Register sql_execution only when some connection can accept raw SQL; in
194
+ // mixed projects the tool stays and executeProjectRawSql rejects restricted
195
+ // connection ids at request time.
196
+ if (options.sqlAnalysis && options.localScan?.createConnector && projectAllowsRawSql(project.config)) {
195
197
  ports.sqlExecution = {
196
198
  async execute(input, executionOptions) {
197
199
  return executeValidatedReadOnlySql(project, options, input, executionOptions?.onProgress);
@@ -109,36 +109,82 @@ declare const connectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
109
109
  url: z.ZodOptional<z.ZodString>;
110
110
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
111
111
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
112
+ query_policy: z.ZodOptional<z.ZodEnum<{
113
+ "read-only-sql": "read-only-sql";
114
+ "semantic-layer-only": "semantic-layer-only";
115
+ }>>;
112
116
  }, z.core.$loose>, z.ZodObject<{
113
117
  driver: z.ZodLiteral<"mysql">;
114
118
  url: z.ZodOptional<z.ZodString>;
115
119
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
116
120
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
121
+ query_policy: z.ZodOptional<z.ZodEnum<{
122
+ "read-only-sql": "read-only-sql";
123
+ "semantic-layer-only": "semantic-layer-only";
124
+ }>>;
117
125
  }, z.core.$loose>, z.ZodObject<{
118
126
  driver: z.ZodLiteral<"snowflake">;
119
127
  url: z.ZodOptional<z.ZodString>;
120
128
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
121
129
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
130
+ query_policy: z.ZodOptional<z.ZodEnum<{
131
+ "read-only-sql": "read-only-sql";
132
+ "semantic-layer-only": "semantic-layer-only";
133
+ }>>;
122
134
  }, z.core.$loose>, z.ZodObject<{
123
135
  driver: z.ZodLiteral<"bigquery">;
124
136
  url: z.ZodOptional<z.ZodString>;
125
137
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
126
138
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
139
+ query_policy: z.ZodOptional<z.ZodEnum<{
140
+ "read-only-sql": "read-only-sql";
141
+ "semantic-layer-only": "semantic-layer-only";
142
+ }>>;
127
143
  }, z.core.$loose>, z.ZodObject<{
128
144
  driver: z.ZodLiteral<"sqlite">;
129
145
  url: z.ZodOptional<z.ZodString>;
130
146
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
131
147
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
148
+ query_policy: z.ZodOptional<z.ZodEnum<{
149
+ "read-only-sql": "read-only-sql";
150
+ "semantic-layer-only": "semantic-layer-only";
151
+ }>>;
152
+ }, z.core.$loose>, z.ZodObject<{
153
+ driver: z.ZodLiteral<"duckdb">;
154
+ url: z.ZodOptional<z.ZodString>;
155
+ enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
156
+ query_timeout_ms: z.ZodOptional<z.ZodNumber>;
157
+ query_policy: z.ZodOptional<z.ZodEnum<{
158
+ "read-only-sql": "read-only-sql";
159
+ "semantic-layer-only": "semantic-layer-only";
160
+ }>>;
132
161
  }, z.core.$loose>, z.ZodObject<{
133
162
  driver: z.ZodLiteral<"clickhouse">;
134
163
  url: z.ZodOptional<z.ZodString>;
135
164
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
136
165
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
166
+ query_policy: z.ZodOptional<z.ZodEnum<{
167
+ "read-only-sql": "read-only-sql";
168
+ "semantic-layer-only": "semantic-layer-only";
169
+ }>>;
137
170
  }, z.core.$loose>, z.ZodObject<{
138
171
  driver: z.ZodLiteral<"sqlserver">;
139
172
  url: z.ZodOptional<z.ZodString>;
140
173
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
141
174
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
175
+ query_policy: z.ZodOptional<z.ZodEnum<{
176
+ "read-only-sql": "read-only-sql";
177
+ "semantic-layer-only": "semantic-layer-only";
178
+ }>>;
179
+ }, z.core.$loose>, z.ZodObject<{
180
+ driver: z.ZodLiteral<"athena">;
181
+ url: z.ZodOptional<z.ZodString>;
182
+ enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
183
+ query_timeout_ms: z.ZodOptional<z.ZodNumber>;
184
+ query_policy: z.ZodOptional<z.ZodEnum<{
185
+ "read-only-sql": "read-only-sql";
186
+ "semantic-layer-only": "semantic-layer-only";
187
+ }>>;
142
188
  }, z.core.$loose>, z.ZodObject<{
143
189
  driver: z.ZodLiteral<"mongodb">;
144
190
  url: z.ZodString;
@@ -248,36 +294,82 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
248
294
  url: z.ZodOptional<z.ZodString>;
249
295
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
250
296
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
297
+ query_policy: z.ZodOptional<z.ZodEnum<{
298
+ "read-only-sql": "read-only-sql";
299
+ "semantic-layer-only": "semantic-layer-only";
300
+ }>>;
251
301
  }, z.core.$loose>, z.ZodObject<{
252
302
  driver: z.ZodLiteral<"mysql">;
253
303
  url: z.ZodOptional<z.ZodString>;
254
304
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
255
305
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
306
+ query_policy: z.ZodOptional<z.ZodEnum<{
307
+ "read-only-sql": "read-only-sql";
308
+ "semantic-layer-only": "semantic-layer-only";
309
+ }>>;
256
310
  }, z.core.$loose>, z.ZodObject<{
257
311
  driver: z.ZodLiteral<"snowflake">;
258
312
  url: z.ZodOptional<z.ZodString>;
259
313
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
260
314
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
315
+ query_policy: z.ZodOptional<z.ZodEnum<{
316
+ "read-only-sql": "read-only-sql";
317
+ "semantic-layer-only": "semantic-layer-only";
318
+ }>>;
261
319
  }, z.core.$loose>, z.ZodObject<{
262
320
  driver: z.ZodLiteral<"bigquery">;
263
321
  url: z.ZodOptional<z.ZodString>;
264
322
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
265
323
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
324
+ query_policy: z.ZodOptional<z.ZodEnum<{
325
+ "read-only-sql": "read-only-sql";
326
+ "semantic-layer-only": "semantic-layer-only";
327
+ }>>;
266
328
  }, z.core.$loose>, z.ZodObject<{
267
329
  driver: z.ZodLiteral<"sqlite">;
268
330
  url: z.ZodOptional<z.ZodString>;
269
331
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
270
332
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
333
+ query_policy: z.ZodOptional<z.ZodEnum<{
334
+ "read-only-sql": "read-only-sql";
335
+ "semantic-layer-only": "semantic-layer-only";
336
+ }>>;
337
+ }, z.core.$loose>, z.ZodObject<{
338
+ driver: z.ZodLiteral<"duckdb">;
339
+ url: z.ZodOptional<z.ZodString>;
340
+ enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
341
+ query_timeout_ms: z.ZodOptional<z.ZodNumber>;
342
+ query_policy: z.ZodOptional<z.ZodEnum<{
343
+ "read-only-sql": "read-only-sql";
344
+ "semantic-layer-only": "semantic-layer-only";
345
+ }>>;
271
346
  }, z.core.$loose>, z.ZodObject<{
272
347
  driver: z.ZodLiteral<"clickhouse">;
273
348
  url: z.ZodOptional<z.ZodString>;
274
349
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
275
350
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
351
+ query_policy: z.ZodOptional<z.ZodEnum<{
352
+ "read-only-sql": "read-only-sql";
353
+ "semantic-layer-only": "semantic-layer-only";
354
+ }>>;
276
355
  }, z.core.$loose>, z.ZodObject<{
277
356
  driver: z.ZodLiteral<"sqlserver">;
278
357
  url: z.ZodOptional<z.ZodString>;
279
358
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
280
359
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
360
+ query_policy: z.ZodOptional<z.ZodEnum<{
361
+ "read-only-sql": "read-only-sql";
362
+ "semantic-layer-only": "semantic-layer-only";
363
+ }>>;
364
+ }, z.core.$loose>, z.ZodObject<{
365
+ driver: z.ZodLiteral<"athena">;
366
+ url: z.ZodOptional<z.ZodString>;
367
+ enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
368
+ query_timeout_ms: z.ZodOptional<z.ZodNumber>;
369
+ query_policy: z.ZodOptional<z.ZodEnum<{
370
+ "read-only-sql": "read-only-sql";
371
+ "semantic-layer-only": "semantic-layer-only";
372
+ }>>;
281
373
  }, z.core.$loose>, z.ZodObject<{
282
374
  driver: z.ZodLiteral<"mongodb">;
283
375
  url: z.ZodString;
@@ -4,36 +4,82 @@ export declare const connectionConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
4
4
  url: z.ZodOptional<z.ZodString>;
5
5
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
6
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
7
+ query_policy: z.ZodOptional<z.ZodEnum<{
8
+ "read-only-sql": "read-only-sql";
9
+ "semantic-layer-only": "semantic-layer-only";
10
+ }>>;
7
11
  }, z.core.$loose>, z.ZodObject<{
8
12
  driver: z.ZodLiteral<"mysql">;
9
13
  url: z.ZodOptional<z.ZodString>;
10
14
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
15
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
16
+ query_policy: z.ZodOptional<z.ZodEnum<{
17
+ "read-only-sql": "read-only-sql";
18
+ "semantic-layer-only": "semantic-layer-only";
19
+ }>>;
12
20
  }, z.core.$loose>, z.ZodObject<{
13
21
  driver: z.ZodLiteral<"snowflake">;
14
22
  url: z.ZodOptional<z.ZodString>;
15
23
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
16
24
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
25
+ query_policy: z.ZodOptional<z.ZodEnum<{
26
+ "read-only-sql": "read-only-sql";
27
+ "semantic-layer-only": "semantic-layer-only";
28
+ }>>;
17
29
  }, z.core.$loose>, z.ZodObject<{
18
30
  driver: z.ZodLiteral<"bigquery">;
19
31
  url: z.ZodOptional<z.ZodString>;
20
32
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
33
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
34
+ query_policy: z.ZodOptional<z.ZodEnum<{
35
+ "read-only-sql": "read-only-sql";
36
+ "semantic-layer-only": "semantic-layer-only";
37
+ }>>;
22
38
  }, z.core.$loose>, z.ZodObject<{
23
39
  driver: z.ZodLiteral<"sqlite">;
24
40
  url: z.ZodOptional<z.ZodString>;
25
41
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
26
42
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
43
+ query_policy: z.ZodOptional<z.ZodEnum<{
44
+ "read-only-sql": "read-only-sql";
45
+ "semantic-layer-only": "semantic-layer-only";
46
+ }>>;
47
+ }, z.core.$loose>, z.ZodObject<{
48
+ driver: z.ZodLiteral<"duckdb">;
49
+ url: z.ZodOptional<z.ZodString>;
50
+ enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
51
+ query_timeout_ms: z.ZodOptional<z.ZodNumber>;
52
+ query_policy: z.ZodOptional<z.ZodEnum<{
53
+ "read-only-sql": "read-only-sql";
54
+ "semantic-layer-only": "semantic-layer-only";
55
+ }>>;
27
56
  }, z.core.$loose>, z.ZodObject<{
28
57
  driver: z.ZodLiteral<"clickhouse">;
29
58
  url: z.ZodOptional<z.ZodString>;
30
59
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
60
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
61
+ query_policy: z.ZodOptional<z.ZodEnum<{
62
+ "read-only-sql": "read-only-sql";
63
+ "semantic-layer-only": "semantic-layer-only";
64
+ }>>;
32
65
  }, z.core.$loose>, z.ZodObject<{
33
66
  driver: z.ZodLiteral<"sqlserver">;
34
67
  url: z.ZodOptional<z.ZodString>;
35
68
  enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
36
69
  query_timeout_ms: z.ZodOptional<z.ZodNumber>;
70
+ query_policy: z.ZodOptional<z.ZodEnum<{
71
+ "read-only-sql": "read-only-sql";
72
+ "semantic-layer-only": "semantic-layer-only";
73
+ }>>;
74
+ }, z.core.$loose>, z.ZodObject<{
75
+ driver: z.ZodLiteral<"athena">;
76
+ url: z.ZodOptional<z.ZodString>;
77
+ enabled_tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
78
+ query_timeout_ms: z.ZodOptional<z.ZodNumber>;
79
+ query_policy: z.ZodOptional<z.ZodEnum<{
80
+ "read-only-sql": "read-only-sql";
81
+ "semantic-layer-only": "semantic-layer-only";
82
+ }>>;
37
83
  }, z.core.$loose>, z.ZodObject<{
38
84
  driver: z.ZodLiteral<"mongodb">;
39
85
  url: z.ZodString;