@malloy-publisher/server 0.0.196-dev → 0.0.196

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 (103) hide show
  1. package/dist/app/api-doc.yaml +213 -214
  2. package/dist/app/assets/EnvironmentPage-1j6QDWAy.js +1 -0
  3. package/dist/app/assets/HomePage-DMop21VG.js +1 -0
  4. package/dist/app/assets/MainPage-BbE8ETz1.js +2 -0
  5. package/dist/app/assets/ModelPage-D2jvfe3t.js +1 -0
  6. package/dist/app/assets/PackagePage-BbnhGoD3.js +1 -0
  7. package/dist/app/assets/{RouteError-DefbDO7F.js → RouteError-D3LGEZ3i.js} +1 -1
  8. package/dist/app/assets/WorkbookPage-DttVIj4u.js +1 -0
  9. package/dist/app/assets/{core-BrfQApxh.es-DnvCX4oH.js → core-w79IMXAG.es-Bd0UlzOL.js} +1 -1
  10. package/dist/app/assets/{index-Bu0ub036.js → index-5K9YjIxF.js} +117 -117
  11. package/dist/app/assets/{index-CkzK3JIl.js → index-C513UodQ.js} +1 -1
  12. package/dist/app/assets/{index-CoA6HIGS.js → index-DIgzgp69.js} +1 -1
  13. package/dist/app/assets/{index.umd-B6Ms2PpL.js → index.umd-BMeMPq_9.js} +1 -1
  14. package/dist/app/index.html +1 -1
  15. package/dist/server.mjs +1954 -1318
  16. package/package.json +1 -1
  17. package/publisher.config.json +2 -2
  18. package/src/config.spec.ts +181 -66
  19. package/src/config.ts +68 -47
  20. package/src/controller/compile.controller.ts +10 -7
  21. package/src/controller/connection.controller.ts +79 -58
  22. package/src/controller/database.controller.ts +10 -7
  23. package/src/controller/manifest.controller.ts +23 -14
  24. package/src/controller/materialization.controller.ts +14 -14
  25. package/src/controller/model.controller.ts +35 -20
  26. package/src/controller/package.controller.ts +83 -49
  27. package/src/controller/query.controller.ts +11 -8
  28. package/src/controller/watch-mode.controller.ts +35 -29
  29. package/src/errors.ts +2 -2
  30. package/src/mcp/error_messages.ts +2 -2
  31. package/src/mcp/handler_utils.ts +23 -20
  32. package/src/mcp/mcp_constants.ts +1 -1
  33. package/src/mcp/prompts/handlers.ts +3 -3
  34. package/src/mcp/prompts/prompt_service.ts +5 -5
  35. package/src/mcp/prompts/utils.ts +12 -12
  36. package/src/mcp/resource_metadata.ts +3 -3
  37. package/src/mcp/resources/environment_resource.ts +187 -0
  38. package/src/mcp/resources/model_resource.ts +19 -17
  39. package/src/mcp/resources/notebook_resource.ts +13 -13
  40. package/src/mcp/resources/package_resource.ts +30 -27
  41. package/src/mcp/resources/query_resource.ts +15 -10
  42. package/src/mcp/resources/source_resource.ts +10 -10
  43. package/src/mcp/resources/view_resource.ts +11 -11
  44. package/src/mcp/server.ts +16 -14
  45. package/src/mcp/tools/discovery_tools.ts +67 -49
  46. package/src/mcp/tools/execute_query_tool.ts +14 -14
  47. package/src/server-old.ts +1119 -0
  48. package/src/server.ts +191 -159
  49. package/src/service/connection.spec.ts +158 -133
  50. package/src/service/connection.ts +42 -39
  51. package/src/service/connection_config.spec.ts +13 -11
  52. package/src/service/connection_config.ts +28 -19
  53. package/src/service/connection_service.spec.ts +63 -43
  54. package/src/service/connection_service.ts +106 -89
  55. package/src/service/{project.ts → environment.ts} +92 -77
  56. package/src/service/{project_compile.spec.ts → environment_compile.spec.ts} +1 -1
  57. package/src/service/{project_store.spec.ts → environment_store.spec.ts} +99 -85
  58. package/src/service/{project_store.ts → environment_store.ts} +368 -326
  59. package/src/service/manifest_service.spec.ts +15 -15
  60. package/src/service/manifest_service.ts +26 -21
  61. package/src/service/materialization_service.spec.ts +93 -59
  62. package/src/service/materialization_service.ts +71 -62
  63. package/src/service/materialized_table_gc.spec.ts +15 -15
  64. package/src/service/materialized_table_gc.ts +3 -3
  65. package/src/service/model.ts +2 -2
  66. package/src/service/package.spec.ts +2 -2
  67. package/src/service/package.ts +23 -21
  68. package/src/service/resolve_environment.ts +15 -0
  69. package/src/storage/DatabaseInterface.ts +34 -25
  70. package/src/storage/StorageManager.mock.ts +3 -3
  71. package/src/storage/StorageManager.ts +24 -23
  72. package/src/storage/duckdb/ConnectionRepository.ts +13 -11
  73. package/src/storage/duckdb/DuckDBConnection.ts +1 -1
  74. package/src/storage/duckdb/DuckDBManifestStore.ts +6 -6
  75. package/src/storage/duckdb/DuckDBRepository.ts +47 -47
  76. package/src/storage/duckdb/{ProjectRepository.ts → EnvironmentRepository.ts} +35 -35
  77. package/src/storage/duckdb/ManifestRepository.ts +21 -20
  78. package/src/storage/duckdb/MaterializationRepository.ts +31 -28
  79. package/src/storage/duckdb/PackageRepository.ts +11 -11
  80. package/src/storage/duckdb/manifest_store.spec.ts +2 -2
  81. package/src/storage/duckdb/schema.ts +61 -20
  82. package/src/storage/ducklake/DuckLakeManifestStore.ts +14 -14
  83. package/tests/fixtures/publisher.config.json +1 -1
  84. package/tests/harness/e2e.ts +1 -1
  85. package/tests/harness/mcp_test_setup.ts +1 -1
  86. package/tests/harness/mocks.ts +10 -8
  87. package/tests/harness/rest_e2e.ts +2 -2
  88. package/tests/integration/legacy_routes/legacy_routes.integration.spec.ts +259 -0
  89. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +4 -4
  90. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +27 -48
  91. package/tests/integration/mcp/mcp_resource.integration.spec.ts +26 -35
  92. package/tests/unit/duckdb/attached_databases.test.ts +51 -33
  93. package/tests/unit/duckdb/legacy_schema_migration.test.ts +194 -0
  94. package/tests/unit/ducklake/ducklake.test.ts +24 -22
  95. package/tests/unit/mcp/prompt_happy.test.ts +8 -8
  96. package/dist/app/assets/HomePage-DbZS0N7G.js +0 -1
  97. package/dist/app/assets/MainPage-CBuWkbmr.js +0 -2
  98. package/dist/app/assets/ModelPage-Bt37smot.js +0 -1
  99. package/dist/app/assets/PackagePage-DLZe50WG.js +0 -1
  100. package/dist/app/assets/ProjectPage-FQTEPXP4.js +0 -1
  101. package/dist/app/assets/WorkbookPage-CkAo16ar.js +0 -1
  102. package/src/mcp/resources/project_resource.ts +0 -184
  103. package/src/service/resolve_project.ts +0 -13
