@malloy-publisher/server 0.0.235 → 0.0.237
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/app/api-doc.yaml +83 -27
- package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
- package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
- package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
- package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
- package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
- package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
- package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
- package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
- package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
- package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
- package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
- package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
- package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +59 -20
- package/dist/server.mjs +1342 -616
- package/package.json +8 -2
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -97
- package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
- package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.example.bigquery.json +0 -33
- package/publisher.config.example.duckdb.json +0 -23
- package/publisher.config.json +0 -23
- package/scripts/bake-duckdb-extensions.js +0 -110
- package/src/config.spec.ts +0 -1535
- package/src/config.theme.spec.ts +0 -178
- package/src/config.ts +0 -1142
- package/src/constants.ts +0 -105
- package/src/controller/compile.controller.ts +0 -40
- package/src/controller/connection.controller.spec.ts +0 -849
- package/src/controller/connection.controller.ts +0 -901
- package/src/controller/database.controller.ts +0 -24
- package/src/controller/materialization.controller.spec.ts +0 -293
- package/src/controller/materialization.controller.ts +0 -319
- package/src/controller/model.controller.ts +0 -164
- package/src/controller/package.controller.spec.ts +0 -273
- package/src/controller/package.controller.ts +0 -306
- package/src/controller/query.controller.ts +0 -152
- package/src/controller/theme.controller.ts +0 -83
- package/src/controller/watch-mode.controller.ts +0 -243
- package/src/data_styles.ts +0 -71
- package/src/default-publisher.config.json +0 -23
- package/src/ducklake_version.spec.ts +0 -163
- package/src/ducklake_version.ts +0 -153
- package/src/errors.spec.ts +0 -119
- package/src/errors.ts +0 -237
- package/src/filter_deprecation.spec.ts +0 -64
- package/src/filter_deprecation.ts +0 -42
- package/src/health.spec.ts +0 -123
- package/src/health.ts +0 -233
- package/src/heap_check.spec.ts +0 -144
- package/src/heap_check.ts +0 -144
- package/src/instrumentation.ts +0 -214
- package/src/json_utils.spec.ts +0 -51
- package/src/json_utils.ts +0 -33
- package/src/logger.spec.ts +0 -210
- package/src/logger.ts +0 -251
- package/src/malloy_pin_prereqs.spec.ts +0 -25
- package/src/materialization_metrics.spec.ts +0 -169
- package/src/materialization_metrics.ts +0 -346
- package/src/mcp/error_messages.spec.ts +0 -35
- package/src/mcp/error_messages.ts +0 -183
- package/src/mcp/handler_utils.spec.ts +0 -108
- package/src/mcp/handler_utils.ts +0 -251
- package/src/mcp/mcp_constants.ts +0 -6
- package/src/mcp/query_envelope.spec.ts +0 -229
- package/src/mcp/query_envelope.ts +0 -240
- package/src/mcp/server.protocol.spec.ts +0 -250
- package/src/mcp/server.ts +0 -87
- package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
- package/src/mcp/skills/build_skills_bundle.ts +0 -176
- package/src/mcp/skills/skills_bundle.json +0 -1
- package/src/mcp/skills/skills_bundle.spec.ts +0 -171
- package/src/mcp/tool_response.spec.ts +0 -108
- package/src/mcp/tool_response.ts +0 -138
- package/src/mcp/tools/compile_tool.spec.ts +0 -315
- package/src/mcp/tools/compile_tool.ts +0 -208
- package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
- package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
- package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
- package/src/mcp/tools/docs_search_tool.ts +0 -128
- package/src/mcp/tools/embedding_index.spec.ts +0 -1236
- package/src/mcp/tools/embedding_index.ts +0 -808
- package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
- package/src/mcp/tools/execute_query_tool.ts +0 -316
- package/src/mcp/tools/get_context_eval.ts +0 -275
- package/src/mcp/tools/get_context_tool.spec.ts +0 -592
- package/src/mcp/tools/get_context_tool.ts +0 -585
- package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
- package/src/mcp/tools/reload_package_tool.ts +0 -132
- package/src/mcp_config.spec.ts +0 -919
- package/src/mcp_config.ts +0 -425
- package/src/oom_guards.integration.spec.ts +0 -269
- package/src/package_load/package_load_pool.spec.ts +0 -252
- package/src/package_load/package_load_pool.ts +0 -931
- package/src/package_load/package_load_worker.ts +0 -1034
- package/src/package_load/protocol.ts +0 -364
- package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
- package/src/package_load/rpc_wait_accountant.ts +0 -76
- package/src/package_load_metrics.spec.ts +0 -114
- package/src/package_load_metrics.ts +0 -127
- package/src/path_safety.spec.ts +0 -158
- package/src/path_safety.ts +0 -146
- package/src/pg_helpers.spec.ts +0 -223
- package/src/pg_helpers.ts +0 -52
- package/src/query_cap_metrics.spec.ts +0 -89
- package/src/query_cap_metrics.ts +0 -116
- package/src/query_concurrency.spec.ts +0 -247
- package/src/query_concurrency.ts +0 -237
- package/src/query_metadata_metrics.ts +0 -49
- package/src/query_param_utils.ts +0 -29
- package/src/query_timeout.spec.ts +0 -224
- package/src/query_timeout.ts +0 -179
- package/src/runtime/publisher.js +0 -328
- package/src/server-old.ts +0 -988
- package/src/server.ts +0 -2059
- package/src/service/annotations.spec.ts +0 -118
- package/src/service/annotations.ts +0 -91
- package/src/service/authorize.spec.ts +0 -154
- package/src/service/authorize.ts +0 -498
- package/src/service/authorize_integration.spec.ts +0 -2041
- package/src/service/build_plan.spec.ts +0 -685
- package/src/service/build_plan.ts +0 -797
- package/src/service/compile_authorize.spec.ts +0 -85
- package/src/service/compile_fragment_techniques.spec.ts +0 -156
- package/src/service/connection.spec.ts +0 -2360
- package/src/service/connection.ts +0 -2187
- package/src/service/connection_config.spec.ts +0 -734
- package/src/service/connection_config.ts +0 -899
- package/src/service/connection_federation.spec.ts +0 -184
- package/src/service/connection_fingerprint.spec.ts +0 -102
- package/src/service/connection_service.spec.ts +0 -711
- package/src/service/connection_service.ts +0 -291
- package/src/service/cron_evaluator.spec.ts +0 -79
- package/src/service/cron_evaluator.ts +0 -105
- package/src/service/db_utils.spec.ts +0 -1085
- package/src/service/db_utils.ts +0 -1502
- package/src/service/duckdb_instance_isolation.spec.ts +0 -137
- package/src/service/ducklake_lazy_attach.spec.ts +0 -110
- package/src/service/embedding_provider.spec.ts +0 -329
- package/src/service/embedding_provider.ts +0 -236
- package/src/service/environment.ts +0 -2191
- package/src/service/environment_admission.spec.ts +0 -344
- package/src/service/environment_compile.spec.ts +0 -197
- package/src/service/environment_store.spec.ts +0 -2274
- package/src/service/environment_store.ts +0 -2434
- package/src/service/environment_store_anchoring.spec.ts +0 -107
- package/src/service/environment_store_clone.spec.ts +0 -350
- package/src/service/explore_visibility.spec.ts +0 -434
- package/src/service/exports_probe.spec.ts +0 -107
- package/src/service/extension_fetch_policy.spec.ts +0 -256
- package/src/service/filter.spec.ts +0 -458
- package/src/service/filter.ts +0 -341
- package/src/service/filter_bypass.spec.ts +0 -418
- package/src/service/filter_integration.spec.ts +0 -1004
- package/src/service/freshness.spec.ts +0 -183
- package/src/service/freshness.ts +0 -112
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/given.ts +0 -105
- package/src/service/givens_integration.spec.ts +0 -219
- package/src/service/manifest_loader.spec.ts +0 -189
- package/src/service/manifest_loader.ts +0 -156
- package/src/service/materialization_build_session.spec.ts +0 -435
- package/src/service/materialization_build_session.ts +0 -681
- package/src/service/materialization_config_validation.spec.ts +0 -99
- package/src/service/materialization_config_validation.ts +0 -120
- package/src/service/materialization_eligibility.spec.ts +0 -158
- package/src/service/materialization_eligibility.ts +0 -305
- package/src/service/materialization_schedule_surface.spec.ts +0 -256
- package/src/service/materialization_scheduler.spec.ts +0 -289
- package/src/service/materialization_scheduler.ts +0 -304
- package/src/service/materialization_serve_transform.spec.ts +0 -1003
- package/src/service/materialization_serve_transform.ts +0 -779
- package/src/service/materialization_service.spec.ts +0 -2373
- package/src/service/materialization_service.ts +0 -2342
- package/src/service/materialization_test_fixtures.ts +0 -249
- package/src/service/model.spec.ts +0 -1157
- package/src/service/model.ts +0 -3370
- package/src/service/model_limits.spec.ts +0 -209
- package/src/service/model_limits.ts +0 -131
- package/src/service/model_storage_serve.spec.ts +0 -193
- package/src/service/model_storage_serve_joins.spec.ts +0 -193
- package/src/service/package.spec.ts +0 -612
- package/src/service/package.ts +0 -1799
- package/src/service/package_manifest.spec.ts +0 -230
- package/src/service/package_manifest.ts +0 -241
- package/src/service/package_memory_governor.spec.ts +0 -173
- package/src/service/package_memory_governor.ts +0 -233
- package/src/service/package_quote_bound_tables.spec.ts +0 -171
- package/src/service/package_race.spec.ts +0 -208
- package/src/service/package_reload_safety.spec.ts +0 -193
- package/src/service/package_rollback.spec.ts +0 -190
- package/src/service/package_worker_path.spec.ts +0 -645
- package/src/service/path_injection.spec.ts +0 -39
- package/src/service/persist_annotation_validation.spec.ts +0 -89
- package/src/service/persist_annotation_validation.ts +0 -59
- package/src/service/persistence_policy.spec.ts +0 -374
- package/src/service/proxy.spec.ts +0 -414
- package/src/service/proxy.ts +0 -248
- package/src/service/query_boundary.spec.ts +0 -470
- package/src/service/query_metadata.spec.ts +0 -408
- package/src/service/query_metadata.ts +0 -492
- package/src/service/query_metadata_identity.spec.ts +0 -149
- package/src/service/query_text.spec.ts +0 -79
- package/src/service/query_text.ts +0 -43
- package/src/service/quoting.spec.ts +0 -135
- package/src/service/quoting.ts +0 -78
- package/src/service/resolve_environment.ts +0 -15
- package/src/service/restricted_mode.spec.ts +0 -299
- package/src/service/source_extraction.ts +0 -225
- package/src/service/theme_store.ts +0 -199
- package/src/storage/DatabaseInterface.ts +0 -198
- package/src/storage/StorageManager.ts +0 -113
- package/src/storage/duckdb/ConnectionRepository.ts +0 -143
- package/src/storage/duckdb/DuckDBConnection.ts +0 -123
- package/src/storage/duckdb/DuckDBRepository.ts +0 -208
- package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
- package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
- package/src/storage/duckdb/MaterializationRepository.ts +0 -336
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/schema.ts +0 -229
- package/src/stream_helpers.spec.ts +0 -280
- package/src/stream_helpers.ts +0 -162
- package/src/telemetry.ts +0 -20
- package/src/test_helpers/metrics_harness.ts +0 -166
- package/src/theme_key_parity.spec.ts +0 -57
- package/src/utils.ts +0 -29
- package/tests/fixtures/authorize-compile/model.malloy +0 -9
- package/tests/fixtures/authorize-compile/publisher.json +0 -4
- package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
- package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/data.csv +0 -3
- package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
- package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
- package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
- package/tests/fixtures/html-pages-test/public/data.json +0 -1
- package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
- package/tests/fixtures/html-pages-test/public/index.html +0 -9
- package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
- package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
- package/tests/fixtures/html-pages-test/public/slides.html +0 -12
- package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
- package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
- package/tests/fixtures/html-pages-test/publisher.json +0 -5
- package/tests/fixtures/html-pages-test/report.malloy +0 -1
- package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
- package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
- package/tests/fixtures/persist-multi-level/publisher.json +0 -5
- package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
- package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
- package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/fixtures/query-givens/data/orders.csv +0 -7
- package/tests/fixtures/query-givens/model.malloy +0 -34
- package/tests/fixtures/query-givens/publisher.json +0 -5
- package/tests/fixtures/xlsx/database.xlsx +0 -0
- package/tests/harness/mcp_test_setup.ts +0 -332
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
- package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
- package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
- package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
- package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
- package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
- package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
- package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
- package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
- package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
- package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
- package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
- package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
- package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
- package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
- package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
- package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
- package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
- package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
- package/tests/unit/controller/theme.controller.spec.ts +0 -141
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
- package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
- package/tests/unit/duckdb/repositories.test.ts +0 -208
- package/tests/unit/ducklake/ducklake.test.ts +0 -952
- package/tests/unit/service/theme_store.spec.ts +0 -139
- package/tsconfig.json +0 -32
package/src/mcp/handler_utils.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { MalloyError } from "@malloydata/malloy";
|
|
2
|
-
import { EnvironmentStore } from "../service/environment_store";
|
|
3
|
-
import {
|
|
4
|
-
AccessDeniedError,
|
|
5
|
-
BadRequestError,
|
|
6
|
-
PackageNotFoundError,
|
|
7
|
-
ModelNotFoundError,
|
|
8
|
-
ModelCompilationError,
|
|
9
|
-
EnvironmentNotFoundError,
|
|
10
|
-
NotQueryableError,
|
|
11
|
-
PayloadTooLargeError,
|
|
12
|
-
QueryTimeoutError,
|
|
13
|
-
ServiceUnavailableError,
|
|
14
|
-
} from "../errors";
|
|
15
|
-
import {
|
|
16
|
-
getNotFoundError,
|
|
17
|
-
getInternalError,
|
|
18
|
-
getMalloyErrorDetails,
|
|
19
|
-
type ErrorDetails,
|
|
20
|
-
} from "./error_messages";
|
|
21
|
-
import type { Model } from "../service/model";
|
|
22
|
-
import type { Environment } from "../service/environment";
|
|
23
|
-
import { logger } from "../logger";
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Remediation for a ServiceUnavailableError, shared by every caller that homes
|
|
27
|
-
* one. The error covers both the memory governor and the concurrency slots, so
|
|
28
|
-
* the wording names both rather than only memory.
|
|
29
|
-
*/
|
|
30
|
-
export const BACK_PRESSURE_SUGGESTIONS: readonly string[] = [
|
|
31
|
-
"Retry once the publisher is below its configured memory or concurrency limit.",
|
|
32
|
-
"If this persists, raise the limit or scale up the pod.",
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Turns a thrown error into agent-facing ErrorDetails, homed by error class.
|
|
37
|
-
*
|
|
38
|
-
* Without this, a tool that funnels every failure through getMalloyErrorDetails
|
|
39
|
-
* dresses each one up as a Malloy language problem: a missing package tells the
|
|
40
|
-
* agent to check its Malloy syntax, and a back-pressure rejection sends it
|
|
41
|
-
* hunting for a typo instead of retrying.
|
|
42
|
-
*
|
|
43
|
-
* Only the classes that really are about the caller's Malloy or request reach
|
|
44
|
-
* the Malloy advice; anything else is reported as internal rather than blamed
|
|
45
|
-
* on the model. Routing by class and not by message is deliberate:
|
|
46
|
-
* getMalloyErrorDetails refines on message patterns that the shipped engine
|
|
47
|
-
* does not emit (its syntax pattern wants "no viable alternative at input"
|
|
48
|
-
* where a real error says "unexpected '@'"), so "did it refine?" would send
|
|
49
|
-
* genuine compile errors to the internal branch.
|
|
50
|
-
*
|
|
51
|
-
* @param operation The operation that failed (e.g. 'compile', 'reloadPackage').
|
|
52
|
-
* @param identifier The resource it failed on (e.g. 'env/package').
|
|
53
|
-
*/
|
|
54
|
-
export function classifyToolError(
|
|
55
|
-
operation: string,
|
|
56
|
-
identifier: string,
|
|
57
|
-
error: unknown,
|
|
58
|
-
): ErrorDetails {
|
|
59
|
-
if (
|
|
60
|
-
error instanceof EnvironmentNotFoundError ||
|
|
61
|
-
error instanceof PackageNotFoundError ||
|
|
62
|
-
error instanceof ModelNotFoundError ||
|
|
63
|
-
// A query-boundary denial is a deliberate 404, and reporting it as a
|
|
64
|
-
// server fault would tell the caller to contact support about a boundary
|
|
65
|
-
// that is working. getNotFoundError also drops the source name this class
|
|
66
|
-
// is careful not to confirm.
|
|
67
|
-
error instanceof NotQueryableError
|
|
68
|
-
) {
|
|
69
|
-
return getNotFoundError(identifier);
|
|
70
|
-
}
|
|
71
|
-
if (error instanceof ServiceUnavailableError) {
|
|
72
|
-
// Back-pressure: surface the server's own message so the caller knows to
|
|
73
|
-
// retry rather than to go edit its Malloy.
|
|
74
|
-
return {
|
|
75
|
-
message: error.message,
|
|
76
|
-
suggestions: [...BACK_PRESSURE_SUGGESTIONS],
|
|
77
|
-
} satisfies ErrorDetails;
|
|
78
|
-
}
|
|
79
|
-
if (
|
|
80
|
-
error instanceof QueryTimeoutError ||
|
|
81
|
-
error instanceof PayloadTooLargeError
|
|
82
|
-
) {
|
|
83
|
-
// The query was too slow or too big. Both classes already carry their own
|
|
84
|
-
// "refine the query" remediation in the message, so the suggestions say
|
|
85
|
-
// the part the message leaves out and the internal branch got backwards:
|
|
86
|
-
// this is not transient, and retrying it unchanged fails identically.
|
|
87
|
-
// That distinction is the whole reason these are 504/413 and not the
|
|
88
|
-
// retryable 503 (see the QueryTimeoutError docstring in errors.ts).
|
|
89
|
-
return {
|
|
90
|
-
message: error.message,
|
|
91
|
-
suggestions: [
|
|
92
|
-
"This is not transient. The same query will fail the same way, so change the query rather than retrying it.",
|
|
93
|
-
"If it is already minimal, raise the configured cap named in the message.",
|
|
94
|
-
],
|
|
95
|
-
} satisfies ErrorDetails;
|
|
96
|
-
}
|
|
97
|
-
if (
|
|
98
|
-
// A raw engine error: what a bad query throws (syntax, undefined name,
|
|
99
|
-
// field not found). executeQuery's catch sees these, so they must keep
|
|
100
|
-
// the Malloy advice.
|
|
101
|
-
error instanceof MalloyError ||
|
|
102
|
-
// The same thing wrapped: what a failed compile or reload throws.
|
|
103
|
-
error instanceof ModelCompilationError ||
|
|
104
|
-
// An #(authorize) denial, whose message getMalloyErrorDetails recognizes.
|
|
105
|
-
error instanceof AccessDeniedError ||
|
|
106
|
-
// A malformed request is the caller's to fix, not ours to retry.
|
|
107
|
-
error instanceof BadRequestError
|
|
108
|
-
) {
|
|
109
|
-
return getMalloyErrorDetails(operation, identifier, error);
|
|
110
|
-
}
|
|
111
|
-
// Everything else (a filesystem EACCES, a TypeError, a worker crash, a
|
|
112
|
-
// timeout) is not the caller's Malloy. Telling them to check their syntax
|
|
113
|
-
// sends them to edit a model that is fine.
|
|
114
|
-
return getInternalError(operation, error);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Fetches and validates the Package and Model instances needed for query execution.
|
|
119
|
-
* Handles errors related to package/model access and initial compilation.
|
|
120
|
-
*
|
|
121
|
-
* The resolved Environment comes back with the model because a query needs it for
|
|
122
|
-
* more than the lookup: it owns the connection configs the per-query metadata
|
|
123
|
-
* layers are read from.
|
|
124
|
-
*
|
|
125
|
-
* @returns An object containing the Model instance or a pre-formatted ErrorDetails object.
|
|
126
|
-
*/
|
|
127
|
-
export async function getModelForQuery(
|
|
128
|
-
environmentStore: EnvironmentStore,
|
|
129
|
-
environmentName: string,
|
|
130
|
-
packageName: string,
|
|
131
|
-
modelPath: string,
|
|
132
|
-
): Promise<
|
|
133
|
-
{ model: Model; environment: Environment } | { error: ErrorDetails }
|
|
134
|
-
> {
|
|
135
|
-
try {
|
|
136
|
-
const environment = await environmentStore.getEnvironment(
|
|
137
|
-
environmentName,
|
|
138
|
-
false,
|
|
139
|
-
);
|
|
140
|
-
// Shed load before any disk / DB work; mirrors the HTTP query
|
|
141
|
-
// controllers so MCP traffic obeys the same back-pressure rules.
|
|
142
|
-
environment.assertCanAdmitQuery();
|
|
143
|
-
const pkg = await environment.getPackage(packageName, false);
|
|
144
|
-
const model = pkg.getModel(modelPath);
|
|
145
|
-
if (!model || model.getModelType() === "notebook") {
|
|
146
|
-
// Ensure it's actually a model
|
|
147
|
-
const details = getNotFoundError(
|
|
148
|
-
`model '${modelPath}' in package '${packageName}' for environment '${environmentName}'`,
|
|
149
|
-
);
|
|
150
|
-
return { error: details };
|
|
151
|
-
}
|
|
152
|
-
// Attempt to get the model definition early to catch initial compilation errors
|
|
153
|
-
await model.getModel(); // This might throw ModelCompilationError
|
|
154
|
-
return { model, environment };
|
|
155
|
-
} catch (error) {
|
|
156
|
-
// Handle errors during package/model access or initial compilation
|
|
157
|
-
let errorDetails: ErrorDetails;
|
|
158
|
-
if (error instanceof EnvironmentNotFoundError) {
|
|
159
|
-
errorDetails = getNotFoundError(`environment '${environmentName}'`);
|
|
160
|
-
} else if (error instanceof PackageNotFoundError) {
|
|
161
|
-
errorDetails = getNotFoundError(
|
|
162
|
-
`package '${packageName}' in environment '${environmentName}'`,
|
|
163
|
-
);
|
|
164
|
-
} else if (error instanceof ModelNotFoundError) {
|
|
165
|
-
errorDetails = getNotFoundError(
|
|
166
|
-
`model '${modelPath}' in package '${packageName}' for environment '${environmentName}'`,
|
|
167
|
-
);
|
|
168
|
-
} else if (error instanceof ModelCompilationError) {
|
|
169
|
-
errorDetails = getMalloyErrorDetails(
|
|
170
|
-
"executeQuery (load model)",
|
|
171
|
-
`${environmentName}/${packageName}/${modelPath}`,
|
|
172
|
-
error,
|
|
173
|
-
);
|
|
174
|
-
} else if (error instanceof AccessDeniedError) {
|
|
175
|
-
// An #(authorize) denial during model setup. Funnel through
|
|
176
|
-
// getMalloyErrorDetails (which recognizes the access-denied message and
|
|
177
|
-
// gives supply-the-givens guidance) so a 403 never surfaces as an
|
|
178
|
-
// opaque internal error. Defensive: the gate fires during query
|
|
179
|
-
// execution today, not setup, but this keeps every error class homed.
|
|
180
|
-
errorDetails = getMalloyErrorDetails(
|
|
181
|
-
"executeQuery (load model)",
|
|
182
|
-
`${environmentName}/${packageName}/${modelPath}`,
|
|
183
|
-
error,
|
|
184
|
-
);
|
|
185
|
-
} else if (error instanceof ServiceUnavailableError) {
|
|
186
|
-
// Back-pressure: don't dress this up as a 404/500. Surface the
|
|
187
|
-
// server's own message so the MCP caller knows to retry.
|
|
188
|
-
errorDetails = {
|
|
189
|
-
message: error.message,
|
|
190
|
-
suggestions: [...BACK_PRESSURE_SUGGESTIONS],
|
|
191
|
-
} satisfies ErrorDetails;
|
|
192
|
-
} else {
|
|
193
|
-
// Unexpected error during setup
|
|
194
|
-
errorDetails = getInternalError("executeQuery (Setup)", error);
|
|
195
|
-
}
|
|
196
|
-
logger.error(
|
|
197
|
-
`[MCP Server Error] Error accessing package/model for query: ${environmentName}/${packageName}/${modelPath}`,
|
|
198
|
-
{ error },
|
|
199
|
-
);
|
|
200
|
-
return { error: errorDetails };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Constructs a malloy:// URI string identifying the model a tool response came
|
|
206
|
-
* from. Attached as `uri` metadata on tool results (get-context, execute-query,
|
|
207
|
-
* docs-search); it is not a fetchable MCP resource.
|
|
208
|
-
*
|
|
209
|
-
* @param components The URI parts (environment, package, and optionally the model).
|
|
210
|
-
* @param fragment Optional fragment identifier (e.g., "result", "get-context").
|
|
211
|
-
* @returns A malloy:// URI string.
|
|
212
|
-
*/
|
|
213
|
-
export function buildMalloyUri(
|
|
214
|
-
components: {
|
|
215
|
-
environment?: string;
|
|
216
|
-
package?: string;
|
|
217
|
-
resourceType?: "models";
|
|
218
|
-
resourceName?: string;
|
|
219
|
-
},
|
|
220
|
-
fragment?: string,
|
|
221
|
-
): string {
|
|
222
|
-
let path = "/environment/";
|
|
223
|
-
|
|
224
|
-
if (components.environment) {
|
|
225
|
-
path += encodeURIComponent(components.environment);
|
|
226
|
-
} else {
|
|
227
|
-
// Default to 'home' if not provided, consistent with current behavior
|
|
228
|
-
path += "home";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (components.package) {
|
|
232
|
-
path += "/package/" + encodeURIComponent(components.package);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (components.resourceType) {
|
|
236
|
-
path += "/" + components.resourceType;
|
|
237
|
-
if (components.resourceName) {
|
|
238
|
-
path += "/" + encodeURIComponent(components.resourceName);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// The URL constructor normalizes malloy://path to malloy:///path, so we
|
|
243
|
-
// build the string manually to keep the malloy://environment/... shape.
|
|
244
|
-
let uriString = "malloy:/" + path; // Start with one slash after scheme
|
|
245
|
-
|
|
246
|
-
if (fragment) {
|
|
247
|
-
uriString += "#" + fragment;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return uriString;
|
|
251
|
-
}
|
package/src/mcp/mcp_constants.ts
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import type * as Malloy from "@malloydata/malloy-interfaces";
|
|
3
|
-
import {
|
|
4
|
-
buildQueryEnvelope,
|
|
5
|
-
serializeEnvelope,
|
|
6
|
-
MAX_RESULT_CHARS,
|
|
7
|
-
} from "./query_envelope";
|
|
8
|
-
|
|
9
|
-
const rows = (n: number) =>
|
|
10
|
-
Array.from({ length: n }, (_, i) => ({ id: i, name: `row ${i}` }));
|
|
11
|
-
|
|
12
|
-
/** A minimal Malloy.Result: only the metadata the envelope reads. */
|
|
13
|
-
const result = (extra: Partial<Malloy.Result> = {}): Malloy.Result =>
|
|
14
|
-
({
|
|
15
|
-
schema: { fields: [{ kind: "dimension", name: "id" }] },
|
|
16
|
-
connection_name: "duckdb",
|
|
17
|
-
...extra,
|
|
18
|
-
}) as Malloy.Result;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The field names here are Credible's, on purpose: a data app authored against
|
|
22
|
-
* Publisher is served through Credible, and an agent that sees one shape while
|
|
23
|
-
* developing and another in production has to learn both. These assertions are
|
|
24
|
-
* the contract that keeps them the same, so renaming a field should fail here.
|
|
25
|
-
*/
|
|
26
|
-
describe("buildQueryEnvelope", () => {
|
|
27
|
-
it("returns flat rows plus Credible's metadata block", () => {
|
|
28
|
-
const e = buildQueryEnvelope(rows(3), 1000, result());
|
|
29
|
-
expect(e.rows).toHaveLength(3);
|
|
30
|
-
expect(e._meta.connection_name).toBe("duckdb");
|
|
31
|
-
expect(e._meta.schema.fields).toHaveLength(1);
|
|
32
|
-
expect(e._meta.annotations).toEqual([]);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("carries the Malloy metadata that flat rows drop", () => {
|
|
36
|
-
// Field types and render tags live in the schema, not the rows, so an
|
|
37
|
-
// agent would otherwise need a second verbose call to reach them.
|
|
38
|
-
const e = buildQueryEnvelope(rows(1), 1000, {
|
|
39
|
-
...result(),
|
|
40
|
-
query_timezone: "UTC",
|
|
41
|
-
model_annotations: [{ value: "# dashboard" }],
|
|
42
|
-
} as Malloy.Result);
|
|
43
|
-
expect(e._meta.query_timezone).toBe("UTC");
|
|
44
|
-
expect(e._meta.model_annotations).toEqual([{ value: "# dashboard" }]);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("omits metadata keys the result did not carry", () => {
|
|
48
|
-
const e = buildQueryEnvelope(rows(1), 1000, result());
|
|
49
|
-
expect("query_timezone" in e._meta).toBe(false);
|
|
50
|
-
expect("source_annotations" in e._meta).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The correctness fix, and the one field a client cannot compute for itself:
|
|
55
|
-
* the cap depends on server config and on the query's own LIMIT.
|
|
56
|
-
*/
|
|
57
|
-
describe("_limit_hit", () => {
|
|
58
|
-
it("is true when the row count lands exactly on the cap", () => {
|
|
59
|
-
const e = buildQueryEnvelope(rows(1000), 1000, result());
|
|
60
|
-
expect(e._limit_hit).toBe(true);
|
|
61
|
-
expect(e._query_row_limit).toBe(1000);
|
|
62
|
-
expect(e.warning).toContain("not a complete result");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("is false when the result came in under the cap", () => {
|
|
66
|
-
const e = buildQueryEnvelope(rows(999), 1000, result());
|
|
67
|
-
expect(e._limit_hit).toBe(false);
|
|
68
|
-
expect(e.warning).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The false positive this guards. resolveModelQueryRowLimit folds the
|
|
73
|
-
* query's own limit:/top: into the same cap, so equality holds every time
|
|
74
|
-
* an author limited the query deliberately. Three of the eight views in
|
|
75
|
-
* the bundled storefront example use `top:`, so a modelled top-N would
|
|
76
|
-
* otherwise report its complete answer as cut off, and the Contract rule
|
|
77
|
-
* would tell an agent that a top-N is "not the answer".
|
|
78
|
-
*/
|
|
79
|
-
it("is false when the author's own limit: produced the cap", () => {
|
|
80
|
-
const e = buildQueryEnvelope(
|
|
81
|
-
rows(10),
|
|
82
|
-
10,
|
|
83
|
-
result(),
|
|
84
|
-
[],
|
|
85
|
-
undefined,
|
|
86
|
-
"query",
|
|
87
|
-
);
|
|
88
|
-
expect(e._limit_hit).toBe(false);
|
|
89
|
-
expect(e._limit_source).toBe("query");
|
|
90
|
-
expect(e.warning).toBeUndefined();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("still fires when the silent server default produced the cap", () => {
|
|
94
|
-
const e = buildQueryEnvelope(
|
|
95
|
-
rows(1000),
|
|
96
|
-
1000,
|
|
97
|
-
result(),
|
|
98
|
-
[],
|
|
99
|
-
undefined,
|
|
100
|
-
"server_default",
|
|
101
|
-
);
|
|
102
|
-
expect(e._limit_hit).toBe(true);
|
|
103
|
-
expect(e._limit_source).toBe("server_default");
|
|
104
|
-
expect(e.warning).toContain("not a complete result");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("reports the source even when the cap was not reached", () => {
|
|
108
|
-
const e = buildQueryEnvelope(
|
|
109
|
-
rows(3),
|
|
110
|
-
10,
|
|
111
|
-
result(),
|
|
112
|
-
[],
|
|
113
|
-
undefined,
|
|
114
|
-
"query",
|
|
115
|
-
);
|
|
116
|
-
expect(e._limit_hit).toBe(false);
|
|
117
|
-
expect(e._limit_source).toBe("query");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("is false when no cap was applied", () => {
|
|
121
|
-
// rowLimit 0 means uncapped; equality against 0 would otherwise call
|
|
122
|
-
// an empty result "limited".
|
|
123
|
-
expect(buildQueryEnvelope([], 0, result())._limit_hit).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("does not call an empty result limited", () => {
|
|
127
|
-
expect(buildQueryEnvelope([], 1000, result())._limit_hit).toBe(false);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* compactResult is raw driver output and DuckDB returns count() as a BigInt,
|
|
133
|
-
* so a plain JSON.stringify throws on the most common query anyone writes.
|
|
134
|
-
*/
|
|
135
|
-
it("serializes BigInt values instead of throwing", () => {
|
|
136
|
-
const e = buildQueryEnvelope([{ c: 150930n }], 1000, result());
|
|
137
|
-
expect(() => serializeEnvelope(e)).not.toThrow();
|
|
138
|
-
expect(JSON.parse(serializeEnvelope(e)).rows[0].c).toBe(150930);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe("payload truncation", () => {
|
|
142
|
-
it("drops rows to fit and reports both counts", () => {
|
|
143
|
-
const e = buildQueryEnvelope(rows(200), 100_000, result(), [], 3_000);
|
|
144
|
-
expect(e._rows_truncated).toBe(true);
|
|
145
|
-
expect(e._total_rows).toBe(200);
|
|
146
|
-
expect(e._returned_rows).toBe((e.rows as unknown[]).length);
|
|
147
|
-
expect(e._returned_rows).toBeGreaterThan(0);
|
|
148
|
-
expect(e._returned_rows).toBeLessThan(200);
|
|
149
|
-
expect(e.warning).toContain("of 200 rows");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("keeps the finished payload under the cap", () => {
|
|
153
|
-
// The bug this pins: setting the marker fields and the warning AFTER
|
|
154
|
-
// the search pushes the payload back over the limit the truncation
|
|
155
|
-
// existed to respect.
|
|
156
|
-
const e = buildQueryEnvelope(rows(400), 100_000, result(), [], 3_000);
|
|
157
|
-
expect(serializeEnvelope(e).length).toBeLessThanOrEqual(3_000);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Reachable because the hard ceiling is maxBytes (50MB), so one row with a
|
|
162
|
-
* large text column passes assertWithinModelResponseLimits and lands here.
|
|
163
|
-
* "Showing 0 of N rows" reads as an empty result set, and an agent will
|
|
164
|
-
* report "no rows matched" for a row that was merely too big to send.
|
|
165
|
-
*/
|
|
166
|
-
it("says no rows fit rather than implying nothing matched", () => {
|
|
167
|
-
const big = [{ doc: "z".repeat(5_000) }, { doc: "small" }];
|
|
168
|
-
const e = buildQueryEnvelope(big, 1000, result(), [], 2_000);
|
|
169
|
-
expect((e.rows as unknown[]).length).toBe(0);
|
|
170
|
-
expect(e._returned_rows).toBe(0);
|
|
171
|
-
expect(e._total_rows).toBe(2);
|
|
172
|
-
expect(e.warning).toContain("No rows fit the result size limit");
|
|
173
|
-
expect(e.warning).toContain("NOT an empty result");
|
|
174
|
-
expect(e.warning).not.toContain("Showing 0 of");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("keeps the zero-row payload under the cap too", () => {
|
|
178
|
-
// The longer of the two wordings is what the search measures, so
|
|
179
|
-
// swapping it in afterwards must not push the payload back over.
|
|
180
|
-
const big = [{ doc: "z".repeat(5_000) }, { doc: "small" }];
|
|
181
|
-
const e = buildQueryEnvelope(big, 1000, result(), [], 2_000);
|
|
182
|
-
expect(serializeEnvelope(e).length).toBeLessThanOrEqual(2_000);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("still reports the row cap when no rows fit", () => {
|
|
186
|
-
// Both shortenings apply at once; neither may swallow the other.
|
|
187
|
-
const big = [{ doc: "z".repeat(5_000) }, { doc: "y".repeat(5_000) }];
|
|
188
|
-
const e = buildQueryEnvelope(big, 2, result(), [], 2_000);
|
|
189
|
-
expect(e._limit_hit).toBe(true);
|
|
190
|
-
expect(e.warning).toContain("not a complete result");
|
|
191
|
-
expect(e.warning).toContain("No rows fit the result size limit");
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("omits the truncation fields entirely when nothing was dropped", () => {
|
|
195
|
-
// Credible's shape: absent rather than false.
|
|
196
|
-
const e = buildQueryEnvelope(rows(3), 1000, result());
|
|
197
|
-
expect("_rows_truncated" in e).toBe(false);
|
|
198
|
-
expect("_total_rows" in e).toBe(false);
|
|
199
|
-
expect("_returned_rows" in e).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("reports both shortenings when they happen together", () => {
|
|
203
|
-
const e = buildQueryEnvelope(rows(200), 200, result(), [], 3_000);
|
|
204
|
-
expect(e._limit_hit).toBe(true);
|
|
205
|
-
expect(e._rows_truncated).toBe(true);
|
|
206
|
-
// One says the query was capped, the other that the payload was.
|
|
207
|
-
// Reporting only one would understate the loss.
|
|
208
|
-
expect(e.warning).toContain("not a complete result");
|
|
209
|
-
expect(e.warning).toContain("result size limit");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("leaves an ordinary result well inside the default budget", () => {
|
|
213
|
-
const e = buildQueryEnvelope(rows(500), 1000, result());
|
|
214
|
-
expect("_rows_truncated" in e).toBe(false);
|
|
215
|
-
expect(serializeEnvelope(e).length).toBeLessThan(MAX_RESULT_CHARS);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it("passes render-tag messages through under Credible's key", () => {
|
|
220
|
-
const e = buildQueryEnvelope(rows(1), 1000, result(), ["bad tag on x"]);
|
|
221
|
-
expect(e.renderLogErrors).toEqual(["bad tag on x"]);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("omits optional keys when they do not apply", () => {
|
|
225
|
-
const e = buildQueryEnvelope(rows(1), 1000, result());
|
|
226
|
-
expect("warning" in e).toBe(false);
|
|
227
|
-
expect("renderLogErrors" in e).toBe(false);
|
|
228
|
-
});
|
|
229
|
-
});
|