@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.
Files changed (319) hide show
  1. package/dist/app/api-doc.yaml +83 -27
  2. package/dist/app/assets/{EnvironmentPage-BsAnavYN.js → EnvironmentPage-DaP6CpgK.js} +1 -1
  3. package/dist/app/assets/{HomePage-CADE138j.js → HomePage-C9yNw3iu.js} +1 -1
  4. package/dist/app/assets/{LightMode-Cfh7KzN8.js → LightMode-Be4-G-dX.js} +1 -1
  5. package/dist/app/assets/{MainPage-CO3pRlnV.js → MainPage-J_rU2SgI.js} +2 -2
  6. package/dist/app/assets/{MaterializationsPage-p9YjkRXZ.js → MaterializationsPage-DHgPI4MO.js} +1 -1
  7. package/dist/app/assets/ModelPage-BzXfjTu5.js +1 -0
  8. package/dist/app/assets/{PackagePage-e4kN75YR.js → PackagePage-ByoSTLJ4.js} +1 -1
  9. package/dist/app/assets/RouteError-CrSeKN2r.js +1 -0
  10. package/dist/app/assets/{ThemeEditorPage-CciagFTq.js → ThemeEditorPage-Btxnmhtk.js} +1 -1
  11. package/dist/app/assets/{WorkbookPage-DNWmkCXa.js → WorkbookPage-D6JLPnWO.js} +1 -1
  12. package/dist/app/assets/{core-Rj_4rRnA.es-BZyvITuO.js → core-BSRgt1mG.es-Bc_D2Lq4.js} +1 -1
  13. package/dist/app/assets/{index-CH2AcDzc.js → index-BRijWj6K.js} +15 -15
  14. package/dist/app/assets/{index-DQa463gC.js → index-BX4D94xw.js} +1 -1
  15. package/dist/app/assets/{index-VBbcc8s6.js → index-Cvy0xtb7.js} +1 -1
  16. package/dist/app/assets/{index-DQpV7MyA.js → index-DMAhIJNC.js} +1 -1
  17. package/dist/app/index.html +1 -1
  18. package/dist/package_load_worker.mjs +59 -20
  19. package/dist/server.mjs +1342 -616
  20. package/package.json +8 -2
  21. package/.eslintrc.json +0 -44
  22. package/.prettierignore +0 -1
  23. package/.prettierrc +0 -4
  24. package/build.ts +0 -97
  25. package/dist/app/assets/ModelPage-C1OSTv-x.js +0 -1
  26. package/dist/app/assets/RouteError-CzbfOkng.js +0 -1
  27. package/eslint.config.mjs +0 -76
  28. package/k6-tests/README.md +0 -144
  29. package/k6-tests/bun.lock +0 -797
  30. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  31. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  32. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  33. package/k6-tests/load-test/load-test-crud.ts +0 -142
  34. package/k6-tests/load-test/load-test.ts +0 -784
  35. package/k6-tests/package.json +0 -27
  36. package/k6-tests/scripts/run-with-otel.sh +0 -50
  37. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  38. package/k6-tests/tsconfig.json +0 -29
  39. package/k6-tests/utils/client_factory.ts +0 -128
  40. package/k6-tests/utils/common.ts +0 -489
  41. package/k6-tests/utils/logger.ts +0 -62
  42. package/k6-tests/utils/types.d.ts +0 -35
  43. package/malloy_mcp.dxt +0 -0
  44. package/publisher.config.example.bigquery.json +0 -33
  45. package/publisher.config.example.duckdb.json +0 -23
  46. package/publisher.config.json +0 -23
  47. package/scripts/bake-duckdb-extensions.js +0 -110
  48. package/src/config.spec.ts +0 -1535
  49. package/src/config.theme.spec.ts +0 -178
  50. package/src/config.ts +0 -1142
  51. package/src/constants.ts +0 -105
  52. package/src/controller/compile.controller.ts +0 -40
  53. package/src/controller/connection.controller.spec.ts +0 -849
  54. package/src/controller/connection.controller.ts +0 -901
  55. package/src/controller/database.controller.ts +0 -24
  56. package/src/controller/materialization.controller.spec.ts +0 -293
  57. package/src/controller/materialization.controller.ts +0 -319
  58. package/src/controller/model.controller.ts +0 -164
  59. package/src/controller/package.controller.spec.ts +0 -273
  60. package/src/controller/package.controller.ts +0 -306
  61. package/src/controller/query.controller.ts +0 -152
  62. package/src/controller/theme.controller.ts +0 -83
  63. package/src/controller/watch-mode.controller.ts +0 -243
  64. package/src/data_styles.ts +0 -71
  65. package/src/default-publisher.config.json +0 -23
  66. package/src/ducklake_version.spec.ts +0 -163
  67. package/src/ducklake_version.ts +0 -153
  68. package/src/errors.spec.ts +0 -119
  69. package/src/errors.ts +0 -237
  70. package/src/filter_deprecation.spec.ts +0 -64
  71. package/src/filter_deprecation.ts +0 -42
  72. package/src/health.spec.ts +0 -123
  73. package/src/health.ts +0 -233
  74. package/src/heap_check.spec.ts +0 -144
  75. package/src/heap_check.ts +0 -144
  76. package/src/instrumentation.ts +0 -214
  77. package/src/json_utils.spec.ts +0 -51
  78. package/src/json_utils.ts +0 -33
  79. package/src/logger.spec.ts +0 -210
  80. package/src/logger.ts +0 -251
  81. package/src/malloy_pin_prereqs.spec.ts +0 -25
  82. package/src/materialization_metrics.spec.ts +0 -169
  83. package/src/materialization_metrics.ts +0 -346
  84. package/src/mcp/error_messages.spec.ts +0 -35
  85. package/src/mcp/error_messages.ts +0 -183
  86. package/src/mcp/handler_utils.spec.ts +0 -108
  87. package/src/mcp/handler_utils.ts +0 -251
  88. package/src/mcp/mcp_constants.ts +0 -6
  89. package/src/mcp/query_envelope.spec.ts +0 -229
  90. package/src/mcp/query_envelope.ts +0 -240
  91. package/src/mcp/server.protocol.spec.ts +0 -250
  92. package/src/mcp/server.ts +0 -87
  93. package/src/mcp/skills/build_skills_bundle.spec.ts +0 -51
  94. package/src/mcp/skills/build_skills_bundle.ts +0 -176
  95. package/src/mcp/skills/skills_bundle.json +0 -1
  96. package/src/mcp/skills/skills_bundle.spec.ts +0 -171
  97. package/src/mcp/tool_response.spec.ts +0 -108
  98. package/src/mcp/tool_response.ts +0 -138
  99. package/src/mcp/tools/compile_tool.spec.ts +0 -315
  100. package/src/mcp/tools/compile_tool.ts +0 -208
  101. package/src/mcp/tools/docs_search/build_docs_index.ts +0 -132
  102. package/src/mcp/tools/docs_search/malloy_docs_index.json +0 -1
  103. package/src/mcp/tools/docs_search_tool.spec.ts +0 -32
  104. package/src/mcp/tools/docs_search_tool.ts +0 -128
  105. package/src/mcp/tools/embedding_index.spec.ts +0 -1236
  106. package/src/mcp/tools/embedding_index.ts +0 -808
  107. package/src/mcp/tools/execute_query_tool.spec.ts +0 -294
  108. package/src/mcp/tools/execute_query_tool.ts +0 -316
  109. package/src/mcp/tools/get_context_eval.ts +0 -275
  110. package/src/mcp/tools/get_context_tool.spec.ts +0 -592
  111. package/src/mcp/tools/get_context_tool.ts +0 -585
  112. package/src/mcp/tools/reload_package_tool.spec.ts +0 -232
  113. package/src/mcp/tools/reload_package_tool.ts +0 -132
  114. package/src/mcp_config.spec.ts +0 -919
  115. package/src/mcp_config.ts +0 -425
  116. package/src/oom_guards.integration.spec.ts +0 -269
  117. package/src/package_load/package_load_pool.spec.ts +0 -252
  118. package/src/package_load/package_load_pool.ts +0 -931
  119. package/src/package_load/package_load_worker.ts +0 -1034
  120. package/src/package_load/protocol.ts +0 -364
  121. package/src/package_load/rpc_wait_accountant.spec.ts +0 -109
  122. package/src/package_load/rpc_wait_accountant.ts +0 -76
  123. package/src/package_load_metrics.spec.ts +0 -114
  124. package/src/package_load_metrics.ts +0 -127
  125. package/src/path_safety.spec.ts +0 -158
  126. package/src/path_safety.ts +0 -146
  127. package/src/pg_helpers.spec.ts +0 -223
  128. package/src/pg_helpers.ts +0 -52
  129. package/src/query_cap_metrics.spec.ts +0 -89
  130. package/src/query_cap_metrics.ts +0 -116
  131. package/src/query_concurrency.spec.ts +0 -247
  132. package/src/query_concurrency.ts +0 -237
  133. package/src/query_metadata_metrics.ts +0 -49
  134. package/src/query_param_utils.ts +0 -29
  135. package/src/query_timeout.spec.ts +0 -224
  136. package/src/query_timeout.ts +0 -179
  137. package/src/runtime/publisher.js +0 -328
  138. package/src/server-old.ts +0 -988
  139. package/src/server.ts +0 -2059
  140. package/src/service/annotations.spec.ts +0 -118
  141. package/src/service/annotations.ts +0 -91
  142. package/src/service/authorize.spec.ts +0 -154
  143. package/src/service/authorize.ts +0 -498
  144. package/src/service/authorize_integration.spec.ts +0 -2041
  145. package/src/service/build_plan.spec.ts +0 -685
  146. package/src/service/build_plan.ts +0 -797
  147. package/src/service/compile_authorize.spec.ts +0 -85
  148. package/src/service/compile_fragment_techniques.spec.ts +0 -156
  149. package/src/service/connection.spec.ts +0 -2360
  150. package/src/service/connection.ts +0 -2187
  151. package/src/service/connection_config.spec.ts +0 -734
  152. package/src/service/connection_config.ts +0 -899
  153. package/src/service/connection_federation.spec.ts +0 -184
  154. package/src/service/connection_fingerprint.spec.ts +0 -102
  155. package/src/service/connection_service.spec.ts +0 -711
  156. package/src/service/connection_service.ts +0 -291
  157. package/src/service/cron_evaluator.spec.ts +0 -79
  158. package/src/service/cron_evaluator.ts +0 -105
  159. package/src/service/db_utils.spec.ts +0 -1085
  160. package/src/service/db_utils.ts +0 -1502
  161. package/src/service/duckdb_instance_isolation.spec.ts +0 -137
  162. package/src/service/ducklake_lazy_attach.spec.ts +0 -110
  163. package/src/service/embedding_provider.spec.ts +0 -329
  164. package/src/service/embedding_provider.ts +0 -236
  165. package/src/service/environment.ts +0 -2191
  166. package/src/service/environment_admission.spec.ts +0 -344
  167. package/src/service/environment_compile.spec.ts +0 -197
  168. package/src/service/environment_store.spec.ts +0 -2274
  169. package/src/service/environment_store.ts +0 -2434
  170. package/src/service/environment_store_anchoring.spec.ts +0 -107
  171. package/src/service/environment_store_clone.spec.ts +0 -350
  172. package/src/service/explore_visibility.spec.ts +0 -434
  173. package/src/service/exports_probe.spec.ts +0 -107
  174. package/src/service/extension_fetch_policy.spec.ts +0 -256
  175. package/src/service/filter.spec.ts +0 -458
  176. package/src/service/filter.ts +0 -341
  177. package/src/service/filter_bypass.spec.ts +0 -418
  178. package/src/service/filter_integration.spec.ts +0 -1004
  179. package/src/service/freshness.spec.ts +0 -183
  180. package/src/service/freshness.ts +0 -112
  181. package/src/service/gcs_s3_utils.ts +0 -427
  182. package/src/service/given.ts +0 -105
  183. package/src/service/givens_integration.spec.ts +0 -219
  184. package/src/service/manifest_loader.spec.ts +0 -189
  185. package/src/service/manifest_loader.ts +0 -156
  186. package/src/service/materialization_build_session.spec.ts +0 -435
  187. package/src/service/materialization_build_session.ts +0 -681
  188. package/src/service/materialization_config_validation.spec.ts +0 -99
  189. package/src/service/materialization_config_validation.ts +0 -120
  190. package/src/service/materialization_eligibility.spec.ts +0 -158
  191. package/src/service/materialization_eligibility.ts +0 -305
  192. package/src/service/materialization_schedule_surface.spec.ts +0 -256
  193. package/src/service/materialization_scheduler.spec.ts +0 -289
  194. package/src/service/materialization_scheduler.ts +0 -304
  195. package/src/service/materialization_serve_transform.spec.ts +0 -1003
  196. package/src/service/materialization_serve_transform.ts +0 -779
  197. package/src/service/materialization_service.spec.ts +0 -2373
  198. package/src/service/materialization_service.ts +0 -2342
  199. package/src/service/materialization_test_fixtures.ts +0 -249
  200. package/src/service/model.spec.ts +0 -1157
  201. package/src/service/model.ts +0 -3370
  202. package/src/service/model_limits.spec.ts +0 -209
  203. package/src/service/model_limits.ts +0 -131
  204. package/src/service/model_storage_serve.spec.ts +0 -193
  205. package/src/service/model_storage_serve_joins.spec.ts +0 -193
  206. package/src/service/package.spec.ts +0 -612
  207. package/src/service/package.ts +0 -1799
  208. package/src/service/package_manifest.spec.ts +0 -230
  209. package/src/service/package_manifest.ts +0 -241
  210. package/src/service/package_memory_governor.spec.ts +0 -173
  211. package/src/service/package_memory_governor.ts +0 -233
  212. package/src/service/package_quote_bound_tables.spec.ts +0 -171
  213. package/src/service/package_race.spec.ts +0 -208
  214. package/src/service/package_reload_safety.spec.ts +0 -193
  215. package/src/service/package_rollback.spec.ts +0 -190
  216. package/src/service/package_worker_path.spec.ts +0 -645
  217. package/src/service/path_injection.spec.ts +0 -39
  218. package/src/service/persist_annotation_validation.spec.ts +0 -89
  219. package/src/service/persist_annotation_validation.ts +0 -59
  220. package/src/service/persistence_policy.spec.ts +0 -374
  221. package/src/service/proxy.spec.ts +0 -414
  222. package/src/service/proxy.ts +0 -248
  223. package/src/service/query_boundary.spec.ts +0 -470
  224. package/src/service/query_metadata.spec.ts +0 -408
  225. package/src/service/query_metadata.ts +0 -492
  226. package/src/service/query_metadata_identity.spec.ts +0 -149
  227. package/src/service/query_text.spec.ts +0 -79
  228. package/src/service/query_text.ts +0 -43
  229. package/src/service/quoting.spec.ts +0 -135
  230. package/src/service/quoting.ts +0 -78
  231. package/src/service/resolve_environment.ts +0 -15
  232. package/src/service/restricted_mode.spec.ts +0 -299
  233. package/src/service/source_extraction.ts +0 -225
  234. package/src/service/theme_store.ts +0 -199
  235. package/src/storage/DatabaseInterface.ts +0 -198
  236. package/src/storage/StorageManager.ts +0 -113
  237. package/src/storage/duckdb/ConnectionRepository.ts +0 -143
  238. package/src/storage/duckdb/DuckDBConnection.ts +0 -123
  239. package/src/storage/duckdb/DuckDBRepository.ts +0 -208
  240. package/src/storage/duckdb/EnvironmentRepository.ts +0 -148
  241. package/src/storage/duckdb/MaterializationRepository.spec.ts +0 -191
  242. package/src/storage/duckdb/MaterializationRepository.ts +0 -336
  243. package/src/storage/duckdb/PackageRepository.ts +0 -135
  244. package/src/storage/duckdb/schema.ts +0 -229
  245. package/src/stream_helpers.spec.ts +0 -280
  246. package/src/stream_helpers.ts +0 -162
  247. package/src/telemetry.ts +0 -20
  248. package/src/test_helpers/metrics_harness.ts +0 -166
  249. package/src/theme_key_parity.spec.ts +0 -57
  250. package/src/utils.ts +0 -29
  251. package/tests/fixtures/authorize-compile/model.malloy +0 -9
  252. package/tests/fixtures/authorize-compile/publisher.json +0 -4
  253. package/tests/fixtures/html-pages-nopublic/model.malloy +0 -1
  254. package/tests/fixtures/html-pages-nopublic/publisher.json +0 -5
  255. package/tests/fixtures/html-pages-test/data.csv +0 -3
  256. package/tests/fixtures/html-pages-test/public/assets/app.css +0 -3
  257. package/tests/fixtures/html-pages-test/public/barehtml.html +0 -4
  258. package/tests/fixtures/html-pages-test/public/bodymeta.html +0 -8
  259. package/tests/fixtures/html-pages-test/public/data.json +0 -1
  260. package/tests/fixtures/html-pages-test/public/fitcomment.html +0 -11
  261. package/tests/fixtures/html-pages-test/public/index.html +0 -9
  262. package/tests/fixtures/html-pages-test/public/nohead.html +0 -8
  263. package/tests/fixtures/html-pages-test/public/notfit.html +0 -13
  264. package/tests/fixtures/html-pages-test/public/slides.html +0 -12
  265. package/tests/fixtures/html-pages-test/public/sub/page2.html +0 -9
  266. package/tests/fixtures/html-pages-test/public/unterminated.html +0 -10
  267. package/tests/fixtures/html-pages-test/publisher.json +0 -5
  268. package/tests/fixtures/html-pages-test/report.malloy +0 -1
  269. package/tests/fixtures/persist-multi-level/data/orders.csv +0 -5
  270. package/tests/fixtures/persist-multi-level/multi_level.malloy +0 -18
  271. package/tests/fixtures/persist-multi-level/publisher.json +0 -5
  272. package/tests/fixtures/persist-schedule-test/data/orders.csv +0 -5
  273. package/tests/fixtures/persist-schedule-test/persist_schedule_test.malloy +0 -11
  274. package/tests/fixtures/persist-schedule-test/publisher.json +0 -7
  275. package/tests/fixtures/persist-schedule-warn-test/data/orders.csv +0 -5
  276. package/tests/fixtures/persist-schedule-warn-test/persist_warn_test.malloy +0 -11
  277. package/tests/fixtures/persist-schedule-warn-test/publisher.json +0 -6
  278. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  279. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  280. package/tests/fixtures/persist-test/publisher.json +0 -5
  281. package/tests/fixtures/publisher.config.json +0 -15
  282. package/tests/fixtures/query-givens/data/orders.csv +0 -7
  283. package/tests/fixtures/query-givens/model.malloy +0 -34
  284. package/tests/fixtures/query-givens/publisher.json +0 -5
  285. package/tests/fixtures/xlsx/database.xlsx +0 -0
  286. package/tests/harness/mcp_test_setup.ts +0 -332
  287. package/tests/harness/rest_e2e.ts +0 -68
  288. package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +0 -92
  289. package/tests/integration/concurrent_package/concurrent_package.integration.spec.ts +0 -280
  290. package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +0 -138
  291. package/tests/integration/first_boot/readiness_line.integration.spec.ts +0 -177
  292. package/tests/integration/html_pages/html_pages.integration.spec.ts +0 -440
  293. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +0 -259
  294. package/tests/integration/materialization/environment_list.integration.spec.ts +0 -133
  295. package/tests/integration/materialization/freshness_gate.integration.spec.ts +0 -292
  296. package/tests/integration/materialization/manifest_binding.integration.spec.ts +0 -430
  297. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -441
  298. package/tests/integration/materialization/orchestrated_rebind.integration.spec.ts +0 -150
  299. package/tests/integration/materialization/reference_manifest.integration.spec.ts +0 -251
  300. package/tests/integration/materialization/schedule_edit.integration.spec.ts +0 -126
  301. package/tests/integration/materialization/scheduler.integration.spec.ts +0 -200
  302. package/tests/integration/materialization/scheduler_recovery.integration.spec.ts +0 -172
  303. package/tests/integration/materialization/scheduler_transitions.integration.spec.ts +0 -256
  304. package/tests/integration/materialization/scheduler_wiring.integration.spec.ts +0 -143
  305. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -438
  306. package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +0 -235
  307. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -98
  308. package/tests/integration/query_givens/query_givens.integration.spec.ts +0 -146
  309. package/tests/integration/query_givens/query_givens_authorize.integration.spec.ts +0 -121
  310. package/tests/integration/sdk_givens/sdk_givens.integration.spec.ts +0 -110
  311. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -421
  312. package/tests/unit/controller/theme.controller.spec.ts +0 -141
  313. package/tests/unit/duckdb/attached_databases.test.ts +0 -1243
  314. package/tests/unit/duckdb/duckdb_connection.test.ts +0 -181
  315. package/tests/unit/duckdb/legacy_schema_migration.test.ts +0 -197
  316. package/tests/unit/duckdb/repositories.test.ts +0 -208
  317. package/tests/unit/ducklake/ducklake.test.ts +0 -952
  318. package/tests/unit/service/theme_store.spec.ts +0 -139
  319. package/tsconfig.json +0 -32