@@ -10,7 +10,7 @@ const __dirname = path.dirname(__filename);
10
10
 
11
11
  const PROJECT_NAME = "test-project";
12
12
  const PACKAGE_NAME = "persist-test";
13
- const API = `/api/v0/projects/${PROJECT_NAME}/packages/${PACKAGE_NAME}`;
13
+ const API = `/api/v0/environments/${PROJECT_NAME}/packages/${PACKAGE_NAME}`;
14
14
 
15
15
  describe("Materialization & Manifest REST API (E2E)", () => {
16
16
  let env: (RestE2EEnv & { stop(): Promise<void> }) | null = null;
@@ -23,7 +23,7 @@ describe("Materialization & Manifest REST API (E2E)", () => {
23
23
  // Create the test project via the REST API using an absolute
24
24
  // path to the fixture so it works regardless of SERVER_ROOT.
25
25
  const fixtureDir = path.resolve(__dirname, "../../fixtures/persist-test");
26
- const createRes = await fetch(`${baseUrl}/api/v0/projects`, {
26
+ const createRes = await fetch(`${baseUrl}/api/v0/environments`, {
27
27
  method: "POST",
28
28
  headers: { "Content-Type": "application/json" },
29
29
  body: JSON.stringify({
@@ -45,7 +45,7 @@ describe("Materialization & Manifest REST API (E2E)", () => {
45
45
  while (!pkgReady && Date.now() < deadline) {
46
46
  try {
47
47
  const res = await fetch(
48
- `${baseUrl}/api/v0/projects/${PROJECT_NAME}/packages/${PACKAGE_NAME}`,
48
+ `${baseUrl}/api/v0/environments/${PROJECT_NAME}/packages/${PACKAGE_NAME}`,
49
49
  );
50
50
  if (res.ok) {
51
51
  pkgReady = true;
@@ -65,7 +65,7 @@ describe("Materialization & Manifest REST API (E2E)", () => {
65
65
  // Tear down the test project, then the HTTP server.
66
66
  if (baseUrl) {
67
67
  try {
68
- await fetch(`${baseUrl}/api/v0/projects/${PROJECT_NAME}`, {
68
+ await fetch(`${baseUrl}/api/v0/environments/${PROJECT_NAME}`, {
69
69
  method: "DELETE",
70
70
  });
71
71
  } catch {
@@ -23,7 +23,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
23
23
  let env: McpE2ETestEnvironment | null = null;
24
24
  let mcpClient: Client;
25
25
 
26
- const PROJECT_NAME = "malloy-samples";
26
+ const ENVIRONMENT_NAME = "malloy-samples";
27
27
  const PACKAGE_NAME = "faa";
28
28
 
29
29
  beforeAll(async () => {
@@ -47,7 +47,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
47
47
  const result = await mcpClient.callTool({
48
48
  name: "malloy_executeQuery",
49
49
  arguments: {
50
- projectName: "malloy-samples",
50
+ environmentName: ENVIRONMENT_NAME,
51
51
  packageName: PACKAGE_NAME,
52
52
  modelPath: "flights.malloy",
53
53
  query: "run: flights->{ aggregate: c is count() }",
@@ -95,7 +95,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
95
95
  async () => {
96
96
  if (!env) throw new Error("Test environment not initialized");
97
97
  const params = {
98
- projectName: PROJECT_NAME,
98
+ environmentName: ENVIRONMENT_NAME,
99
99
  packageName: PACKAGE_NAME,
100
100
  modelPath: "flights.malloy",
101
101
  sourceName: "flights", // Added sourceName
@@ -140,7 +140,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
140
140
  async () => {
141
141
  if (!env) throw new Error("Test environment not initialized");
142
142
  const params = {
143
- projectName: PROJECT_NAME,
143
+ environmentName: ENVIRONMENT_NAME,
144
144
  packageName: PACKAGE_NAME,
145
145
  modelPath: "flights.malloy",
146
146
  query: "run: flights->{BAD SYNTAX aggregate: flight_count is count()}",
@@ -179,7 +179,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
179
179
  it("should RESOLVE with InvalidParams for conflicting parameters (query and queryName)", async () => {
180
180
  if (!env) throw new Error("Test environment not initialized");
181
181
  const params = {
182
- projectName: PROJECT_NAME,
182
+ environmentName: ENVIRONMENT_NAME,
183
183
  packageName: PACKAGE_NAME,
184
184
  modelPath: "flights.malloy",
185
185
  query: "run: flights->{aggregate: c is count()}",
@@ -207,7 +207,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
207
207
  it("should RESOLVE with InvalidParams if required params are missing (e.g., query or queryName)", async () => {
208
208
  if (!env) throw new Error("Test environment not initialized");
209
209
  const params = {
210
- projectName: PROJECT_NAME,
210
+ environmentName: ENVIRONMENT_NAME,
211
211
  packageName: PACKAGE_NAME,
212
212
  modelPath: "flights.malloy",
213
213
  // Missing query AND queryName
@@ -235,7 +235,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
235
235
  if (!env) throw new Error("Test environment not initialized");
236
236
  const params = {
237
237
  // Missing modelPath
238
- projectName: PROJECT_NAME,
238
+ environmentName: ENVIRONMENT_NAME,
239
239
  packageName: PACKAGE_NAME,
240
240
  query: "run: flights->{aggregate: flight_count is count()}",
241
241
  };
@@ -257,7 +257,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
257
257
  it("should return application error if package not found", async () => {
258
258
  if (!env) throw new Error("Test environment not initialized");
259
259
  const params = {
260
- projectName: PROJECT_NAME,
260
+ environmentName: ENVIRONMENT_NAME,
261
261
  packageName: "nonexistent_package", // Use a package that doesn't exist
262
262
  modelPath: "flights.malloy",
263
263
  query: "run: flights->{aggregate: c is count()}",
@@ -292,7 +292,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
292
292
  expect(errorPayloadPkgNotFound.suggestions.length).toBeGreaterThan(0);
293
293
 
294
294
  // Check the specific error message within the parsed object
295
- const expectedErrorMessageNotFound = `Resource not found: package '${params.packageName}' in project '${params.projectName}'`;
295
+ const expectedErrorMessageNotFound = `Resource not found: package '${params.packageName}' in environment '${params.environmentName}'`;
296
296
  expect(errorPayloadPkgNotFound.error).toEqual(
297
297
  expectedErrorMessageNotFound,
298
298
  );
@@ -301,7 +301,7 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
301
301
  it("should return application error if model not found within package", async () => {
302
302
  if (!env) throw new Error("Test environment not initialized");
303
303
  const params = {
304
- projectName: PROJECT_NAME,
304
+ environmentName: ENVIRONMENT_NAME,
305
305
  packageName: PACKAGE_NAME,
306
306
  modelPath: "nonexistent_model.malloy", // Use a model that doesn't exist
307
307
  query: "run: flights->{aggregate: c is count()}",
@@ -335,68 +335,47 @@ describe.serial("MCP Tool Handlers (E2E Integration)", () => {
335
335
  expect(errorPayloadModel.suggestions.length).toBeGreaterThan(0);
336
336
 
337
337
  // Check the specific error message within the parsed object
338
- const expectedErrorMessageModel = `Resource not found: model '${params.modelPath}' in package '${params.packageName}' for project '${params.projectName}'`;
338
+ const expectedErrorMessageModel = `Resource not found: model '${params.modelPath}' in package '${params.packageName}' for environment '${params.environmentName}'`;
339
339
  expect(errorPayloadModel.error).toEqual(expectedErrorMessageModel);
340
340
 
341
341
  // Check for the specific model name and context in the message
342
342
  expect(errorPayloadModel.error).toMatch(/Resource not found/i);
343
343
  });
344
344
 
345
- // Added from mcp_query_tool.spec.ts
346
- it("should handle query cancellation via client close", async () => {
345
+ // Stateless HTTP + fast queries make true in-flight cancellation flaky
346
+ // (the response often completes before close wins the race). Assert the
347
+ // transport contract instead: a closed client cannot issue further tools.
348
+ it("should reject malloy_executeQuery after the MCP client is closed", async () => {
347
349
  if (!env) throw new Error("Test environment not initialized");
348
350
 
349
- // Create a new client *specifically* for this test so we can close it
350
- // without affecting other tests running concurrently (if any).
351
- const cancelClient = new Client<Request, Notification, Result>({
352
- name: "cancel-test-client",
351
+ const closedClient = new Client<Request, Notification, Result>({
352
+ name: "closed-client-test",
353
353
  version: "1.0",
354
354
  });
355
- // Corrected: Use StreamableHTTPClientTransport with the server URL + /mcp endpoint
356
- const cancelTransport = new StreamableHTTPClientTransport(
355
+ const transport = new StreamableHTTPClientTransport(
357
356
  new URL(env.serverUrl + "/mcp"),
358
357
  );
359
- await cancelClient.connect(cancelTransport);
358
+ await closedClient.connect(transport);
359
+ await closedClient.close();
360
360
 
361
- expect.assertions(2); // Expecting two assertions: instanceof Error and message match
362
- let toolPromise;
363
- try {
364
- toolPromise = cancelClient.callTool({
361
+ await expect(
362
+ closedClient.callTool({
365
363
  name: "malloy_executeQuery",
366
364
  arguments: {
367
- projectName: PROJECT_NAME,
365
+ environmentName: ENVIRONMENT_NAME,
368
366
  packageName: PACKAGE_NAME,
369
367
  modelPath: "flights.malloy",
370
- // Use a query known to take a little time if possible, otherwise a simple one
371
- query: "run: flights->{aggregate: c is count() for 100}",
368
+ query: "run: flights->{aggregate: c is count()}",
372
369
  },
373
- });
374
-
375
- // Give the request a moment to start on the server
376
- await new Promise((resolve) => setTimeout(resolve, 100));
377
-
378
- // Close the client to trigger cancellation
379
- await cancelClient.close();
380
-
381
- // Await the promise - it should reject due to the closure
382
- await toolPromise;
383
-
384
- throw new Error("Promise should have rejected due to cancellation");
385
- } catch (error) {
386
- // Check that the error is an Error instance and the message indicates closure/cancellation
387
- expect(error).toBeInstanceOf(Error);
388
- expect((error as Error).message).toMatch(/cancel|closed/i);
389
- } finally {
390
- // Ensure the temporary client is closed even if the test failed unexpectedly
391
- await cancelClient.close().catch(() => {}); // Ignore errors on final cleanup
392
- }
370
+ }),
371
+ ).rejects.toThrow();
393
372
  });
394
373
 
395
374
  // Test invalid usage - nested view called without sourceName
396
375
  it("should return application error for nested view without sourceName", async () => {
397
376
  if (!env) throw new Error("Test environment not initialized");
398
377
  const params = {
399
- projectName: PROJECT_NAME,
378
+ environmentName: ENVIRONMENT_NAME,
400
379
  packageName: PACKAGE_NAME,
401
380
  modelPath: "flights.malloy",
402
381
  queryName: "top_carriers", // Nested view, but sourceName is missing
@@ -37,29 +37,29 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
37
37
  });
38
38
 
39
39
  // --- Test Constants ---
40
- const homeProjectUri = "malloy://project/malloy-samples";
41
- const faaPackageUri = "malloy://project/malloy-samples/package/faa";
40
+ const homeProjectUri = "malloy://environment/malloy-samples";
41
+ const faaPackageUri = "malloy://environment/malloy-samples/package/faa";
42
42
  const flightsModelUri =
43
- "malloy://project/malloy-samples/package/faa/models/flights.malloy";
43
+ "malloy://environment/malloy-samples/package/faa/models/flights.malloy";
44
44
  const FLIGHTS_SOURCE = "flights";
45
45
  const FLIGHTS_CARRIER_QUERY = "flights_by_carrier";
46
46
  const FLIGHTS_MONTH_VIEW = "flights_by_month";
47
47
  const OVERVIEW_NOTEBOOK = "overview.malloynb";
48
48
  const nonExistentPackageUri =
49
- "malloy://project/malloy-samples/package/nonexistent";
49
+ "malloy://environment/malloy-samples/package/nonexistent";
50
50
  const nonExistentModelUri =
51
- "malloy://project/malloy-samples/package/faa/models/nonexistent.malloy";
52
- const nonExistentProjectUri = "malloy://project/invalid_project";
51
+ "malloy://environment/malloy-samples/package/faa/models/nonexistent.malloy";
52
+ const nonExistentProjectUri = "malloy://environment/invalid_project";
53
53
  const invalidUri = "invalid://format";
54
54
 
55
- const validSourceUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}`;
56
- const validQueryUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/queries/${FLIGHTS_CARRIER_QUERY}`;
57
- const validViewUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/${FLIGHTS_MONTH_VIEW}`;
58
- const validNotebookUri = `malloy://project/malloy-samples/package/faa/notebooks/${OVERVIEW_NOTEBOOK}`;
59
- const nonExistentSourceUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/sources/non_existent_source`;
60
- const nonExistentQueryUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/queries/non_existent_query`;
61
- const nonExistentViewUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/non_existent_view`;
62
- const nonExistentNotebookUri = `malloy://project/malloy-samples/package/faa/notebooks/non_existent.malloynb`;
55
+ const validSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}`;
56
+ const validQueryUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/queries/${FLIGHTS_CARRIER_QUERY}`;
57
+ const validViewUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/${FLIGHTS_MONTH_VIEW}`;
58
+ const validNotebookUri = `malloy://environment/malloy-samples/package/faa/notebooks/${OVERVIEW_NOTEBOOK}`;
59
+ const nonExistentSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/non_existent_source`;
60
+ const nonExistentQueryUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/queries/non_existent_query`;
61
+ const nonExistentViewUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/${FLIGHTS_SOURCE}/views/non_existent_view`;
62
+ const nonExistentNotebookUri = `malloy://environment/malloy-samples/package/faa/notebooks/non_existent.malloynb`;
63
63
 
64
64
  describe("client.listResources", () => {
65
65
  it(
@@ -217,7 +217,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
217
217
  );
218
218
  if (flightsEntry) {
219
219
  expect(flightsEntry.uri).toBe(
220
- "malloy://project/malloy-samples/package/faa/sources/flights.malloy",
220
+ "malloy://environment/malloy-samples/package/faa/sources/flights.malloy",
221
221
  );
222
222
  expect(flightsEntry.metadata).toBeDefined();
223
223
  expect(flightsEntry.metadata!.description).toContain(
@@ -232,7 +232,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
232
232
  );
233
233
  if (notebookEntry) {
234
234
  expect(notebookEntry.uri).toBe(
235
- "malloy://project/malloy-samples/package/faa/notebooks/aircraft_analysis.malloynb",
235
+ "malloy://environment/malloy-samples/package/faa/notebooks/aircraft_analysis.malloynb",
236
236
  );
237
237
  expect(notebookEntry.metadata).toBeDefined();
238
238
  expect(notebookEntry.metadata!.description).toContain(
@@ -505,7 +505,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
505
505
  // Adjust test to expect the generic "Resource not found" error, as the specific
506
506
  // "not a notebook" detail isn't easily surfaced in the standard error format.
507
507
  expect(errorPayload.error).toMatch(/Notebook 'overview.malloynb'/);
508
- expect(errorPayload.error).toMatch(/project 'malloy-samples'/); // Check project name
508
+ expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
509
509
  expect(errorPayload.suggestions).toBeDefined();
510
510
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
511
511
  expect(errorPayload.suggestions.length).toBeGreaterThan(0);
@@ -532,8 +532,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
532
532
  expect(errorPayload.error).toMatch(/Resource not found/i);
533
533
  // Check for the specific source name in the message
534
534
  expect(errorPayload.error).toMatch(/Source 'non_existent_source'/);
535
- // Adjust project name expectation
536
- expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
535
+ expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
537
536
  expect(errorPayload.suggestions).toBeDefined();
538
537
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
539
538
  expect(errorPayload.suggestions.length).toBeGreaterThan(0);
@@ -558,8 +557,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
558
557
  expect(errorPayload.error).toMatch(/Resource not found/i);
559
558
  // Check for the specific query name in the message
560
559
  expect(errorPayload.error).toMatch(/Query 'non_existent_query'/);
561
- // Adjust project name expectation
562
- expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
560
+ expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
563
561
  expect(errorPayload.suggestions).toBeDefined();
564
562
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
565
563
  expect(errorPayload.suggestions.length).toBeGreaterThan(0);
@@ -584,8 +582,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
584
582
  expect(errorPayload.error).toMatch(/Resource not found/i);
585
583
  // Check for the specific view name in the message
586
584
  expect(errorPayload.error).toMatch(/View 'non_existent_view'/);
587
- // Adjust project name expectation
588
- expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
585
+ expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
589
586
  expect(errorPayload.suggestions).toBeDefined();
590
587
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
591
588
  expect(errorPayload.suggestions.length).toBeGreaterThan(0);
@@ -611,8 +608,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
611
608
  // Check for the specific notebook name and context in the message
612
609
  // Adjust test to expect the generic "Resource not found" error
613
610
  expect(errorPayload.error).toMatch(/Notebook 'non_existent.malloynb'/);
614
- // Adjust project name expectation
615
- expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
611
+ expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
616
612
  expect(errorPayload.suggestions).toBeDefined();
617
613
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
618
614
  expect(errorPayload.suggestions.length).toBeGreaterThan(0);
@@ -623,7 +619,7 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
623
619
 
624
620
  it("should return structured app error when requesting view from wrong source", async () => {
625
621
  if (!env) throw new Error("Test environment not initialized");
626
- const wrongSourceUri = `malloy://project/malloy-samples/package/faa/models/flights.malloy/sources/aircraft/views/${FLIGHTS_MONTH_VIEW}`;
622
+ const wrongSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/aircraft/views/${FLIGHTS_MONTH_VIEW}`;
627
623
  const result = await mcpClient.readResource({ uri: wrongSourceUri });
628
624
  expect(result.isError).toBe(true);
629
625
  expect(result.contents).toBeDefined();
@@ -633,22 +629,17 @@ describe.serial("MCP Resource Handlers (E2E Integration)", () => {
633
629
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
634
630
  const errorPayload = JSON.parse((result.contents![0] as any).text);
635
631
  expect(errorPayload.error).toBeDefined();
636
- // Adjust expectation: The primary error should be the project not found
637
632
  expect(errorPayload.error).toMatch(/Resource not found/i);
638
- expect(errorPayload.error).toMatch(/project 'malloy-samples'/);
639
- // Remove checks for view/source name in the error for this specific case
633
+ expect(errorPayload.error).toMatch(/View 'flights_by_month'/);
634
+ expect(errorPayload.error).toMatch(/source 'aircraft'/);
635
+ expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
640
636
  expect(errorPayload.suggestions).toBeDefined();
641
637
  expect(Array.isArray(errorPayload.suggestions)).toBe(true);
642
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
643
-
644
- // The suggestions come as full sentences, so we check for expected content
645
638
  expect(errorPayload.suggestions.length).toBe(3);
646
639
  expect(errorPayload.suggestions[0]).toContain(
647
640
  "Verify the identifier or URI",
648
641
  );
649
- expect(errorPayload.suggestions[0]).toContain(
650
- "project 'malloy-samples'",
651
- );
642
+ expect(errorPayload.suggestions[0]).toContain("flights_by_month");
652
643
  expect(errorPayload.suggestions[0]).toContain("is spelled correctly");
653
644
  expect(errorPayload.suggestions[0]).toContain(
654
645
  "Check capitalization and path separators",