@malloy-publisher/server 0.0.382-dev → 0.2.1

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 (175) hide show
  1. package/README.docker.md +145 -20
  2. package/README.md +65 -12
  3. package/dist/app/api-doc.html +5 -0
  4. package/dist/app/api-doc.yaml +3929 -534
  5. package/dist/app/assets/EnvironmentPage-CHq5BF4l.js +1 -0
  6. package/dist/app/assets/HomePage-CIp-pJCa.js +1 -0
  7. package/dist/app/assets/LightMode-mHHPjEeA.js +1 -0
  8. package/dist/app/assets/MainPage-CylxO5Yu.js +2 -0
  9. package/dist/app/assets/MaterializationsPage-a0vjqxib.js +1 -0
  10. package/dist/app/assets/ModelPage-Cz-MrSZQ.js +1 -0
  11. package/dist/app/assets/PackagePage-Cdg5D7AN.js +1 -0
  12. package/dist/app/assets/RouteError-B5SIyn6G.js +1 -0
  13. package/dist/app/assets/ThemeEditorPage-B62rRc-y.js +1 -0
  14. package/dist/app/assets/WorkbookPage-45cG1Gx6.js +1 -0
  15. package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-DyU47reh.es-DY9b3WjS.js} +1 -1
  16. package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
  17. package/dist/app/assets/index-5eLCcNmP.css +1 -0
  18. package/dist/app/assets/index-C5ClsnS7.js +1774 -0
  19. package/dist/app/assets/index-DF9tVqwN.js +1 -0
  20. package/dist/app/assets/index-DSoCUF-f.js +2725 -0
  21. package/dist/app/assets/index-DgVRz2wm.js +2 -0
  22. package/dist/app/assets/index-P-pF8nU5.js +632 -0
  23. package/dist/app/assets/index-w_0OQJgZ.js +23 -0
  24. package/dist/app/index.html +8 -4
  25. package/dist/cpufeatures-1yrn0vtw.node +0 -0
  26. package/dist/default-publisher.config.json +23 -0
  27. package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
  28. package/dist/package_load_worker.mjs +15219 -0
  29. package/dist/runtime/publisher.js +331 -0
  30. package/dist/{server.js → server.mjs} +141276 -74705
  31. package/dist/sshcrypto-vd2k5hq9.node +0 -0
  32. package/package.json +41 -28
  33. package/publisher.config.example.bigquery.json +33 -0
  34. package/publisher.config.example.duckdb.json +23 -0
  35. package/.eslintrc.json +0 -44
  36. package/.prettierignore +0 -1
  37. package/.prettierrc +0 -4
  38. package/build.ts +0 -38
  39. package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
  40. package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
  41. package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
  42. package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
  43. package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
  44. package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
  45. package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
  46. package/dist/app/assets/index-15BOvhp0.js +0 -456
  47. package/dist/app/assets/index-Bb2jqquW.js +0 -676
  48. package/dist/app/assets/index-CMlGQMcl.css +0 -1
  49. package/dist/app/assets/index-D68X76-7.js +0 -1276
  50. package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
  51. package/dxt/malloy_bridge.py +0 -354
  52. package/dxt/manifest.json +0 -22
  53. package/eslint.config.mjs +0 -76
  54. package/k6-tests/README.md +0 -144
  55. package/k6-tests/bun.lock +0 -797
  56. package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
  57. package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
  58. package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
  59. package/k6-tests/load-test/load-test-crud.ts +0 -142
  60. package/k6-tests/load-test/load-test.ts +0 -784
  61. package/k6-tests/package.json +0 -27
  62. package/k6-tests/scripts/run-with-otel.sh +0 -50
  63. package/k6-tests/smoke-test/smoke-test.ts +0 -158
  64. package/k6-tests/tsconfig.json +0 -29
  65. package/k6-tests/utils/client_factory.ts +0 -128
  66. package/k6-tests/utils/common.ts +0 -489
  67. package/k6-tests/utils/logger.ts +0 -62
  68. package/k6-tests/utils/types.d.ts +0 -35
  69. package/malloy_mcp.dxt +0 -0
  70. package/publisher.config.json +0 -33
  71. package/src/config.spec.ts +0 -850
  72. package/src/config.ts +0 -305
  73. package/src/constants.ts +0 -10
  74. package/src/controller/compile.controller.ts +0 -35
  75. package/src/controller/connection.controller.ts +0 -515
  76. package/src/controller/database.controller.ts +0 -21
  77. package/src/controller/manifest.controller.ts +0 -29
  78. package/src/controller/materialization.controller.ts +0 -125
  79. package/src/controller/model.controller.ts +0 -107
  80. package/src/controller/package.controller.ts +0 -182
  81. package/src/controller/query.controller.ts +0 -60
  82. package/src/controller/watch-mode.controller.ts +0 -98
  83. package/src/data_styles.ts +0 -71
  84. package/src/dto/connection.dto.spec.ts +0 -131
  85. package/src/dto/connection.dto.ts +0 -174
  86. package/src/dto/index.ts +0 -2
  87. package/src/dto/package.dto.spec.ts +0 -42
  88. package/src/dto/package.dto.ts +0 -12
  89. package/src/dto/validate.spec.ts +0 -76
  90. package/src/dto/validate.ts +0 -31
  91. package/src/errors.ts +0 -116
  92. package/src/health.ts +0 -189
  93. package/src/instrumentation.ts +0 -148
  94. package/src/logger.ts +0 -154
  95. package/src/mcp/error_messages.ts +0 -199
  96. package/src/mcp/handler_utils.ts +0 -239
  97. package/src/mcp/mcp_constants.ts +0 -22
  98. package/src/mcp/prompts/handlers.ts +0 -84
  99. package/src/mcp/prompts/index.ts +0 -11
  100. package/src/mcp/prompts/prompt_definitions.ts +0 -160
  101. package/src/mcp/prompts/prompt_service.ts +0 -67
  102. package/src/mcp/prompts/utils.ts +0 -62
  103. package/src/mcp/resource_metadata.ts +0 -47
  104. package/src/mcp/resources/model_resource.ts +0 -153
  105. package/src/mcp/resources/notebook_resource.ts +0 -137
  106. package/src/mcp/resources/package_resource.ts +0 -370
  107. package/src/mcp/resources/project_resource.ts +0 -184
  108. package/src/mcp/resources/query_resource.ts +0 -117
  109. package/src/mcp/resources/source_resource.ts +0 -141
  110. package/src/mcp/resources/view_resource.ts +0 -136
  111. package/src/mcp/server.ts +0 -62
  112. package/src/mcp/tools/discovery_tools.ts +0 -258
  113. package/src/mcp/tools/execute_query_tool.ts +0 -265
  114. package/src/server.ts +0 -1195
  115. package/src/service/connection.spec.ts +0 -1331
  116. package/src/service/connection.ts +0 -1511
  117. package/src/service/connection_service.spec.ts +0 -641
  118. package/src/service/connection_service.ts +0 -181
  119. package/src/service/db_utils.spec.ts +0 -714
  120. package/src/service/db_utils.ts +0 -1227
  121. package/src/service/filter.spec.ts +0 -447
  122. package/src/service/filter.ts +0 -337
  123. package/src/service/filter_integration.spec.ts +0 -825
  124. package/src/service/gcs_s3_utils.ts +0 -427
  125. package/src/service/manifest_service.spec.ts +0 -201
  126. package/src/service/manifest_service.ts +0 -106
  127. package/src/service/materialization_service.spec.ts +0 -648
  128. package/src/service/materialization_service.ts +0 -929
  129. package/src/service/materialized_table_gc.spec.ts +0 -383
  130. package/src/service/materialized_table_gc.ts +0 -279
  131. package/src/service/model.spec.ts +0 -257
  132. package/src/service/model.ts +0 -1069
  133. package/src/service/package.spec.ts +0 -236
  134. package/src/service/package.ts +0 -463
  135. package/src/service/project.ts +0 -681
  136. package/src/service/project_compile.spec.ts +0 -197
  137. package/src/service/project_store.spec.ts +0 -859
  138. package/src/service/project_store.ts +0 -1531
  139. package/src/service/quoting.ts +0 -41
  140. package/src/service/resolve_project.ts +0 -13
  141. package/src/storage/BaseRepository.ts +0 -31
  142. package/src/storage/DatabaseInterface.ts +0 -180
  143. package/src/storage/StorageManager.mock.ts +0 -59
  144. package/src/storage/StorageManager.ts +0 -210
  145. package/src/storage/duckdb/ConnectionRepository.ts +0 -141
  146. package/src/storage/duckdb/DuckDBConnection.ts +0 -177
  147. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  148. package/src/storage/duckdb/DuckDBRepository.ts +0 -224
  149. package/src/storage/duckdb/ManifestRepository.ts +0 -119
  150. package/src/storage/duckdb/MaterializationRepository.ts +0 -249
  151. package/src/storage/duckdb/PackageRepository.ts +0 -135
  152. package/src/storage/duckdb/ProjectRepository.ts +0 -148
  153. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  154. package/src/storage/duckdb/schema.ts +0 -147
  155. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
  156. package/src/utils.ts +0 -13
  157. package/tests/fixtures/persist-test/data/orders.csv +0 -5
  158. package/tests/fixtures/persist-test/persist_test.malloy +0 -11
  159. package/tests/fixtures/persist-test/publisher.json +0 -5
  160. package/tests/fixtures/publisher.config.json +0 -15
  161. package/tests/harness/e2e.ts +0 -96
  162. package/tests/harness/mcp_test_setup.ts +0 -344
  163. package/tests/harness/mocks.ts +0 -37
  164. package/tests/harness/rest_e2e.ts +0 -68
  165. package/tests/harness/uris.ts +0 -31
  166. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
  167. package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
  168. package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
  169. package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
  170. package/tests/integration/mcp/setup.spec.ts +0 -5
  171. package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
  172. package/tests/unit/ducklake/ducklake.test.ts +0 -950
  173. package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
  174. package/tests/unit/mcp/prompt_happy.test.ts +0 -51
  175. package/tsconfig.json +0 -32