@@ -1,315 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { formatDiagnosticsText, registerCompileTool } from "./compile_tool";
3
- import type { EnvironmentStore } from "../../service/environment_store";
4
- import { PackageNotFoundError, ServiceUnavailableError } from "../../errors";
5
-
6
- // Capture the handler registerCompileTool passes to McpServer.tool, so it can
7
- // be exercised against a mocked EnvironmentStore. The tool builds a
8
- // CompileController internally, which calls environment.compileSource, so the
9
- // mock only needs getEnvironment -> { compileSource }.
10
- type Content = Array<{
11
- type?: string;
12
- text?: string;
13
- resource?: { text: string };
14
- }>;
15
-
16
- type Handler = (params: Record<string, unknown>) => Promise<{
17
- isError?: boolean;
18
- content: Content;
19
- }>;
20
-
21
- function captureHandler(store: Partial<EnvironmentStore>): Handler {
22
- let handler: Handler | undefined;
23
- const fakeServer = {
24
- tool: (_name: string, _desc: string, _shape: unknown, h: Handler) => {
25
- handler = h;
26
- },
27
- };
28
- registerCompileTool(fakeServer as never, store as EnvironmentStore);
29
- if (!handler) throw new Error("handler was not registered");
30
- return handler;
31
- }
32
-
33
- function parse(result: { content: Content }) {
34
- return JSON.parse(result.content[0].resource!.text);
35
- }
36
-
37
- function textBlock(result: { content: Content }) {
38
- return result.content.find((b) => b.type === "text")?.text;
39
- }
40
-
41
- function storeReturning(
42
- problems: Array<Record<string, unknown>>,
43
- sql?: string,
44
- ): Partial<EnvironmentStore> {
45
- return {
46
- getEnvironment: async () =>
47
- ({
48
- compileSource: async () => ({ problems, sql }),
49
- }) as never,
50
- };
51
- }
52
-
53
- const args = {
54
- environmentName: "malloy-samples",
55
- packageName: "ecommerce",
56
- modelPath: "ecommerce.malloy",
57
- source: "run: order_items -> { aggregate: c is count() }",
58
- };
59
-
60
- describe("malloy_compile tool", () => {
61
- it("returns status success and empty diagnostics for a clean compile", async () => {
62
- const handler = captureHandler(storeReturning([]));
63
- const result = await handler(args);
64
- expect(result.isError).toBe(false);
65
- expect(parse(result)).toEqual({ status: "success", diagnostics: [] });
66
- });
67
-
68
- it("flattens an error diagnostic and sets isError", async () => {
69
- const handler = captureHandler(
70
- storeReturning([
71
- {
72
- severity: "error",
73
- message: "'nope' is not defined",
74
- code: "field-not-found",
75
- at: {
76
- range: {
77
- start: { line: 2, character: 3 },
78
- end: { line: 2, character: 7 },
79
- },
80
- },
81
- replacement: undefined,
82
- },
83
- ]),
84
- );
85
- const result = await handler(args);
86
- expect(result.isError).toBe(true);
87
- const parsed = parse(result);
88
- expect(parsed.status).toBe("error");
89
- expect(parsed.diagnostics).toEqual([
90
- {
91
- severity: "error",
92
- message: "'nope' is not defined",
93
- code: "field-not-found",
94
- line: 2,
95
- character: 3,
96
- endLine: 2,
97
- endCharacter: 7,
98
- },
99
- ]);
100
- });
101
-
102
- it("also states the diagnostics in a text block", async () => {
103
- // The regression pin for the isError path that matters most: a bad Malloy
104
- // snippet. A client that renders only text blocks on isError sees nothing
105
- // without this, and reports a bare "Unknown error" while the diagnostics
106
- // sit unread in the resource block.
107
- const handler = captureHandler(
108
- storeReturning([
109
- {
110
- severity: "error",
111
- message: "'nope' is not defined",
112
- code: "field-not-found",
113
- at: {
114
- range: {
115
- start: { line: 2, character: 3 },
116
- end: { line: 2, character: 7 },
117
- },
118
- },
119
- },
120
- ]),
121
- );
122
- const result = await handler(args);
123
- expect(textBlock(result)).toBe(
124
- "Compile failed with 1 error (positions are 0-based):\n\n" +
125
- "- 'nope' is not defined (line 2, character 3) [field-not-found]",
126
- );
127
- });
128
-
129
- it("adds no text block to a clean compile", async () => {
130
- // Success stays resource-only: the payload is the answer, and duplicating
131
- // it as prose would double every response for no gain.
132
- const handler = captureHandler(storeReturning([]));
133
- expect(textBlock(await handler(args))).toBeUndefined();
134
- });
135
-
136
- it("treats a warning as a clean compile (status success, isError false)", async () => {
137
- const handler = captureHandler(
138
- storeReturning([
139
- {
140
- severity: "warn",
141
- message: "deprecated syntax",
142
- code: "deprecated",
143
- at: {
144
- range: {
145
- start: { line: 0, character: 0 },
146
- end: { line: 0, character: 4 },
147
- },
148
- },
149
- },
150
- ]),
151
- );
152
- const result = await handler(args);
153
- expect(result.isError).toBe(false);
154
- const parsed = parse(result);
155
- expect(parsed.status).toBe("success");
156
- expect(parsed.diagnostics[0].severity).toBe("warn");
157
- });
158
-
159
- it("includes sql when the controller returns it", async () => {
160
- const handler = captureHandler(storeReturning([], "SELECT 1"));
161
- const parsed = parse(await handler({ ...args, includeSql: true }));
162
- expect(parsed.sql).toBe("SELECT 1");
163
- });
164
-
165
- it("surfaces a thrown error as a tool error, not a transport fault", async () => {
166
- const handler = captureHandler({
167
- getEnvironment: async () => {
168
- throw new Error("Environment 'nope' could not be resolved");
169
- },
170
- });
171
- const result = await handler({ ...args, environmentName: "nope" });
172
- expect(result.isError).toBe(true);
173
- const parsed = parse(result);
174
- expect(parsed.error).toBeDefined();
175
- });
176
-
177
- it("reports a missing package as not-found, not as a Malloy syntax problem", async () => {
178
- // Pin the remediation text, not just that an error came back. Asserting
179
- // only that `error` is defined passes even when every class funnels
180
- // through the Malloy helper, which is the bug classifyToolError exists to
181
- // fix: a typo'd package name told the caller to check its Malloy syntax.
182
- const handler = captureHandler({
183
- getEnvironment: async () => {
184
- throw new PackageNotFoundError("Package 'nope' not found");
185
- },
186
- });
187
- const parsed = parse(await handler({ ...args, packageName: "nope" }));
188
- expect(parsed.error).toContain("Resource not found");
189
- expect(JSON.stringify(parsed.suggestions)).not.toContain("Malloy file");
190
- });
191
-
192
- it("reports back-pressure as retryable, not as a Malloy syntax problem", async () => {
193
- const handler = captureHandler({
194
- getEnvironment: async () => {
195
- throw new ServiceUnavailableError("Memory limit reached");
196
- },
197
- });
198
- const parsed = parse(await handler(args));
199
- expect(parsed.error).toContain("Memory limit reached");
200
- expect(JSON.stringify(parsed.suggestions)).toContain("Retry");
201
- expect(JSON.stringify(parsed.suggestions)).not.toContain("Malloy file");
202
- });
203
-
204
- it("reports an unexpected internal error without blaming the Malloy", async () => {
205
- // The catch-all used to hand a TypeError to the Malloy helper, so a bug
206
- // in our own code told the agent to go edit a model that was fine.
207
- const handler = captureHandler({
208
- getEnvironment: async () => {
209
- throw new TypeError("cannot read properties of undefined");
210
- },
211
- });
212
- const parsed = parse(await handler(args));
213
- expect(parsed.error).toContain("unexpected internal error");
214
- expect(JSON.stringify(parsed.suggestions)).not.toContain("Malloy file");
215
- });
216
-
217
- it("omits position fields when a diagnostic has no location", async () => {
218
- const handler = captureHandler(
219
- storeReturning([
220
- { severity: "error", message: "no location", code: "syntax" },
221
- ]),
222
- );
223
- const parsed = parse(await handler(args));
224
- expect(parsed.diagnostics[0]).toEqual({
225
- severity: "error",
226
- message: "no location",
227
- code: "syntax",
228
- });
229
- });
230
-
231
- describe("formatDiagnosticsText", () => {
232
- it("renders only the error-severity diagnostics", () => {
233
- const text = formatDiagnosticsText([
234
- { severity: "warn", message: "deprecated syntax", code: "dep" },
235
- { severity: "error", message: "first", line: 1, character: 0 },
236
- { severity: "error", message: "second", line: 4, character: 2 },
237
- ]);
238
- expect(text).toBe(
239
- "Compile failed with 2 errors (positions are 0-based):\n\n" +
240
- "- first (line 1, character 0)\n" +
241
- "- second (line 4, character 2)",
242
- );
243
- expect(text).not.toContain("deprecated syntax");
244
- });
245
-
246
- it("omits the position when a diagnostic has no location", () => {
247
- expect(
248
- formatDiagnosticsText([
249
- { severity: "error", message: "no location", code: "syntax" },
250
- ]),
251
- ).toBe(
252
- "Compile failed with 1 error (positions are 0-based):\n\n" +
253
- "- no location [syntax]",
254
- );
255
- });
256
-
257
- it("renders line 0 / character 0 rather than dropping them", () => {
258
- // 0 is a real position, not a missing one; a falsy check here would
259
- // silently swallow the first line of the file.
260
- expect(
261
- formatDiagnosticsText([
262
- {
263
- severity: "error",
264
- message: "at the top",
265
- line: 0,
266
- character: 0,
267
- },
268
- ]),
269
- ).toContain("- at the top (line 0, character 0)");
270
- });
271
-
272
- it("defaults character to 0 when only the line is known", () => {
273
- expect(
274
- formatDiagnosticsText([
275
- { severity: "error", message: "line only", line: 7 },
276
- ]),
277
- ).toContain("- line only (line 7, character 0)");
278
- });
279
-
280
- it("falls back to every diagnostic when none has error severity", () => {
281
- // Defensive: status is "error" only when an error-severity diagnostic
282
- // exists, so this should be unreachable. It must not render an empty
283
- // list if that ever changes.
284
- expect(
285
- formatDiagnosticsText([
286
- { severity: "warn", message: "only a warn" },
287
- ]),
288
- ).toBe(
289
- "Compile failed with 1 error (positions are 0-based):\n\n" +
290
- "- only a warn",
291
- );
292
- });
293
- });
294
-
295
- it("forwards givens to compileSource", async () => {
296
- let captured: unknown;
297
- const handler = captureHandler({
298
- getEnvironment: async () =>
299
- ({
300
- compileSource: async (
301
- _pkg: string,
302
- _model: string,
303
- _source: string,
304
- _includeSql: boolean,
305
- givens: unknown,
306
- ) => {
307
- captured = givens;
308
- return { problems: [] };
309
- },
310
- }) as never,
311
- });
312
- await handler({ ...args, givens: { region: "west" } });
313
- expect(captured).toEqual({ region: "west" });
314
- });
315
- });
@@ -1,208 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import type { GivenValue } from "@malloydata/malloy";
4
- import { logger } from "../../logger";
5
- import { EnvironmentStore } from "../../service/environment_store";
6
- import { CompileController } from "../../controller/compile.controller";
7
- import { type ErrorDetails } from "../error_messages";
8
- import { buildMalloyUri, classifyToolError } from "../handler_utils";
9
- import { jsonResource, jsonToolError } from "../tool_response";
10
-
11
- // Zod shape for malloy_compile. environmentName/packageName mirror the other
12
- // tools and point the agent at malloy_getContext for name discovery.
13
- const compileShape = {
14
- environmentName: z
15
- .string()
16
- .describe(
17
- "Environment name. Call malloy_getContext with no arguments to list the available environments.",
18
- ),
19
- packageName: z
20
- .string()
21
- .describe(
22
- "Package containing the model. Call malloy_getContext with just environmentName to list its packages.",
23
- ),
24
- modelPath: z
25
- .string()
26
- .describe(
27
- "Path to the .malloy model whose namespace the source compiles against. The source is appended to this model, so its imports, sources, and queries are in scope.",
28
- ),
29
- source: z
30
- .string()
31
- .describe(
32
- "The Malloy source to validate. Compiled in the context of modelPath and not executed.",
33
- ),
34
- includeSql: z
35
- .boolean()
36
- .optional()
37
- .describe(
38
- "When true and the source ends in a runnable query, also return the generated SQL for inspection. The query is still not run.",
39
- ),
40
- givens: z
41
- .record(z.unknown())
42
- .optional()
43
- .describe(
44
- "Given values for the model's given: block. Also required to satisfy any #(authorize) gate on the target model, whether or not includeSql is set.",
45
- ),
46
- };
47
-
48
- const COMPILE_DESCRIPTION = `Compile-check Malloy source against a model and return structured diagnostics WITHOUT running a query. Use this to validate a model or a change while authoring, instead of firing a throwaway malloy_executeQuery just to see whether it parses.
49
-
50
- ## Parameters
51
- - environmentName, packageName, modelPath (required): the model whose namespace the source compiles against. The source is appended to that model, so its imports, sources, and queries are in scope, and modelPath is real context, not a label.
52
- - source (required): the Malloy text to validate.
53
- - includeSql (optional): also return the generated SQL when the source ends in a runnable query. The query is still not executed and no data is scanned.
54
-
55
- ## Checking part of a source
56
- Your source is APPENDED, so it must stand alone as top-level Malloy: a bare \`view:\`, \`dimension:\` or \`measure:\` does not compile on its own, and resubmitting a source the model already declares reports "Cannot redefine". docs/ai-agents.md gives the two forms that work.
57
-
58
- ## Response
59
- A JSON object with status ("success" or "error") and diagnostics: an array of { severity ("error" / "warn" / "debug"), message, code, line, character, endLine, endCharacter, replacement }. Positions are 0-based (line and character start at 0) and relative to the model file with your source appended to it, so a diagnostic in your submitted source lands after the model's own line count, and a diagnostic may point at pre-existing content in the model rather than at your source. Any wrapper you add counts toward that offset too. A clean compile can still return warnings; status is "error" only when at least one diagnostic has error severity. When status is "error" the response also states the error diagnostics in a plain text block alongside the JSON, so the failure is legible without parsing the payload.`;
60
-
61
- /** The flattened diagnostic shape this tool returns. */
62
- type CompileDiagnostic = {
63
- severity: string;
64
- message: string;
65
- code?: string;
66
- line?: number;
67
- character?: number;
68
- };
69
-
70
- /**
71
- * Renders error diagnostics as the prose a text-only client will show.
72
- *
73
- * A failed compile is an isError result whose diagnostics live in the resource
74
- * block, so a client that renders only text blocks on isError sees nothing —
75
- * which is how a real compile error gets reported as a bare "Unknown error".
76
- * The resource block stays the parseable channel; this is the legible one.
77
- * Positions are labelled 0-based to match the payload rather than introduce a
78
- * second convention for the same numbers.
79
- *
80
- * Only error-severity diagnostics are rendered: status is "error" because of
81
- * those, and they are what the caller has to fix. Falling back to every
82
- * diagnostic keeps the text non-empty if that invariant ever breaks.
83
- *
84
- * Exported so a spec can pin the rendering rather than merely its presence.
85
- */
86
- export function formatDiagnosticsText(
87
- diagnostics: CompileDiagnostic[],
88
- ): string {
89
- const errors = diagnostics.filter((d) => d.severity === "error");
90
- const shown = errors.length > 0 ? errors : diagnostics;
91
- const lines = shown.map((d) => {
92
- const at =
93
- d.line !== undefined
94
- ? ` (line ${d.line}, character ${d.character ?? 0})`
95
- : "";
96
- const code = d.code ? ` [${d.code}]` : "";
97
- return `- ${d.message}${at}${code}`;
98
- });
99
- const noun = shown.length === 1 ? "error" : "errors";
100
- return `Compile failed with ${shown.length} ${noun} (positions are 0-based):\n\n${lines.join("\n")}`;
101
- }
102
-
103
- /**
104
- * Registers the malloy_compile MCP tool: validates Malloy source against a model
105
- * and returns structured diagnostics without executing a query. Wraps the same
106
- * in-process compile path the REST /compile endpoint uses (CompileController).
107
- */
108
- export function registerCompileTool(
109
- mcpServer: McpServer,
110
- environmentStore: EnvironmentStore,
111
- ): void {
112
- const compileController = new CompileController(environmentStore);
113
-
114
- mcpServer.tool(
115
- "malloy_compile",
116
- COMPILE_DESCRIPTION,
117
- compileShape,
118
- async (params) => {
119
- const {
120
- environmentName,
121
- packageName,
122
- modelPath,
123
- source,
124
- includeSql,
125
- givens,
126
- } = params;
127
-
128
- logger.info("[MCP Tool compile] Compiling source", {
129
- environmentName,
130
- packageName,
131
- modelPath,
132
- includeSql: !!includeSql,
133
- });
134
-
135
- const uri = buildMalloyUri(
136
- {
137
- environment: environmentName,
138
- package: packageName,
139
- resourceType: "models",
140
- resourceName: modelPath,
141
- },
142
- "compile",
143
- );
144
-
145
- try {
146
- const result = await compileController.compile(
147
- environmentName,
148
- packageName,
149
- modelPath,
150
- source,
151
- includeSql ?? false,
152
- givens as Record<string, GivenValue> | undefined,
153
- );
154
-
155
- // Flatten each LogMessage's nested at.range into line/character so
156
- // agents do not have to walk it. Positions are 0-based (LSP-style).
157
- const diagnostics = result.problems.map((p) => ({
158
- severity: p.severity,
159
- message: p.message,
160
- code: p.code,
161
- line: p.at?.range.start.line,
162
- character: p.at?.range.start.character,
163
- endLine: p.at?.range.end.line,
164
- endCharacter: p.at?.range.end.character,
165
- replacement: p.replacement,
166
- }));
167
-
168
- const payload = {
169
- status: result.status,
170
- diagnostics,
171
- ...(result.sql !== undefined && { sql: result.sql }),
172
- };
173
-
174
- // A compile that returns error diagnostics is still a successful
175
- // tool call in the transport sense, but isError tells the agent not
176
- // to treat the model as valid. This is the most common way the tool
177
- // fails, so it carries a text block for the same reason the catch
178
- // below does: the diagnostics are only the message to a client that
179
- // parses the resource. The payload keeps its documented
180
- // {status, diagnostics} shape rather than the {error, suggestions}
181
- // of jsonToolError, so parsing clients are unaffected.
182
- const isError = result.status === "error";
183
- return jsonResource(uri, payload, {
184
- isError,
185
- ...(isError && { text: formatDiagnosticsText(diagnostics) }),
186
- });
187
- } catch (error) {
188
- // Unknown environment/package, a notebook (.malloynb) rejected up
189
- // front, an authorize denial, or a system error: surface as a clean
190
- // isError payload rather than a transport fault. A missing modelPath
191
- // does NOT error here; compileSource compiles the source against an
192
- // empty namespace, so a typo in modelPath yields a normal result.
193
- logger.warn("[MCP Tool compile] compile failed", {
194
- environmentName,
195
- packageName,
196
- modelPath,
197
- error: error instanceof Error ? error.message : String(error),
198
- });
199
- const errorDetails: ErrorDetails = classifyToolError(
200
- "compile",
201
- `${environmentName}/${packageName}/${modelPath}`,
202
- error,
203
- );
204
- return jsonToolError(uri, errorDetails);
205
- }
206
- },
207
- );
208
- }
@@ -1,132 +0,0 @@
1
- /**
2
- * Build a bundled lunr index over the Malloy documentation for the malloy_searchDocs MCP tool.
3
- *
4
- * The output JSON is committed as a build-time asset so the server has no runtime network
5
- * dependency. Regenerate it from a Malloy docs checkout when the docs are updated.
6
- *
7
- * Usage:
8
- * bun run packages/server/src/mcp/tools/docs_search/build_docs_index.ts <docs-dir> [out.json]
9
- *
10
- * <docs-dir> points at the documentation root of a Malloy docs checkout, e.g.
11
- * .../malloydata.github.io/src/documentation
12
- */
13
- import * as fs from "fs";
14
- import * as path from "path";
15
- import lunr from "lunr";
16
-
17
- const DOCS_BASE_URL = "https://docs.malloydata.dev";
18
- const DOC_EXTENSIONS = new Set([".md", ".malloynb"]);
19
-
20
- interface DocRecord {
21
- id: string;
22
- title: string;
23
- path: string;
24
- url: string;
25
- body: string;
26
- }
27
-
28
- function walk(dir: string, acc: string[] = []): string[] {
29
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
30
- const full = path.join(dir, entry.name);
31
- if (entry.isDirectory()) {
32
- walk(full, acc);
33
- } else if (DOC_EXTENSIONS.has(path.extname(entry.name))) {
34
- acc.push(full);
35
- }
36
- }
37
- return acc;
38
- }
39
-
40
- /** First markdown heading, else a title-cased file name. */
41
- function deriveTitle(body: string, relPath: string): string {
42
- const heading = body.match(/^#\s+(.+)$/m);
43
- if (heading) return heading[1].trim();
44
- const base = path.basename(relPath).replace(/\.[^.]+$/, "");
45
- return base.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
46
- }
47
-
48
- /** docs/foo/bar.md -> https://docs.malloydata.dev/documentation/foo/bar */
49
- function deriveUrl(relPath: string): string {
50
- const noExt = relPath.replace(/\.[^.]+$/, "");
51
- return `${DOCS_BASE_URL}/documentation/${noExt}`;
52
- }
53
-
54
- /** A short, single-line excerpt: first prose line that is not a heading or fence. */
55
- function deriveExcerpt(body: string): string {
56
- for (const raw of body.split("\n")) {
57
- const line = raw.trim();
58
- if (
59
- !line ||
60
- line.startsWith("#") ||
61
- line.startsWith("```") ||
62
- line.startsWith(">>>") ||
63
- line === "---"
64
- )
65
- continue;
66
- return line.replace(/\s+/g, " ").slice(0, 280);
67
- }
68
- return "";
69
- }
70
-
71
- function main(): void {
72
- const docsDir = process.argv[2];
73
- const outFile =
74
- process.argv[3] || path.join(import.meta.dir, "malloy_docs_index.json");
75
- if (!docsDir || !fs.existsSync(docsDir)) {
76
- console.error(
77
- `docs dir not found: ${docsDir}\nUsage: bun run build_docs_index.ts <docs-dir> [out.json]`,
78
- );
79
- process.exit(1);
80
- }
81
-
82
- const files = walk(docsDir);
83
- const docs: DocRecord[] = files.map((file, i) => {
84
- const body = fs.readFileSync(file, "utf8");
85
- const relPath = path.relative(docsDir, file);
86
- return {
87
- id: String(i),
88
- title: deriveTitle(body, relPath),
89
- path: relPath,
90
- url: deriveUrl(relPath),
91
- body,
92
- };
93
- });
94
-
95
- const index = lunr(function () {
96
- this.ref("id");
97
- this.field("title", { boost: 4 });
98
- this.field("body");
99
- for (const doc of docs) {
100
- this.add({ id: doc.id, title: doc.title, body: doc.body });
101
- }
102
- });
103
-
104
- // Store metadata + excerpt per doc for the tool's response; the raw body is not
105
- // shipped (only the serialized lunr postings are needed for matching).
106
- const store: Record<string, Omit<DocRecord, "body">> = {};
107
- for (const doc of docs) {
108
- store[doc.id] = {
109
- id: doc.id,
110
- title: doc.title,
111
- path: doc.path,
112
- url: doc.url,
113
- excerpt: deriveExcerpt(doc.body),
114
- } as Omit<DocRecord, "body">;
115
- }
116
-
117
- fs.writeFileSync(
118
- outFile,
119
- JSON.stringify({
120
- builtFrom: docsDir,
121
- docCount: docs.length,
122
- index,
123
- store,
124
- }),
125
- );
126
- console.log(`Wrote ${docs.length} docs to ${outFile}`);
127
- }
128
-
129
- // Only run when invoked directly (bun run ...), not when imported by a test.
130
- if (import.meta.main) {
131
- main();
132
- }