@malloy-publisher/server 0.0.225 → 0.0.227

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 (98) hide show
  1. package/README.md +2 -11
  2. package/dist/app/api-doc.yaml +29 -24
  3. package/dist/app/assets/{EnvironmentPage-DYTeXDll.js → EnvironmentPage-DvOJ7L_b.js} +1 -1
  4. package/dist/app/assets/{HomePage-pDK2BPJY.js → HomePage-CXguJsXS.js} +1 -1
  5. package/dist/app/assets/{LightMode-C2bwGPY1.js → LightMode-ZsshUznu.js} +1 -1
  6. package/dist/app/assets/{MainPage-WtBulMH_.js → MainPage-BIe0VwBa.js} +2 -2
  7. package/dist/app/assets/{MaterializationsPage-hMgOtflG.js → MaterializationsPage-BuZ6UJVx.js} +1 -1
  8. package/dist/app/assets/{ModelPage-B2N5kYII.js → ModelPage-DsPf-s8B.js} +1 -1
  9. package/dist/app/assets/{PackagePage-CEN90nQG.js → PackagePage-CEVNAKZa.js} +1 -1
  10. package/dist/app/assets/{RouteError-BG2c5Zf0.js → RouteError-Chn7lL96.js} +1 -1
  11. package/dist/app/assets/{ThemeEditorPage-DNfeUwEZ.js → ThemeEditorPage-DWC_FdNU.js} +1 -1
  12. package/dist/app/assets/{WorkbookPage-NKI1BhFS.js → WorkbookPage-CGrsFz8p.js} +1 -1
  13. package/dist/app/assets/{core-C6anj5c0.es-DDLHqpzt.js → core-vVgoO8IR.es-BD_THWs_.js} +1 -1
  14. package/dist/app/assets/{index-DMQtnaf4.js → index-BioohWQj.js} +1 -1
  15. package/dist/app/assets/{index-CzNqKMVl.js → index-D6YtyiJ0.js} +1 -1
  16. package/dist/app/assets/{index-C6gZ6sSY.js → index-DNUZpnaa.js} +4 -4
  17. package/dist/app/assets/{index-JXgvyZna.js → index-gEWxu09x.js} +1 -1
  18. package/dist/app/index.html +1 -1
  19. package/dist/instrumentation.mjs +71 -15
  20. package/dist/package_load_worker.mjs +169 -40
  21. package/dist/server.mjs +782 -7050
  22. package/package.json +1 -4
  23. package/src/controller/package.controller.spec.ts +17 -17
  24. package/src/controller/package.controller.ts +7 -6
  25. package/src/health.ts +3 -8
  26. package/src/logger.spec.ts +193 -0
  27. package/src/logger.ts +115 -18
  28. package/src/mcp/error_messages.ts +8 -37
  29. package/src/mcp/handler_utils.ts +10 -129
  30. package/src/mcp/mcp_constants.ts +0 -16
  31. package/src/mcp/{agent_server.protocol.spec.ts → server.protocol.spec.ts} +14 -12
  32. package/src/mcp/server.ts +29 -37
  33. package/src/mcp/skills/build_skills_bundle.ts +1 -1
  34. package/src/mcp/skills/skills_bundle.json +1 -1
  35. package/src/mcp/tools/docs_search_tool.ts +1 -1
  36. package/src/mcp/tools/execute_query_tool.ts +2 -2
  37. package/src/mcp/tools/get_context_eval.ts +3 -3
  38. package/src/mcp/tools/get_context_tool.spec.ts +196 -1
  39. package/src/mcp/tools/get_context_tool.ts +165 -67
  40. package/src/package_load/package_load_pool.ts +8 -1
  41. package/src/package_load/package_load_worker.ts +75 -24
  42. package/src/package_load/protocol.ts +21 -1
  43. package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
  44. package/src/package_load/rpc_wait_accountant.ts +76 -0
  45. package/src/package_load_metrics.spec.ts +114 -0
  46. package/src/package_load_metrics.ts +127 -0
  47. package/src/pg_helpers.spec.ts +2 -206
  48. package/src/pg_helpers.ts +4 -120
  49. package/src/server.ts +0 -16
  50. package/src/service/build_plan.spec.ts +42 -60
  51. package/src/service/build_plan.ts +37 -61
  52. package/src/service/environment.ts +5 -1
  53. package/src/service/materialization_test_fixtures.ts +6 -8
  54. package/src/service/package.ts +165 -86
  55. package/src/service/package_manifest.spec.ts +22 -1
  56. package/src/service/package_manifest.ts +29 -0
  57. package/src/service/persistence_policy.spec.ts +234 -0
  58. package/src/test_helpers/metrics_harness.ts +40 -0
  59. package/tests/harness/mcp_test_setup.ts +1 -1
  60. package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
  61. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
  62. package/dxt/malloy_bridge.py +0 -354
  63. package/dxt/manifest.json +0 -22
  64. package/src/dto/connection.dto.spec.ts +0 -186
  65. package/src/dto/connection.dto.ts +0 -308
  66. package/src/dto/index.ts +0 -2
  67. package/src/dto/package.dto.spec.ts +0 -42
  68. package/src/dto/package.dto.ts +0 -27
  69. package/src/dto/validate.spec.ts +0 -76
  70. package/src/dto/validate.ts +0 -31
  71. package/src/ducklake_version.spec.ts +0 -43
  72. package/src/ducklake_version.ts +0 -26
  73. package/src/mcp/agent_server.spec.ts +0 -18
  74. package/src/mcp/agent_server.ts +0 -144
  75. package/src/mcp/prompts/handlers.ts +0 -84
  76. package/src/mcp/prompts/index.ts +0 -11
  77. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  78. package/src/mcp/prompts/prompt_service.ts +0 -67
  79. package/src/mcp/prompts/utils.ts +0 -62
  80. package/src/mcp/resource_metadata.ts +0 -47
  81. package/src/mcp/resources/environment_resource.ts +0 -187
  82. package/src/mcp/resources/model_resource.ts +0 -155
  83. package/src/mcp/resources/notebook_resource.ts +0 -137
  84. package/src/mcp/resources/package_resource.ts +0 -373
  85. package/src/mcp/resources/query_resource.ts +0 -122
  86. package/src/mcp/resources/source_resource.ts +0 -141
  87. package/src/mcp/resources/view_resource.ts +0 -136
  88. package/src/mcp/tools/discovery_tools.ts +0 -280
  89. package/src/service/materialization_cron_gate.spec.ts +0 -181
  90. package/src/storage/BaseRepository.ts +0 -31
  91. package/src/storage/StorageManager.mock.ts +0 -50
  92. package/tests/harness/e2e.ts +0 -96
  93. package/tests/harness/mocks.ts +0 -39
  94. package/tests/harness/uris.ts +0 -31
  95. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
  96. package/tests/integration/mcp/setup.spec.ts +0 -5
  97. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  98. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