@@ -1,370 +0,0 @@
1
- import {
2
- McpServer,
3
- ResourceTemplate,
4
- } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
6
- import { URL } from "url";
7
- import { PackageNotFoundError } from "../../errors";
8
- import { logger } from "../../logger";
9
- import { ProjectStore } from "../../service/project_store";
10
- import {
11
- getInternalError,
12
- getNotFoundError,
13
- type ErrorDetails,
14
- } from "../error_messages";
15
- import {
16
- buildMalloyUri,
17
- handleResourceGet,
18
- McpGetResourceError,
19
- } from "../handler_utils";
20
- import { RESOURCE_METADATA } from "../resource_metadata";
21
-
22
- // *** Define handleGetPackageContents function ***
23
- async function handleGetPackageContents(
24
- uri: URL,
25
- params: { projectName?: string; packageName?: string },
26
- projectStore: ProjectStore,
27
- ) {
28
- try {
29
- const { projectName, packageName } = params;
30
- if (typeof projectName !== "string" || projectName === "") {
31
- throw new Error("Invalid project name parameter.");
32
- }
33
- if (typeof packageName !== "string" || packageName === "") {
34
- throw new Error("Invalid package name parameter.");
35
- }
36
-
37
- const project = await projectStore.getProject(projectName, false);
38
- const packageInstance = await project.getPackage(packageName, false);
39
-
40
- // Use listModels() which returns { path: string, type: 'source' | 'notebook' }[]
41
- const entries = await packageInstance.listModels();
42
-
43
- // Use a type that includes metadata explicitly
44
- const resourceDefinitions: { uri: string; metadata: unknown }[] = [];
45
-
46
- for (const entry of entries) {
47
- const entryPath = entry.path; // e.g., "flights.malloy", "overview.malloynb"
48
- // @ts-expect-error TODO: Fix missing type in API
49
- const entryType = entry.type; // 'source' or 'notebook'
50
-
51
- if (typeof entryPath !== "string" || entryPath === "") {
52
- logger.warn(
53
- `[MCP Server Warning] Skipping entry in package ${packageName} with invalid path:`,
54
- entry,
55
- );
56
- continue;
57
- }
58
-
59
- // Common components for URI building
60
- const baseUriComponents = {
61
- project: projectName,
62
- package: packageName,
63
- };
64
-
65
- switch (entryType) {
66
- case "notebook": {
67
- const resourceMetadata = RESOURCE_METADATA.notebook;
68
- if (!resourceMetadata) {
69
- logger.warn(
70
- `[MCP Server Warning] No metadata found for entry type 'notebook' path ${entryPath} in package ${packageName}`,
71
- );
72
- continue;
73
- }
74
- const notebookUri = buildMalloyUri({
75
- ...baseUriComponents,
76
- resourceType: "notebooks",
77
- resourceName: entryPath,
78
- });
79
- resourceDefinitions.push({
80
- uri: notebookUri,
81
- metadata: resourceMetadata,
82
- });
83
- break;
84
- }
85
- case "source": {
86
- // 1. Add the source file itself
87
- const sourceResourceMetadata = RESOURCE_METADATA.source;
88
- if (!sourceResourceMetadata) {
89
- logger.warn(
90
- `[MCP Server Warning] No metadata found for entry type 'source' path ${entryPath} in package ${packageName}`,
91
- );
92
- // Continue processing views even if source metadata is missing
93
- } else {
94
- const sourceUri = buildMalloyUri({
95
- ...baseUriComponents,
96
- resourceType: "sources", // Use 'sources' type for the source file resource
97
- resourceName: entryPath,
98
- });
99
- resourceDefinitions.push({
100
- uri: sourceUri,
101
- metadata: sourceResourceMetadata,
102
- });
103
- }
104
-
105
- // 2. Try to load the model to find views within it
106
- try {
107
- const model = await packageInstance.getModel(entryPath); // Await the promise
108
- if (model) {
109
- // Ensure model exists before proceeding
110
- const modelSources = model.getSources(); // This should be synchronous if model is loaded
111
-
112
- if (modelSources && Array.isArray(modelSources)) {
113
- for (const source of modelSources) {
114
- if (source.views && Array.isArray(source.views)) {
115
- for (const view of source.views) {
116
- const viewResourceMetadata =
117
- RESOURCE_METADATA.view;
118
- if (!viewResourceMetadata) {
119
- logger.warn(
120
- `[MCP Server Warning] No metadata found for entry type 'view' named '${view.name}' in source '${source.name}'`,
121
- );
122
- continue;
123
- }
124
- // Build the nested view URI using the helper
125
- const viewUri = buildMalloyUri({
126
- ...baseUriComponents,
127
- resourceType: "models", // Base is the model
128
- resourceName: entryPath, // Model file path
129
- subResourceType: "views",
130
- subResourceName: view.name,
131
- });
132
- resourceDefinitions.push({
133
- uri: viewUri,
134
- metadata: viewResourceMetadata,
135
- });
136
- }
137
- }
138
- }
139
- } else {
140
- logger.warn(
141
- `[MCP Server Warning] Could not retrieve sources or sources is not an array for model ${entryPath}`,
142
- );
143
- }
144
- } else {
145
- logger.warn(
146
- `[MCP Server Warning] Could not load model for path ${entryPath} to extract views.`,
147
- );
148
- }
149
- } catch (modelLoadError) {
150
- // Log error if model loading fails, but continue processing other files
151
- logger.warn(
152
- `[MCP Server Warning] Failed to load model ${entryPath} to extract views: ${modelLoadError instanceof Error ? modelLoadError.message : String(modelLoadError)}`,
153
- );
154
- }
155
- break;
156
- }
157
- default:
158
- logger.warn(
159
- `[MCP Server Warning] Unknown entry type '${entryType}' for path ${entryPath} in package ${packageName}`,
160
- );
161
- continue; // Skip unknown types
162
- }
163
- }
164
-
165
- return resourceDefinitions; // handleResourceGet will stringify this
166
- } catch (error) {
167
- let errorDetails;
168
- if (error instanceof PackageNotFoundError) {
169
- errorDetails = getNotFoundError(
170
- `Package for contents listing matching URI '${uri.href}'`,
171
- );
172
- } else if (
173
- error instanceof Error &&
174
- (error.message.includes("Invalid package name") ||
175
- error.message.includes("Invalid project name"))
176
- ) {
177
- errorDetails = getNotFoundError(
178
- `Invalid project/package identifier in URI '${uri.href}'`,
179
- );
180
- } else {
181
- logger.error(
182
- `[MCP Server Error] Error getting package contents for ${uri.href}:`,
183
- { error },
184
- );
185
- errorDetails = getInternalError(
186
- `GetResource (package contents: ${uri.href})`,
187
- error,
188
- );
189
- }
190
- throw new McpGetResourceError(errorDetails);
191
- }
192
- }
193
-
194
- /**
195
- * Registers the Malloy Package resource type with the MCP server.
196
- * Handles listing models/notebooks/sources within packages and getting package details.
197
- */
198
- export function registerPackageResource(
199
- mcpServer: McpServer,
200
- projectStore: ProjectStore,
201
- ): void {
202
- // Define the expected parameter type for the read callback
203
- type ReadPackageParams = {
204
- fsPath?: string; // Expect string
205
- projectName?: string; // Expect string
206
- packageName?: string; // Expect string
207
- };
208
-
209
- mcpServer.resource(
210
- "package",
211
- new ResourceTemplate(
212
- "malloy://project/{projectName}/package/{packageName}",
213
- {
214
- list: undefined, // TODO: Implement and add listAllPackages here if needed
215
- },
216
- ),
217
- (uri, params) =>
218
- handleResourceGet(
219
- uri,
220
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
- params as ReadPackageParams, // Cast params to the updated type
222
- "package",
223
- async ({
224
- projectName,
225
- packageName,
226
- }: {
227
- // Keep unknown here as it reflects the raw input possibility
228
- // before validation inside the async function
229
- projectName?: unknown;
230
- packageName?: unknown;
231
- }) => {
232
- try {
233
- if (typeof projectName !== "string") {
234
- throw new Error("Invalid project name parameter.");
235
- }
236
- if (typeof packageName !== "string") {
237
- throw new Error("Invalid package name parameter.");
238
- }
239
- const project = await projectStore.getProject(
240
- projectName,
241
- false,
242
- );
243
- const pkg = await project.getPackage(packageName, false);
244
- return pkg.getPackageMetadata();
245
- } catch (error) {
246
- let errorDetails;
247
- if (error instanceof PackageNotFoundError) {
248
- errorDetails = getNotFoundError(
249
- `Package resource matching URI '${uri.href}'`,
250
- );
251
- } else if (
252
- error instanceof Error &&
253
- (error.message.includes("Invalid package name") ||
254
- error.message.includes("Invalid project name"))
255
- ) {
256
- errorDetails = getNotFoundError(
257
- `Invalid project/package identifier in URI '${uri.href}'`,
258
- );
259
- } else {
260
- errorDetails = getInternalError(
261
- `GetResource (package: ${uri.href})`,
262
- error,
263
- );
264
- }
265
- throw new McpGetResourceError(errorDetails);
266
- }
267
- },
268
- RESOURCE_METADATA.package,
269
- ),
270
- );
271
-
272
- // *** Register Package Contents Resource ***
273
- mcpServer.resource(
274
- "package-contents",
275
- new ResourceTemplate(
276
- "malloy://project/{projectName}/package/{packageName}/contents",
277
- {
278
- // Contents are listed via GetResource on this specific URI,
279
- // so no general list callback needed here.
280
- list: undefined,
281
- },
282
- ),
283
- // Custom handler because the response format is just the array, not {definition, metadata}
284
- async (
285
- uri: URL,
286
- params: ReadPackageParams,
287
- ): Promise<ReadResourceResult> => {
288
- try {
289
- // Reuse param validation/typing
290
- const validatedParams = params as ReadPackageParams;
291
-
292
- // Get the array of resource definitions
293
- const resourceDefinitions = await handleGetPackageContents(
294
- uri,
295
- validatedParams,
296
- projectStore,
297
- );
298
-
299
- // Return the array directly as JSON content
300
- return {
301
- contents: [
302
- {
303
- type: "application/json",
304
- uri: uri.href,
305
- text: JSON.stringify(resourceDefinitions, null, 2),
306
- },
307
- ],
308
- };
309
- } catch (error) {
310
- logger.error(
311
- `[MCP Server Error] Error reading package contents ${uri.href}:`,
312
- { error },
313
- );
314
-
315
- let errorDetails: ErrorDetails;
316
-
317
- // Check if it's the specific error thrown by handleGetPackageContents
318
- if (error instanceof McpGetResourceError) {
319
- // Specifically check if it's a 'not found' error from the inner handler
320
- if (error.message.includes("not found")) {
321
- // Re-use the not found details directly
322
- errorDetails = error.details;
323
- } else {
324
- // It was a different McpGetResourceError (e.g., internal from inner handler)
325
- errorDetails = error.details;
326
- }
327
- } else if (error instanceof PackageNotFoundError) {
328
- // Handle PackageNotFoundError specifically if it bubbles up here
329
- errorDetails = getNotFoundError(
330
- `Package for contents listing matching URI '${uri.href}'`,
331
- );
332
- } else if (
333
- error instanceof Error &&
334
- (error.message.includes("Invalid package name") ||
335
- error.message.includes("Invalid project name"))
336
- ) {
337
- // Handle invalid identifier errors specifically
338
- errorDetails = getNotFoundError(
339
- `Invalid project/package identifier in URI '${uri.href}'`,
340
- );
341
- } else {
342
- // Handle other unexpected errors
343
- errorDetails = getInternalError(
344
- `GetResource (package contents: ${uri.href})`,
345
- error,
346
- );
347
- }
348
-
349
- // Format the error response
350
- return {
351
- isError: true,
352
- contents: [
353
- {
354
- type: "application/json",
355
- uri: uri.href,
356
- text: JSON.stringify(
357
- {
358
- error: errorDetails.message,
359
- suggestions: errorDetails.suggestions,
360
- },
361
- null,
362
- 2,
363
- ),
364
- },
365
- ],
366
- };
367
- }
368
- },
369
- );
370
- }
@@ -1,184 +0,0 @@
1
- import {
2
- McpServer,
3
- ResourceTemplate,
4
- } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import type { ListResourcesResult } from "@modelcontextprotocol/sdk/types.js"; // Needed for list return type
6
- import { logger } from "../../logger";
7
- import { ProjectStore } from "../../service/project_store";
8
- import { getInternalError, getNotFoundError } from "../error_messages"; // Needed for error handling in list AND get
9
- import { handleResourceGet, McpGetResourceError } from "../handler_utils";
10
- import { RESOURCE_METADATA } from "../resource_metadata";
11
-
12
- // Define an interface for the package object augmented with project name
13
- interface PackageWithProject {
14
- name?: string;
15
- // Add other relevant package properties if needed
16
- projectName: string;
17
- }
18
-
19
- /**
20
- * Registers the Malloy Project resource type with the MCP server.
21
- * Handles getting details for the hardcoded 'home' project and listing packages within it.
22
- */
23
- export function registerProjectResource(
24
- mcpServer: McpServer,
25
- projectStore: ProjectStore,
26
- ): void {
27
- mcpServer.resource(
28
- "project",
29
- new ResourceTemplate("malloy://project/{projectName}", {
30
- /**
31
- * Handles ListResources requests.
32
- * If projectName is specified, lists packages for that project (only 'home' supported).
33
- * If projectName is not specified (general ListResources call), lists packages for the default 'home' project.
34
- */
35
- list: async (/* extra: ListProjectExtra - Deleted */): Promise<ListResourcesResult> => {
36
- logger.info(
37
- "[MCP LOG] Entering ListResources (project) handler (listing ALL packages)...",
38
- );
39
- // Ignore parameters from 'extra' as URI path params aren't passed to list handlers.
40
-
41
- try {
42
- const allProjects = await projectStore.listProjects();
43
- logger.info(
44
- `[MCP LOG] Found ${allProjects.length} projects defined.`,
45
- );
46
-
47
- const packagePromises = allProjects.map(async (proj) => {
48
- try {
49
- logger.info(
50
- `[MCP LOG] Getting project '${proj.name}' to list its packages...`,
51
- );
52
- const projectInstance = await projectStore.getProject(
53
- proj.name!,
54
- false,
55
- ); // Use proj.name
56
- const packages = await projectInstance.listPackages();
57
- logger.info(
58
- `[MCP LOG] Found ${packages.length} packages in project '${proj.name}'.`,
59
- );
60
- // Return packages along with their project name for URI construction
61
- return packages.map((pkg) => ({
62
- ...pkg,
63
- projectName: proj.name,
64
- }));
65
- } catch (projectError) {
66
- logger.error(
67
- `[MCP Server Error] Error getting/listing packages for project ${proj.name}:`,
68
- { error: projectError },
69
- );
70
- return []; // Return empty array for this project on error
71
- }
72
- });
73
-
74
- const results = await Promise.allSettled(packagePromises);
75
- const allPackagesWithProjectName = results
76
- .filter((result) => result.status === "fulfilled")
77
- // Use the specific interface instead of any[]
78
- .flatMap(
79
- (result) =>
80
- (result as PromiseFulfilledResult<PackageWithProject[]>)
81
- .value,
82
- );
83
-
84
- logger.info(
85
- `[MCP LOG] Total packages found across all projects: ${allPackagesWithProjectName.length}`,
86
- );
87
-
88
- const packageMetadata = RESOURCE_METADATA.package;
89
- const mappedResources = allPackagesWithProjectName.map((pkg) => {
90
- const name = pkg.name || "unknown";
91
- // Construct URI using the package's specific projectName
92
- const uri = `malloy://project/${pkg.projectName}/package/${name}`;
93
- return {
94
- uri: uri,
95
- name: name,
96
- type: "package",
97
- description: packageMetadata?.description as
98
- | string
99
- | undefined,
100
- metadata: packageMetadata,
101
- };
102
- });
103
-
104
- logger.info(
105
- `[MCP LOG] ListResources (project): Returning ${mappedResources.length} package resources.`,
106
- );
107
- return {
108
- resources: mappedResources,
109
- };
110
- } catch (error) {
111
- // Catch errors from projectStore.listProjects() itself
112
- logger.error(`[MCP Server Error] Error listing projects:`, {
113
- error,
114
- });
115
- const errorDetails = getInternalError(
116
- `ListResources (project - initial list)`,
117
- error,
118
- );
119
- logger.error("MCP ListResources (project) error:", {
120
- error: errorDetails.message,
121
- });
122
- logger.info(
123
- "[MCP LOG] ListResources (project): Returning empty on error listing projects.",
124
- );
125
- return { resources: [] };
126
- }
127
- },
128
- }),
129
- /** Handles GetResource requests for Malloy Projects */
130
- (uri, params) =>
131
- handleResourceGet(
132
- uri,
133
- params,
134
- "project",
135
- async ({ projectName }: { projectName?: unknown }) => {
136
- logger.info(
137
- `[MCP LOG] Entering GetResource (project) handler for projectName: ${projectName}`,
138
- );
139
- // Validate project name parameter
140
- if (typeof projectName !== "string") {
141
- logger.error(
142
- "[MCP LOG] GetResource (project): Invalid project name param.",
143
- );
144
- throw new Error("Invalid project name parameter.");
145
- }
146
-
147
- try {
148
- logger.info(
149
- `[MCP LOG] GetResource: Getting project '${projectName}'...`,
150
- );
151
- // Get the project instance, but we might not need its metadata directly
152
- await projectStore.getProject(projectName, false);
153
- // Construct the definition object expected by the test
154
- const definition = { name: projectName };
155
- logger.info(
156
- `[MCP LOG] GetResource (project): Returning definition for '${projectName}'.`,
157
- );
158
- // Return the explicit definition structure
159
- return definition;
160
- } catch (error) {
161
- logger.error(
162
- `[MCP LOG] GetResource (project): Error caught for '${projectName}':`,
163
- { error },
164
- );
165
- // Catch expected errors from this specific resource logic
166
- if (error instanceof Error) {
167
- // Use getNotFoundError for the specific project not found case
168
- // or a generic message for the invalid param case.
169
- const errorDetails = getNotFoundError(
170
- error.message.includes("not found")
171
- ? `Project '${projectName}'` // More specific context
172
- : `Invalid project identifier provided for URI ${uri.href}`, // Generic but informative
173
- );
174
- // Re-throw structured error for handleResourceGet to catch
175
- throw new McpGetResourceError(errorDetails);
176
- }
177
- // Re-throw unexpected errors to be caught by handleResourceGet's generic handler
178
- throw error;
179
- }
180
- },
181
- RESOURCE_METADATA.project,
182
- ),
183
- );
184
- }
@@ -1,117 +0,0 @@
1
- import {
2
- McpServer,
3
- ResourceTemplate,
4
- } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { URL } from "url";
6
- import type { components } from "../../api"; // Need this for Query definition type
7
- import { ModelCompilationError } from "../../errors";
8
- import { logger } from "../../logger";
9
- import { ProjectStore } from "../../service/project_store";
10
- import {
11
- getInternalError,
12
- getMalloyErrorDetails,
13
- getNotFoundError,
14
- } from "../error_messages";
15
- import {
16
- getModelForQuery,
17
- handleResourceGet,
18
- McpGetResourceError,
19
- } from "../handler_utils";
20
- import { RESOURCE_METADATA } from "../resource_metadata";
21
-
22
- // Define the expected parameter types
23
- type QueryParams = {
24
- projectName?: unknown;
25
- packageName?: unknown;
26
- modelPath?: unknown;
27
- queryName?: unknown;
28
- };
29
-
30
- /**
31
- * Registers the Malloy Query resource type (nested within a Model).
32
- */
33
- export function registerQueryResource(
34
- mcpServer: McpServer,
35
- projectStore: ProjectStore,
36
- ): void {
37
- mcpServer.resource(
38
- "query",
39
- new ResourceTemplate(
40
- "malloy://project/{projectName}/package/{packageName}/models/{modelPath}/queries/{queryName}",
41
- { list: undefined }, // Listing queries is not supported via this template
42
- ),
43
- (uri, params) =>
44
- handleResourceGet(
45
- uri,
46
- params as QueryParams,
47
- "query",
48
- async (
49
- { projectName, packageName, modelPath, queryName }: QueryParams,
50
- uri: URL,
51
- ) => {
52
- if (
53
- typeof projectName !== "string" ||
54
- typeof packageName !== "string" ||
55
- typeof modelPath !== "string" ||
56
- typeof queryName !== "string"
57
- ) {
58
- throw new Error("Invalid parameters for query resource.");
59
- }
60
-
61
- try {
62
- const modelResult = await getModelForQuery(
63
- projectStore,
64
- projectName,
65
- packageName,
66
- modelPath,
67
- );
68
- if ("error" in modelResult) {
69
- throw new McpGetResourceError(modelResult.error);
70
- }
71
- const { model } = modelResult;
72
-
73
- // Attempt to get the query definition from the model
74
- const queries = await model.getQueries(); // Assuming this method exists
75
- if (!queries) {
76
- throw new Error("Could not retrieve queries from model.");
77
- }
78
- const query = queries.find(
79
- (q: components["schemas"]["Query"]) =>
80
- q.name === queryName,
81
- );
82
-
83
- if (!query) {
84
- // Specific "Query not found" error
85
- const errorDetails = getNotFoundError(
86
- `Query '${queryName}' in model '${modelPath}' package '${packageName}' project '${projectName}'`,
87
- );
88
- throw new McpGetResourceError(errorDetails);
89
- }
90
- return query;
91
- } catch (error) {
92
- if (error instanceof McpGetResourceError) {
93
- throw error; // Re-throw already formatted
94
- }
95
- if (error instanceof ModelCompilationError) {
96
- const errorDetails = getMalloyErrorDetails(
97
- "GetResource (query - model compilation)",
98
- `${projectName}/${packageName}/${modelPath}`,
99
- error,
100
- );
101
- throw new McpGetResourceError(errorDetails);
102
- }
103
- logger.error(
104
- `[MCP Server Error] Error fetching query '${queryName}' from ${uri.href}:`,
105
- { error },
106
- );
107
- const fallbackErrorDetails = getInternalError(
108
- `GetResource (query: ${uri.href})`,
109
- error,
110
- );
111
- throw new McpGetResourceError(fallbackErrorDetails);
112
- }
113
- },
114
- RESOURCE_METADATA.query,
115
- ),
116
- );
117
- }