@@ -1,655 +0,0 @@
1
- /// <reference types="bun-types" />
2
-
3
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
- import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
5
- import { afterAll, beforeAll, describe, expect, it } from "bun:test";
6
- import {
7
- cleanupE2ETestEnvironment,
8
- McpE2ETestEnvironment,
9
- setupE2ETestEnvironment,
10
- } from "../../harness/mcp_test_setup";
11
-
12
- // Define an interface for the expected structure of package content entries
13
- interface PackageContentEntry {
14
- uri?: string;
15
- metadata?: {
16
- description?: string;
17
- // Add other expected metadata fields if known
18
- };
19
- // Add other expected fields if known
20
- }
21
-
22
- // --- Test Suite ---
23
- // Serial: one shared MCP client; concurrent it() blocks can interleave StreamableHTTP requests.
24
- describe.serial("MCP Resource Handlers (E2E Integration)", () => {
25
- let env: McpE2ETestEnvironment | null = null;
26
- let mcpClient: Client;
27
-
28
- // --- Setup/Teardown using E2E Scaffolding ---
29
- beforeAll(async () => {
30
- env = await setupE2ETestEnvironment();
31
- mcpClient = env.mcpClient;
32
- });
33
-
34
- afterAll(async () => {
35
- await cleanupE2ETestEnvironment(env);
36
- env = null;
37
- });
38
-
39
- // --- Test Constants ---
40
- const homeProjectUri = "malloy://environment/malloy-samples";
41
- const faaPackageUri = "malloy://environment/malloy-samples/package/faa";
42
- const flightsModelUri =
43
- "malloy://environment/malloy-samples/package/faa/models/flights.malloy";
44
- const FLIGHTS_SOURCE = "flights";
45
- const FLIGHTS_CARRIER_QUERY = "flights_by_carrier";
46
- const FLIGHTS_MONTH_VIEW = "flights_by_month";
47
- const OVERVIEW_NOTEBOOK = "overview.malloynb";
48
- const nonExistentPackageUri =
49
- "malloy://environment/malloy-samples/package/nonexistent";
50
- const nonExistentModelUri =
51
- "malloy://environment/malloy-samples/package/faa/models/nonexistent.malloy";
52
- const nonExistentProjectUri = "malloy://environment/invalid_project";
53
- const invalidUri = "invalid://format";
54
-
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
-
64
- describe("client.listResources", () => {
65
- it(
66
- "should list all resources, including package entries (models/notebooks/sources)",
67
- async () => {
68
- console.log(
69
- "[TEST LOG] Starting: listResources - check package entries",
70
- );
71
- if (!env) throw new Error("Test environment not initialized");
72
- const result = await mcpClient.listResources({});
73
- console.log(
74
- "[TEST LOG] listResources result received (count):",
75
- result?.resources?.length ?? 0,
76
- );
77
-
78
- expect(result).toBeDefined();
79
- expect(result.resources).toBeDefined();
80
- expect(Array.isArray(result.resources)).toBe(true);
81
- expect(result.resources.length).toBeGreaterThan(0);
82
-
83
- const faaPackageEntry = result.resources.find(
84
- (r) => r.uri === faaPackageUri,
85
- );
86
- expect(faaPackageEntry).toBeDefined();
87
- expect(faaPackageEntry?.name).toBe("faa");
88
-
89
- // Assert shape on a known row (order of listResources is not guaranteed).
90
- const sample = faaPackageEntry!;
91
- expect(sample.uri).toBeDefined();
92
- expect(typeof sample.uri).toBe("string");
93
- expect(sample.uri).toMatch(/^malloy:\/\//);
94
- expect(sample.name).toBeDefined();
95
- expect(typeof sample.name).toBe("string");
96
- expect(sample.description).toBeDefined();
97
- expect(typeof sample.description).toBe("string");
98
- if (sample.mimeType) {
99
- expect(typeof sample.mimeType).toBe("string");
100
- }
101
- },
102
- { timeout: 30000 },
103
- );
104
-
105
- it("should resolve even if called with a specific URI (behavior might vary)", async () => {
106
- console.log("[TEST LOG] Starting: listResources - with URI");
107
- if (!env) throw new Error("Test environment not initialized");
108
- const result = await mcpClient.listResources({ uri: homeProjectUri });
109
- console.log(
110
- "[TEST LOG] listResources (with URI) result received (count):",
111
- result?.resources?.length ?? 0,
112
- );
113
- expect(result).toBeDefined();
114
- expect(result.resources).toBeDefined();
115
- expect(Array.isArray(result.resources)).toBe(true);
116
- expect(result.resources.length).toBeGreaterThan(0);
117
- });
118
- });
119
-
120
- describe("client.readResource (Basic Types)", () => {
121
- it("should return details for the project URI", async () => {
122
- console.log("[TEST LOG] Starting: readResource - project success");
123
- if (!env) throw new Error("Test environment not initialized");
124
- const resource = await mcpClient.readResource({ uri: homeProjectUri });
125
- console.log(
126
- "[TEST LOG] readResource (project) result received:",
127
- resource ? "object" : "null",
128
- );
129
- expect(resource).toBeDefined();
130
- expect(resource.contents).toBeDefined();
131
- expect(Array.isArray(resource.contents)).toBe(true);
132
- expect(resource.contents).toHaveLength(1);
133
- expect(resource.contents[0].type).toBe("application/json");
134
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
- const responsePayload = JSON.parse((resource.contents[0] as any).text);
136
- expect(responsePayload).toBeDefined();
137
- expect(responsePayload.definition).toBeDefined();
138
- expect(responsePayload.metadata).toBeDefined();
139
- // Check definition content - Project name is 'malloy-samples' from URI param
140
- expect(responsePayload.definition.name).toBe("malloy-samples");
141
- // Check metadata content (can be more specific if needed)
142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
- expect(typeof responsePayload.metadata.description).toBe("string");
144
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
- expect(typeof responsePayload.metadata.usage).toBe("string");
146
- });
147
-
148
- it("should return details for a valid package URI", async () => {
149
- console.log("[TEST LOG] Starting: readResource - package success");
150
- if (!env) throw new Error("Test environment not initialized");
151
- const resource = await mcpClient.readResource({ uri: faaPackageUri });
152
- console.log(
153
- "[TEST LOG] readResource (package) result received:",
154
- resource ? "object" : "null",
155
- );
156
- expect(resource).toBeDefined();
157
- expect(resource.contents).toBeDefined();
158
- expect(Array.isArray(resource.contents)).toBe(true);
159
- expect(resource.contents).toHaveLength(1);
160
- expect(resource.contents[0].type).toBe("application/json");
161
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
- const responsePayload = JSON.parse((resource.contents[0] as any).text);
163
- expect(responsePayload).toBeDefined();
164
- expect(responsePayload.definition).toBeDefined();
165
- expect(responsePayload.metadata).toBeDefined();
166
- // Check definition content (matches old packageMetadata check)
167
- expect(responsePayload.definition.name).toBe("faa");
168
- expect(responsePayload.definition.description).toBeDefined();
169
- expect(typeof responsePayload.definition.description).toBe("string");
170
- // Check metadata content
171
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
- expect(typeof responsePayload.metadata.description).toBe("string");
173
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
- expect(typeof responsePayload.metadata.usage).toBe("string");
175
- });
176
-
177
- it("should return a list of contained resources for a valid package contents URI", async () => {
178
- console.log(
179
- "[TEST LOG] Starting: readResource - package contents success",
180
- );
181
- if (!env) throw new Error("Test environment not initialized");
182
- const contentsUri = `${faaPackageUri}/contents`; // Construct the contents URI
183
- const resource = await mcpClient.readResource({ uri: contentsUri });
184
-
185
- console.log(
186
- "[TEST DEBUG] Received Package Contents:",
187
- JSON.stringify(resource, null, 2),
188
- );
189
- console.log(
190
- "[TEST LOG] readResource (package contents) result received:",
191
- resource ? "object" : "null",
192
- );
193
- expect(resource).toBeDefined();
194
- expect(resource.isError).not.toBe(true);
195
- expect(resource.contents).toBeDefined();
196
- expect(Array.isArray(resource.contents)).toBe(true);
197
- expect(resource.contents).toHaveLength(1);
198
- expect(resource.contents[0].type).toBe("application/json");
199
-
200
- // The content should be a JSON array of resource definitions
201
- // Explicitly type the parsed array
202
- const contentArray = JSON.parse(
203
- (resource.contents[0] as { text: string }).text, // Use a specific type for content item
204
- ) as PackageContentEntry[];
205
- expect(Array.isArray(contentArray)).toBe(true);
206
- // Note: Package contents may be empty if models are not properly recognized
207
- // This is a known issue with the package contents handler
208
- expect(contentArray.length).toBeGreaterThanOrEqual(0);
209
-
210
- // Only check for specific entries if the array is not empty
211
- if (contentArray.length > 0) {
212
- // Check for a specific known source entry (e.g., flights.malloy)
213
- // Find the entry by URI suffix, don't assume order
214
- // Remove 'any' type from entry parameter
215
- const flightsEntry = contentArray.find((entry) =>
216
- entry?.uri?.endsWith("/sources/flights.malloy"),
217
- );
218
- if (flightsEntry) {
219
- expect(flightsEntry.uri).toBe(
220
- "malloy://environment/malloy-samples/package/faa/sources/flights.malloy",
221
- );
222
- expect(flightsEntry.metadata).toBeDefined();
223
- expect(flightsEntry.metadata!.description).toContain(
224
- "Represents a table or dataset",
225
- );
226
- }
227
-
228
- // Check for a specific known notebook entry (e.g., aircraft_analysis.malloynb)
229
- // Remove 'any' type from entry parameter
230
- const notebookEntry = contentArray.find((entry) =>
231
- entry?.uri?.endsWith("/notebooks/aircraft_analysis.malloynb"),
232
- );
233
- if (notebookEntry) {
234
- expect(notebookEntry.uri).toBe(
235
- "malloy://environment/malloy-samples/package/faa/notebooks/aircraft_analysis.malloynb",
236
- );
237
- expect(notebookEntry.metadata).toBeDefined();
238
- expect(notebookEntry.metadata!.description).toContain(
239
- "interactive document",
240
- );
241
- }
242
-
243
- // Check overall structure of the first item in the *original array*
244
- const firstItem = contentArray[0];
245
- expect(firstItem.uri).toBeDefined();
246
- expect(typeof firstItem.uri).toBe("string");
247
- expect(firstItem.metadata).toBeDefined();
248
- expect(typeof firstItem.metadata!.description).toBe("string");
249
- }
250
- });
251
-
252
- it("should return details for a valid model URI", async () => {
253
- console.log("[TEST LOG] Starting: readResource - model success");
254
- if (!env) throw new Error("Test environment not initialized");
255
- const resource = await mcpClient.readResource({
256
- uri: flightsModelUri,
257
- });
258
- console.log(
259
- "[TEST LOG] readResource (model) result received:",
260
- resource ? "object" : "null",
261
- );
262
- expect(resource).toBeDefined();
263
- expect(resource.isError).not.toBe(true);
264
- expect(resource.contents).toBeDefined();
265
- expect(Array.isArray(resource.contents)).toBe(true);
266
- expect(resource.contents).toHaveLength(1);
267
- expect(resource.contents[0].type).toBe("application/json");
268
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
269
- const responsePayload = JSON.parse((resource.contents[0] as any).text);
270
- expect(responsePayload).toBeDefined();
271
- expect(responsePayload.definition).toBeDefined();
272
- expect(responsePayload.metadata).toBeDefined();
273
- // Check definition content (like old compiledModel check)
274
- expect(responsePayload.definition.modelPath).toBe("flights.malloy");
275
- expect(responsePayload.definition.packageName).toBe("faa");
276
- expect(responsePayload.definition.sources).toBeDefined(); // Example check
277
- // Check metadata content
278
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
279
- expect(typeof responsePayload.metadata.description).toBe("string");
280
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
281
- expect(typeof responsePayload.metadata.usage).toBe("string");
282
- });
283
-
284
- it("should reject with InvalidParams for an invalid URI structure", async () => {
285
- console.log(
286
- "[TEST LOG] Starting: readResource - invalid URI structure",
287
- );
288
- if (!env) throw new Error("Test environment not initialized");
289
- await expect(
290
- mcpClient.readResource({ uri: invalidUri }),
291
- ).rejects.toMatchObject({
292
- code: ErrorCode.InvalidParams,
293
- message: expect.stringMatching(/Resource .* not found/i),
294
- });
295
- });
296
-
297
- it("should return application error response if package not found", async () => {
298
- console.log("[TEST LOG] Starting: readResource - package not found");
299
- if (!env) throw new Error("Test environment not initialized");
300
- const result = await mcpClient.readResource({
301
- uri: nonExistentPackageUri,
302
- });
303
- console.log(
304
- "[TEST LOG] readResource (package not found) result received:",
305
- result ? "object" : "null",
306
- );
307
- expect(result).toBeDefined();
308
- expect(result.isError).toBe(true);
309
- expect(result.contents).toBeDefined();
310
- expect(Array.isArray(result.contents)).toBe(true);
311
- expect(result.contents).toHaveLength(1);
312
- expect(result.contents[0].type).toBe("application/json");
313
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
314
- const responsePayload = JSON.parse((result.contents[0] as any).text);
315
- expect(responsePayload).toBeDefined();
316
- expect(responsePayload.error).toBeDefined();
317
- expect(responsePayload.suggestions).toBeDefined();
318
- expect(Array.isArray(responsePayload.suggestions)).toBe(true);
319
- // Adjust regex to match the actual "Resource not found: Package..." message format
320
- expect(responsePayload.error).toMatch(/^Resource not found: Package/i);
321
- expect(responsePayload.suggestions.length).toBeGreaterThan(0);
322
- });
323
-
324
- it("should return application error response if model not found", async () => {
325
- console.log("[TEST LOG] Starting: readResource - model not found");
326
- if (!env) throw new Error("Test environment not initialized");
327
- const result = await mcpClient.readResource({
328
- uri: nonExistentModelUri,
329
- });
330
- console.log(
331
- "[TEST LOG] readResource (model not found) result received:",
332
- result ? "object" : "null",
333
- );
334
- expect(result).toBeDefined();
335
- expect(result.isError).toBe(true);
336
- expect(result.contents).toBeDefined();
337
- expect(Array.isArray(result.contents)).toBe(true);
338
- expect(result.contents).toHaveLength(1);
339
- expect(result.contents[0].type).toBe("application/json");
340
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
341
- const responsePayload = JSON.parse((result.contents[0] as any).text);
342
- expect(responsePayload).toBeDefined();
343
- expect(responsePayload.error).toBeDefined();
344
- expect(responsePayload.suggestions).toBeDefined();
345
- expect(Array.isArray(responsePayload.suggestions)).toBe(true);
346
- expect(responsePayload.error).toMatch(/Resource not found/i);
347
- expect(responsePayload.suggestions.length).toBeGreaterThan(0);
348
- });
349
-
350
- it("should return application error response if project not found", async () => {
351
- console.log("[TEST LOG] Starting: readResource - project not found");
352
- if (!env) throw new Error("Test environment not initialized");
353
- const result = await mcpClient.readResource({
354
- uri: nonExistentProjectUri,
355
- });
356
- console.log(
357
- "[TEST LOG] readResource (project not found) result received:",
358
- result ? "object" : "null",
359
- );
360
- expect(result).toBeDefined();
361
- expect(result.isError).toBe(true);
362
- expect(result.contents).toBeDefined();
363
- expect(Array.isArray(result.contents)).toBe(true);
364
- expect(result.contents).toHaveLength(1);
365
- expect(result.contents[0].type).toBe("application/json");
366
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
- const responsePayload = JSON.parse((result.contents[0] as any).text);
368
- expect(responsePayload).toBeDefined();
369
- expect(responsePayload.error).toBeDefined();
370
- expect(responsePayload.suggestions).toBeDefined();
371
- expect(Array.isArray(responsePayload.suggestions)).toBe(true);
372
- expect(responsePayload.error).toMatch(/Resource not found/i);
373
- });
374
-
375
- it("should reject for syntactically invalid URI string", async () => {
376
- console.log(
377
- "[TEST LOG] Starting: readResource - syntactically invalid URI",
378
- );
379
- if (!env) throw new Error("Test environment not initialized");
380
- const syntacticallyInvalidUri = "i am /// not valid?";
381
- await expect(
382
- mcpClient.readResource({ uri: syntacticallyInvalidUri }),
383
- ).rejects.toMatchObject({
384
- code: expect.any(Number),
385
- message: expect.any(String),
386
- });
387
- });
388
-
389
- it("should return application error response if package contents URI references non-existent package", async () => {
390
- console.log(
391
- "[TEST LOG] Starting: readResource - package contents not found",
392
- );
393
- if (!env) throw new Error("Test environment not initialized");
394
- const nonExistentContentsUri = `${nonExistentPackageUri}/contents`;
395
- const result = await mcpClient.readResource({
396
- uri: nonExistentContentsUri,
397
- });
398
-
399
- console.log(
400
- "[TEST DEBUG] Received Package Contents (Not Found Case):",
401
- JSON.stringify(result, null, 2),
402
- );
403
- console.log(
404
- "[TEST LOG] readResource (package contents not found) result received:",
405
- result ? "object" : "null",
406
- );
407
- expect(result).toBeDefined();
408
- expect(result.isError).toBe(true);
409
- expect(result.contents).toBeDefined();
410
- expect(Array.isArray(result.contents)).toBe(true);
411
- expect(result.contents).toHaveLength(1);
412
- expect(result.contents[0].type).toBe("application/json");
413
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
414
- const responsePayload = JSON.parse((result.contents[0] as any).text);
415
- expect(responsePayload).toBeDefined();
416
- expect(responsePayload.error).toBeDefined();
417
- expect(responsePayload.error).toMatch(/Resource not found/i);
418
- expect(responsePayload.suggestions).toBeDefined();
419
- expect(Array.isArray(responsePayload.suggestions)).toBe(true);
420
- });
421
- });
422
-
423
- describe("client.readResource (Source/Query/View/Notebook)", () => {
424
- it("should get a valid source resource", async () => {
425
- if (!env) throw new Error("Test environment not initialized");
426
- const result = await mcpClient.readResource({ uri: validSourceUri });
427
- expect(result.isError).not.toBe(true);
428
- expect(result.contents).toBeDefined();
429
- expect(Array.isArray(result.contents)).toBe(true);
430
- expect(result.contents).toHaveLength(1);
431
- expect(result.contents[0].type).toBe("application/json");
432
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
433
- const responsePayload = JSON.parse((result.contents[0] as any).text);
434
- expect(responsePayload).toBeDefined();
435
- expect(responsePayload.definition).toBeDefined();
436
- expect(responsePayload.metadata).toBeDefined();
437
- expect(responsePayload.definition.name).toBe(FLIGHTS_SOURCE);
438
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
439
- expect(typeof responsePayload.metadata.description).toBe("string");
440
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
441
- expect(typeof responsePayload.metadata.usage).toBe("string");
442
- });
443
-
444
- it("should return structured app error for a query that is not found", async () => {
445
- if (!env) throw new Error("Test environment not initialized");
446
- const result = await mcpClient.readResource({ uri: validQueryUri });
447
- expect(result.isError).toBe(true);
448
- expect(result.contents).toBeDefined();
449
- expect(Array.isArray(result.contents)).toBe(true);
450
- expect(result.contents).toHaveLength(1);
451
- expect(result.contents?.[0]?.type).toBe("application/json");
452
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
453
- const errorPayload = JSON.parse((result.contents![0] as any).text);
454
- expect(errorPayload.error).toBeDefined();
455
- expect(errorPayload.error).toMatch(/Resource not found/i);
456
- // Check for the specific query name in the message
457
- expect(errorPayload.error).toMatch(
458
- new RegExp(`Query '${FLIGHTS_CARRIER_QUERY}'`),
459
- );
460
- expect(errorPayload.suggestions).toBeDefined();
461
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
462
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
463
- expect(errorPayload.suggestions[0]).toContain(
464
- "Verify the identifier or URI",
465
- ); // Check suggestion content
466
- });
467
-
468
- it("should return structured app error for a view that is not found", async () => {
469
- if (!env) throw new Error("Test environment not initialized");
470
- const result = await mcpClient.readResource({ uri: validViewUri });
471
- expect(result.isError).toBe(true);
472
- expect(result.contents).toBeDefined();
473
- expect(Array.isArray(result.contents)).toBe(true);
474
- expect(result.contents).toHaveLength(1);
475
- expect(result.contents?.[0]?.type).toBe("application/json");
476
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
477
- const errorPayload = JSON.parse((result.contents![0] as any).text);
478
- expect(errorPayload.error).toBeDefined();
479
- expect(errorPayload.error).toMatch(/Resource not found/i);
480
- // Check for the specific view name in the message
481
- expect(errorPayload.error).toMatch(
482
- new RegExp(`View '${FLIGHTS_MONTH_VIEW}'`),
483
- );
484
- expect(errorPayload.suggestions).toBeDefined();
485
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
486
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
487
- expect(errorPayload.suggestions[0]).toContain(
488
- "Verify the identifier or URI",
489
- ); // Check suggestion content
490
- });
491
-
492
- it("should return structured app error for a notebook that is not found or invalid", async () => {
493
- if (!env) throw new Error("Test environment not initialized");
494
- const result = await mcpClient.readResource({ uri: validNotebookUri });
495
- expect(result.isError).toBe(true);
496
- expect(result.contents).toBeDefined();
497
- expect(Array.isArray(result.contents)).toBe(true);
498
- expect(result.contents).toHaveLength(1);
499
- expect(result.contents?.[0]?.type).toBe("application/json");
500
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
501
- const errorPayload = JSON.parse((result.contents![0] as any).text);
502
- expect(errorPayload.error).toBeDefined();
503
- expect(errorPayload.error).toMatch(/Resource not found/i);
504
- // Check for the specific notebook name and context in the message
505
- // Adjust test to expect the generic "Resource not found" error, as the specific
506
- // "not a notebook" detail isn't easily surfaced in the standard error format.
507
- expect(errorPayload.error).toMatch(/Notebook 'overview.malloynb'/);
508
- expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
509
- expect(errorPayload.suggestions).toBeDefined();
510
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
511
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
512
- expect(errorPayload.suggestions[0]).toContain(
513
- "Verify the identifier or URI",
514
- );
515
- // This case might also hit ModelCompilationError if the .malloynb exists but is invalid
516
- // Adding a check for compilation-related suggestions could be useful if applicable
517
- });
518
-
519
- it("should return structured app error for non-existent source", async () => {
520
- if (!env) throw new Error("Test environment not initialized");
521
- const result = await mcpClient.readResource({
522
- uri: nonExistentSourceUri,
523
- });
524
- expect(result.isError).toBe(true);
525
- expect(result.contents).toBeDefined();
526
- expect(Array.isArray(result.contents)).toBe(true);
527
- expect(result.contents).toHaveLength(1);
528
- expect(result.contents?.[0]?.type).toBe("application/json");
529
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
530
- const errorPayload = JSON.parse((result.contents![0] as any).text);
531
- expect(errorPayload.error).toBeDefined();
532
- expect(errorPayload.error).toMatch(/Resource not found/i);
533
- // Check for the specific source name in the message
534
- expect(errorPayload.error).toMatch(/Source 'non_existent_source'/);
535
- expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
536
- expect(errorPayload.suggestions).toBeDefined();
537
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
538
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
539
- expect(errorPayload.suggestions[0]).toContain(
540
- "Verify the identifier or URI",
541
- );
542
- });
543
-
544
- it("should return structured app error for non-existent query", async () => {
545
- if (!env) throw new Error("Test environment not initialized");
546
- const result = await mcpClient.readResource({
547
- uri: nonExistentQueryUri,
548
- });
549
- expect(result.isError).toBe(true);
550
- expect(result.contents).toBeDefined();
551
- expect(Array.isArray(result.contents)).toBe(true);
552
- expect(result.contents).toHaveLength(1);
553
- expect(result.contents?.[0]?.type).toBe("application/json");
554
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
555
- const errorPayload = JSON.parse((result.contents![0] as any).text);
556
- expect(errorPayload.error).toBeDefined();
557
- expect(errorPayload.error).toMatch(/Resource not found/i);
558
- // Check for the specific query name in the message
559
- expect(errorPayload.error).toMatch(/Query 'non_existent_query'/);
560
- expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
561
- expect(errorPayload.suggestions).toBeDefined();
562
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
563
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
564
- expect(errorPayload.suggestions[0]).toContain(
565
- "Verify the identifier or URI",
566
- );
567
- });
568
-
569
- it("should return structured app error for non-existent view", async () => {
570
- if (!env) throw new Error("Test environment not initialized");
571
- const result = await mcpClient.readResource({
572
- uri: nonExistentViewUri,
573
- });
574
- expect(result.isError).toBe(true);
575
- expect(result.contents).toBeDefined();
576
- expect(Array.isArray(result.contents)).toBe(true);
577
- expect(result.contents).toHaveLength(1);
578
- expect(result.contents?.[0]?.type).toBe("application/json");
579
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
580
- const errorPayload = JSON.parse((result.contents![0] as any).text);
581
- expect(errorPayload.error).toBeDefined();
582
- expect(errorPayload.error).toMatch(/Resource not found/i);
583
- // Check for the specific view name in the message
584
- expect(errorPayload.error).toMatch(/View 'non_existent_view'/);
585
- expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
586
- expect(errorPayload.suggestions).toBeDefined();
587
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
588
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
589
- expect(errorPayload.suggestions[0]).toContain(
590
- "Verify the identifier or URI",
591
- );
592
- });
593
-
594
- it("should return structured app error for non-existent notebook", async () => {
595
- if (!env) throw new Error("Test environment not initialized");
596
- const result = await mcpClient.readResource({
597
- uri: nonExistentNotebookUri,
598
- });
599
- expect(result.isError).toBe(true);
600
- expect(result.contents).toBeDefined();
601
- expect(Array.isArray(result.contents)).toBe(true);
602
- expect(result.contents).toHaveLength(1);
603
- expect(result.contents?.[0]?.type).toBe("application/json");
604
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
605
- const errorPayload = JSON.parse((result.contents![0] as any).text);
606
- expect(errorPayload.error).toBeDefined();
607
- expect(errorPayload.error).toMatch(/Resource not found/i);
608
- // Check for the specific notebook name and context in the message
609
- // Adjust test to expect the generic "Resource not found" error
610
- expect(errorPayload.error).toMatch(/Notebook 'non_existent.malloynb'/);
611
- expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
612
- expect(errorPayload.suggestions).toBeDefined();
613
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
614
- expect(errorPayload.suggestions.length).toBeGreaterThan(0);
615
- expect(errorPayload.suggestions[0]).toContain(
616
- "Verify the identifier or URI",
617
- );
618
- });
619
-
620
- it("should return structured app error when requesting view from wrong source", async () => {
621
- if (!env) throw new Error("Test environment not initialized");
622
- const wrongSourceUri = `malloy://environment/malloy-samples/package/faa/models/flights.malloy/sources/aircraft/views/${FLIGHTS_MONTH_VIEW}`;
623
- const result = await mcpClient.readResource({ uri: wrongSourceUri });
624
- expect(result.isError).toBe(true);
625
- expect(result.contents).toBeDefined();
626
- expect(Array.isArray(result.contents)).toBe(true);
627
- expect(result.contents).toHaveLength(1);
628
- expect(result.contents?.[0]?.type).toBe("application/json");
629
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
630
- const errorPayload = JSON.parse((result.contents![0] as any).text);
631
- expect(errorPayload.error).toBeDefined();
632
- expect(errorPayload.error).toMatch(/Resource not found/i);
633
- expect(errorPayload.error).toMatch(/View 'flights_by_month'/);
634
- expect(errorPayload.error).toMatch(/source 'aircraft'/);
635
- expect(errorPayload.error).toMatch(/environment 'malloy-samples'/);
636
- expect(errorPayload.suggestions).toBeDefined();
637
- expect(Array.isArray(errorPayload.suggestions)).toBe(true);
638
- expect(errorPayload.suggestions.length).toBe(3);
639
- expect(errorPayload.suggestions[0]).toContain(
640
- "Verify the identifier or URI",
641
- );
642
- expect(errorPayload.suggestions[0]).toContain("flights_by_month");
643
- expect(errorPayload.suggestions[0]).toContain("is spelled correctly");
644
- expect(errorPayload.suggestions[0]).toContain(
645
- "Check capitalization and path separators",
646
- );
647
- expect(errorPayload.suggestions[1]).toContain(
648
- "If using a URI, ensure it follows the correct format",
649
- );
650
- expect(errorPayload.suggestions[2]).toContain(
651
- "Check if the resource exists and is correctly named",
652
- );
653
- });
654
- });
655
- });
@@ -1,5 +0,0 @@
1
- import { expect, test } from "bun:test";
2
-
3
- test("Test runner setup check", () => {
4
- expect(2 + 2).toBe(4);
5
- });